@praxisjs/decorators 0.5.0 → 0.6.1
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 +22 -0
- package/dist/__tests__/functions.test.js +63 -0
- package/dist/__tests__/functions.test.js.map +1 -1
- package/dist/__tests__/memo.test.js +53 -0
- 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.js +24 -0
- package/dist/__tests__/resource.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 +108 -0
- package/dist/__tests__/virtual.test.js.map +1 -1
- package/dist/component/virtual.d.ts.map +1 -1
- package/dist/component/virtual.js +3 -0
- package/dist/component/virtual.js.map +1 -1
- package/dist/create-method-decorator.d.ts +2 -2
- package/dist/create-method-decorator.d.ts.map +1 -1
- package/dist/create-method-decorator.js +3 -1
- package/dist/create-method-decorator.js.map +1 -1
- package/dist/events/emit.d.ts +1 -1
- package/dist/events/emit.d.ts.map +1 -1
- package/dist/functions/bind.d.ts +1 -1
- package/dist/functions/bind.d.ts.map +1 -1
- package/dist/functions/debounce.d.ts +2 -1
- package/dist/functions/debounce.d.ts.map +1 -1
- package/dist/functions/debounce.js +10 -1
- 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 -1
- package/dist/functions/log.d.ts.map +1 -1
- package/dist/functions/memo.d.ts +1 -1
- package/dist/functions/memo.d.ts.map +1 -1
- package/dist/functions/memo.js +16 -2
- package/dist/functions/memo.js.map +1 -1
- package/dist/functions/once.d.ts +1 -1
- package/dist/functions/once.d.ts.map +1 -1
- package/dist/functions/retry.d.ts +1 -1
- package/dist/functions/retry.d.ts.map +1 -1
- package/dist/functions/throttle.d.ts +1 -1
- package/dist/functions/throttle.d.ts.map +1 -1
- package/dist/functions/throttle.js +2 -1
- 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/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/functions.test.ts +80 -0
- package/src/__tests__/memo.test.ts +67 -0
- package/src/__tests__/properties.test.ts +233 -0
- package/src/__tests__/resource.test.ts +28 -0
- package/src/__tests__/until.test.ts +113 -0
- package/src/__tests__/virtual.test.ts +109 -0
- package/src/component/virtual.tsx +5 -0
- package/src/create-method-decorator.ts +6 -3
- package/src/functions/debounce.ts +14 -1
- package/src/functions/index.ts +1 -0
- package/src/functions/memo.ts +17 -1
- package/src/functions/throttle.ts +2 -1
- package/src/functions/until.ts +19 -0
- package/src/index.ts +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/functions/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAC5B,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,KAAK,EAAE,KAAK,QAAQ,EAAE,KAAK,SAAS,EAAE,MAAM,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/functions/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAC5B,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,KAAK,EAAE,KAAK,QAAQ,EAAE,KAAK,SAAS,EAAE,MAAM,SAAS,CAAC"}
|
package/dist/functions/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/functions/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAC5B,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,KAAK,EAAiC,MAAM,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/functions/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAC5B,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,KAAK,EAAiC,MAAM,SAAS,CAAC"}
|
package/dist/functions/log.d.ts
CHANGED
|
@@ -5,5 +5,5 @@ export interface LogOptions {
|
|
|
5
5
|
time?: boolean;
|
|
6
6
|
devOnly?: boolean;
|
|
7
7
|
}
|
|
8
|
-
export declare function Log(options?: LogOptions): (value: (this: object, ...args:
|
|
8
|
+
export declare function Log(options?: LogOptions): (value: (this: object, ...args: any[]) => any, context: ClassMethodDecoratorContext<import("@praxisjs/core").StatefulComponent>) => void;
|
|
9
9
|
//# sourceMappingURL=log.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../../src/functions/log.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;IAC3C,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,GAAG,CAAC,OAAO,GAAE,UAAe,
|
|
1
|
+
{"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../../src/functions/log.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;IAC3C,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,GAAG,CAAC,OAAO,GAAE,UAAe,0BAalC,GAAL,+GAyCJ"}
|
package/dist/functions/memo.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare function Memo(): (value: (this: object, ...args:
|
|
1
|
+
export declare function Memo(): (value: (this: object, ...args: any[]) => any, context: ClassMethodDecoratorContext<import("@praxisjs/core").StatefulComponent>) => void;
|
|
2
2
|
//# sourceMappingURL=memo.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memo.d.ts","sourceRoot":"","sources":["../../src/functions/memo.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"memo.d.ts","sourceRoot":"","sources":["../../src/functions/memo.ts"],"names":[],"mappings":"AAgCA,wBAAgB,IAAI,2BAflB,GAAG,+GA8BJ"}
|
package/dist/functions/memo.js
CHANGED
|
@@ -1,12 +1,26 @@
|
|
|
1
1
|
import { computed } from "@praxisjs/core/internal";
|
|
2
2
|
import { createMethodDecorator } from "../create-method-decorator";
|
|
3
|
+
const objectIdMap = new WeakMap();
|
|
4
|
+
let objectIdCounter = 0;
|
|
5
|
+
function objectIdentityKey(obj) {
|
|
6
|
+
if (!objectIdMap.has(obj)) {
|
|
7
|
+
objectIdMap.set(obj, ++objectIdCounter);
|
|
8
|
+
}
|
|
9
|
+
return `__obj_${String(objectIdMap.get(obj))}__`;
|
|
10
|
+
}
|
|
3
11
|
function serializeArgs(args) {
|
|
4
12
|
if (args.length === 0)
|
|
5
13
|
return "__no_args__";
|
|
6
14
|
return args
|
|
7
15
|
.map((a) => {
|
|
8
|
-
if (a === null || typeof a === "object")
|
|
9
|
-
|
|
16
|
+
if (a === null || typeof a === "object") {
|
|
17
|
+
try {
|
|
18
|
+
return JSON.stringify(a);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return a === null ? "null" : objectIdentityKey(a);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
10
24
|
if (typeof a === "symbol")
|
|
11
25
|
return a.toString();
|
|
12
26
|
return String(a);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memo.js","sourceRoot":"","sources":["../../src/functions/memo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAGnD,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAEnE,SAAS,aAAa,CAAC,IAAe;IACpC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,aAAa,CAAC;IAC5C,OAAO,IAAI;SACR,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACT,IAAI,CAAC,KAAK,IAAI,IAAI,OAAO,CAAC,KAAK,QAAQ;
|
|
1
|
+
{"version":3,"file":"memo.js","sourceRoot":"","sources":["../../src/functions/memo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAGnD,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAEnE,MAAM,WAAW,GAAG,IAAI,OAAO,EAAkB,CAAC;AAClD,IAAI,eAAe,GAAG,CAAC,CAAC;AAExB,SAAS,iBAAiB,CAAC,GAAW;IACpC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1B,WAAW,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,eAAe,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,SAAS,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;AACnD,CAAC;AAED,SAAS,aAAa,CAAC,IAAe;IACpC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,aAAa,CAAC;IAC5C,OAAO,IAAI;SACR,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACT,IAAI,CAAC,KAAK,IAAI,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;YACxC,IAAI,CAAC;gBACH,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;YAC3B,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC;YACpD,CAAC;QACH,CAAC;QACD,IAAI,OAAO,CAAC,KAAK,QAAQ;YAAE,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC/C,OAAO,MAAM,CAAC,CAAmD,CAAC,CAAC;IACrE,CAAC,CAAC;SACD,IAAI,CAAC,GAAG,CAAC,CAAC;AACf,CAAC;AAED,MAAM,UAAU,IAAI;IAClB,OAAO,qBAAqB,CAAC;QAC3B,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK;YAC5B,MAAM,KAAK,GAAG,IAAI,GAAG,EAA6B,CAAC;YACnD,OAAO,CAAC,GAAG,IAAe,EAAE,EAAE;gBAC5B,MAAM,GAAG,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;gBAChC,IAAI,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;gBAC9B,IAAI,CAAC,QAAQ,EAAE,CAAC;oBACd,QAAQ,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC;oBAC1D,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;gBAC3B,CAAC;gBACD,OAAO,QAAQ,EAAE,CAAC;YACpB,CAAC,CAAC;QACJ,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
|
package/dist/functions/once.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare function Once(): (value: (this: object, ...args:
|
|
1
|
+
export declare function Once(): (value: (this: object, ...args: any[]) => any, context: ClassMethodDecoratorContext<import("@praxisjs/core").StatefulComponent>) => void;
|
|
2
2
|
//# sourceMappingURL=once.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"once.d.ts","sourceRoot":"","sources":["../../src/functions/once.ts"],"names":[],"mappings":"AAEA,wBAAgB,IAAI,
|
|
1
|
+
{"version":3,"file":"once.d.ts","sourceRoot":"","sources":["../../src/functions/once.ts"],"names":[],"mappings":"AAEA,wBAAgB,IAAI,2BAcoG,GAAI,+GAD3H"}
|
|
@@ -3,5 +3,5 @@ export interface RetryOptions {
|
|
|
3
3
|
backoff?: number;
|
|
4
4
|
onRetry?: (error: Error, attempt: number) => void;
|
|
5
5
|
}
|
|
6
|
-
export declare function Retry(maxAttempts: number, options?: RetryOptions): (value: (this: object, ...args:
|
|
6
|
+
export declare function Retry(maxAttempts: number, options?: RetryOptions): (value: (this: object, ...args: any[]) => any, context: ClassMethodDecoratorContext<import("@praxisjs/core").StatefulComponent>) => void;
|
|
7
7
|
//# sourceMappingURL=retry.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../../src/functions/retry.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACnD;AAED,wBAAgB,KAAK,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,
|
|
1
|
+
{"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../../src/functions/retry.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACnD;AAED,wBAAgB,KAAK,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,0BAOrC,GAE/B,+GAmBA"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare function Throttle(ms: number): (value: (this: object, ...args:
|
|
1
|
+
export declare function Throttle(ms: number): (value: (this: object, ...args: any[]) => any, context: ClassMethodDecoratorContext<import("@praxisjs/core").StatefulComponent>) => void;
|
|
2
2
|
//# sourceMappingURL=throttle.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"throttle.d.ts","sourceRoot":"","sources":["../../src/functions/throttle.ts"],"names":[],"mappings":"AAEA,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"throttle.d.ts","sourceRoot":"","sources":["../../src/functions/throttle.ts"],"names":[],"mappings":"AAEA,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,0BAckD,GAAI,+GADxF"}
|
|
@@ -2,10 +2,11 @@ import { createMethodDecorator } from "../create-method-decorator";
|
|
|
2
2
|
export function Throttle(ms) {
|
|
3
3
|
return createMethodDecorator({
|
|
4
4
|
wrap(original, instance) {
|
|
5
|
+
const clampedMs = Math.max(0, ms);
|
|
5
6
|
let last = 0;
|
|
6
7
|
return (...args) => {
|
|
7
8
|
const now = Date.now();
|
|
8
|
-
if (now - last <
|
|
9
|
+
if (now - last < clampedMs)
|
|
9
10
|
return;
|
|
10
11
|
last = now;
|
|
11
12
|
return original.apply(instance, args);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"throttle.js","sourceRoot":"","sources":["../../src/functions/throttle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAEnE,MAAM,UAAU,QAAQ,CAAC,EAAU;IACjC,OAAO,qBAAqB,CAAC;QAC3B,IAAI,CAAC,QAAQ,EAAE,QAAQ;YACrB,IAAI,IAAI,GAAG,CAAC,CAAC;YACb,OAAO,CAAC,GAAG,IAAe,EAAE,EAAE;gBAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;gBACvB,IAAI,GAAG,GAAG,IAAI,GAAG,
|
|
1
|
+
{"version":3,"file":"throttle.js","sourceRoot":"","sources":["../../src/functions/throttle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAEnE,MAAM,UAAU,QAAQ,CAAC,EAAU;IACjC,OAAO,qBAAqB,CAAC;QAC3B,IAAI,CAAC,QAAQ,EAAE,QAAQ;YACrB,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAClC,IAAI,IAAI,GAAG,CAAC,CAAC;YACb,OAAO,CAAC,GAAG,IAAe,EAAE,EAAE;gBAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;gBACvB,IAAI,GAAG,GAAG,IAAI,GAAG,SAAS;oBAAE,OAAO;gBACnC,IAAI,GAAG,GAAG,CAAC;gBACX,OAAO,QAAQ,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;YACxC,CAAC,CAAC;QACJ,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"until.d.ts","sourceRoot":"","sources":["../../src/functions/until.ts"],"names":[],"mappings":"AAMA,wBAAgB,KAAK,CAAC,QAAQ,EAAE,MAAM,0BAOrB,GAAG,+GAKnB"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { until } from "@praxisjs/core/internal";
|
|
2
|
+
import { isComputed } from "@praxisjs/shared/internal";
|
|
3
|
+
import { createMethodDecorator } from "../create-method-decorator";
|
|
4
|
+
export function Until(propName) {
|
|
5
|
+
return createMethodDecorator({
|
|
6
|
+
wrap(_original, instance) {
|
|
7
|
+
return () => {
|
|
8
|
+
const raw = instance[propName];
|
|
9
|
+
const source = isComputed(raw)
|
|
10
|
+
? raw
|
|
11
|
+
: raw;
|
|
12
|
+
return until(source);
|
|
13
|
+
};
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=until.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"until.js","sourceRoot":"","sources":["../../src/functions/until.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAC;AAEhD,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAEvD,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAEnE,MAAM,UAAU,KAAK,CAAC,QAAgB;IACpC,OAAO,qBAAqB,CAAC;QAC3B,IAAI,CAAC,SAAS,EAAE,QAAQ;YACtB,OAAO,GAAG,EAAE;gBACV,MAAM,GAAG,GAAI,QAAoC,CAAC,QAAQ,CAAC,CAAC;gBAC5D,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC;oBAC5B,CAAC,CAAE,GAAyB;oBAC5B,CAAC,CAAE,GAAuB,CAAC;gBAC7B,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC;YACvB,CAAC,CAAC;QACJ,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { Component, Lazy, Virtual } from "./component";
|
|
2
|
-
export { Memo, Bind, Log, Once, Retry, Debounce, Throttle, When, Watch, type WatchVal, type WatchVals, } from "./functions";
|
|
2
|
+
export { Memo, Bind, Log, Once, Retry, Debounce, Throttle, When, Until, Watch, type WatchVal, type WatchVals, } from "./functions";
|
|
3
3
|
export { Prop, State, Computed, Persisted, Slot, initSlots, History, type WithHistory, Compose, Resource, type ResourceInstance, type ResourceOptions, } from "./properties";
|
|
4
4
|
export { type Command, createCommand, Emit, OnCommand } from "./events";
|
|
5
5
|
export { createFieldDecorator, type FieldBehavior, type FieldBinding, } from "./create-field-decorator";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,EACL,IAAI,EACJ,IAAI,EACJ,GAAG,EACH,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,IAAI,EACJ,KAAK,EACL,KAAK,QAAQ,EACb,KAAK,SAAS,GACf,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,SAAS,EACT,IAAI,EACJ,SAAS,EACT,OAAO,EACP,KAAK,WAAW,EAChB,OAAO,EACP,QAAQ,EACR,KAAK,gBAAgB,EACrB,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,KAAK,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACxE,OAAO,EACL,oBAAoB,EACpB,KAAK,aAAa,EAClB,KAAK,YAAY,GAClB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,oBAAoB,EACpB,aAAa,EACb,KAAK,gBAAgB,GACtB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,qBAAqB,EACrB,KAAK,cAAc,GACpB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,8BAA8B,EAC9B,KAAK,uBAAuB,GAC7B,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EACL,qBAAqB,EACrB,KAAK,cAAc,EACnB,6BAA6B,EAC7B,KAAK,sBAAsB,GAC5B,MAAM,2BAA2B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,EACL,IAAI,EACJ,IAAI,EACJ,GAAG,EACH,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,IAAI,EACJ,KAAK,EACL,KAAK,EACL,KAAK,QAAQ,EACb,KAAK,SAAS,GACf,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,SAAS,EACT,IAAI,EACJ,SAAS,EACT,OAAO,EACP,KAAK,WAAW,EAChB,OAAO,EACP,QAAQ,EACR,KAAK,gBAAgB,EACrB,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,KAAK,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACxE,OAAO,EACL,oBAAoB,EACpB,KAAK,aAAa,EAClB,KAAK,YAAY,GAClB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,oBAAoB,EACpB,aAAa,EACb,KAAK,gBAAgB,GACtB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,qBAAqB,EACrB,KAAK,cAAc,GACpB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,8BAA8B,EAC9B,KAAK,uBAAuB,GAC7B,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EACL,qBAAqB,EACrB,KAAK,cAAc,EACnB,6BAA6B,EAC7B,KAAK,sBAAsB,GAC5B,MAAM,2BAA2B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { Component, Lazy, Virtual } from "./component";
|
|
2
|
-
export { Memo, Bind, Log, Once, Retry, Debounce, Throttle, When, Watch, } from "./functions";
|
|
2
|
+
export { Memo, Bind, Log, Once, Retry, Debounce, Throttle, When, Until, Watch, } from "./functions";
|
|
3
3
|
export { Prop, State, Computed, Persisted, Slot, initSlots, History, Compose, Resource, } from "./properties";
|
|
4
4
|
export { createCommand, Emit, OnCommand } from "./events";
|
|
5
5
|
export { createFieldDecorator, } from "./create-field-decorator";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,EACL,IAAI,EACJ,IAAI,EACJ,GAAG,EACH,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,IAAI,EACJ,KAAK,GAGN,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,SAAS,EACT,IAAI,EACJ,SAAS,EACT,OAAO,EAEP,OAAO,EACP,QAAQ,GAGT,MAAM,cAAc,CAAC;AACtB,OAAO,EAAgB,aAAa,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACxE,OAAO,EACL,oBAAoB,GAGrB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,oBAAoB,EACpB,aAAa,GAEd,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,qBAAqB,GAEtB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,8BAA8B,GAE/B,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EACL,qBAAqB,EAErB,6BAA6B,GAE9B,MAAM,2BAA2B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,EACL,IAAI,EACJ,IAAI,EACJ,GAAG,EACH,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,IAAI,EACJ,KAAK,EACL,KAAK,GAGN,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,SAAS,EACT,IAAI,EACJ,SAAS,EACT,OAAO,EAEP,OAAO,EACP,QAAQ,GAGT,MAAM,cAAc,CAAC;AACtB,OAAO,EAAgB,aAAa,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACxE,OAAO,EACL,oBAAoB,GAGrB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,oBAAoB,EACpB,aAAa,GAEd,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,qBAAqB,GAEtB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,8BAA8B,GAE/B,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EACL,qBAAqB,EAErB,6BAA6B,GAE9B,MAAM,2BAA2B,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@praxisjs/decorators",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"typescript": "^5.9.3"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@praxisjs/core": "1.
|
|
18
|
+
"@praxisjs/core": "1.1.0",
|
|
19
19
|
"@praxisjs/shared": "0.2.0"
|
|
20
20
|
},
|
|
21
21
|
"scripts": {
|
|
@@ -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";
|
|
@@ -587,6 +589,52 @@ describe("Debounce", () => {
|
|
|
587
589
|
expect(fn).toHaveBeenCalledWith("a");
|
|
588
590
|
vi.useRealTimers();
|
|
589
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
|
+
});
|
|
590
638
|
});
|
|
591
639
|
|
|
592
640
|
// ── Throttle ─────────────────────────────────────────────────────────────────
|
|
@@ -677,4 +725,36 @@ describe("Throttle", () => {
|
|
|
677
725
|
expect(fn).toHaveBeenCalledTimes(2);
|
|
678
726
|
vi.useRealTimers();
|
|
679
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
|
+
});
|
|
680
760
|
});
|
|
@@ -138,4 +138,71 @@ describe("Memo", () => {
|
|
|
138
138
|
method(sym);
|
|
139
139
|
expect(fn).toHaveBeenCalledTimes(1);
|
|
140
140
|
});
|
|
141
|
+
|
|
142
|
+
it("circular object argument does not crash — falls back to stable identity key", () => {
|
|
143
|
+
const fn = vi.fn((o: unknown) => o);
|
|
144
|
+
const ctx = mockCtx("circular");
|
|
145
|
+
Memo()(fn, ctx);
|
|
146
|
+
|
|
147
|
+
const obj = {};
|
|
148
|
+
ctx.runInitializers(obj);
|
|
149
|
+
const method = (obj as Record<string, (...a: unknown[]) => unknown>).circular;
|
|
150
|
+
|
|
151
|
+
// Create a circular reference
|
|
152
|
+
const circ: Record<string, unknown> = {};
|
|
153
|
+
circ.self = circ;
|
|
154
|
+
|
|
155
|
+
expect(() => method(circ)).not.toThrow();
|
|
156
|
+
// Calling again with the same object should hit cache (not call fn again)
|
|
157
|
+
method(circ);
|
|
158
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("two circular objects produce different cache keys", () => {
|
|
162
|
+
const fn = vi.fn((o: unknown) => o);
|
|
163
|
+
const ctx = mockCtx("circular2");
|
|
164
|
+
Memo()(fn, ctx);
|
|
165
|
+
|
|
166
|
+
const obj = {};
|
|
167
|
+
ctx.runInitializers(obj);
|
|
168
|
+
const method = (obj as Record<string, (...a: unknown[]) => unknown>).circular2;
|
|
169
|
+
|
|
170
|
+
const a: Record<string, unknown> = {};
|
|
171
|
+
a.self = a;
|
|
172
|
+
const b: Record<string, unknown> = {};
|
|
173
|
+
b.self = b;
|
|
174
|
+
|
|
175
|
+
method(a);
|
|
176
|
+
method(b);
|
|
177
|
+
expect(fn).toHaveBeenCalledTimes(2);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("undefined vs null as arguments produce different cache keys", () => {
|
|
181
|
+
const fn = vi.fn((x: unknown) => x);
|
|
182
|
+
const ctx = mockCtx("nullUndef");
|
|
183
|
+
Memo()(fn, ctx);
|
|
184
|
+
|
|
185
|
+
const obj = {};
|
|
186
|
+
ctx.runInitializers(obj);
|
|
187
|
+
const method = (obj as Record<string, (...a: unknown[]) => unknown>).nullUndef;
|
|
188
|
+
|
|
189
|
+
method(undefined);
|
|
190
|
+
method(null);
|
|
191
|
+
expect(fn).toHaveBeenCalledTimes(2);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("BigInt argument does not crash JSON.stringify path", () => {
|
|
195
|
+
const fn = vi.fn((x: unknown) => x);
|
|
196
|
+
const ctx = mockCtx("bigint");
|
|
197
|
+
Memo()(fn, ctx);
|
|
198
|
+
|
|
199
|
+
const obj = {};
|
|
200
|
+
ctx.runInitializers(obj);
|
|
201
|
+
const method = (obj as Record<string, (...a: unknown[]) => unknown>).bigint;
|
|
202
|
+
|
|
203
|
+
// BigInt is a non-object, non-symbol primitive — goes through String() path
|
|
204
|
+
expect(() => method(BigInt(42))).not.toThrow();
|
|
205
|
+
method(BigInt(42));
|
|
206
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
207
|
+
});
|
|
141
208
|
});
|
|
@@ -5,6 +5,7 @@ import { signal, computed } from "@praxisjs/core/internal";
|
|
|
5
5
|
|
|
6
6
|
import { Watch } from "../functions/watch";
|
|
7
7
|
import { When } from "../functions/when";
|
|
8
|
+
import { Compose } from "../properties/compose";
|
|
8
9
|
import { Computed } from "../properties/computed";
|
|
9
10
|
import { History } from "../properties/history";
|
|
10
11
|
import { Prop } from "../properties/prop";
|
|
@@ -723,3 +724,235 @@ describe("Watch decorator — reading computed values", () => {
|
|
|
723
724
|
expect(handler).toHaveBeenCalledTimes(1);
|
|
724
725
|
});
|
|
725
726
|
});
|
|
727
|
+
|
|
728
|
+
// ── Watch — additional new tests ──────────────────────────────────────────────
|
|
729
|
+
|
|
730
|
+
describe("Watch decorator — new cases", () => {
|
|
731
|
+
it("NaN → NaN does NOT fire (Object.is semantics)", () => {
|
|
732
|
+
const { ctx, run } = methodCtx("onNaN");
|
|
733
|
+
const handler = vi.fn();
|
|
734
|
+
Watch("nanProp" as never)(handler, ctx as unknown as ClassMethodDecoratorContext);
|
|
735
|
+
|
|
736
|
+
const s = signal<number>(NaN);
|
|
737
|
+
const instance = new TestComponent();
|
|
738
|
+
Object.defineProperty(instance, "nanProp", { get: () => s(), configurable: true });
|
|
739
|
+
run(instance);
|
|
740
|
+
(instance as unknown as { onMount: () => void }).onMount?.();
|
|
741
|
+
|
|
742
|
+
s.set(NaN); // Object.is(NaN, NaN) === true → no change
|
|
743
|
+
expect(handler).not.toHaveBeenCalled();
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
it("watch callback that throws does not crash the effect system", () => {
|
|
747
|
+
const { ctx, run } = methodCtx("onThrow");
|
|
748
|
+
const throwingHandler = vi.fn(() => { throw new Error("handler error"); });
|
|
749
|
+
Watch("tProp" as never)(throwingHandler, ctx as unknown as ClassMethodDecoratorContext);
|
|
750
|
+
|
|
751
|
+
const s = signal(0);
|
|
752
|
+
const instance = new TestComponent();
|
|
753
|
+
Object.defineProperty(instance, "tProp", { get: () => s(), configurable: true });
|
|
754
|
+
run(instance);
|
|
755
|
+
(instance as unknown as { onMount: () => void }).onMount?.();
|
|
756
|
+
|
|
757
|
+
// Even if the handler throws, the signal change shouldn't crash the effect framework
|
|
758
|
+
// The throw propagates from the effect, so we catch it here
|
|
759
|
+
expect(() => { s.set(1); }).toThrow("handler error");
|
|
760
|
+
// The system should still be intact — further calls are not tested for recovery
|
|
761
|
+
expect(throwingHandler).toHaveBeenCalledTimes(1);
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
it("multiple @Watch decorators on different methods both fire independently", () => {
|
|
765
|
+
const { ctx: ctxA, run: runA } = methodCtx("onA");
|
|
766
|
+
const { ctx: ctxB, run: runB } = methodCtx("onB");
|
|
767
|
+
const handlerA = vi.fn();
|
|
768
|
+
const handlerB = vi.fn();
|
|
769
|
+
Watch("propA" as never)(handlerA, ctxA as unknown as ClassMethodDecoratorContext);
|
|
770
|
+
Watch("propB" as never)(handlerB, ctxB as unknown as ClassMethodDecoratorContext);
|
|
771
|
+
|
|
772
|
+
const sa = signal(0);
|
|
773
|
+
const sb = signal(0);
|
|
774
|
+
const instance = new TestComponent();
|
|
775
|
+
Object.defineProperty(instance, "propA", { get: () => sa(), configurable: true });
|
|
776
|
+
Object.defineProperty(instance, "propB", { get: () => sb(), configurable: true });
|
|
777
|
+
runA(instance);
|
|
778
|
+
runB(instance);
|
|
779
|
+
(instance as unknown as { onMount: () => void }).onMount?.();
|
|
780
|
+
|
|
781
|
+
sa.set(1);
|
|
782
|
+
expect(handlerA).toHaveBeenCalledTimes(1);
|
|
783
|
+
expect(handlerB).not.toHaveBeenCalled();
|
|
784
|
+
|
|
785
|
+
sb.set(1);
|
|
786
|
+
expect(handlerA).toHaveBeenCalledTimes(1);
|
|
787
|
+
expect(handlerB).toHaveBeenCalledTimes(1);
|
|
788
|
+
});
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
// ── When — additional new tests ───────────────────────────────────────────────
|
|
792
|
+
|
|
793
|
+
describe("When decorator — new cases", () => {
|
|
794
|
+
function methodCtxFor(name: string) {
|
|
795
|
+
const initializers: Array<(this: unknown) => void> = [];
|
|
796
|
+
return {
|
|
797
|
+
ctx: {
|
|
798
|
+
name,
|
|
799
|
+
kind: "method" as const,
|
|
800
|
+
addInitializer(fn: (this: unknown) => void) {
|
|
801
|
+
initializers.push(fn);
|
|
802
|
+
},
|
|
803
|
+
} as ClassMethodDecoratorContext,
|
|
804
|
+
run(instance: unknown) {
|
|
805
|
+
initializers.forEach((fn) => { fn.call(instance); });
|
|
806
|
+
},
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
it("@When fires only once — first truthy value; subsequent truthy values are ignored", () => {
|
|
811
|
+
const { ctx, run } = methodCtxFor("onToggle");
|
|
812
|
+
const handler = vi.fn();
|
|
813
|
+
When("toggle")(handler, ctx as unknown as ClassMethodDecoratorContext);
|
|
814
|
+
|
|
815
|
+
const s = signal<number | null>(null);
|
|
816
|
+
const instance = new TestComponent();
|
|
817
|
+
(instance as unknown as Record<string, unknown>).toggle = s;
|
|
818
|
+
run(instance);
|
|
819
|
+
(instance as unknown as { onMount: () => void }).onMount?.();
|
|
820
|
+
|
|
821
|
+
s.set(1); // truthy → fires (first and only time)
|
|
822
|
+
s.set(null); // falsy
|
|
823
|
+
s.set(2); // truthy again — but when() has already disposed itself
|
|
824
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
825
|
+
expect(handler).toHaveBeenCalledWith(1);
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
it("@When callback that throws — onUnmount cleanup still works", () => {
|
|
829
|
+
const { ctx, run } = methodCtxFor("onThrowWhen");
|
|
830
|
+
const throwingHandler = vi.fn(() => { throw new Error("when error"); });
|
|
831
|
+
When("whenProp")(throwingHandler, ctx as unknown as ClassMethodDecoratorContext);
|
|
832
|
+
|
|
833
|
+
const s = signal<number | null>(null);
|
|
834
|
+
const instance = new TestComponent();
|
|
835
|
+
(instance as unknown as Record<string, unknown>).whenProp = s;
|
|
836
|
+
run(instance);
|
|
837
|
+
(instance as unknown as { onMount: () => void }).onMount?.();
|
|
838
|
+
|
|
839
|
+
// Triggering truthy fires handler which throws
|
|
840
|
+
expect(() => { s.set(1); }).toThrow("when error");
|
|
841
|
+
|
|
842
|
+
// onUnmount should still work without crashing
|
|
843
|
+
expect(() => {
|
|
844
|
+
(instance as unknown as { onUnmount: () => void }).onUnmount?.();
|
|
845
|
+
}).not.toThrow();
|
|
846
|
+
});
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
// ── State — additional new tests ──────────────────────────────────────────────
|
|
850
|
+
|
|
851
|
+
describe("State decorator — new cases", () => {
|
|
852
|
+
it("two @State fields can be set independently without cross-contamination", () => {
|
|
853
|
+
const { ctx: ctxX, run: runX } = fieldCtx("x");
|
|
854
|
+
const { ctx: ctxY, run: runY } = fieldCtx("y");
|
|
855
|
+
State()(undefined, ctxX);
|
|
856
|
+
State()(undefined, ctxY);
|
|
857
|
+
|
|
858
|
+
const instance = new TestComponent();
|
|
859
|
+
(instance as unknown as Record<string, unknown>).x = 10;
|
|
860
|
+
(instance as unknown as Record<string, unknown>).y = 20;
|
|
861
|
+
runX(instance);
|
|
862
|
+
runY(instance);
|
|
863
|
+
|
|
864
|
+
(instance as unknown as Record<string, unknown>).x = 99;
|
|
865
|
+
expect((instance as unknown as Record<string, unknown>).x).toBe(99);
|
|
866
|
+
expect((instance as unknown as Record<string, unknown>).y).toBe(20);
|
|
867
|
+
|
|
868
|
+
(instance as unknown as Record<string, unknown>).y = 77;
|
|
869
|
+
expect((instance as unknown as Record<string, unknown>).x).toBe(99);
|
|
870
|
+
expect((instance as unknown as Record<string, unknown>).y).toBe(77);
|
|
871
|
+
});
|
|
872
|
+
|
|
873
|
+
it("setting @State to undefined works (signal stores undefined)", () => {
|
|
874
|
+
const { ctx, run } = fieldCtx("maybeVal");
|
|
875
|
+
State()(undefined, ctx);
|
|
876
|
+
|
|
877
|
+
const instance = new TestComponent();
|
|
878
|
+
(instance as unknown as Record<string, unknown>).maybeVal = "initial";
|
|
879
|
+
run(instance);
|
|
880
|
+
|
|
881
|
+
(instance as unknown as Record<string, unknown>).maybeVal = undefined;
|
|
882
|
+
expect((instance as unknown as Record<string, unknown>).maybeVal).toBeUndefined();
|
|
883
|
+
});
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
// ── Prop — additional new tests ───────────────────────────────────────────────
|
|
887
|
+
|
|
888
|
+
describe("Prop decorator — new cases", () => {
|
|
889
|
+
it("function prop that throws — getter propagates the error", () => {
|
|
890
|
+
const { ctx, run } = fieldCtx("dynProp");
|
|
891
|
+
Prop()(undefined, ctx);
|
|
892
|
+
|
|
893
|
+
const throwingFn = () => { throw new Error("prop error"); };
|
|
894
|
+
const instance = new TestComponent({ dynProp: throwingFn });
|
|
895
|
+
run(instance);
|
|
896
|
+
|
|
897
|
+
expect(() => (instance as unknown as Record<string, unknown>).dynProp).toThrow("prop error");
|
|
898
|
+
});
|
|
899
|
+
|
|
900
|
+
it("undefined passed explicitly vs key not present — both fall back to default", () => {
|
|
901
|
+
const { ctx: ctx1, run: run1 } = fieldCtx("optA");
|
|
902
|
+
const { ctx: ctx2, run: run2 } = fieldCtx("optB");
|
|
903
|
+
Prop()(undefined, ctx1);
|
|
904
|
+
Prop()(undefined, ctx2);
|
|
905
|
+
|
|
906
|
+
// Case 1: key explicitly set to undefined in rawProps
|
|
907
|
+
const instance1 = new TestComponent({ optA: undefined });
|
|
908
|
+
(instance1 as unknown as Record<string, unknown>).optA = "default-a";
|
|
909
|
+
run1(instance1);
|
|
910
|
+
// undefined from rawProps — Prop checks `!== undefined`, so falls back to default
|
|
911
|
+
expect((instance1 as unknown as Record<string, unknown>).optA).toBe("default-a");
|
|
912
|
+
|
|
913
|
+
// Case 2: key not present in rawProps at all
|
|
914
|
+
const instance2 = new TestComponent({});
|
|
915
|
+
(instance2 as unknown as Record<string, unknown>).optB = "default-b";
|
|
916
|
+
run2(instance2);
|
|
917
|
+
expect((instance2 as unknown as Record<string, unknown>).optB).toBe("default-b");
|
|
918
|
+
});
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
// ── Compose — additional new tests ────────────────────────────────────────────
|
|
922
|
+
|
|
923
|
+
describe("@Compose decorator — new cases", () => {
|
|
924
|
+
it("setup() that throws — field access on the instance throws", () => {
|
|
925
|
+
class ThrowingComposable {
|
|
926
|
+
setup(): Record<string, unknown> {
|
|
927
|
+
throw new Error("setup failed");
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
const { ctx, run } = fieldCtx("throwing");
|
|
932
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
933
|
+
expect(() => {
|
|
934
|
+
const instance = new TestComponent();
|
|
935
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
936
|
+
Compose(ThrowingComposable as any)(undefined, ctx);
|
|
937
|
+
run(instance);
|
|
938
|
+
}).toThrow("setup failed");
|
|
939
|
+
});
|
|
940
|
+
|
|
941
|
+
it("string argument resolution where the instance property is undefined", () => {
|
|
942
|
+
class ArgComposable {
|
|
943
|
+
constructor(private val: unknown) {}
|
|
944
|
+
setup() { return { resolved: this.val }; }
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
const { ctx, run } = fieldCtx("strArgUndef");
|
|
948
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
949
|
+
Compose(ArgComposable as any, "missingProp")(undefined, ctx);
|
|
950
|
+
|
|
951
|
+
const instance = new TestComponent();
|
|
952
|
+
// missingProp is not set on instance — resolves to undefined
|
|
953
|
+
run(instance);
|
|
954
|
+
|
|
955
|
+
const view = (instance as unknown as Record<string, unknown>).strArgUndef as Record<string, unknown>;
|
|
956
|
+
expect(view.resolved).toBeUndefined();
|
|
957
|
+
});
|
|
958
|
+
});
|