@praxisjs/decorators 0.4.3 → 0.6.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 +39 -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 +205 -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 +114 -25
- package/dist/__tests__/memo.test.js.map +1 -1
- package/dist/__tests__/properties.test.js +193 -0
- 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 +127 -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__/until.test.d.ts +2 -0
- package/dist/__tests__/until.test.d.ts.map +1 -0
- package/dist/__tests__/until.test.js +88 -0
- package/dist/__tests__/until.test.js.map +1 -0
- package/dist/__tests__/virtual.test.js +164 -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 +49 -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.map +1 -1
- package/dist/functions/debounce.js +21 -20
- package/dist/functions/debounce.js.map +1 -1
- package/dist/functions/index.d.ts +1 -0
- package/dist/functions/index.d.ts.map +1 -1
- package/dist/functions/index.js +1 -0
- package/dist/functions/index.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 +29 -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 +11 -17
- package/dist/functions/throttle.js.map +1 -1
- package/dist/functions/until.d.ts +2 -0
- package/dist/functions/until.d.ts.map +1 -0
- package/dist/functions/until.js +17 -0
- package/dist/functions/until.js.map +1 -0
- 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 +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -2
- 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 +257 -46
- package/src/__tests__/lazy.test.ts +3 -5
- package/src/__tests__/memo.test.ts +143 -25
- package/src/__tests__/properties.test.ts +233 -0
- package/src/__tests__/resource.test.ts +141 -0
- package/src/__tests__/slot.test.ts +38 -0
- package/src/__tests__/until.test.ts +113 -0
- package/src/__tests__/virtual.test.ts +135 -7
- package/src/component/component.ts +27 -12
- package/src/component/lazy.ts +33 -37
- package/src/component/virtual.tsx +67 -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 +21 -23
- package/src/functions/index.ts +1 -0
- package/src/functions/log.ts +40 -43
- package/src/functions/memo.ts +28 -42
- package/src/functions/once.ts +13 -16
- package/src/functions/retry.ts +21 -22
- package/src/functions/throttle.ts +11 -24
- package/src/functions/until.ts +19 -0
- package/src/functions/watch.ts +27 -43
- package/src/functions/when.ts +11 -36
- package/src/index.ts +29 -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
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect, vi } from "vitest";
|
|
2
2
|
|
|
3
|
+
import { StatefulComponent } from "@praxisjs/core";
|
|
4
|
+
|
|
3
5
|
import { Bind } from "../functions/bind";
|
|
4
6
|
import { Debounce } from "../functions/debounce";
|
|
5
7
|
import { Log } from "../functions/log";
|
|
@@ -29,11 +31,14 @@ describe("Once", () => {
|
|
|
29
31
|
it("only calls the original function once", () => {
|
|
30
32
|
const original = vi.fn(() => 42);
|
|
31
33
|
const ctx = mockMethodContext("doSomething");
|
|
32
|
-
|
|
34
|
+
Once()(original, ctx as unknown as ClassMethodDecoratorContext);
|
|
33
35
|
|
|
34
36
|
const obj = {};
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
ctx.runInitializers(obj);
|
|
38
|
+
const method = (obj as Record<string, () => unknown>).doSomething;
|
|
39
|
+
|
|
40
|
+
expect(method()).toBe(42);
|
|
41
|
+
expect(method()).toBe(42);
|
|
37
42
|
expect(original).toHaveBeenCalledTimes(1);
|
|
38
43
|
});
|
|
39
44
|
|
|
@@ -41,24 +46,32 @@ describe("Once", () => {
|
|
|
41
46
|
let counter = 0;
|
|
42
47
|
const original = () => ++counter;
|
|
43
48
|
const ctx = mockMethodContext("inc");
|
|
44
|
-
|
|
49
|
+
Once()(original, ctx as unknown as ClassMethodDecoratorContext);
|
|
45
50
|
|
|
46
51
|
const obj = {};
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
52
|
+
ctx.runInitializers(obj);
|
|
53
|
+
const method = (obj as Record<string, () => unknown>).inc;
|
|
54
|
+
|
|
55
|
+
method();
|
|
56
|
+
method();
|
|
57
|
+
method();
|
|
50
58
|
expect(counter).toBe(1);
|
|
51
59
|
});
|
|
52
60
|
|
|
53
61
|
it("is per-instance — different instances call independently", () => {
|
|
54
62
|
const original = vi.fn(() => "result");
|
|
55
63
|
const ctx = mockMethodContext("fn");
|
|
56
|
-
|
|
64
|
+
Once()(original, ctx as unknown as ClassMethodDecoratorContext);
|
|
57
65
|
|
|
58
66
|
const a = {};
|
|
59
67
|
const b = {};
|
|
60
|
-
|
|
61
|
-
|
|
68
|
+
ctx.runInitializers(a);
|
|
69
|
+
ctx.runInitializers(b);
|
|
70
|
+
|
|
71
|
+
const methodA = (a as Record<string, () => unknown>).fn;
|
|
72
|
+
const methodB = (b as Record<string, () => unknown>).fn;
|
|
73
|
+
methodA();
|
|
74
|
+
methodB();
|
|
62
75
|
expect(original).toHaveBeenCalledTimes(2);
|
|
63
76
|
});
|
|
64
77
|
});
|
|
@@ -69,9 +82,13 @@ describe("Retry", () => {
|
|
|
69
82
|
it("returns result if the first attempt succeeds", async () => {
|
|
70
83
|
const original = vi.fn(async () => "ok");
|
|
71
84
|
const ctx = mockMethodContext("fetch");
|
|
72
|
-
|
|
85
|
+
Retry(3)(original, ctx as unknown as ClassMethodDecoratorContext);
|
|
86
|
+
|
|
87
|
+
const obj = {};
|
|
88
|
+
ctx.runInitializers(obj);
|
|
89
|
+
const method = (obj as Record<string, () => Promise<unknown>>).fetch;
|
|
73
90
|
|
|
74
|
-
const result = await
|
|
91
|
+
const result = await method();
|
|
75
92
|
expect(result).toBe("ok");
|
|
76
93
|
expect(original).toHaveBeenCalledTimes(1);
|
|
77
94
|
});
|
|
@@ -84,9 +101,13 @@ describe("Retry", () => {
|
|
|
84
101
|
return "success";
|
|
85
102
|
});
|
|
86
103
|
const ctx = mockMethodContext("call");
|
|
87
|
-
|
|
104
|
+
Retry(3)(original, ctx as unknown as ClassMethodDecoratorContext);
|
|
88
105
|
|
|
89
|
-
const
|
|
106
|
+
const obj = {};
|
|
107
|
+
ctx.runInitializers(obj);
|
|
108
|
+
const method = (obj as Record<string, () => Promise<unknown>>).call;
|
|
109
|
+
|
|
110
|
+
const result = await method();
|
|
90
111
|
expect(result).toBe("success");
|
|
91
112
|
expect(original).toHaveBeenCalledTimes(3);
|
|
92
113
|
});
|
|
@@ -96,9 +117,13 @@ describe("Retry", () => {
|
|
|
96
117
|
throw new Error("always fails");
|
|
97
118
|
});
|
|
98
119
|
const ctx = mockMethodContext("bad");
|
|
99
|
-
|
|
120
|
+
Retry(3)(original, ctx as unknown as ClassMethodDecoratorContext);
|
|
100
121
|
|
|
101
|
-
|
|
122
|
+
const obj = {};
|
|
123
|
+
ctx.runInitializers(obj);
|
|
124
|
+
const method = (obj as Record<string, () => Promise<unknown>>).bad;
|
|
125
|
+
|
|
126
|
+
await expect(method()).rejects.toThrow("always fails");
|
|
102
127
|
expect(original).toHaveBeenCalledTimes(3);
|
|
103
128
|
});
|
|
104
129
|
|
|
@@ -106,11 +131,16 @@ describe("Retry", () => {
|
|
|
106
131
|
vi.useFakeTimers();
|
|
107
132
|
const original = vi.fn(async () => { throw new Error("fail"); });
|
|
108
133
|
const ctx = mockMethodContext("delayed");
|
|
109
|
-
|
|
134
|
+
Retry(3, { delay: 100, backoff: 2 })(
|
|
110
135
|
original,
|
|
111
136
|
ctx as unknown as ClassMethodDecoratorContext,
|
|
112
137
|
);
|
|
113
|
-
|
|
138
|
+
|
|
139
|
+
const obj = {};
|
|
140
|
+
ctx.runInitializers(obj);
|
|
141
|
+
const method = (obj as Record<string, () => Promise<unknown>>).delayed;
|
|
142
|
+
|
|
143
|
+
const p = method();
|
|
114
144
|
const rejection = expect(p).rejects.toThrow("fail");
|
|
115
145
|
await vi.runAllTimersAsync();
|
|
116
146
|
await rejection;
|
|
@@ -124,18 +154,26 @@ describe("Retry", () => {
|
|
|
124
154
|
throw new Error("err");
|
|
125
155
|
});
|
|
126
156
|
const ctx = mockMethodContext("op");
|
|
127
|
-
|
|
157
|
+
Retry(3, { onRetry })(original, ctx as unknown as ClassMethodDecoratorContext);
|
|
128
158
|
|
|
129
|
-
|
|
159
|
+
const obj = {};
|
|
160
|
+
ctx.runInitializers(obj);
|
|
161
|
+
const method = (obj as Record<string, () => Promise<unknown>>).op;
|
|
162
|
+
|
|
163
|
+
await expect(method()).rejects.toThrow();
|
|
130
164
|
expect(onRetry).toHaveBeenCalledTimes(2); // attempts 1 and 2, not the last
|
|
131
165
|
});
|
|
132
166
|
|
|
133
167
|
it("maxAttempts=1 fails immediately without retrying", async () => {
|
|
134
168
|
const original = vi.fn(async () => { throw new Error("instant fail"); });
|
|
135
169
|
const ctx = mockMethodContext("once");
|
|
136
|
-
|
|
170
|
+
Retry(1)(original, ctx as unknown as ClassMethodDecoratorContext);
|
|
171
|
+
|
|
172
|
+
const obj = {};
|
|
173
|
+
ctx.runInitializers(obj);
|
|
174
|
+
const method = (obj as Record<string, () => Promise<unknown>>).once;
|
|
137
175
|
|
|
138
|
-
await expect(
|
|
176
|
+
await expect(method()).rejects.toThrow("instant fail");
|
|
139
177
|
expect(original).toHaveBeenCalledTimes(1);
|
|
140
178
|
});
|
|
141
179
|
|
|
@@ -145,17 +183,25 @@ describe("Retry", () => {
|
|
|
145
183
|
throw "string error";
|
|
146
184
|
});
|
|
147
185
|
const ctx = mockMethodContext("strThrow");
|
|
148
|
-
|
|
186
|
+
Retry(1)(original, ctx as unknown as ClassMethodDecoratorContext);
|
|
187
|
+
|
|
188
|
+
const obj = {};
|
|
189
|
+
ctx.runInitializers(obj);
|
|
190
|
+
const method = (obj as Record<string, () => Promise<unknown>>).strThrow;
|
|
149
191
|
|
|
150
|
-
await expect(
|
|
192
|
+
await expect(method()).rejects.toThrow("string error");
|
|
151
193
|
});
|
|
152
194
|
|
|
153
195
|
it("maxAttempts=0 throws immediately without calling the function", async () => {
|
|
154
196
|
const original = vi.fn(async () => "ok");
|
|
155
197
|
const ctx = mockMethodContext("never");
|
|
156
|
-
|
|
198
|
+
Retry(0)(original, ctx as unknown as ClassMethodDecoratorContext);
|
|
157
199
|
|
|
158
|
-
|
|
200
|
+
const obj = {};
|
|
201
|
+
ctx.runInitializers(obj);
|
|
202
|
+
const method = (obj as Record<string, () => Promise<unknown>>).never;
|
|
203
|
+
|
|
204
|
+
await expect(method()).rejects.toThrow("Unknown error");
|
|
159
205
|
expect(original).not.toHaveBeenCalled();
|
|
160
206
|
});
|
|
161
207
|
|
|
@@ -163,11 +209,15 @@ describe("Retry", () => {
|
|
|
163
209
|
const attempts: number[] = [];
|
|
164
210
|
const original = vi.fn(async () => { throw new Error("fail"); });
|
|
165
211
|
const ctx = mockMethodContext("numbered");
|
|
166
|
-
|
|
212
|
+
Retry(3, {
|
|
167
213
|
onRetry: (_err, attempt) => { attempts.push(attempt); },
|
|
168
214
|
})(original, ctx as unknown as ClassMethodDecoratorContext);
|
|
169
215
|
|
|
170
|
-
|
|
216
|
+
const obj = {};
|
|
217
|
+
ctx.runInitializers(obj);
|
|
218
|
+
const method = (obj as Record<string, () => Promise<unknown>>).numbered;
|
|
219
|
+
|
|
220
|
+
await expect(method()).rejects.toThrow();
|
|
171
221
|
expect(attempts).toEqual([1, 2]); // attempt 1 failed → onRetry(1), attempt 2 failed → onRetry(2)
|
|
172
222
|
});
|
|
173
223
|
|
|
@@ -179,12 +229,16 @@ describe("Retry", () => {
|
|
|
179
229
|
throw new Error("fail");
|
|
180
230
|
});
|
|
181
231
|
const ctx = mockMethodContext("backoffTest");
|
|
182
|
-
|
|
232
|
+
Retry(3, { delay: 100, backoff: 2 })(
|
|
183
233
|
original,
|
|
184
234
|
ctx as unknown as ClassMethodDecoratorContext,
|
|
185
235
|
);
|
|
186
236
|
|
|
187
|
-
const
|
|
237
|
+
const obj = {};
|
|
238
|
+
ctx.runInitializers(obj);
|
|
239
|
+
const method = (obj as Record<string, () => Promise<unknown>>).backoffTest;
|
|
240
|
+
|
|
241
|
+
const p = method();
|
|
188
242
|
const rejection = expect(p).rejects.toThrow("fail");
|
|
189
243
|
await vi.runAllTimersAsync();
|
|
190
244
|
await rejection;
|
|
@@ -205,13 +259,17 @@ describe("Log", () => {
|
|
|
205
259
|
const spy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
206
260
|
const original = vi.fn(() => 99);
|
|
207
261
|
const ctx = mockMethodContext("myMethod");
|
|
208
|
-
|
|
262
|
+
Log({ args: false, result: false })(
|
|
209
263
|
original,
|
|
210
264
|
ctx as unknown as ClassMethodDecoratorContext,
|
|
211
265
|
);
|
|
212
266
|
|
|
213
267
|
class Dummy {}
|
|
214
|
-
const
|
|
268
|
+
const dummy = new Dummy();
|
|
269
|
+
ctx.runInitializers(dummy);
|
|
270
|
+
const method = (dummy as Record<string, () => unknown>).myMethod;
|
|
271
|
+
|
|
272
|
+
const result = method();
|
|
215
273
|
expect(result).toBe(99);
|
|
216
274
|
spy.mockRestore();
|
|
217
275
|
});
|
|
@@ -220,13 +278,17 @@ describe("Log", () => {
|
|
|
220
278
|
const spy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
221
279
|
const original = vi.fn((x: unknown) => x);
|
|
222
280
|
const ctx = mockMethodContext("greet");
|
|
223
|
-
|
|
281
|
+
Log({ args: true, result: false })(
|
|
224
282
|
original,
|
|
225
283
|
ctx as unknown as ClassMethodDecoratorContext,
|
|
226
284
|
);
|
|
227
285
|
|
|
228
286
|
class Foo {}
|
|
229
|
-
|
|
287
|
+
const foo = new Foo();
|
|
288
|
+
ctx.runInitializers(foo);
|
|
289
|
+
const method = (foo as Record<string, (...a: unknown[]) => unknown>).greet;
|
|
290
|
+
|
|
291
|
+
method("hello");
|
|
230
292
|
expect(spy).toHaveBeenCalledWith(expect.stringContaining("args:"), ["hello"]);
|
|
231
293
|
spy.mockRestore();
|
|
232
294
|
});
|
|
@@ -235,7 +297,7 @@ describe("Log", () => {
|
|
|
235
297
|
const spy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
236
298
|
const original = vi.fn(() => "value");
|
|
237
299
|
const ctx = mockMethodContext("prod");
|
|
238
|
-
|
|
300
|
+
Log({ devOnly: true })(
|
|
239
301
|
original,
|
|
240
302
|
ctx as unknown as ClassMethodDecoratorContext,
|
|
241
303
|
);
|
|
@@ -244,7 +306,11 @@ describe("Log", () => {
|
|
|
244
306
|
process.env.NODE_ENV = "production";
|
|
245
307
|
|
|
246
308
|
class App {}
|
|
247
|
-
|
|
309
|
+
const app = new App();
|
|
310
|
+
ctx.runInitializers(app);
|
|
311
|
+
const method = (app as Record<string, () => unknown>).prod;
|
|
312
|
+
|
|
313
|
+
method();
|
|
248
314
|
expect(spy).not.toHaveBeenCalled();
|
|
249
315
|
|
|
250
316
|
process.env.NODE_ENV = prevEnv;
|
|
@@ -255,13 +321,17 @@ describe("Log", () => {
|
|
|
255
321
|
const spy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
256
322
|
const original = vi.fn(async () => "async-result");
|
|
257
323
|
const ctx = mockMethodContext("load");
|
|
258
|
-
|
|
324
|
+
Log({ args: false, result: true })(
|
|
259
325
|
original,
|
|
260
326
|
ctx as unknown as ClassMethodDecoratorContext,
|
|
261
327
|
);
|
|
262
328
|
|
|
263
329
|
class Svc {}
|
|
264
|
-
|
|
330
|
+
const svc = new Svc();
|
|
331
|
+
ctx.runInitializers(svc);
|
|
332
|
+
const method = (svc as Record<string, () => Promise<unknown>>).load;
|
|
333
|
+
|
|
334
|
+
await method();
|
|
265
335
|
expect(spy).toHaveBeenCalledWith(
|
|
266
336
|
expect.stringContaining("resolved:"),
|
|
267
337
|
"async-result",
|
|
@@ -274,12 +344,17 @@ describe("Log", () => {
|
|
|
274
344
|
const spy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
275
345
|
const original = vi.fn(async () => { throw new Error("async-error"); });
|
|
276
346
|
const ctx = mockMethodContext("failing");
|
|
277
|
-
|
|
347
|
+
Log({ args: false, result: true })(
|
|
278
348
|
original,
|
|
279
349
|
ctx as unknown as ClassMethodDecoratorContext,
|
|
280
350
|
);
|
|
351
|
+
|
|
281
352
|
class Svc {}
|
|
282
|
-
|
|
353
|
+
const svc = new Svc();
|
|
354
|
+
ctx.runInitializers(svc);
|
|
355
|
+
const method = (svc as Record<string, () => Promise<unknown>>).failing;
|
|
356
|
+
|
|
357
|
+
await expect(method()).rejects.toThrow("async-error");
|
|
283
358
|
expect(spy).toHaveBeenCalledWith(
|
|
284
359
|
expect.stringContaining("rejected:"),
|
|
285
360
|
expect.any(Error),
|
|
@@ -295,13 +370,17 @@ describe("Log — time option", () => {
|
|
|
295
370
|
const spy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
296
371
|
const original = vi.fn(() => "value");
|
|
297
372
|
const ctx = mockMethodContext("timed");
|
|
298
|
-
|
|
373
|
+
Log({ args: false, result: true, time: true })(
|
|
299
374
|
original,
|
|
300
375
|
ctx as unknown as ClassMethodDecoratorContext,
|
|
301
376
|
);
|
|
302
377
|
|
|
303
378
|
class Svc {}
|
|
304
|
-
|
|
379
|
+
const svc = new Svc();
|
|
380
|
+
ctx.runInitializers(svc);
|
|
381
|
+
const method = (svc as Record<string, () => unknown>).timed;
|
|
382
|
+
|
|
383
|
+
method();
|
|
305
384
|
// Third argument should be a string containing "ms"
|
|
306
385
|
expect(spy).toHaveBeenCalledWith(
|
|
307
386
|
expect.stringContaining("returned:"),
|
|
@@ -315,13 +394,17 @@ describe("Log — time option", () => {
|
|
|
315
394
|
const spy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
316
395
|
const original = vi.fn(async () => "async-val");
|
|
317
396
|
const ctx = mockMethodContext("asyncTimed");
|
|
318
|
-
|
|
397
|
+
Log({ args: false, result: true, time: true })(
|
|
319
398
|
original,
|
|
320
399
|
ctx as unknown as ClassMethodDecoratorContext,
|
|
321
400
|
);
|
|
322
401
|
|
|
323
402
|
class Svc {}
|
|
324
|
-
|
|
403
|
+
const svc = new Svc();
|
|
404
|
+
ctx.runInitializers(svc);
|
|
405
|
+
const method = (svc as Record<string, () => Promise<unknown>>).asyncTimed;
|
|
406
|
+
|
|
407
|
+
await method();
|
|
325
408
|
expect(spy).toHaveBeenCalledWith(
|
|
326
409
|
expect.stringContaining("resolved:"),
|
|
327
410
|
"async-val",
|
|
@@ -334,18 +417,68 @@ describe("Log — time option", () => {
|
|
|
334
417
|
const spy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
335
418
|
const original = vi.fn(() => 42);
|
|
336
419
|
const ctx = mockMethodContext("noResult");
|
|
337
|
-
|
|
420
|
+
Log({ args: false, result: false })(
|
|
338
421
|
original,
|
|
339
422
|
ctx as unknown as ClassMethodDecoratorContext,
|
|
340
423
|
);
|
|
341
424
|
|
|
342
425
|
class Svc {}
|
|
343
|
-
|
|
426
|
+
const svc = new Svc();
|
|
427
|
+
ctx.runInitializers(svc);
|
|
428
|
+
const method = (svc as Record<string, () => unknown>).noResult;
|
|
429
|
+
|
|
430
|
+
method();
|
|
344
431
|
// Should not log a "returned:" line
|
|
345
432
|
const calls = spy.mock.calls.flat().join(" ");
|
|
346
433
|
expect(calls).not.toContain("returned:");
|
|
347
434
|
spy.mockRestore();
|
|
348
435
|
});
|
|
436
|
+
|
|
437
|
+
it("skips resolved log when result=false for async functions", async () => {
|
|
438
|
+
const spy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
439
|
+
const original = vi.fn(async () => "async-value");
|
|
440
|
+
const ctx = mockMethodContext("asyncNoResult");
|
|
441
|
+
Log({ args: false, result: false })(
|
|
442
|
+
original,
|
|
443
|
+
ctx as unknown as ClassMethodDecoratorContext,
|
|
444
|
+
);
|
|
445
|
+
|
|
446
|
+
class Svc {}
|
|
447
|
+
const svc = new Svc();
|
|
448
|
+
ctx.runInitializers(svc);
|
|
449
|
+
const method = (svc as Record<string, () => Promise<unknown>>).asyncNoResult;
|
|
450
|
+
|
|
451
|
+
const result = await method();
|
|
452
|
+
expect(result).toBe("async-value");
|
|
453
|
+
const calls = spy.mock.calls.flat().join(" ");
|
|
454
|
+
expect(calls).not.toContain("resolved:");
|
|
455
|
+
spy.mockRestore();
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
it("uses 'Unknown' as class name when constructor.name is undefined", () => {
|
|
459
|
+
const spy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
460
|
+
const original = vi.fn(() => "val");
|
|
461
|
+
const ctx = mockMethodContext("m");
|
|
462
|
+
Log({ args: false, result: true })(
|
|
463
|
+
original,
|
|
464
|
+
ctx as unknown as ClassMethodDecoratorContext,
|
|
465
|
+
);
|
|
466
|
+
|
|
467
|
+
// Construct an object whose constructor.name is undefined to trigger ?? "Unknown"
|
|
468
|
+
const fakeInstance = Object.assign(Object.create(null) as object, {
|
|
469
|
+
constructor: { name: undefined },
|
|
470
|
+
});
|
|
471
|
+
ctx.runInitializers(fakeInstance);
|
|
472
|
+
const method = (fakeInstance as Record<string, () => unknown>).m;
|
|
473
|
+
|
|
474
|
+
method();
|
|
475
|
+
expect(spy).toHaveBeenCalledWith(
|
|
476
|
+
expect.stringContaining("Unknown"),
|
|
477
|
+
expect.anything(),
|
|
478
|
+
expect.anything(),
|
|
479
|
+
);
|
|
480
|
+
spy.mockRestore();
|
|
481
|
+
});
|
|
349
482
|
});
|
|
350
483
|
|
|
351
484
|
// ── Bind ─────────────────────────────────────────────────────────────────────
|
|
@@ -456,6 +589,52 @@ describe("Debounce", () => {
|
|
|
456
589
|
expect(fn).toHaveBeenCalledWith("a");
|
|
457
590
|
vi.useRealTimers();
|
|
458
591
|
});
|
|
592
|
+
|
|
593
|
+
it("timer is cleared if onUnmount fires before debounce completes", () => {
|
|
594
|
+
vi.useFakeTimers();
|
|
595
|
+
const fn = vi.fn();
|
|
596
|
+
const ctx = mockMethodContext("debounced");
|
|
597
|
+
|
|
598
|
+
class TestComp extends StatefulComponent {
|
|
599
|
+
render() { return null; }
|
|
600
|
+
}
|
|
601
|
+
Debounce(100)(fn, ctx as unknown as ClassMethodDecoratorContext);
|
|
602
|
+
|
|
603
|
+
const instance = new TestComp();
|
|
604
|
+
ctx.runInitializers(instance);
|
|
605
|
+
const method = (instance as unknown as Record<string, (...a: unknown[]) => void>).debounced;
|
|
606
|
+
|
|
607
|
+
method("hello");
|
|
608
|
+
// Unmount before the 100ms debounce fires
|
|
609
|
+
instance.onUnmount?.();
|
|
610
|
+
|
|
611
|
+
vi.advanceTimersByTime(200);
|
|
612
|
+
expect(fn).not.toHaveBeenCalled();
|
|
613
|
+
vi.useRealTimers();
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
it("original method is NOT called after unmount if timer was pending", () => {
|
|
617
|
+
vi.useFakeTimers();
|
|
618
|
+
const fn = vi.fn();
|
|
619
|
+
const ctx = mockMethodContext("debouncedOp");
|
|
620
|
+
|
|
621
|
+
class TestComp2 extends StatefulComponent {
|
|
622
|
+
render() { return null; }
|
|
623
|
+
}
|
|
624
|
+
Debounce(50)(fn, ctx as unknown as ClassMethodDecoratorContext);
|
|
625
|
+
|
|
626
|
+
const instance = new TestComp2();
|
|
627
|
+
ctx.runInitializers(instance);
|
|
628
|
+
const method = (instance as unknown as Record<string, (...a: unknown[]) => void>).debouncedOp;
|
|
629
|
+
|
|
630
|
+
method("arg1");
|
|
631
|
+
method("arg2");
|
|
632
|
+
instance.onUnmount?.();
|
|
633
|
+
|
|
634
|
+
vi.runAllTimers();
|
|
635
|
+
expect(fn).not.toHaveBeenCalled();
|
|
636
|
+
vi.useRealTimers();
|
|
637
|
+
});
|
|
459
638
|
});
|
|
460
639
|
|
|
461
640
|
// ── Throttle ─────────────────────────────────────────────────────────────────
|
|
@@ -546,4 +725,36 @@ describe("Throttle", () => {
|
|
|
546
725
|
expect(fn).toHaveBeenCalledTimes(2);
|
|
547
726
|
vi.useRealTimers();
|
|
548
727
|
});
|
|
728
|
+
|
|
729
|
+
it("negative delay is treated as 0 — first call always executes", () => {
|
|
730
|
+
vi.useFakeTimers();
|
|
731
|
+
const fn = vi.fn();
|
|
732
|
+
const ctx = mockMethodContext("negDelay");
|
|
733
|
+
Throttle(-100)(fn, ctx as unknown as ClassMethodDecoratorContext);
|
|
734
|
+
|
|
735
|
+
const obj = {};
|
|
736
|
+
ctx.runInitializers(obj);
|
|
737
|
+
const method = (obj as Record<string, (...a: unknown[]) => void>).negDelay;
|
|
738
|
+
|
|
739
|
+
method();
|
|
740
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
741
|
+
vi.useRealTimers();
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
it("call at exactly the ms boundary executes again", () => {
|
|
745
|
+
vi.useFakeTimers();
|
|
746
|
+
const fn = vi.fn();
|
|
747
|
+
const ctx = mockMethodContext("boundary");
|
|
748
|
+
Throttle(100)(fn, ctx as unknown as ClassMethodDecoratorContext);
|
|
749
|
+
|
|
750
|
+
const obj = {};
|
|
751
|
+
ctx.runInitializers(obj);
|
|
752
|
+
const method = (obj as Record<string, (...a: unknown[]) => void>).boundary;
|
|
753
|
+
|
|
754
|
+
method(); // first call at t=0
|
|
755
|
+
vi.advanceTimersByTime(100); // advance to exactly 100ms
|
|
756
|
+
method(); // second call at t=100 — should pass (now - last === ms, not less than ms)
|
|
757
|
+
expect(fn).toHaveBeenCalledTimes(2);
|
|
758
|
+
vi.useRealTimers();
|
|
759
|
+
});
|
|
549
760
|
});
|
|
@@ -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
|
});
|