diff options
Diffstat (limited to 'test')
-rw-r--r-- | test/bun.js/decorators.test.ts | 787 | ||||
-rw-r--r-- | test/scripts/snippets.json | 1 | ||||
-rw-r--r-- | test/snippets/package.json | 1 | ||||
-rw-r--r-- | test/snippets/simple-lit-example.ts | 76 |
4 files changed, 865 insertions, 0 deletions
diff --git a/test/bun.js/decorators.test.ts b/test/bun.js/decorators.test.ts new file mode 100644 index 000000000..545030ef1 --- /dev/null +++ b/test/bun.js/decorators.test.ts @@ -0,0 +1,787 @@ +import { test, expect } from "bun:test"; + +test("decorator order of evaluation", () => { + let counter = 0; + const computedProp: unique symbol = Symbol("computedProp"); + + @decorator1 + @decorator2 + class BugReport { + @decorator7 + type: string; + + @decorator3 + x: number = 20; + + @decorator5 + private _y: number = 12; + + @decorator10 + get y() { + return this._y; + } + @decorator11 + set y(newY: number) { + this._y = newY; + } + + @decorator9 + [computedProp]: string = "yes"; + + constructor(@decorator8 type: string) { + this.type = type; + } + + @decorator6 + move(newX: number, @decorator12 newY: number) { + this.x = newX; + this._y = newY; + } + + @decorator4 + jump() { + this._y += 30; + } + } + + function decorator1(target, propertyKey) { + expect(counter++).toBe(11); + expect(target === BugReport).toBe(true); + expect(propertyKey).toBe(undefined); + } + + function decorator2(target, propertyKey) { + expect(counter++).toBe(10); + expect(target === BugReport).toBe(true); + expect(propertyKey).toBe(undefined); + } + + function decorator3(target, propertyKey) { + expect(counter++).toBe(1); + expect(target === BugReport.prototype).toBe(true); + expect(propertyKey).toBe("x"); + } + + function decorator4(target, propertyKey) { + expect(counter++).toBe(8); + expect(target === BugReport.prototype).toBe(true); + expect(propertyKey).toBe("jump"); + } + + function decorator5(target, propertyKey) { + expect(counter++).toBe(2); + expect(target === BugReport.prototype).toBe(true); + expect(propertyKey).toBe("_y"); + } + + function decorator6(target, propertyKey) { + expect(counter++).toBe(7); + expect(target === BugReport.prototype).toBe(true); + expect(propertyKey).toBe("move"); + } + + function decorator7(target, propertyKey) { + expect(counter++).toBe(0); + expect(target === BugReport.prototype).toBe(true); + expect(propertyKey).toBe("type"); + } + + function decorator8(target, propertyKey) { + expect(counter++).toBe(9); + expect(target === BugReport).toBe(true); + expect(propertyKey).toBe(undefined); + } + + function decorator9(target, propertyKey) { + expect(counter++).toBe(5); + expect(target === BugReport.prototype).toBe(true); + expect(propertyKey).toBe(computedProp); + } + + function decorator10(target, propertyKey) { + expect(counter++).toBe(3); + expect(target === BugReport.prototype).toBe(true); + expect(propertyKey).toBe("y"); + } + + function decorator11(target, propertyKey) { + expect(counter++).toBe(4); + expect(target === BugReport.prototype).toBe(true); + expect(propertyKey).toBe("y"); + } + + function decorator12(target, propertyKey) { + expect(counter++).toBe(6); + expect(target === BugReport.prototype).toBe(true); + expect(propertyKey).toBe("move"); + } +}); + +test("decorator factories order of evaluation", () => { + let counter = 0; + const computedProp: unique symbol = Symbol("computedProp"); + + @decorator1() + @decorator2() + class BugReport { + @decorator7() + type: string; + + @decorator3() + x: number = 20; + + @decorator5() + private _y: number = 12; + + @decorator10() + get y() { + return this._y; + } + @decorator11() + set y(newY: number) { + this._y = newY; + } + + @decorator9() + [computedProp]: string = "yes"; + + constructor(@decorator8() type: string) { + this.type = type; + } + + @decorator6() + move(newX: number, @decorator12() newY: number) { + this.x = newX; + this._y = newY; + } + + @decorator4() + jump() { + this._y += 30; + } + } + + function decorator1() { + expect(counter++).toBe(18); + return function (target, descriptorKey) { + expect(counter++).toBe(23); + }; + } + + function decorator2() { + expect(counter++).toBe(19); + return function (target, descriptorKey) { + expect(counter++).toBe(22); + }; + } + + function decorator3() { + expect(counter++).toBe(2); + return function (target, descriptorKey) { + expect(counter++).toBe(3); + }; + } + + function decorator4() { + expect(counter++).toBe(16); + return function (target, descriptorKey) { + expect(counter++).toBe(17); + }; + } + + function decorator5() { + expect(counter++).toBe(4); + return function (target, descriptorKey) { + expect(counter++).toBe(5); + }; + } + + function decorator6() { + expect(counter++).toBe(12); + return function (target, descriptorKey) { + expect(counter++).toBe(15); + }; + } + + function decorator7() { + expect(counter++).toBe(0); + return function (target, descriptorKey) { + expect(counter++).toBe(1); + }; + } + + function decorator8() { + expect(counter++).toBe(20); + return function (target, descriptorKey) { + expect(counter++).toBe(21); + }; + } + + function decorator9() { + expect(counter++).toBe(10); + return function (target, descriptorKey) { + expect(counter++).toBe(11); + }; + } + + function decorator10() { + expect(counter++).toBe(6); + return function (target, descriptorKey) { + expect(counter++).toBe(7); + }; + } + + function decorator11() { + expect(counter++).toBe(8); + return function (target, descriptorKey) { + expect(counter++).toBe(9); + }; + } + + function decorator12() { + expect(counter++).toBe(13); + return function (target, descriptorKey) { + expect(counter++).toBe(14); + }; + } +}); + +test("parameter decorators", () => { + let counter = 0; + class HappyDecorator { + width: number; + height: number; + x: number; + y: number; + + move(@d4 x: number, @d5 @d6 y: number) { + this.x = x; + this.y = y; + } + + constructor( + one: number, + two: string, + three: boolean, + @d1 @d2 width: number, + @d3 height: number + ) { + this.width = width; + this.height = height; + } + + dance(@d7 @d8 intensity: number) { + this.width *= intensity; + this.height *= intensity; + } + } + + function d1(target, propertyKey, parameterIndex) { + expect(counter++).toBe(7); + expect(target === HappyDecorator).toBe(true); + expect(propertyKey).toBe(undefined); + expect(parameterIndex).toBe(3); + } + + function d2(target, propertyKey, parameterIndex) { + expect(counter++).toBe(6); + expect(target === HappyDecorator).toBe(true); + expect(propertyKey).toBe(undefined); + expect(parameterIndex).toBe(3); + } + + function d3(target, propertyKey, parameterIndex) { + expect(counter++).toBe(5); + expect(target === HappyDecorator).toBe(true); + expect(propertyKey).toBe(undefined); + expect(parameterIndex).toBe(4); + } + + function d4(target, propertyKey, parameterIndex) { + expect(counter++).toBe(2); + expect(target === HappyDecorator.prototype).toBe(true); + expect(propertyKey).toBe("move"); + expect(parameterIndex).toBe(0); + } + + function d5(target, propertyKey, parameterIndex) { + expect(counter++).toBe(1); + expect(target === HappyDecorator.prototype).toBe(true); + expect(propertyKey).toBe("move"); + expect(parameterIndex).toBe(1); + } + + function d6(target, propertyKey, parameterIndex) { + expect(counter++).toBe(0); + expect(target === HappyDecorator.prototype).toBe(true); + expect(propertyKey).toBe("move"); + expect(parameterIndex).toBe(1); + } + + function d7(target, propertyKey, parameterIndex) { + expect(counter++).toBe(4); + expect(target === HappyDecorator.prototype).toBe(true); + expect(propertyKey).toBe("dance"); + expect(parameterIndex).toBe(0); + } + + function d8(target, propertyKey, parameterIndex) { + expect(counter++).toBe(3); + expect(target === HappyDecorator.prototype).toBe(true); + expect(propertyKey).toBe("dance"); + expect(parameterIndex).toBe(0); + } + + class Maybe { + constructor( + @m1 private x: number, + @m2 public y: boolean, + @m3 protected z: string + ) {} + } + + function m1(target, propertyKey, index) { + expect(target === Maybe).toBe(true); + expect(propertyKey).toBe(undefined); + expect(index).toBe(0); + } + + function m2(target, propertyKey, index) { + expect(target === Maybe).toBe(true); + expect(propertyKey).toBe(undefined); + expect(index).toBe(1); + } + + function m3(target, propertyKey, index) { + expect(target === Maybe).toBe(true); + expect(propertyKey).toBe(undefined); + expect(index).toBe(2); + } +}); + +test("decorators random", () => { + @Frozen + class IceCream {} + + function Frozen(constructor: Function) { + Object.freeze(constructor); + Object.freeze(constructor.prototype); + } + + expect(Object.isFrozen(IceCream)).toBe(true); + + class IceCreamComponent { + @Emoji() + flavor = "vanilla"; + } + + // Property Decorator + function Emoji() { + return function (target: Object, key: string | symbol) { + let val = target[key]; + + const getter = () => { + return val; + }; + const setter = (next) => { + val = `๐ฆ ${next} ๐ฆ`; + }; + + Object.defineProperty(target, key, { + get: getter, + set: setter, + enumerable: true, + configurable: true, + }); + }; + } + + const iceCream = new IceCreamComponent(); + expect(iceCream.flavor === "๐ฆ vanilla ๐ฆ").toBe(true); + iceCream.flavor = "chocolate"; + expect(iceCream.flavor === "๐ฆ chocolate ๐ฆ").toBe(true); + + const i: unique symbol = Symbol.for("i"); + const h: unique symbol = Symbol.for("h"); + const t: unique symbol = Symbol.for("t"); + const q: unique symbol = Symbol.for("q"); + const p: unique symbol = Symbol.for("p"); + const u3: unique symbol = Symbol.for("u3"); + const u5: unique symbol = Symbol.for("u5"); + const u6: unique symbol = Symbol.for("u6"); + const u8: unique symbol = Symbol.for("u8"); + + class S { + @StringAppender("๐") k = 35; + @StringAppender("๐ค ") static j = 4; + @StringAppender("๐ตโ๐ซ") private static [h] = 30; + @StringAppender("๐คฏ") private static u = 60; + @StringAppender("๐คช") private [t] = 32; + @StringAppender("๐ค") [i] = 8; + @StringAppender("๐") private e = 10; + @StringAppender("๐ป") static [q] = 202; + @StringAppender("๐") r = S[h]; + _y: number; + @StringAppender("๐คก") get y() { + return this._y; + } + set y(next) { + this._y = next; + } + #o = 100; + + @StringAppender("๐") u1: number; + @StringAppender("๐ฅณ") static u2: number; + @StringAppender("๐ค") private static [u3]: number; + @StringAppender("๐ฅบ") private static u4: number; + @StringAppender("๐คฏ") private [u5]: number; + @StringAppender("๐คฉ") [u6]: number; + @StringAppender("โน๏ธ") private u7: number; + @StringAppender("๐") static [u8]: number; + + @StringAppender("๐ค") u9 = this.u1; + @StringAppender("๐คจ") u10 = this.u2; + @StringAppender("๐") u11 = S[u3]; + @StringAppender("๐") u12 = S.u4; + @StringAppender("๐") u13 = this[u5]; + @StringAppender("๐") u14 = this[u6]; + @StringAppender("๐ถ") u15 = this.u7; + @StringAppender("๐") u16 = S[u8]; + + constructor() { + this.k = 3; + expect(this.k).toBe("3 ๐"); + expect(S.j).toBe(4); + expect(this[i]).toBe("8 ๐ค"); + expect(this.e).toBe("10 ๐"); + expect(S[h]).toBe(30); + expect(S.u).toBe(60); + expect(this[t]).toBe("32 ๐คช"); + expect(S[q]).toBe(202); + expect(this.#o).toBe(100); + expect(this.r).toBe("30 ๐"); + expect(this.y).toBe(undefined); + this.y = 100; + expect(this.y).toBe(100); + + expect(this.u1).toBe(undefined); + expect(S.u2).toBe(undefined); + expect(S[u3]).toBe(undefined); + expect(S.u4).toBe(undefined); + expect(this[u5]).toBe(undefined); + expect(this[u6]).toBe(undefined); + expect(this.u7).toBe(undefined); + expect(S[u8]).toBe(undefined); + + expect(this.u9).toBe("undefined ๐ค"); + expect(this.u10).toBe("undefined ๐คจ"); + expect(this.u11).toBe("undefined ๐"); + expect(this.u12).toBe("undefined ๐"); + expect(this.u13).toBe("undefined ๐"); + expect(this.u14).toBe("undefined ๐"); + expect(this.u15).toBe("undefined ๐ถ"); + expect(this.u16).toBe("undefined ๐"); + + this.u1 = 100; + expect(this.u1).toBe("100 ๐"); + S.u2 = 100; + expect(S.u2).toBe("100 ๐ฅณ"); + S[u3] = 100; + expect(S[u3]).toBe("100 ๐ค"); + S.u4 = 100; + expect(S.u4).toBe("100 ๐ฅบ"); + this[u5] = 100; + expect(this[u5]).toBe("100 ๐คฏ"); + this[u6] = 100; + expect(this[u6]).toBe("100 ๐คฉ"); + this.u7 = 100; + expect(this.u7).toBe("100 โน๏ธ"); + S[u8] = 100; + expect(S[u8]).toBe("100 ๐"); + + expect(this.u9).toBe("undefined ๐ค"); + expect(this.u10).toBe("undefined ๐คจ"); + expect(this.u11).toBe("undefined ๐"); + expect(this.u12).toBe("undefined ๐"); + expect(this.u13).toBe("undefined ๐"); + expect(this.u14).toBe("undefined ๐"); + expect(this.u15).toBe("undefined ๐ถ"); + expect(this.u16).toBe("undefined ๐"); + } + } + + let s = new S(); + expect(s.u9).toBe("undefined ๐ค"); + expect(s.u10).toBe("undefined ๐คจ"); + expect(s.u11).toBe("undefined ๐"); + expect(s.u12).toBe("undefined ๐"); + expect(s.u13).toBe("undefined ๐"); + expect(s.u14).toBe("undefined ๐"); + expect(s.u15).toBe("undefined ๐ถ"); + expect(s.u16).toBe("undefined ๐"); + + s.u9 = 35; + expect(s.u9).toBe("35 ๐ค"); + s.u10 = 36; + expect(s.u10).toBe("36 ๐คจ"); + s.u11 = 37; + expect(s.u11).toBe("37 ๐"); + s.u12 = 38; + expect(s.u12).toBe("38 ๐"); + s.u13 = 39; + expect(s.u13).toBe("39 ๐"); + s.u14 = 40; + expect(s.u14).toBe("40 ๐"); + s.u15 = 41; + expect(s.u15).toBe("41 ๐ถ"); + s.u16 = 42; + expect(s.u16).toBe("42 ๐"); + + function StringAppender(emoji: string) { + return function (target: Object, key: string | symbol) { + let val = target[key]; + + const getter = () => { + return val; + }; + const setter = (value) => { + val = `${value} ${emoji}`; + }; + + Object.defineProperty(target, key, { + get: getter, + set: setter, + enumerable: true, + configurable: true, + }); + }; + } +}); + +test("class field order", () => { + class N { + l = 455; + } + class M { + u = 4; + @d1 w = 9; + constructor() { + // this.w = 9 should be moved here + expect(this.u).toBe(4); + expect(this.w).toBe(9); + this.u = 3; + this.w = 6; + expect(this.u).toBe(3); + expect(this.w).toBe(6); + } + } + + function d1(target, propertyKey) { + expect(target === M.prototype).toBe(true); + expect(propertyKey).toBe("w"); + } + + let m = new M(); + expect(m.u).toBe(3); + expect(m.w).toBe(6); +}); + +test("changing static method", () => { + class A { + static bar() { + return 1; + } + } + + @changeMethodReturn("bar", 5) + class A_2 { + static bar() { + return 7; + } + } + + function changeMethodReturn(method, value) { + return function (target) { + target[method] = function () { + return value; + }; + return target; + }; + } + + @changeMethodReturn("bar", 2) + class B extends A {} + + @changeMethodReturn("bar", 9) + class C extends B {} + + expect(A_2.bar()).toBe(5); + expect(A.bar()).toBe(1); + expect(B.bar()).toBe(2); + expect(C.bar()).toBe(9); +}); + +test("class extending from another class", () => { + class A { + a: number; + constructor() { + this.a = 3; + } + } + + class B extends A { + a: number = 9; + } + + expect(new A().a).toBe(3); + expect(new B().a).toBe(9); + + class C { + a: number = 80; + } + + class D extends C { + a: number = 32; + constructor() { + super(); + } + } + + expect(new C().a).toBe(80); + expect(new D().a).toBe(32); + + class E { + a: number = 40; + constructor() { + expect(this.a).toBe(40); + } + } + + class F extends E { + @d1 a: number = 50; + constructor() { + super(); + expect(this.a).toBe(50); + this.a = 60; + expect(this.a).toBe(60); + } + } + + function d1(target) { + target.a = 100; + } +}); + +test("decorated fields moving to constructor", () => { + class A { + @d1 a = 3; + @d2 b = 4; + @d3 c = 5; + } + + function d1(target, propertyKey) { + expect(target === A.prototype).toBe(true); + expect(propertyKey).toBe("a"); + } + + function d2(target, propertyKey) { + expect(target === A.prototype).toBe(true); + expect(propertyKey).toBe("b"); + } + + function d3(target, propertyKey) { + expect(target === A.prototype).toBe(true); + expect(propertyKey).toBe("c"); + } + + let a = new A(); + expect(a.a).toBe(3); + expect(a.b).toBe(4); + expect(a.c).toBe(5); +}); + +test("only class decorator", () => { + let a = 0; + @d1 + class A {} + + let aa = new A(); + + function d1(target) { + a = 1; + expect(target).toBe(A); + } + + expect(a).toBe(1); +}); + +test("only property decorators", () => { + let a = 0; + class A { + @d1 a() {} + } + + let b = 0; + class B { + @d2 b = 3; + } + + let c = 0; + class C { + @d3 get c() { + return 3; + } + } + + function d1(target, propertyKey) { + a = 1; + expect(target === A.prototype).toBe(true); + expect(propertyKey).toBe("a"); + } + expect(a).toBe(1); + + function d2(target, propertyKey) { + b = 1; + expect(target === B.prototype).toBe(true); + expect(propertyKey).toBe("b"); + } + expect(b).toBe(1); + + function d3(target, propertyKey) { + c = 1; + expect(target === C.prototype).toBe(true); + expect(propertyKey).toBe("c"); + } + expect(c).toBe(1); +}); + +test("only argument decorators", () => { + let a = 0; + class A { + a(@d1 a: string) {} + } + + function d1(target, propertyKey, parameterIndex) { + a = 1; + expect(target === A.prototype).toBe(true); + expect(propertyKey).toBe("a"); + expect(parameterIndex).toBe(0); + } + + expect(a).toBe(1); +}); + +test("no decorators", () => { + let a = 0; + class A { + b: number; + constructor() { + a = 1; + this.b = 300000; + } + } + + let aa = new A(); + expect(a).toBe(1); + expect(aa.b).toBe(300000); +}); diff --git a/test/scripts/snippets.json b/test/scripts/snippets.json index ebdec23d3..1829eb9c4 100644 --- a/test/scripts/snippets.json +++ b/test/scripts/snippets.json @@ -25,6 +25,7 @@ "/optional-chain-with-function.js", "/template-literal.js", "/number-literal-bug.js", + "/simple-lit-example.ts", "/caught-require.js", "/package-json-utf8.js", "/multiple-var.js", diff --git a/test/snippets/package.json b/test/snippets/package.json index 07b349f86..0c05b97be 100644 --- a/test/snippets/package.json +++ b/test/snippets/package.json @@ -4,6 +4,7 @@ "dependencies": { "@emotion/core": "^11.0.0", "@emotion/react": "^11.4.1", + "lit": "^2.4.0", "lodash": "^4.17.21", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/test/snippets/simple-lit-example.ts b/test/snippets/simple-lit-example.ts new file mode 100644 index 000000000..34446e418 --- /dev/null +++ b/test/snippets/simple-lit-example.ts @@ -0,0 +1,76 @@ +import { LitElement, html, css } from "lit"; +import { customElement, property, eventOptions } from "lit/decorators.js"; + +var loadedResolve; +var loadedPromise = new Promise((resolve) => { + loadedResolve = resolve; +}); + +if (document?.readyState === "loading") { + document.addEventListener( + "DOMContentLoaded", + () => { + loadedResolve(); + }, + { once: true } + ); +} else { + loadedResolve(); +} + +@customElement("my-element") +export class MyElement extends LitElement { + static styles = css` + :host { + display: inline-block; + padding: 10px; + background: lightgray; + } + .planet { + color: var(--planet-color, blue); + } + `; + + @property() planet = "Earth"; + + render() { + return html` + <span @click=${this.togglePlanet} class="planet" id="planet-id" + >${this.planet}</span + > + `; + } + + @eventOptions({ once: true }) + togglePlanet() { + this.planet = this.planet === "Earth" ? "Mars" : "Earth"; + } +} + +function setup() { + let element = document.createElement("my-element"); + element.id = "my-element-id"; + document.body.appendChild(element); +} + +export async function test() { + setup(); + await loadedPromise; + + let element = document.getElementById("my-element-id"); + let shadowRoot = element.shadowRoot; + let planet = shadowRoot.getElementById("planet-id"); + if (element.__planet !== "Earth") { + throw new Error("Unexpected planet name: " + element.__planet); + } + planet.click(); + if (element.__planet !== "Mars") { + throw new Error("Unexpected planet name: " + element.__planet); + } + planet.click(); + if (element.__planet !== "Mars") { + throw new Error("Unexpected planet name: " + element.__planet); + } + + return testDone(import.meta.url); +} |