@praxisjs/decorators 0.4.2 → 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 +43 -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 +146 -0
- package/dist/__tests__/component.test.js.map +1 -0
- 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.d.ts +2 -0
- package/dist/__tests__/events.test.d.ts.map +1 -0
- package/dist/__tests__/events.test.js +216 -0
- package/dist/__tests__/events.test.js.map +1 -0
- package/dist/__tests__/functions.test.js +283 -31
- package/dist/__tests__/functions.test.js.map +1 -1
- 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 +163 -0
- package/dist/__tests__/lazy.test.js.map +1 -0
- package/dist/__tests__/memo.test.js +71 -22
- package/dist/__tests__/memo.test.js.map +1 -1
- package/dist/__tests__/properties.test.js +191 -1
- package/dist/__tests__/properties.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.d.ts +2 -0
- package/dist/__tests__/slot.test.d.ts.map +1 -0
- package/dist/__tests__/slot.test.js +147 -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 +242 -0
- package/dist/__tests__/virtual.test.js.map +1 -0
- 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 +1 -1
- package/dist/component/lazy.d.ts.map +1 -1
- package/dist/component/lazy.js +29 -27
- 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/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 -31
- 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 +166 -0
- package/src/__tests__/compose.test.ts +202 -0
- package/src/__tests__/events.test.ts +268 -0
- package/src/__tests__/functions.test.ts +368 -30
- package/src/__tests__/lazy.test.ts +218 -0
- package/src/__tests__/memo.test.ts +89 -22
- package/src/__tests__/properties.test.ts +249 -1
- package/src/__tests__/resource.test.ts +113 -0
- package/src/__tests__/slot.test.ts +179 -0
- package/src/__tests__/virtual.test.ts +260 -0
- package/src/component/component.ts +27 -12
- package/src/component/lazy.ts +33 -35
- 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 -34
- 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);
|
|
84
|
+
|
|
85
|
+
const obj = {};
|
|
86
|
+
ctx.runInitializers(obj);
|
|
87
|
+
const method = (obj as Record<string, () => Promise<unknown>>).fetch;
|
|
73
88
|
|
|
74
|
-
const result = await
|
|
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);
|
|
119
|
+
|
|
120
|
+
const obj = {};
|
|
121
|
+
ctx.runInitializers(obj);
|
|
122
|
+
const method = (obj as Record<string, () => Promise<unknown>>).bad;
|
|
100
123
|
|
|
101
|
-
await expect(
|
|
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,11 +152,102 @@ 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
|
});
|
|
164
|
+
|
|
165
|
+
it("maxAttempts=1 fails immediately without retrying", async () => {
|
|
166
|
+
const original = vi.fn(async () => { throw new Error("instant fail"); });
|
|
167
|
+
const ctx = mockMethodContext("once");
|
|
168
|
+
Retry(1)(original, ctx as unknown as ClassMethodDecoratorContext);
|
|
169
|
+
|
|
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");
|
|
175
|
+
expect(original).toHaveBeenCalledTimes(1);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("non-Error throw is wrapped in an Error", async () => {
|
|
179
|
+
const original = vi.fn(async () => {
|
|
180
|
+
// eslint-disable-next-line @typescript-eslint/only-throw-error
|
|
181
|
+
throw "string error";
|
|
182
|
+
});
|
|
183
|
+
const ctx = mockMethodContext("strThrow");
|
|
184
|
+
Retry(1)(original, ctx as unknown as ClassMethodDecoratorContext);
|
|
185
|
+
|
|
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");
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("maxAttempts=0 throws immediately without calling the function", async () => {
|
|
194
|
+
const original = vi.fn(async () => "ok");
|
|
195
|
+
const ctx = mockMethodContext("never");
|
|
196
|
+
Retry(0)(original, ctx as unknown as ClassMethodDecoratorContext);
|
|
197
|
+
|
|
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");
|
|
203
|
+
expect(original).not.toHaveBeenCalled();
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("onRetry receives the correct attempt number", async () => {
|
|
207
|
+
const attempts: number[] = [];
|
|
208
|
+
const original = vi.fn(async () => { throw new Error("fail"); });
|
|
209
|
+
const ctx = mockMethodContext("numbered");
|
|
210
|
+
Retry(3, {
|
|
211
|
+
onRetry: (_err, attempt) => { attempts.push(attempt); },
|
|
212
|
+
})(original, ctx as unknown as ClassMethodDecoratorContext);
|
|
213
|
+
|
|
214
|
+
const obj = {};
|
|
215
|
+
ctx.runInitializers(obj);
|
|
216
|
+
const method = (obj as Record<string, () => Promise<unknown>>).numbered;
|
|
217
|
+
|
|
218
|
+
await expect(method()).rejects.toThrow();
|
|
219
|
+
expect(attempts).toEqual([1, 2]); // attempt 1 failed → onRetry(1), attempt 2 failed → onRetry(2)
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("backoff multiplies delay correctly", async () => {
|
|
223
|
+
vi.useFakeTimers();
|
|
224
|
+
const callTimes: number[] = [];
|
|
225
|
+
const original = vi.fn(async () => {
|
|
226
|
+
callTimes.push(Date.now());
|
|
227
|
+
throw new Error("fail");
|
|
228
|
+
});
|
|
229
|
+
const ctx = mockMethodContext("backoffTest");
|
|
230
|
+
Retry(3, { delay: 100, backoff: 2 })(
|
|
231
|
+
original,
|
|
232
|
+
ctx as unknown as ClassMethodDecoratorContext,
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
const obj = {};
|
|
236
|
+
ctx.runInitializers(obj);
|
|
237
|
+
const method = (obj as Record<string, () => Promise<unknown>>).backoffTest;
|
|
238
|
+
|
|
239
|
+
const p = method();
|
|
240
|
+
const rejection = expect(p).rejects.toThrow("fail");
|
|
241
|
+
await vi.runAllTimersAsync();
|
|
242
|
+
await rejection;
|
|
243
|
+
|
|
244
|
+
expect(callTimes.length).toBe(3);
|
|
245
|
+
// Attempt 2 runs 100ms after attempt 1
|
|
246
|
+
expect(callTimes[1] - callTimes[0]).toBeGreaterThanOrEqual(100);
|
|
247
|
+
// Attempt 3 runs 200ms after attempt 2 (backoff × 2)
|
|
248
|
+
expect(callTimes[2] - callTimes[1]).toBeGreaterThanOrEqual(200);
|
|
249
|
+
vi.useRealTimers();
|
|
250
|
+
});
|
|
132
251
|
});
|
|
133
252
|
|
|
134
253
|
// ── Log ──────────────────────────────────────────────────────────────────────
|
|
@@ -138,13 +257,17 @@ describe("Log", () => {
|
|
|
138
257
|
const spy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
139
258
|
const original = vi.fn(() => 99);
|
|
140
259
|
const ctx = mockMethodContext("myMethod");
|
|
141
|
-
|
|
260
|
+
Log({ args: false, result: false })(
|
|
142
261
|
original,
|
|
143
262
|
ctx as unknown as ClassMethodDecoratorContext,
|
|
144
263
|
);
|
|
145
264
|
|
|
146
265
|
class Dummy {}
|
|
147
|
-
const
|
|
266
|
+
const dummy = new Dummy();
|
|
267
|
+
ctx.runInitializers(dummy);
|
|
268
|
+
const method = (dummy as Record<string, () => unknown>).myMethod;
|
|
269
|
+
|
|
270
|
+
const result = method();
|
|
148
271
|
expect(result).toBe(99);
|
|
149
272
|
spy.mockRestore();
|
|
150
273
|
});
|
|
@@ -153,13 +276,17 @@ describe("Log", () => {
|
|
|
153
276
|
const spy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
154
277
|
const original = vi.fn((x: unknown) => x);
|
|
155
278
|
const ctx = mockMethodContext("greet");
|
|
156
|
-
|
|
279
|
+
Log({ args: true, result: false })(
|
|
157
280
|
original,
|
|
158
281
|
ctx as unknown as ClassMethodDecoratorContext,
|
|
159
282
|
);
|
|
160
283
|
|
|
161
284
|
class Foo {}
|
|
162
|
-
|
|
285
|
+
const foo = new Foo();
|
|
286
|
+
ctx.runInitializers(foo);
|
|
287
|
+
const method = (foo as Record<string, (...a: unknown[]) => unknown>).greet;
|
|
288
|
+
|
|
289
|
+
method("hello");
|
|
163
290
|
expect(spy).toHaveBeenCalledWith(expect.stringContaining("args:"), ["hello"]);
|
|
164
291
|
spy.mockRestore();
|
|
165
292
|
});
|
|
@@ -168,7 +295,7 @@ describe("Log", () => {
|
|
|
168
295
|
const spy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
169
296
|
const original = vi.fn(() => "value");
|
|
170
297
|
const ctx = mockMethodContext("prod");
|
|
171
|
-
|
|
298
|
+
Log({ devOnly: true })(
|
|
172
299
|
original,
|
|
173
300
|
ctx as unknown as ClassMethodDecoratorContext,
|
|
174
301
|
);
|
|
@@ -177,7 +304,11 @@ describe("Log", () => {
|
|
|
177
304
|
process.env.NODE_ENV = "production";
|
|
178
305
|
|
|
179
306
|
class App {}
|
|
180
|
-
|
|
307
|
+
const app = new App();
|
|
308
|
+
ctx.runInitializers(app);
|
|
309
|
+
const method = (app as Record<string, () => unknown>).prod;
|
|
310
|
+
|
|
311
|
+
method();
|
|
181
312
|
expect(spy).not.toHaveBeenCalled();
|
|
182
313
|
|
|
183
314
|
process.env.NODE_ENV = prevEnv;
|
|
@@ -188,13 +319,17 @@ describe("Log", () => {
|
|
|
188
319
|
const spy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
189
320
|
const original = vi.fn(async () => "async-result");
|
|
190
321
|
const ctx = mockMethodContext("load");
|
|
191
|
-
|
|
322
|
+
Log({ args: false, result: true })(
|
|
192
323
|
original,
|
|
193
324
|
ctx as unknown as ClassMethodDecoratorContext,
|
|
194
325
|
);
|
|
195
326
|
|
|
196
327
|
class Svc {}
|
|
197
|
-
|
|
328
|
+
const svc = new Svc();
|
|
329
|
+
ctx.runInitializers(svc);
|
|
330
|
+
const method = (svc as Record<string, () => Promise<unknown>>).load;
|
|
331
|
+
|
|
332
|
+
await method();
|
|
198
333
|
expect(spy).toHaveBeenCalledWith(
|
|
199
334
|
expect.stringContaining("resolved:"),
|
|
200
335
|
"async-result",
|
|
@@ -207,12 +342,17 @@ describe("Log", () => {
|
|
|
207
342
|
const spy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
208
343
|
const original = vi.fn(async () => { throw new Error("async-error"); });
|
|
209
344
|
const ctx = mockMethodContext("failing");
|
|
210
|
-
|
|
345
|
+
Log({ args: false, result: true })(
|
|
211
346
|
original,
|
|
212
347
|
ctx as unknown as ClassMethodDecoratorContext,
|
|
213
348
|
);
|
|
349
|
+
|
|
214
350
|
class Svc {}
|
|
215
|
-
|
|
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");
|
|
216
356
|
expect(spy).toHaveBeenCalledWith(
|
|
217
357
|
expect.stringContaining("rejected:"),
|
|
218
358
|
expect.any(Error),
|
|
@@ -221,6 +361,124 @@ describe("Log", () => {
|
|
|
221
361
|
});
|
|
222
362
|
});
|
|
223
363
|
|
|
364
|
+
// ── Log (additional branches) ─────────────────────────────────────────────────
|
|
365
|
+
|
|
366
|
+
describe("Log — time option", () => {
|
|
367
|
+
it("includes elapsed time in sync result log when time=true", () => {
|
|
368
|
+
const spy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
369
|
+
const original = vi.fn(() => "value");
|
|
370
|
+
const ctx = mockMethodContext("timed");
|
|
371
|
+
Log({ args: false, result: true, time: true })(
|
|
372
|
+
original,
|
|
373
|
+
ctx as unknown as ClassMethodDecoratorContext,
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
class Svc {}
|
|
377
|
+
const svc = new Svc();
|
|
378
|
+
ctx.runInitializers(svc);
|
|
379
|
+
const method = (svc as Record<string, () => unknown>).timed;
|
|
380
|
+
|
|
381
|
+
method();
|
|
382
|
+
// Third argument should be a string containing "ms"
|
|
383
|
+
expect(spy).toHaveBeenCalledWith(
|
|
384
|
+
expect.stringContaining("returned:"),
|
|
385
|
+
"value",
|
|
386
|
+
expect.stringContaining("ms"),
|
|
387
|
+
);
|
|
388
|
+
spy.mockRestore();
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
it("includes elapsed time in async result log when time=true", async () => {
|
|
392
|
+
const spy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
393
|
+
const original = vi.fn(async () => "async-val");
|
|
394
|
+
const ctx = mockMethodContext("asyncTimed");
|
|
395
|
+
Log({ args: false, result: true, time: true })(
|
|
396
|
+
original,
|
|
397
|
+
ctx as unknown as ClassMethodDecoratorContext,
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
class Svc {}
|
|
401
|
+
const svc = new Svc();
|
|
402
|
+
ctx.runInitializers(svc);
|
|
403
|
+
const method = (svc as Record<string, () => Promise<unknown>>).asyncTimed;
|
|
404
|
+
|
|
405
|
+
await method();
|
|
406
|
+
expect(spy).toHaveBeenCalledWith(
|
|
407
|
+
expect.stringContaining("resolved:"),
|
|
408
|
+
"async-val",
|
|
409
|
+
expect.stringContaining("ms"),
|
|
410
|
+
);
|
|
411
|
+
spy.mockRestore();
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
it("skips result log when result=false", () => {
|
|
415
|
+
const spy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
416
|
+
const original = vi.fn(() => 42);
|
|
417
|
+
const ctx = mockMethodContext("noResult");
|
|
418
|
+
Log({ args: false, result: false })(
|
|
419
|
+
original,
|
|
420
|
+
ctx as unknown as ClassMethodDecoratorContext,
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
class Svc {}
|
|
424
|
+
const svc = new Svc();
|
|
425
|
+
ctx.runInitializers(svc);
|
|
426
|
+
const method = (svc as Record<string, () => unknown>).noResult;
|
|
427
|
+
|
|
428
|
+
method();
|
|
429
|
+
// Should not log a "returned:" line
|
|
430
|
+
const calls = spy.mock.calls.flat().join(" ");
|
|
431
|
+
expect(calls).not.toContain("returned:");
|
|
432
|
+
spy.mockRestore();
|
|
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
|
+
});
|
|
480
|
+
});
|
|
481
|
+
|
|
224
482
|
// ── Bind ─────────────────────────────────────────────────────────────────────
|
|
225
483
|
|
|
226
484
|
describe("Bind", () => {
|
|
@@ -286,6 +544,49 @@ describe("Debounce", () => {
|
|
|
286
544
|
expect(fn).toHaveBeenCalledOnce();
|
|
287
545
|
vi.useRealTimers();
|
|
288
546
|
});
|
|
547
|
+
|
|
548
|
+
it("multiple instances have independent timers", () => {
|
|
549
|
+
vi.useFakeTimers();
|
|
550
|
+
const fn = vi.fn();
|
|
551
|
+
const ctx = mockMethodContext("act");
|
|
552
|
+
Debounce(100)(fn, ctx as unknown as ClassMethodDecoratorContext);
|
|
553
|
+
|
|
554
|
+
const obj1 = {};
|
|
555
|
+
const obj2 = {};
|
|
556
|
+
ctx.runInitializers(obj1);
|
|
557
|
+
ctx.runInitializers(obj2);
|
|
558
|
+
|
|
559
|
+
const m1 = (obj1 as Record<string, (...a: unknown[]) => void>).act;
|
|
560
|
+
const m2 = (obj2 as Record<string, (...a: unknown[]) => void>).act;
|
|
561
|
+
|
|
562
|
+
m1("from-1");
|
|
563
|
+
m2("from-2");
|
|
564
|
+
|
|
565
|
+
vi.advanceTimersByTime(100);
|
|
566
|
+
// Both timers fire independently
|
|
567
|
+
expect(fn).toHaveBeenCalledTimes(2);
|
|
568
|
+
expect(fn).toHaveBeenCalledWith("from-1");
|
|
569
|
+
expect(fn).toHaveBeenCalledWith("from-2");
|
|
570
|
+
vi.useRealTimers();
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
it("delay=0 fires after the next macrotask", () => {
|
|
574
|
+
vi.useFakeTimers();
|
|
575
|
+
const fn = vi.fn();
|
|
576
|
+
const ctx = mockMethodContext("instant");
|
|
577
|
+
Debounce(0)(fn, ctx as unknown as ClassMethodDecoratorContext);
|
|
578
|
+
|
|
579
|
+
const obj = {};
|
|
580
|
+
ctx.runInitializers(obj);
|
|
581
|
+
const method = (obj as Record<string, (...a: unknown[]) => void>).instant;
|
|
582
|
+
|
|
583
|
+
method("a");
|
|
584
|
+
expect(fn).not.toHaveBeenCalled();
|
|
585
|
+
vi.advanceTimersByTime(0);
|
|
586
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
587
|
+
expect(fn).toHaveBeenCalledWith("a");
|
|
588
|
+
vi.useRealTimers();
|
|
589
|
+
});
|
|
289
590
|
});
|
|
290
591
|
|
|
291
592
|
// ── Throttle ─────────────────────────────────────────────────────────────────
|
|
@@ -339,4 +640,41 @@ describe("Throttle", () => {
|
|
|
339
640
|
expect(fn).toHaveBeenCalledTimes(2);
|
|
340
641
|
vi.useRealTimers();
|
|
341
642
|
});
|
|
643
|
+
|
|
644
|
+
it("passes the first call's arguments to the underlying function", () => {
|
|
645
|
+
vi.useFakeTimers();
|
|
646
|
+
const fn = vi.fn();
|
|
647
|
+
const ctx = mockMethodContext("click");
|
|
648
|
+
Throttle(100)(fn, ctx as unknown as ClassMethodDecoratorContext);
|
|
649
|
+
|
|
650
|
+
const obj = {};
|
|
651
|
+
ctx.runInitializers(obj);
|
|
652
|
+
const method = (obj as Record<string, (...a: unknown[]) => void>).click;
|
|
653
|
+
|
|
654
|
+
method("first-arg");
|
|
655
|
+
method("second-arg");
|
|
656
|
+
expect(fn).toHaveBeenCalledWith("first-arg");
|
|
657
|
+
vi.useRealTimers();
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
it("multiple instances throttle independently", () => {
|
|
661
|
+
vi.useFakeTimers();
|
|
662
|
+
const fn = vi.fn();
|
|
663
|
+
const ctx = mockMethodContext("ping");
|
|
664
|
+
Throttle(100)(fn, ctx as unknown as ClassMethodDecoratorContext);
|
|
665
|
+
|
|
666
|
+
const obj1 = {};
|
|
667
|
+
const obj2 = {};
|
|
668
|
+
ctx.runInitializers(obj1);
|
|
669
|
+
ctx.runInitializers(obj2);
|
|
670
|
+
|
|
671
|
+
const m1 = (obj1 as Record<string, (...a: unknown[]) => void>).ping;
|
|
672
|
+
const m2 = (obj2 as Record<string, (...a: unknown[]) => void>).ping;
|
|
673
|
+
|
|
674
|
+
m1("a");
|
|
675
|
+
m2("b");
|
|
676
|
+
// Both first calls go through
|
|
677
|
+
expect(fn).toHaveBeenCalledTimes(2);
|
|
678
|
+
vi.useRealTimers();
|
|
679
|
+
});
|
|
342
680
|
});
|