@praxisjs/decorators 0.4.1 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/dist/__tests__/component.test.d.ts +2 -0
- package/dist/__tests__/component.test.d.ts.map +1 -0
- package/dist/__tests__/component.test.js +137 -0
- package/dist/__tests__/component.test.js.map +1 -0
- package/dist/__tests__/events.test.d.ts +2 -0
- package/dist/__tests__/events.test.d.ts.map +1 -0
- package/dist/__tests__/events.test.js +190 -0
- package/dist/__tests__/events.test.js.map +1 -0
- package/dist/__tests__/functions.test.d.ts +2 -0
- package/dist/__tests__/functions.test.d.ts.map +1 -0
- package/dist/__tests__/functions.test.js +427 -0
- package/dist/__tests__/functions.test.js.map +1 -0
- package/dist/__tests__/lazy.test.d.ts +2 -0
- package/dist/__tests__/lazy.test.d.ts.map +1 -0
- package/dist/__tests__/lazy.test.js +165 -0
- package/dist/__tests__/lazy.test.js.map +1 -0
- package/dist/__tests__/memo.test.d.ts +2 -0
- package/dist/__tests__/memo.test.d.ts.map +1 -0
- package/dist/__tests__/memo.test.js +79 -0
- package/dist/__tests__/memo.test.js.map +1 -0
- package/dist/__tests__/properties-browser.test.d.ts +2 -0
- package/dist/__tests__/properties-browser.test.d.ts.map +1 -0
- package/dist/__tests__/properties-browser.test.js +178 -0
- package/dist/__tests__/properties-browser.test.js.map +1 -0
- package/dist/__tests__/properties.test.d.ts +2 -0
- package/dist/__tests__/properties.test.d.ts.map +1 -0
- package/dist/__tests__/properties.test.js +571 -0
- package/dist/__tests__/properties.test.js.map +1 -0
- package/dist/__tests__/slot.test.d.ts +2 -0
- package/dist/__tests__/slot.test.d.ts.map +1 -0
- package/dist/__tests__/slot.test.js +115 -0
- package/dist/__tests__/slot.test.js.map +1 -0
- package/dist/__tests__/virtual.test.d.ts +2 -0
- package/dist/__tests__/virtual.test.d.ts.map +1 -0
- package/dist/__tests__/virtual.test.js +201 -0
- package/dist/__tests__/virtual.test.js.map +1 -0
- package/dist/component/lazy.d.ts +1 -1
- package/dist/component/lazy.d.ts.map +1 -1
- package/dist/component/lazy.js +4 -1
- package/dist/component/lazy.js.map +1 -1
- package/dist/component/virtual.d.ts +1 -1
- package/dist/component/virtual.d.ts.map +1 -1
- package/dist/functions/watch.d.ts.map +1 -1
- package/dist/functions/watch.js +10 -2
- package/dist/functions/watch.js.map +1 -1
- package/dist/properties/history.d.ts.map +1 -1
- package/dist/properties/history.js +4 -8
- package/dist/properties/history.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/component.test.ts +156 -0
- package/src/__tests__/events.test.ts +236 -0
- package/src/__tests__/functions.test.ts +549 -0
- package/src/__tests__/lazy.test.ts +220 -0
- package/src/__tests__/memo.test.ts +90 -0
- package/src/__tests__/properties-browser.test.ts +234 -0
- package/src/__tests__/properties.test.ts +725 -0
- package/src/__tests__/slot.test.ts +141 -0
- package/src/__tests__/virtual.test.ts +241 -0
- package/src/component/lazy.ts +4 -2
- package/src/component/virtual.tsx +1 -1
- package/src/functions/watch.ts +11 -2
- package/src/properties/history.ts +4 -8
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { StatefulComponent, StatelessComponent } from "@praxisjs/core";
|
|
4
|
+
|
|
5
|
+
import { Component } from "../component/component";
|
|
6
|
+
|
|
7
|
+
// ── Component decorator ───────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
describe("Component decorator", () => {
|
|
10
|
+
it("sets __isComponent = true on the enhanced class", () => {
|
|
11
|
+
class MyComp extends StatefulComponent {
|
|
12
|
+
render() { return null; }
|
|
13
|
+
}
|
|
14
|
+
const Enhanced = Component()(MyComp, {} as ClassDecoratorContext);
|
|
15
|
+
expect(Enhanced.__isComponent).toBe(true);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("preserves the original class name", () => {
|
|
19
|
+
class MyButton extends StatefulComponent {
|
|
20
|
+
render() { return null; }
|
|
21
|
+
}
|
|
22
|
+
const Enhanced = Component()(MyButton, {} as ClassDecoratorContext);
|
|
23
|
+
expect(Enhanced.name).toBe("MyButton");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("marks __isStateless = false for StatefulComponent subclasses", () => {
|
|
27
|
+
class MyStateful extends StatefulComponent {
|
|
28
|
+
render() { return null; }
|
|
29
|
+
}
|
|
30
|
+
const Enhanced = Component()(MyStateful, {} as ClassDecoratorContext);
|
|
31
|
+
expect(Enhanced.__isStateless).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("marks __isStateless = true for StatelessComponent subclasses", () => {
|
|
35
|
+
class MyStateless extends StatelessComponent {
|
|
36
|
+
render() { return null; }
|
|
37
|
+
}
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
39
|
+
const Enhanced = Component()(MyStateless as any, {} as ClassDecoratorContext);
|
|
40
|
+
expect(Enhanced.__isStateless).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("enhanced class can still be instantiated", () => {
|
|
44
|
+
class Counter extends StatefulComponent {
|
|
45
|
+
render() { return null; }
|
|
46
|
+
}
|
|
47
|
+
const Enhanced = Component()(Counter, {} as ClassDecoratorContext);
|
|
48
|
+
const instance = new Enhanced();
|
|
49
|
+
expect(instance).toBeInstanceOf(Counter);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("returns a subclass of the original constructor", () => {
|
|
53
|
+
class Base extends StatefulComponent {
|
|
54
|
+
render() { return null; }
|
|
55
|
+
}
|
|
56
|
+
const Enhanced = Component()(Base, {} as ClassDecoratorContext);
|
|
57
|
+
expect(Enhanced.prototype).toBeInstanceOf(Base);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// ── Happy paths ──────────────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
it("forwards constructor args — props reach the base constructor", () => {
|
|
63
|
+
class WithProps extends StatefulComponent {
|
|
64
|
+
render() { return null; }
|
|
65
|
+
}
|
|
66
|
+
const Enhanced = Component()(WithProps, {} as ClassDecoratorContext);
|
|
67
|
+
const instance = new Enhanced({ greeting: "hello" });
|
|
68
|
+
expect(instance.props.greeting).toBe("hello");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("instance methods from the original class remain accessible", () => {
|
|
72
|
+
class Greeter extends StatefulComponent {
|
|
73
|
+
greet() { return "hello"; }
|
|
74
|
+
render() { return null; }
|
|
75
|
+
}
|
|
76
|
+
const Enhanced = Component()(Greeter, {} as ClassDecoratorContext);
|
|
77
|
+
const instance = new Enhanced();
|
|
78
|
+
expect((instance as Greeter).greet()).toBe("hello");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("static properties of the original class are preserved on Enhanced", () => {
|
|
82
|
+
class WithStatic extends StatefulComponent {
|
|
83
|
+
static readonly version = "1.0";
|
|
84
|
+
render() { return null; }
|
|
85
|
+
}
|
|
86
|
+
const Enhanced = Component()(WithStatic, {} as ClassDecoratorContext);
|
|
87
|
+
expect((Enhanced as typeof WithStatic).version).toBe("1.0");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// ── Edge cases ───────────────────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
it("applying Component() twice preserves name and flags", () => {
|
|
93
|
+
class MyComp extends StatefulComponent {
|
|
94
|
+
render() { return null; }
|
|
95
|
+
}
|
|
96
|
+
const E1 = Component()(MyComp, {} as ClassDecoratorContext);
|
|
97
|
+
const E2 = Component()(E1, {} as ClassDecoratorContext);
|
|
98
|
+
expect(E2.__isComponent).toBe(true);
|
|
99
|
+
expect(E2.__isStateless).toBe(false);
|
|
100
|
+
expect(E2.name).toBe("MyComp"); // name survives double-wrap
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("deep StatelessComponent inheritance — __isStateless is still true", () => {
|
|
104
|
+
class BaseStateless extends StatelessComponent {
|
|
105
|
+
render() { return null; }
|
|
106
|
+
}
|
|
107
|
+
class DeepChild extends BaseStateless {
|
|
108
|
+
render() { return null; }
|
|
109
|
+
}
|
|
110
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
111
|
+
const Enhanced = Component()(DeepChild as any, {} as ClassDecoratorContext);
|
|
112
|
+
expect(Enhanced.__isStateless).toBe(true);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("anonymous class gets an empty name", () => {
|
|
116
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
117
|
+
const Enhanced = Component()(
|
|
118
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
119
|
+
class extends StatefulComponent { render() { return null; } } as any,
|
|
120
|
+
{} as ClassDecoratorContext,
|
|
121
|
+
);
|
|
122
|
+
expect(Enhanced.name).toBe("");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("__isComponent and __isStateless are static — not own properties of instances", () => {
|
|
126
|
+
class MyComp extends StatefulComponent {
|
|
127
|
+
render() { return null; }
|
|
128
|
+
}
|
|
129
|
+
const Enhanced = Component()(MyComp, {} as ClassDecoratorContext);
|
|
130
|
+
const instance = new Enhanced();
|
|
131
|
+
// Static fields live on the class, not on instances
|
|
132
|
+
expect(Object.prototype.hasOwnProperty.call(instance, "__isComponent")).toBe(false);
|
|
133
|
+
expect(Object.prototype.hasOwnProperty.call(instance, "__isStateless")).toBe(false);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("subclassing Enhanced inherits __isComponent and __isStateless", () => {
|
|
137
|
+
class MyComp extends StatefulComponent {
|
|
138
|
+
render() { return null; }
|
|
139
|
+
}
|
|
140
|
+
const Enhanced = Component()(MyComp, {} as ClassDecoratorContext);
|
|
141
|
+
// `class Sub extends Enhanced` sets Object.getPrototypeOf(Sub) === Enhanced,
|
|
142
|
+
// making static fields visible through the chain. Simulate that here to
|
|
143
|
+
// avoid TS2510 (conflicting constructor return types in the intersection type).
|
|
144
|
+
const Sub = Object.create(Enhanced) as typeof Enhanced;
|
|
145
|
+
expect(Sub.__isComponent).toBe(true);
|
|
146
|
+
expect(Sub.__isStateless).toBe(false);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("Enhanced is a different reference from the original constructor", () => {
|
|
150
|
+
class MyComp extends StatefulComponent {
|
|
151
|
+
render() { return null; }
|
|
152
|
+
}
|
|
153
|
+
const Enhanced = Component()(MyComp, {} as ClassDecoratorContext);
|
|
154
|
+
expect(Enhanced).not.toBe(MyComp);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { StatefulComponent } from "@praxisjs/core";
|
|
4
|
+
|
|
5
|
+
import { createCommand } from "../events/command";
|
|
6
|
+
import { Emit } from "../events/emit";
|
|
7
|
+
import { readProp } from "../events/helper";
|
|
8
|
+
import { OnCommand } from "../events/on-command";
|
|
9
|
+
|
|
10
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
class TestComponent extends StatefulComponent {
|
|
13
|
+
render() { return null; }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function methodCtx(name: string) {
|
|
17
|
+
const initializers: Array<(this: unknown) => void> = [];
|
|
18
|
+
return {
|
|
19
|
+
ctx: {
|
|
20
|
+
name,
|
|
21
|
+
kind: "method" as const,
|
|
22
|
+
addInitializer(fn: (this: unknown) => void) { initializers.push(fn); },
|
|
23
|
+
} as ClassMethodDecoratorContext,
|
|
24
|
+
run(instance: unknown) { initializers.forEach((fn) => { fn.call(instance); }); },
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ── createCommand ─────────────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
describe("createCommand", () => {
|
|
31
|
+
it("trigger calls all subscribed handlers with the argument", () => {
|
|
32
|
+
const cmd = createCommand<number>();
|
|
33
|
+
const h1 = vi.fn();
|
|
34
|
+
const h2 = vi.fn();
|
|
35
|
+
cmd.subscribe(h1);
|
|
36
|
+
cmd.subscribe(h2);
|
|
37
|
+
cmd.trigger(42);
|
|
38
|
+
expect(h1).toHaveBeenCalledWith(42);
|
|
39
|
+
expect(h2).toHaveBeenCalledWith(42);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("trigger does nothing when no handlers are subscribed", () => {
|
|
43
|
+
const cmd = createCommand<string>();
|
|
44
|
+
expect(() => cmd.trigger("hello")).not.toThrow();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("subscribe returns an unsubscribe function", () => {
|
|
48
|
+
const cmd = createCommand<void>();
|
|
49
|
+
const handler = vi.fn();
|
|
50
|
+
const unsub = cmd.subscribe(handler);
|
|
51
|
+
unsub();
|
|
52
|
+
cmd.trigger();
|
|
53
|
+
expect(handler).not.toHaveBeenCalled();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("void command can be triggered without args", () => {
|
|
57
|
+
const cmd = createCommand();
|
|
58
|
+
const handler = vi.fn();
|
|
59
|
+
cmd.subscribe(handler);
|
|
60
|
+
cmd.trigger(undefined as unknown as void);
|
|
61
|
+
expect(handler).toHaveBeenCalledOnce();
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// ── readProp ──────────────────────────────────────────────────────────────────
|
|
66
|
+
|
|
67
|
+
describe("readProp", () => {
|
|
68
|
+
it("returns value from _rawProps when defined", () => {
|
|
69
|
+
const instance = new TestComponent({ myProp: "fromParent" });
|
|
70
|
+
expect(readProp(instance, "myProp")).toBe("fromParent");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("falls back to _defaults when _rawProps does not define the prop", () => {
|
|
74
|
+
const instance = new TestComponent();
|
|
75
|
+
instance._defaults.myProp = "default";
|
|
76
|
+
expect(readProp(instance, "myProp")).toBe("default");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("returns undefined when prop is not in either", () => {
|
|
80
|
+
const instance = new TestComponent();
|
|
81
|
+
expect(readProp(instance, "missing")).toBeUndefined();
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// ── Emit ─────────────────────────────────────────────────────────────────────
|
|
86
|
+
|
|
87
|
+
describe("Emit decorator", () => {
|
|
88
|
+
it("calls the prop callback with the return value of the method", () => {
|
|
89
|
+
const { ctx, run } = methodCtx("handleInput");
|
|
90
|
+
const original = function (this: object, value: string) { return value.toUpperCase(); };
|
|
91
|
+
Emit("onChange")(original as never, ctx as unknown as ClassMethodDecoratorContext<StatefulComponent>);
|
|
92
|
+
|
|
93
|
+
const callback = vi.fn();
|
|
94
|
+
const instance = new TestComponent({ onChange: callback });
|
|
95
|
+
run(instance);
|
|
96
|
+
|
|
97
|
+
const method = (instance as unknown as Record<string, (v: string) => void>).handleInput;
|
|
98
|
+
method.call(instance, "hello");
|
|
99
|
+
expect(callback).toHaveBeenCalledWith("HELLO");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("calls the prop callback with the method args when return value is undefined", () => {
|
|
103
|
+
const { ctx, run } = methodCtx("doSomething");
|
|
104
|
+
const original = function (this: object, a: number, b: number) { return undefined; };
|
|
105
|
+
Emit("onDo")(original as never, ctx as unknown as ClassMethodDecoratorContext<StatefulComponent>);
|
|
106
|
+
|
|
107
|
+
const callback = vi.fn();
|
|
108
|
+
const instance = new TestComponent({ onDo: callback });
|
|
109
|
+
run(instance);
|
|
110
|
+
|
|
111
|
+
const method = (instance as unknown as Record<string, (a: number, b: number) => void>).doSomething;
|
|
112
|
+
method.call(instance, 1, 2);
|
|
113
|
+
expect(callback).toHaveBeenCalledWith(1, 2);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("calls the prop callback with no args when return is undefined and no args", () => {
|
|
117
|
+
const { ctx, run } = methodCtx("fire");
|
|
118
|
+
const original = function (this: object) { return undefined; };
|
|
119
|
+
Emit("onFire")(original as never, ctx as unknown as ClassMethodDecoratorContext<StatefulComponent>);
|
|
120
|
+
|
|
121
|
+
const callback = vi.fn();
|
|
122
|
+
const instance = new TestComponent({ onFire: callback });
|
|
123
|
+
run(instance);
|
|
124
|
+
|
|
125
|
+
const method = (instance as unknown as Record<string, () => void>).fire;
|
|
126
|
+
method.call(instance);
|
|
127
|
+
expect(callback).toHaveBeenCalledOnce();
|
|
128
|
+
expect(callback).toHaveBeenCalledWith();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("does not throw when the prop callback is not a function", () => {
|
|
132
|
+
const { ctx, run } = methodCtx("submit");
|
|
133
|
+
const original = function (this: object) { return "result"; };
|
|
134
|
+
Emit("onSubmit")(original as never, ctx as unknown as ClassMethodDecoratorContext<StatefulComponent>);
|
|
135
|
+
|
|
136
|
+
const instance = new TestComponent(); // no onSubmit prop
|
|
137
|
+
run(instance);
|
|
138
|
+
|
|
139
|
+
const method = (instance as unknown as Record<string, () => void>).submit;
|
|
140
|
+
expect(() => method.call(instance)).not.toThrow();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("still returns the original method's return value", () => {
|
|
144
|
+
const { ctx, run } = methodCtx("compute");
|
|
145
|
+
const original = function (this: object) { return 99; };
|
|
146
|
+
Emit("onResult")(original as never, ctx as unknown as ClassMethodDecoratorContext<StatefulComponent>);
|
|
147
|
+
|
|
148
|
+
const callback = vi.fn();
|
|
149
|
+
const instance = new TestComponent({ onResult: callback });
|
|
150
|
+
run(instance);
|
|
151
|
+
|
|
152
|
+
const method = (instance as unknown as Record<string, () => number>).compute;
|
|
153
|
+
const result = method.call(instance);
|
|
154
|
+
expect(result).toBe(99);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// ── OnCommand ─────────────────────────────────────────────────────────────────
|
|
159
|
+
|
|
160
|
+
describe("OnCommand decorator", () => {
|
|
161
|
+
it("subscribes to the command on mount and calls the method when triggered", () => {
|
|
162
|
+
const { ctx, run } = methodCtx("handleCmd");
|
|
163
|
+
const handler = vi.fn();
|
|
164
|
+
OnCommand("myCommand")(handler as never, ctx as unknown as ClassMethodDecoratorContext<StatefulComponent>);
|
|
165
|
+
|
|
166
|
+
const cmd = createCommand<string>();
|
|
167
|
+
const instance = new TestComponent({ myCommand: cmd });
|
|
168
|
+
run(instance);
|
|
169
|
+
|
|
170
|
+
instance.onMount?.();
|
|
171
|
+
cmd.trigger("payload");
|
|
172
|
+
expect(handler).toHaveBeenCalledWith("payload");
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("unsubscribes from the command on unmount", () => {
|
|
176
|
+
const { ctx, run } = methodCtx("onMsg");
|
|
177
|
+
const handler = vi.fn();
|
|
178
|
+
OnCommand("msg")(handler as never, ctx as unknown as ClassMethodDecoratorContext<StatefulComponent>);
|
|
179
|
+
|
|
180
|
+
const cmd = createCommand<string>();
|
|
181
|
+
const instance = new TestComponent({ msg: cmd });
|
|
182
|
+
run(instance);
|
|
183
|
+
|
|
184
|
+
instance.onMount?.();
|
|
185
|
+
instance.onUnmount?.();
|
|
186
|
+
cmd.trigger("after-unmount");
|
|
187
|
+
expect(handler).not.toHaveBeenCalled();
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("warns when the command prop is not provided", () => {
|
|
191
|
+
const { ctx, run } = methodCtx("onMissing");
|
|
192
|
+
OnCommand("missingProp")(vi.fn() as never, ctx as unknown as ClassMethodDecoratorContext<StatefulComponent>);
|
|
193
|
+
|
|
194
|
+
const instance = new TestComponent(); // no missingProp
|
|
195
|
+
run(instance);
|
|
196
|
+
|
|
197
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
198
|
+
instance.onMount?.();
|
|
199
|
+
expect(warn).toHaveBeenCalledWith(expect.stringContaining("[OnCommand]"));
|
|
200
|
+
warn.mockRestore();
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("warns when the command prop is not a valid Command", () => {
|
|
204
|
+
const { ctx, run } = methodCtx("onBadCmd");
|
|
205
|
+
OnCommand("badProp")(vi.fn() as never, ctx as unknown as ClassMethodDecoratorContext<StatefulComponent>);
|
|
206
|
+
|
|
207
|
+
const instance = new TestComponent({ badProp: "not-a-command" });
|
|
208
|
+
run(instance);
|
|
209
|
+
|
|
210
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
211
|
+
instance.onMount?.();
|
|
212
|
+
expect(warn).toHaveBeenCalledWith(expect.stringContaining("[OnCommand]"));
|
|
213
|
+
warn.mockRestore();
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it("preserves the original onMount and onUnmount implementations", () => {
|
|
217
|
+
const { ctx, run } = methodCtx("onEvt");
|
|
218
|
+
OnCommand("evt")(vi.fn() as never, ctx as unknown as ClassMethodDecoratorContext<StatefulComponent>);
|
|
219
|
+
|
|
220
|
+
const cmd = createCommand<void>();
|
|
221
|
+
const instance = new TestComponent({ evt: cmd });
|
|
222
|
+
run(instance);
|
|
223
|
+
|
|
224
|
+
const mountSpy = vi.fn();
|
|
225
|
+
const unmountSpy = vi.fn();
|
|
226
|
+
const prevMount = instance.onMount?.bind(instance);
|
|
227
|
+
const prevUnmount = instance.onUnmount?.bind(instance);
|
|
228
|
+
instance.onMount = function () { mountSpy(); prevMount?.(); };
|
|
229
|
+
instance.onUnmount = function () { unmountSpy(); prevUnmount?.(); };
|
|
230
|
+
|
|
231
|
+
instance.onMount();
|
|
232
|
+
instance.onUnmount();
|
|
233
|
+
expect(mountSpy).toHaveBeenCalled();
|
|
234
|
+
expect(unmountSpy).toHaveBeenCalled();
|
|
235
|
+
});
|
|
236
|
+
});
|