@praxisjs/decorators 0.4.3 → 0.5.0
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 +25 -0
- package/dist/__tests__/component.test.js +10 -1
- package/dist/__tests__/component.test.js.map +1 -1
- package/dist/__tests__/compose.test.d.ts +2 -0
- package/dist/__tests__/compose.test.d.ts.map +1 -0
- package/dist/__tests__/compose.test.js +171 -0
- package/dist/__tests__/compose.test.js.map +1 -0
- package/dist/__tests__/events.test.js +26 -0
- package/dist/__tests__/events.test.js.map +1 -1
- package/dist/__tests__/functions.test.js +142 -47
- package/dist/__tests__/functions.test.js.map +1 -1
- package/dist/__tests__/lazy.test.js +5 -7
- package/dist/__tests__/lazy.test.js.map +1 -1
- package/dist/__tests__/memo.test.js +61 -25
- package/dist/__tests__/memo.test.js.map +1 -1
- package/dist/__tests__/resource.test.d.ts +2 -0
- package/dist/__tests__/resource.test.d.ts.map +1 -0
- package/dist/__tests__/resource.test.js +103 -0
- package/dist/__tests__/resource.test.js.map +1 -0
- package/dist/__tests__/slot.test.js +32 -0
- package/dist/__tests__/slot.test.js.map +1 -1
- package/dist/__tests__/virtual.test.js +56 -15
- package/dist/__tests__/virtual.test.js.map +1 -1
- package/dist/component/component.d.ts +1 -1
- package/dist/component/component.d.ts.map +1 -1
- package/dist/component/component.js +15 -10
- package/dist/component/component.js.map +1 -1
- package/dist/component/lazy.d.ts.map +1 -1
- package/dist/component/lazy.js +29 -30
- package/dist/component/lazy.js.map +1 -1
- package/dist/component/virtual.d.ts.map +1 -1
- package/dist/component/virtual.js +46 -50
- package/dist/component/virtual.js.map +1 -1
- package/dist/create-class-decorator.d.ts +16 -0
- package/dist/create-class-decorator.d.ts.map +1 -0
- package/dist/create-class-decorator.js +35 -0
- package/dist/create-class-decorator.js.map +1 -0
- package/dist/create-field-decorator.d.ts +14 -0
- package/dist/create-field-decorator.d.ts.map +1 -0
- package/dist/create-field-decorator.js +37 -0
- package/dist/create-field-decorator.js.map +1 -0
- package/dist/create-getter-decorator.d.ts +18 -0
- package/dist/create-getter-decorator.d.ts.map +1 -0
- package/dist/create-getter-decorator.js +15 -0
- package/dist/create-getter-decorator.js.map +1 -0
- package/dist/create-lifecycle-method-decorator.d.ts +6 -0
- package/dist/create-lifecycle-method-decorator.d.ts.map +1 -0
- package/dist/create-lifecycle-method-decorator.js +22 -0
- package/dist/create-lifecycle-method-decorator.js.map +1 -0
- package/dist/create-method-decorator.d.ts +6 -0
- package/dist/create-method-decorator.d.ts.map +1 -0
- package/dist/create-method-decorator.js +14 -0
- package/dist/create-method-decorator.js.map +1 -0
- package/dist/events/emit.d.ts +1 -1
- package/dist/events/emit.d.ts.map +1 -1
- package/dist/events/emit.js +24 -29
- package/dist/events/emit.js.map +1 -1
- package/dist/events/on-command.d.ts +1 -2
- package/dist/events/on-command.d.ts.map +1 -1
- package/dist/events/on-command.js +15 -32
- package/dist/events/on-command.js.map +1 -1
- package/dist/functions/bind.d.ts +1 -2
- package/dist/functions/bind.d.ts.map +1 -1
- package/dist/functions/bind.js +4 -11
- package/dist/functions/bind.js.map +1 -1
- package/dist/functions/debounce.d.ts +1 -2
- package/dist/functions/debounce.d.ts.map +1 -1
- package/dist/functions/debounce.js +12 -20
- package/dist/functions/debounce.js.map +1 -1
- package/dist/functions/log.d.ts +1 -2
- package/dist/functions/log.d.ts.map +1 -1
- package/dist/functions/log.js +40 -38
- package/dist/functions/log.js.map +1 -1
- package/dist/functions/memo.d.ts +1 -2
- package/dist/functions/memo.d.ts.map +1 -1
- package/dist/functions/memo.js +15 -27
- package/dist/functions/memo.js.map +1 -1
- package/dist/functions/once.d.ts +1 -2
- package/dist/functions/once.d.ts.map +1 -1
- package/dist/functions/once.js +14 -12
- package/dist/functions/once.js.map +1 -1
- package/dist/functions/retry.d.ts +1 -2
- package/dist/functions/retry.d.ts.map +1 -1
- package/dist/functions/retry.js +23 -20
- package/dist/functions/retry.js.map +1 -1
- package/dist/functions/throttle.d.ts +1 -2
- package/dist/functions/throttle.d.ts.map +1 -1
- package/dist/functions/throttle.js +9 -16
- package/dist/functions/throttle.js.map +1 -1
- package/dist/functions/watch.d.ts +1 -1
- package/dist/functions/watch.d.ts.map +1 -1
- package/dist/functions/watch.js +25 -39
- package/dist/functions/watch.js.map +1 -1
- package/dist/functions/when.d.ts +1 -2
- package/dist/functions/when.d.ts.map +1 -1
- package/dist/functions/when.js +10 -26
- package/dist/functions/when.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/properties/compose.d.ts +3 -0
- package/dist/properties/compose.d.ts.map +1 -0
- package/dist/properties/compose.js +40 -0
- package/dist/properties/compose.js.map +1 -0
- package/dist/properties/computed.d.ts +1 -2
- package/dist/properties/computed.d.ts.map +1 -1
- package/dist/properties/computed.js +10 -9
- package/dist/properties/computed.js.map +1 -1
- package/dist/properties/history.d.ts.map +1 -1
- package/dist/properties/history.js +37 -32
- package/dist/properties/history.js.map +1 -1
- package/dist/properties/index.d.ts +2 -0
- package/dist/properties/index.d.ts.map +1 -1
- package/dist/properties/index.js +2 -0
- package/dist/properties/index.js.map +1 -1
- package/dist/properties/persisted.d.ts +1 -2
- package/dist/properties/persisted.d.ts.map +1 -1
- package/dist/properties/persisted.js +13 -29
- package/dist/properties/persisted.js.map +1 -1
- package/dist/properties/prop.d.ts +1 -2
- package/dist/properties/prop.d.ts.map +1 -1
- package/dist/properties/prop.js +21 -25
- package/dist/properties/prop.js.map +1 -1
- package/dist/properties/resource.d.ts +14 -0
- package/dist/properties/resource.d.ts.map +1 -0
- package/dist/properties/resource.js +27 -0
- package/dist/properties/resource.js.map +1 -0
- package/dist/properties/slot.d.ts +1 -2
- package/dist/properties/slot.d.ts.map +1 -1
- package/dist/properties/slot.js +15 -20
- package/dist/properties/slot.js.map +1 -1
- package/dist/properties/state.d.ts +1 -2
- package/dist/properties/state.d.ts.map +1 -1
- package/dist/properties/state.js +13 -18
- package/dist/properties/state.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/component.test.ts +10 -0
- package/src/__tests__/compose.test.ts +202 -0
- package/src/__tests__/events.test.ts +32 -0
- package/src/__tests__/functions.test.ts +177 -46
- package/src/__tests__/lazy.test.ts +3 -5
- package/src/__tests__/memo.test.ts +76 -25
- package/src/__tests__/resource.test.ts +113 -0
- package/src/__tests__/slot.test.ts +38 -0
- package/src/__tests__/virtual.test.ts +26 -7
- package/src/component/component.ts +27 -12
- package/src/component/lazy.ts +33 -37
- package/src/component/virtual.tsx +62 -75
- package/src/create-class-decorator.ts +61 -0
- package/src/create-field-decorator.ts +66 -0
- package/src/create-getter-decorator.ts +52 -0
- package/src/create-lifecycle-method-decorator.ts +38 -0
- package/src/create-method-decorator.ts +26 -0
- package/src/events/emit.ts +22 -33
- package/src/events/on-command.ts +22 -47
- package/src/functions/bind.ts +4 -15
- package/src/functions/debounce.ts +11 -26
- package/src/functions/log.ts +40 -43
- package/src/functions/memo.ts +15 -45
- package/src/functions/once.ts +13 -16
- package/src/functions/retry.ts +21 -22
- package/src/functions/throttle.ts +9 -23
- package/src/functions/watch.ts +27 -43
- package/src/functions/when.ts +11 -36
- package/src/index.ts +28 -0
- package/src/properties/compose.ts +53 -0
- package/src/properties/computed.ts +11 -13
- package/src/properties/history.ts +42 -41
- package/src/properties/index.ts +2 -0
- package/src/properties/persisted.ts +17 -58
- package/src/properties/prop.ts +21 -31
- package/src/properties/resource.ts +34 -0
- package/src/properties/slot.ts +17 -30
- package/src/properties/state.ts +14 -24
|
@@ -29,11 +29,14 @@ describe("Once", () => {
|
|
|
29
29
|
it("only calls the original function once", () => {
|
|
30
30
|
const original = vi.fn(() => 42);
|
|
31
31
|
const ctx = mockMethodContext("doSomething");
|
|
32
|
-
|
|
32
|
+
Once()(original, ctx as unknown as ClassMethodDecoratorContext);
|
|
33
33
|
|
|
34
34
|
const obj = {};
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
ctx.runInitializers(obj);
|
|
36
|
+
const method = (obj as Record<string, () => unknown>).doSomething;
|
|
37
|
+
|
|
38
|
+
expect(method()).toBe(42);
|
|
39
|
+
expect(method()).toBe(42);
|
|
37
40
|
expect(original).toHaveBeenCalledTimes(1);
|
|
38
41
|
});
|
|
39
42
|
|
|
@@ -41,24 +44,32 @@ describe("Once", () => {
|
|
|
41
44
|
let counter = 0;
|
|
42
45
|
const original = () => ++counter;
|
|
43
46
|
const ctx = mockMethodContext("inc");
|
|
44
|
-
|
|
47
|
+
Once()(original, ctx as unknown as ClassMethodDecoratorContext);
|
|
45
48
|
|
|
46
49
|
const obj = {};
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
ctx.runInitializers(obj);
|
|
51
|
+
const method = (obj as Record<string, () => unknown>).inc;
|
|
52
|
+
|
|
53
|
+
method();
|
|
54
|
+
method();
|
|
55
|
+
method();
|
|
50
56
|
expect(counter).toBe(1);
|
|
51
57
|
});
|
|
52
58
|
|
|
53
59
|
it("is per-instance — different instances call independently", () => {
|
|
54
60
|
const original = vi.fn(() => "result");
|
|
55
61
|
const ctx = mockMethodContext("fn");
|
|
56
|
-
|
|
62
|
+
Once()(original, ctx as unknown as ClassMethodDecoratorContext);
|
|
57
63
|
|
|
58
64
|
const a = {};
|
|
59
65
|
const b = {};
|
|
60
|
-
|
|
61
|
-
|
|
66
|
+
ctx.runInitializers(a);
|
|
67
|
+
ctx.runInitializers(b);
|
|
68
|
+
|
|
69
|
+
const methodA = (a as Record<string, () => unknown>).fn;
|
|
70
|
+
const methodB = (b as Record<string, () => unknown>).fn;
|
|
71
|
+
methodA();
|
|
72
|
+
methodB();
|
|
62
73
|
expect(original).toHaveBeenCalledTimes(2);
|
|
63
74
|
});
|
|
64
75
|
});
|
|
@@ -69,9 +80,13 @@ describe("Retry", () => {
|
|
|
69
80
|
it("returns result if the first attempt succeeds", async () => {
|
|
70
81
|
const original = vi.fn(async () => "ok");
|
|
71
82
|
const ctx = mockMethodContext("fetch");
|
|
72
|
-
|
|
83
|
+
Retry(3)(original, ctx as unknown as ClassMethodDecoratorContext);
|
|
73
84
|
|
|
74
|
-
const
|
|
85
|
+
const obj = {};
|
|
86
|
+
ctx.runInitializers(obj);
|
|
87
|
+
const method = (obj as Record<string, () => Promise<unknown>>).fetch;
|
|
88
|
+
|
|
89
|
+
const result = await method();
|
|
75
90
|
expect(result).toBe("ok");
|
|
76
91
|
expect(original).toHaveBeenCalledTimes(1);
|
|
77
92
|
});
|
|
@@ -84,9 +99,13 @@ describe("Retry", () => {
|
|
|
84
99
|
return "success";
|
|
85
100
|
});
|
|
86
101
|
const ctx = mockMethodContext("call");
|
|
87
|
-
|
|
102
|
+
Retry(3)(original, ctx as unknown as ClassMethodDecoratorContext);
|
|
88
103
|
|
|
89
|
-
const
|
|
104
|
+
const obj = {};
|
|
105
|
+
ctx.runInitializers(obj);
|
|
106
|
+
const method = (obj as Record<string, () => Promise<unknown>>).call;
|
|
107
|
+
|
|
108
|
+
const result = await method();
|
|
90
109
|
expect(result).toBe("success");
|
|
91
110
|
expect(original).toHaveBeenCalledTimes(3);
|
|
92
111
|
});
|
|
@@ -96,9 +115,13 @@ describe("Retry", () => {
|
|
|
96
115
|
throw new Error("always fails");
|
|
97
116
|
});
|
|
98
117
|
const ctx = mockMethodContext("bad");
|
|
99
|
-
|
|
118
|
+
Retry(3)(original, ctx as unknown as ClassMethodDecoratorContext);
|
|
100
119
|
|
|
101
|
-
|
|
120
|
+
const obj = {};
|
|
121
|
+
ctx.runInitializers(obj);
|
|
122
|
+
const method = (obj as Record<string, () => Promise<unknown>>).bad;
|
|
123
|
+
|
|
124
|
+
await expect(method()).rejects.toThrow("always fails");
|
|
102
125
|
expect(original).toHaveBeenCalledTimes(3);
|
|
103
126
|
});
|
|
104
127
|
|
|
@@ -106,11 +129,16 @@ describe("Retry", () => {
|
|
|
106
129
|
vi.useFakeTimers();
|
|
107
130
|
const original = vi.fn(async () => { throw new Error("fail"); });
|
|
108
131
|
const ctx = mockMethodContext("delayed");
|
|
109
|
-
|
|
132
|
+
Retry(3, { delay: 100, backoff: 2 })(
|
|
110
133
|
original,
|
|
111
134
|
ctx as unknown as ClassMethodDecoratorContext,
|
|
112
135
|
);
|
|
113
|
-
|
|
136
|
+
|
|
137
|
+
const obj = {};
|
|
138
|
+
ctx.runInitializers(obj);
|
|
139
|
+
const method = (obj as Record<string, () => Promise<unknown>>).delayed;
|
|
140
|
+
|
|
141
|
+
const p = method();
|
|
114
142
|
const rejection = expect(p).rejects.toThrow("fail");
|
|
115
143
|
await vi.runAllTimersAsync();
|
|
116
144
|
await rejection;
|
|
@@ -124,18 +152,26 @@ describe("Retry", () => {
|
|
|
124
152
|
throw new Error("err");
|
|
125
153
|
});
|
|
126
154
|
const ctx = mockMethodContext("op");
|
|
127
|
-
|
|
155
|
+
Retry(3, { onRetry })(original, ctx as unknown as ClassMethodDecoratorContext);
|
|
156
|
+
|
|
157
|
+
const obj = {};
|
|
158
|
+
ctx.runInitializers(obj);
|
|
159
|
+
const method = (obj as Record<string, () => Promise<unknown>>).op;
|
|
128
160
|
|
|
129
|
-
await expect(
|
|
161
|
+
await expect(method()).rejects.toThrow();
|
|
130
162
|
expect(onRetry).toHaveBeenCalledTimes(2); // attempts 1 and 2, not the last
|
|
131
163
|
});
|
|
132
164
|
|
|
133
165
|
it("maxAttempts=1 fails immediately without retrying", async () => {
|
|
134
166
|
const original = vi.fn(async () => { throw new Error("instant fail"); });
|
|
135
167
|
const ctx = mockMethodContext("once");
|
|
136
|
-
|
|
168
|
+
Retry(1)(original, ctx as unknown as ClassMethodDecoratorContext);
|
|
137
169
|
|
|
138
|
-
|
|
170
|
+
const obj = {};
|
|
171
|
+
ctx.runInitializers(obj);
|
|
172
|
+
const method = (obj as Record<string, () => Promise<unknown>>).once;
|
|
173
|
+
|
|
174
|
+
await expect(method()).rejects.toThrow("instant fail");
|
|
139
175
|
expect(original).toHaveBeenCalledTimes(1);
|
|
140
176
|
});
|
|
141
177
|
|
|
@@ -145,17 +181,25 @@ describe("Retry", () => {
|
|
|
145
181
|
throw "string error";
|
|
146
182
|
});
|
|
147
183
|
const ctx = mockMethodContext("strThrow");
|
|
148
|
-
|
|
184
|
+
Retry(1)(original, ctx as unknown as ClassMethodDecoratorContext);
|
|
149
185
|
|
|
150
|
-
|
|
186
|
+
const obj = {};
|
|
187
|
+
ctx.runInitializers(obj);
|
|
188
|
+
const method = (obj as Record<string, () => Promise<unknown>>).strThrow;
|
|
189
|
+
|
|
190
|
+
await expect(method()).rejects.toThrow("string error");
|
|
151
191
|
});
|
|
152
192
|
|
|
153
193
|
it("maxAttempts=0 throws immediately without calling the function", async () => {
|
|
154
194
|
const original = vi.fn(async () => "ok");
|
|
155
195
|
const ctx = mockMethodContext("never");
|
|
156
|
-
|
|
196
|
+
Retry(0)(original, ctx as unknown as ClassMethodDecoratorContext);
|
|
157
197
|
|
|
158
|
-
|
|
198
|
+
const obj = {};
|
|
199
|
+
ctx.runInitializers(obj);
|
|
200
|
+
const method = (obj as Record<string, () => Promise<unknown>>).never;
|
|
201
|
+
|
|
202
|
+
await expect(method()).rejects.toThrow("Unknown error");
|
|
159
203
|
expect(original).not.toHaveBeenCalled();
|
|
160
204
|
});
|
|
161
205
|
|
|
@@ -163,11 +207,15 @@ describe("Retry", () => {
|
|
|
163
207
|
const attempts: number[] = [];
|
|
164
208
|
const original = vi.fn(async () => { throw new Error("fail"); });
|
|
165
209
|
const ctx = mockMethodContext("numbered");
|
|
166
|
-
|
|
210
|
+
Retry(3, {
|
|
167
211
|
onRetry: (_err, attempt) => { attempts.push(attempt); },
|
|
168
212
|
})(original, ctx as unknown as ClassMethodDecoratorContext);
|
|
169
213
|
|
|
170
|
-
|
|
214
|
+
const obj = {};
|
|
215
|
+
ctx.runInitializers(obj);
|
|
216
|
+
const method = (obj as Record<string, () => Promise<unknown>>).numbered;
|
|
217
|
+
|
|
218
|
+
await expect(method()).rejects.toThrow();
|
|
171
219
|
expect(attempts).toEqual([1, 2]); // attempt 1 failed → onRetry(1), attempt 2 failed → onRetry(2)
|
|
172
220
|
});
|
|
173
221
|
|
|
@@ -179,12 +227,16 @@ describe("Retry", () => {
|
|
|
179
227
|
throw new Error("fail");
|
|
180
228
|
});
|
|
181
229
|
const ctx = mockMethodContext("backoffTest");
|
|
182
|
-
|
|
230
|
+
Retry(3, { delay: 100, backoff: 2 })(
|
|
183
231
|
original,
|
|
184
232
|
ctx as unknown as ClassMethodDecoratorContext,
|
|
185
233
|
);
|
|
186
234
|
|
|
187
|
-
const
|
|
235
|
+
const obj = {};
|
|
236
|
+
ctx.runInitializers(obj);
|
|
237
|
+
const method = (obj as Record<string, () => Promise<unknown>>).backoffTest;
|
|
238
|
+
|
|
239
|
+
const p = method();
|
|
188
240
|
const rejection = expect(p).rejects.toThrow("fail");
|
|
189
241
|
await vi.runAllTimersAsync();
|
|
190
242
|
await rejection;
|
|
@@ -205,13 +257,17 @@ describe("Log", () => {
|
|
|
205
257
|
const spy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
206
258
|
const original = vi.fn(() => 99);
|
|
207
259
|
const ctx = mockMethodContext("myMethod");
|
|
208
|
-
|
|
260
|
+
Log({ args: false, result: false })(
|
|
209
261
|
original,
|
|
210
262
|
ctx as unknown as ClassMethodDecoratorContext,
|
|
211
263
|
);
|
|
212
264
|
|
|
213
265
|
class Dummy {}
|
|
214
|
-
const
|
|
266
|
+
const dummy = new Dummy();
|
|
267
|
+
ctx.runInitializers(dummy);
|
|
268
|
+
const method = (dummy as Record<string, () => unknown>).myMethod;
|
|
269
|
+
|
|
270
|
+
const result = method();
|
|
215
271
|
expect(result).toBe(99);
|
|
216
272
|
spy.mockRestore();
|
|
217
273
|
});
|
|
@@ -220,13 +276,17 @@ describe("Log", () => {
|
|
|
220
276
|
const spy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
221
277
|
const original = vi.fn((x: unknown) => x);
|
|
222
278
|
const ctx = mockMethodContext("greet");
|
|
223
|
-
|
|
279
|
+
Log({ args: true, result: false })(
|
|
224
280
|
original,
|
|
225
281
|
ctx as unknown as ClassMethodDecoratorContext,
|
|
226
282
|
);
|
|
227
283
|
|
|
228
284
|
class Foo {}
|
|
229
|
-
|
|
285
|
+
const foo = new Foo();
|
|
286
|
+
ctx.runInitializers(foo);
|
|
287
|
+
const method = (foo as Record<string, (...a: unknown[]) => unknown>).greet;
|
|
288
|
+
|
|
289
|
+
method("hello");
|
|
230
290
|
expect(spy).toHaveBeenCalledWith(expect.stringContaining("args:"), ["hello"]);
|
|
231
291
|
spy.mockRestore();
|
|
232
292
|
});
|
|
@@ -235,7 +295,7 @@ describe("Log", () => {
|
|
|
235
295
|
const spy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
236
296
|
const original = vi.fn(() => "value");
|
|
237
297
|
const ctx = mockMethodContext("prod");
|
|
238
|
-
|
|
298
|
+
Log({ devOnly: true })(
|
|
239
299
|
original,
|
|
240
300
|
ctx as unknown as ClassMethodDecoratorContext,
|
|
241
301
|
);
|
|
@@ -244,7 +304,11 @@ describe("Log", () => {
|
|
|
244
304
|
process.env.NODE_ENV = "production";
|
|
245
305
|
|
|
246
306
|
class App {}
|
|
247
|
-
|
|
307
|
+
const app = new App();
|
|
308
|
+
ctx.runInitializers(app);
|
|
309
|
+
const method = (app as Record<string, () => unknown>).prod;
|
|
310
|
+
|
|
311
|
+
method();
|
|
248
312
|
expect(spy).not.toHaveBeenCalled();
|
|
249
313
|
|
|
250
314
|
process.env.NODE_ENV = prevEnv;
|
|
@@ -255,13 +319,17 @@ describe("Log", () => {
|
|
|
255
319
|
const spy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
256
320
|
const original = vi.fn(async () => "async-result");
|
|
257
321
|
const ctx = mockMethodContext("load");
|
|
258
|
-
|
|
322
|
+
Log({ args: false, result: true })(
|
|
259
323
|
original,
|
|
260
324
|
ctx as unknown as ClassMethodDecoratorContext,
|
|
261
325
|
);
|
|
262
326
|
|
|
263
327
|
class Svc {}
|
|
264
|
-
|
|
328
|
+
const svc = new Svc();
|
|
329
|
+
ctx.runInitializers(svc);
|
|
330
|
+
const method = (svc as Record<string, () => Promise<unknown>>).load;
|
|
331
|
+
|
|
332
|
+
await method();
|
|
265
333
|
expect(spy).toHaveBeenCalledWith(
|
|
266
334
|
expect.stringContaining("resolved:"),
|
|
267
335
|
"async-result",
|
|
@@ -274,12 +342,17 @@ describe("Log", () => {
|
|
|
274
342
|
const spy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
275
343
|
const original = vi.fn(async () => { throw new Error("async-error"); });
|
|
276
344
|
const ctx = mockMethodContext("failing");
|
|
277
|
-
|
|
345
|
+
Log({ args: false, result: true })(
|
|
278
346
|
original,
|
|
279
347
|
ctx as unknown as ClassMethodDecoratorContext,
|
|
280
348
|
);
|
|
349
|
+
|
|
281
350
|
class Svc {}
|
|
282
|
-
|
|
351
|
+
const svc = new Svc();
|
|
352
|
+
ctx.runInitializers(svc);
|
|
353
|
+
const method = (svc as Record<string, () => Promise<unknown>>).failing;
|
|
354
|
+
|
|
355
|
+
await expect(method()).rejects.toThrow("async-error");
|
|
283
356
|
expect(spy).toHaveBeenCalledWith(
|
|
284
357
|
expect.stringContaining("rejected:"),
|
|
285
358
|
expect.any(Error),
|
|
@@ -295,13 +368,17 @@ describe("Log — time option", () => {
|
|
|
295
368
|
const spy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
296
369
|
const original = vi.fn(() => "value");
|
|
297
370
|
const ctx = mockMethodContext("timed");
|
|
298
|
-
|
|
371
|
+
Log({ args: false, result: true, time: true })(
|
|
299
372
|
original,
|
|
300
373
|
ctx as unknown as ClassMethodDecoratorContext,
|
|
301
374
|
);
|
|
302
375
|
|
|
303
376
|
class Svc {}
|
|
304
|
-
|
|
377
|
+
const svc = new Svc();
|
|
378
|
+
ctx.runInitializers(svc);
|
|
379
|
+
const method = (svc as Record<string, () => unknown>).timed;
|
|
380
|
+
|
|
381
|
+
method();
|
|
305
382
|
// Third argument should be a string containing "ms"
|
|
306
383
|
expect(spy).toHaveBeenCalledWith(
|
|
307
384
|
expect.stringContaining("returned:"),
|
|
@@ -315,13 +392,17 @@ describe("Log — time option", () => {
|
|
|
315
392
|
const spy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
316
393
|
const original = vi.fn(async () => "async-val");
|
|
317
394
|
const ctx = mockMethodContext("asyncTimed");
|
|
318
|
-
|
|
395
|
+
Log({ args: false, result: true, time: true })(
|
|
319
396
|
original,
|
|
320
397
|
ctx as unknown as ClassMethodDecoratorContext,
|
|
321
398
|
);
|
|
322
399
|
|
|
323
400
|
class Svc {}
|
|
324
|
-
|
|
401
|
+
const svc = new Svc();
|
|
402
|
+
ctx.runInitializers(svc);
|
|
403
|
+
const method = (svc as Record<string, () => Promise<unknown>>).asyncTimed;
|
|
404
|
+
|
|
405
|
+
await method();
|
|
325
406
|
expect(spy).toHaveBeenCalledWith(
|
|
326
407
|
expect.stringContaining("resolved:"),
|
|
327
408
|
"async-val",
|
|
@@ -334,18 +415,68 @@ describe("Log — time option", () => {
|
|
|
334
415
|
const spy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
335
416
|
const original = vi.fn(() => 42);
|
|
336
417
|
const ctx = mockMethodContext("noResult");
|
|
337
|
-
|
|
418
|
+
Log({ args: false, result: false })(
|
|
338
419
|
original,
|
|
339
420
|
ctx as unknown as ClassMethodDecoratorContext,
|
|
340
421
|
);
|
|
341
422
|
|
|
342
423
|
class Svc {}
|
|
343
|
-
|
|
424
|
+
const svc = new Svc();
|
|
425
|
+
ctx.runInitializers(svc);
|
|
426
|
+
const method = (svc as Record<string, () => unknown>).noResult;
|
|
427
|
+
|
|
428
|
+
method();
|
|
344
429
|
// Should not log a "returned:" line
|
|
345
430
|
const calls = spy.mock.calls.flat().join(" ");
|
|
346
431
|
expect(calls).not.toContain("returned:");
|
|
347
432
|
spy.mockRestore();
|
|
348
433
|
});
|
|
434
|
+
|
|
435
|
+
it("skips resolved log when result=false for async functions", async () => {
|
|
436
|
+
const spy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
437
|
+
const original = vi.fn(async () => "async-value");
|
|
438
|
+
const ctx = mockMethodContext("asyncNoResult");
|
|
439
|
+
Log({ args: false, result: false })(
|
|
440
|
+
original,
|
|
441
|
+
ctx as unknown as ClassMethodDecoratorContext,
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
class Svc {}
|
|
445
|
+
const svc = new Svc();
|
|
446
|
+
ctx.runInitializers(svc);
|
|
447
|
+
const method = (svc as Record<string, () => Promise<unknown>>).asyncNoResult;
|
|
448
|
+
|
|
449
|
+
const result = await method();
|
|
450
|
+
expect(result).toBe("async-value");
|
|
451
|
+
const calls = spy.mock.calls.flat().join(" ");
|
|
452
|
+
expect(calls).not.toContain("resolved:");
|
|
453
|
+
spy.mockRestore();
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
it("uses 'Unknown' as class name when constructor.name is undefined", () => {
|
|
457
|
+
const spy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
458
|
+
const original = vi.fn(() => "val");
|
|
459
|
+
const ctx = mockMethodContext("m");
|
|
460
|
+
Log({ args: false, result: true })(
|
|
461
|
+
original,
|
|
462
|
+
ctx as unknown as ClassMethodDecoratorContext,
|
|
463
|
+
);
|
|
464
|
+
|
|
465
|
+
// Construct an object whose constructor.name is undefined to trigger ?? "Unknown"
|
|
466
|
+
const fakeInstance = Object.assign(Object.create(null) as object, {
|
|
467
|
+
constructor: { name: undefined },
|
|
468
|
+
});
|
|
469
|
+
ctx.runInitializers(fakeInstance);
|
|
470
|
+
const method = (fakeInstance as Record<string, () => unknown>).m;
|
|
471
|
+
|
|
472
|
+
method();
|
|
473
|
+
expect(spy).toHaveBeenCalledWith(
|
|
474
|
+
expect.stringContaining("Unknown"),
|
|
475
|
+
expect.anything(),
|
|
476
|
+
expect.anything(),
|
|
477
|
+
);
|
|
478
|
+
spy.mockRestore();
|
|
479
|
+
});
|
|
349
480
|
});
|
|
350
481
|
|
|
351
482
|
// ── Bind ─────────────────────────────────────────────────────────────────────
|
|
@@ -64,9 +64,8 @@ describe("Lazy decorator", () => {
|
|
|
64
64
|
(instance as unknown as { _anchor: Comment })._anchor = anchor;
|
|
65
65
|
|
|
66
66
|
instance.onMount?.();
|
|
67
|
-
// Without IntersectionObserver,
|
|
68
|
-
|
|
69
|
-
expect(lazyVisible()).toBe(true);
|
|
67
|
+
// Without IntersectionObserver, component should immediately become visible
|
|
68
|
+
expect(instance.render()).not.toBeNull();
|
|
70
69
|
document.body.removeChild(parent);
|
|
71
70
|
});
|
|
72
71
|
|
|
@@ -104,8 +103,7 @@ describe("Lazy decorator", () => {
|
|
|
104
103
|
);
|
|
105
104
|
|
|
106
105
|
expect(parent.style.minHeight).toBe("");
|
|
107
|
-
|
|
108
|
-
expect(lazyVisible()).toBe(true);
|
|
106
|
+
expect(instance.render()).not.toBeNull(); // component became visible after intersection
|
|
109
107
|
expect(mockDisconnect).toHaveBeenCalled();
|
|
110
108
|
document.body.removeChild(parent);
|
|
111
109
|
});
|
|
@@ -4,87 +4,138 @@ import { signal } from "@praxisjs/core/internal";
|
|
|
4
4
|
import { Memo } from "../functions/memo";
|
|
5
5
|
|
|
6
6
|
function mockCtx(name: string) {
|
|
7
|
-
|
|
7
|
+
const initializers: Array<(this: object) => void> = [];
|
|
8
|
+
return {
|
|
9
|
+
name,
|
|
10
|
+
kind: "method" as const,
|
|
11
|
+
addInitializer(fn: (this: object) => void) {
|
|
12
|
+
initializers.push(fn);
|
|
13
|
+
},
|
|
14
|
+
runInitializers(instance: object) {
|
|
15
|
+
initializers.forEach((fn) => { fn.call(instance); });
|
|
16
|
+
},
|
|
17
|
+
} as unknown as ClassMethodDecoratorContext & { runInitializers(instance: object): void };
|
|
8
18
|
}
|
|
9
19
|
|
|
10
20
|
describe("Memo", () => {
|
|
11
21
|
it("returns the computed value", () => {
|
|
12
22
|
const fn = vi.fn((x: unknown) => (x as number) * 2);
|
|
13
|
-
const
|
|
23
|
+
const ctx = mockCtx("double");
|
|
24
|
+
Memo()(fn, ctx);
|
|
25
|
+
|
|
14
26
|
const obj = {};
|
|
15
|
-
|
|
27
|
+
ctx.runInitializers(obj);
|
|
28
|
+
const method = (obj as Record<string, (...a: unknown[]) => unknown>).double;
|
|
29
|
+
|
|
30
|
+
expect(method(5)).toBe(10);
|
|
16
31
|
});
|
|
17
32
|
|
|
18
33
|
it("caches result for same args — calls original only once", () => {
|
|
19
34
|
const fn = vi.fn((x: unknown) => (x as number) + 1);
|
|
20
|
-
const
|
|
35
|
+
const ctx = mockCtx("inc");
|
|
36
|
+
Memo()(fn, ctx);
|
|
37
|
+
|
|
21
38
|
const obj = {};
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
39
|
+
ctx.runInitializers(obj);
|
|
40
|
+
const method = (obj as Record<string, (...a: unknown[]) => unknown>).inc;
|
|
41
|
+
|
|
42
|
+
method(3);
|
|
43
|
+
method(3);
|
|
44
|
+
method(3);
|
|
25
45
|
expect(fn).toHaveBeenCalledTimes(1);
|
|
26
46
|
});
|
|
27
47
|
|
|
28
48
|
it("computes separately for different args", () => {
|
|
29
49
|
const fn = vi.fn((x: unknown) => (x as number) * 10);
|
|
30
|
-
const
|
|
50
|
+
const ctx = mockCtx("mul");
|
|
51
|
+
Memo()(fn, ctx);
|
|
52
|
+
|
|
31
53
|
const obj = {};
|
|
32
|
-
|
|
33
|
-
|
|
54
|
+
ctx.runInitializers(obj);
|
|
55
|
+
const method = (obj as Record<string, (...a: unknown[]) => unknown>).mul;
|
|
56
|
+
|
|
57
|
+
expect(method(2)).toBe(20);
|
|
58
|
+
expect(method(3)).toBe(30);
|
|
34
59
|
expect(fn).toHaveBeenCalledTimes(2);
|
|
35
60
|
});
|
|
36
61
|
|
|
37
62
|
it("is per-instance — different instances have separate caches", () => {
|
|
38
63
|
const fn = vi.fn(() => Math.random());
|
|
39
|
-
const
|
|
64
|
+
const ctx = mockCtx("rand");
|
|
65
|
+
Memo()(fn, ctx);
|
|
66
|
+
|
|
40
67
|
const a = {};
|
|
41
68
|
const b = {};
|
|
42
|
-
|
|
43
|
-
|
|
69
|
+
ctx.runInitializers(a);
|
|
70
|
+
ctx.runInitializers(b);
|
|
71
|
+
|
|
72
|
+
const methodA = (a as Record<string, () => unknown>).rand;
|
|
73
|
+
const methodB = (b as Record<string, () => unknown>).rand;
|
|
74
|
+
const v1 = methodA();
|
|
75
|
+
const v2 = methodB();
|
|
44
76
|
expect(fn).toHaveBeenCalledTimes(2);
|
|
45
77
|
expect(v1).not.toBe(v2); // different random values per instance
|
|
46
78
|
});
|
|
47
79
|
|
|
48
80
|
it("serializes no-args calls with __no_args__ key", () => {
|
|
49
81
|
const fn = vi.fn(() => 42);
|
|
50
|
-
const
|
|
82
|
+
const ctx = mockCtx("noop");
|
|
83
|
+
Memo()(fn, ctx);
|
|
84
|
+
|
|
51
85
|
const obj = {};
|
|
52
|
-
|
|
53
|
-
|
|
86
|
+
ctx.runInitializers(obj);
|
|
87
|
+
const method = (obj as Record<string, () => unknown>).noop;
|
|
88
|
+
|
|
89
|
+
method();
|
|
90
|
+
method();
|
|
54
91
|
expect(fn).toHaveBeenCalledTimes(1);
|
|
55
92
|
});
|
|
56
93
|
|
|
57
94
|
it("serializes object args via JSON.stringify", () => {
|
|
58
95
|
const fn = vi.fn((o: unknown) => JSON.stringify(o));
|
|
59
|
-
const
|
|
96
|
+
const ctx = mockCtx("obj");
|
|
97
|
+
Memo()(fn, ctx);
|
|
98
|
+
|
|
60
99
|
const obj = {};
|
|
61
|
-
|
|
62
|
-
|
|
100
|
+
ctx.runInitializers(obj);
|
|
101
|
+
const method = (obj as Record<string, (...a: unknown[]) => unknown>).obj;
|
|
102
|
+
|
|
103
|
+
method({ a: 1 });
|
|
104
|
+
method({ a: 1 }); // same key
|
|
63
105
|
expect(fn).toHaveBeenCalledTimes(1);
|
|
64
106
|
});
|
|
65
107
|
|
|
66
108
|
it("re-evaluates when a reactive signal read inside the method changes", () => {
|
|
67
109
|
const s = signal(1);
|
|
68
110
|
const fn = vi.fn(() => s() * 10);
|
|
69
|
-
const
|
|
111
|
+
const ctx = mockCtx("reactive");
|
|
112
|
+
Memo()(fn, ctx);
|
|
113
|
+
|
|
70
114
|
const obj = {};
|
|
115
|
+
ctx.runInitializers(obj);
|
|
116
|
+
const method = (obj as Record<string, () => unknown>).reactive;
|
|
71
117
|
|
|
72
|
-
expect(
|
|
118
|
+
expect(method()).toBe(10);
|
|
73
119
|
expect(fn).toHaveBeenCalledTimes(1);
|
|
74
120
|
|
|
75
121
|
s.set(5);
|
|
76
122
|
// The computed tracks s() — next read triggers recomputation
|
|
77
|
-
expect(
|
|
123
|
+
expect(method()).toBe(50);
|
|
78
124
|
expect(fn).toHaveBeenCalledTimes(2);
|
|
79
125
|
});
|
|
80
126
|
|
|
81
127
|
it("serializes symbol args via toString()", () => {
|
|
82
128
|
const fn = vi.fn((s: unknown) => String(s));
|
|
83
|
-
const
|
|
129
|
+
const ctx = mockCtx("sym");
|
|
130
|
+
Memo()(fn, ctx);
|
|
131
|
+
|
|
84
132
|
const obj = {};
|
|
133
|
+
ctx.runInitializers(obj);
|
|
134
|
+
const method = (obj as Record<string, (...a: unknown[]) => unknown>).sym;
|
|
135
|
+
|
|
85
136
|
const sym = Symbol("test");
|
|
86
|
-
|
|
87
|
-
|
|
137
|
+
method(sym);
|
|
138
|
+
method(sym);
|
|
88
139
|
expect(fn).toHaveBeenCalledTimes(1);
|
|
89
140
|
});
|
|
90
141
|
});
|