@praxisjs/decorators 1.6.1 → 2.0.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 +46 -0
- package/dist/component/component.d.ts +1 -1
- package/dist/component/component.d.ts.map +1 -1
- package/dist/component/component.js +8 -15
- 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 +44 -36
- package/dist/component/lazy.js.map +1 -1
- package/dist/create-class-decorator.d.ts +27 -5
- package/dist/create-class-decorator.d.ts.map +1 -1
- package/dist/create-class-decorator.js +53 -18
- package/dist/create-class-decorator.js.map +1 -1
- package/dist/create-field-decorator.d.ts +42 -3
- package/dist/create-field-decorator.d.ts.map +1 -1
- package/dist/create-field-decorator.js +63 -27
- package/dist/create-field-decorator.js.map +1 -1
- package/dist/create-getter-decorator.d.ts +81 -16
- package/dist/create-getter-decorator.d.ts.map +1 -1
- package/dist/create-getter-decorator.js +127 -15
- package/dist/create-getter-decorator.js.map +1 -1
- package/dist/create-lifecycle-method-decorator.d.ts +21 -3
- package/dist/create-lifecycle-method-decorator.d.ts.map +1 -1
- package/dist/create-lifecycle-method-decorator.js +25 -18
- package/dist/create-lifecycle-method-decorator.js.map +1 -1
- package/dist/create-method-decorator.d.ts +43 -3
- package/dist/create-method-decorator.d.ts.map +1 -1
- package/dist/create-method-decorator.js +49 -8
- package/dist/create-method-decorator.js.map +1 -1
- package/dist/events/command.d.ts +2 -1
- package/dist/events/command.d.ts.map +1 -1
- package/dist/events/command.js +10 -8
- package/dist/events/command.js.map +1 -1
- package/dist/events/emit.d.ts +2 -2
- package/dist/events/emit.d.ts.map +1 -1
- package/dist/events/emit.js +18 -24
- package/dist/events/emit.js.map +1 -1
- package/dist/events/on-command.d.ts +2 -1
- package/dist/events/on-command.d.ts.map +1 -1
- package/dist/events/on-command.js +12 -14
- package/dist/events/on-command.js.map +1 -1
- package/dist/field.d.ts +51 -0
- package/dist/field.d.ts.map +1 -0
- package/dist/field.js +82 -0
- package/dist/field.js.map +1 -0
- package/dist/functions/bind.d.ts +2 -1
- package/dist/functions/bind.d.ts.map +1 -1
- package/dist/functions/bind.js +4 -4
- package/dist/functions/bind.js.map +1 -1
- package/dist/functions/debounce.d.ts +2 -2
- package/dist/functions/debounce.d.ts.map +1 -1
- package/dist/functions/debounce.js +25 -23
- package/dist/functions/debounce.js.map +1 -1
- package/dist/functions/index.d.ts +1 -1
- package/dist/functions/index.d.ts.map +1 -1
- package/dist/functions/index.js.map +1 -1
- package/dist/functions/log.d.ts +2 -1
- package/dist/functions/log.d.ts.map +1 -1
- package/dist/functions/log.js +38 -40
- package/dist/functions/log.js.map +1 -1
- package/dist/functions/memo.d.ts +9 -1
- package/dist/functions/memo.d.ts.map +1 -1
- package/dist/functions/memo.js +44 -30
- package/dist/functions/memo.js.map +1 -1
- package/dist/functions/once.d.ts +2 -1
- package/dist/functions/once.d.ts.map +1 -1
- package/dist/functions/once.js +10 -14
- package/dist/functions/once.js.map +1 -1
- package/dist/functions/retry.d.ts +2 -1
- package/dist/functions/retry.d.ts.map +1 -1
- package/dist/functions/retry.js +21 -23
- package/dist/functions/retry.js.map +1 -1
- package/dist/functions/throttle.d.ts +2 -1
- package/dist/functions/throttle.d.ts.map +1 -1
- package/dist/functions/throttle.js +12 -14
- package/dist/functions/throttle.js.map +1 -1
- package/dist/functions/until.d.ts +2 -1
- package/dist/functions/until.d.ts.map +1 -1
- package/dist/functions/until.js +13 -14
- package/dist/functions/until.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 +36 -34
- package/dist/functions/watch.js.map +1 -1
- package/dist/functions/when.d.ts +1 -1
- package/dist/functions/when.d.ts.map +1 -1
- package/dist/functions/when.js +19 -21
- package/dist/functions/when.js.map +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/lifecycle.d.ts +24 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +55 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/properties/compose.d.ts +3 -2
- package/dist/properties/compose.d.ts.map +1 -1
- package/dist/properties/compose.js +33 -19
- package/dist/properties/compose.js.map +1 -1
- package/dist/properties/computed.d.ts +12 -10
- package/dist/properties/computed.d.ts.map +1 -1
- package/dist/properties/computed.js +26 -21
- package/dist/properties/computed.js.map +1 -1
- package/dist/properties/deep-state.d.ts +2 -1
- package/dist/properties/deep-state.d.ts.map +1 -1
- package/dist/properties/deep-state.js +66 -49
- package/dist/properties/deep-state.js.map +1 -1
- package/dist/properties/function-prop.d.ts +2 -1
- package/dist/properties/function-prop.d.ts.map +1 -1
- package/dist/properties/function-prop.js +20 -22
- package/dist/properties/function-prop.js.map +1 -1
- package/dist/properties/history.d.ts +2 -2
- package/dist/properties/history.d.ts.map +1 -1
- package/dist/properties/history.js +34 -31
- package/dist/properties/history.js.map +1 -1
- package/dist/properties/index.d.ts +1 -0
- package/dist/properties/index.d.ts.map +1 -1
- package/dist/properties/index.js +1 -0
- package/dist/properties/index.js.map +1 -1
- package/dist/properties/persisted.d.ts +2 -1
- package/dist/properties/persisted.d.ts.map +1 -1
- package/dist/properties/persisted.js +30 -19
- package/dist/properties/persisted.js.map +1 -1
- package/dist/properties/prop.d.ts +2 -1
- package/dist/properties/prop.d.ts.map +1 -1
- package/dist/properties/prop.js +26 -22
- package/dist/properties/prop.js.map +1 -1
- package/dist/properties/ref.d.ts +2 -1
- package/dist/properties/ref.d.ts.map +1 -1
- package/dist/properties/ref.js +11 -8
- package/dist/properties/ref.js.map +1 -1
- package/dist/properties/resource.d.ts.map +1 -1
- package/dist/properties/resource.js +21 -16
- package/dist/properties/resource.js.map +1 -1
- package/dist/properties/selector.d.ts +12 -0
- package/dist/properties/selector.d.ts.map +1 -0
- package/dist/properties/selector.js +25 -0
- package/dist/properties/selector.js.map +1 -0
- package/dist/properties/slot.d.ts +2 -1
- package/dist/properties/slot.d.ts.map +1 -1
- package/dist/properties/slot.js +22 -20
- package/dist/properties/slot.js.map +1 -1
- package/dist/properties/state.d.ts.map +1 -1
- package/dist/properties/state.js +27 -16
- package/dist/properties/state.js.map +1 -1
- package/dist/properties/synced.d.ts +2 -1
- package/dist/properties/synced.d.ts.map +1 -1
- package/dist/properties/synced.js +27 -19
- package/dist/properties/synced.js.map +1 -1
- package/dist/shared-method.d.ts +14 -0
- package/dist/shared-method.d.ts.map +1 -0
- package/dist/shared-method.js +87 -0
- package/dist/shared-method.js.map +1 -0
- package/package.json +14 -5
- package/src/component/component.ts +8 -27
- package/src/component/lazy.ts +51 -41
- package/src/create-class-decorator.ts +90 -31
- package/src/create-field-decorator.ts +120 -43
- package/src/create-getter-decorator.ts +263 -66
- package/src/create-lifecycle-method-decorator.ts +50 -28
- package/src/create-method-decorator.ts +119 -13
- package/src/events/command.ts +11 -9
- package/src/events/emit.ts +16 -22
- package/src/events/on-command.ts +17 -18
- package/src/field.ts +116 -0
- package/src/functions/bind.ts +4 -4
- package/src/functions/debounce.ts +25 -23
- package/src/functions/index.ts +1 -1
- package/src/functions/log.ts +44 -46
- package/src/functions/memo.ts +54 -28
- package/src/functions/once.ts +10 -13
- package/src/functions/retry.ts +19 -21
- package/src/functions/throttle.ts +12 -13
- package/src/functions/until.ts +14 -14
- package/src/functions/watch.ts +37 -36
- package/src/functions/when.ts +18 -20
- package/src/index.ts +13 -5
- package/src/lifecycle.ts +74 -0
- package/src/properties/compose.ts +33 -20
- package/src/properties/computed.ts +35 -45
- package/src/properties/deep-state.ts +68 -53
- package/src/properties/function-prop.ts +25 -27
- package/src/properties/history.ts +34 -33
- package/src/properties/index.ts +1 -0
- package/src/properties/persisted.ts +41 -27
- package/src/properties/prop.ts +30 -22
- package/src/properties/ref.ts +12 -10
- package/src/properties/resource.ts +22 -17
- package/src/properties/selector.ts +45 -0
- package/src/properties/slot.ts +28 -22
- package/src/properties/state.ts +32 -16
- package/src/properties/synced.ts +31 -19
- package/src/shared-method.ts +101 -0
- package/dist/__tests__/component.test.d.ts +0 -2
- package/dist/__tests__/component.test.d.ts.map +0 -1
- package/dist/__tests__/component.test.js +0 -147
- package/dist/__tests__/component.test.js.map +0 -1
- package/dist/__tests__/compose.test.d.ts +0 -2
- package/dist/__tests__/compose.test.d.ts.map +0 -1
- package/dist/__tests__/compose.test.js +0 -164
- package/dist/__tests__/compose.test.js.map +0 -1
- package/dist/__tests__/deep-state.test.d.ts +0 -2
- package/dist/__tests__/deep-state.test.d.ts.map +0 -1
- package/dist/__tests__/deep-state.test.js +0 -150
- package/dist/__tests__/deep-state.test.js.map +0 -1
- package/dist/__tests__/events.test.d.ts +0 -2
- package/dist/__tests__/events.test.d.ts.map +0 -1
- package/dist/__tests__/events.test.js +0 -295
- package/dist/__tests__/events.test.js.map +0 -1
- package/dist/__tests__/functions.test.d.ts +0 -2
- package/dist/__tests__/functions.test.d.ts.map +0 -1
- package/dist/__tests__/functions.test.js +0 -618
- package/dist/__tests__/functions.test.js.map +0 -1
- package/dist/__tests__/lazy.test.d.ts +0 -2
- package/dist/__tests__/lazy.test.d.ts.map +0 -1
- package/dist/__tests__/lazy.test.js +0 -227
- package/dist/__tests__/lazy.test.js.map +0 -1
- package/dist/__tests__/memo.test.d.ts +0 -2
- package/dist/__tests__/memo.test.d.ts.map +0 -1
- package/dist/__tests__/memo.test.js +0 -168
- package/dist/__tests__/memo.test.js.map +0 -1
- package/dist/__tests__/properties-browser.test.d.ts +0 -2
- package/dist/__tests__/properties-browser.test.d.ts.map +0 -1
- package/dist/__tests__/properties-browser.test.js +0 -178
- package/dist/__tests__/properties-browser.test.js.map +0 -1
- package/dist/__tests__/properties.test.d.ts +0 -2
- package/dist/__tests__/properties.test.d.ts.map +0 -1
- package/dist/__tests__/properties.test.js +0 -1226
- package/dist/__tests__/properties.test.js.map +0 -1
- package/dist/__tests__/ref.test.d.ts +0 -2
- package/dist/__tests__/ref.test.d.ts.map +0 -1
- package/dist/__tests__/ref.test.js +0 -106
- package/dist/__tests__/ref.test.js.map +0 -1
- package/dist/__tests__/resource.test.d.ts +0 -2
- package/dist/__tests__/resource.test.d.ts.map +0 -1
- package/dist/__tests__/resource.test.js +0 -217
- package/dist/__tests__/resource.test.js.map +0 -1
- package/dist/__tests__/slot.test.d.ts +0 -2
- package/dist/__tests__/slot.test.d.ts.map +0 -1
- package/dist/__tests__/slot.test.js +0 -147
- package/dist/__tests__/slot.test.js.map +0 -1
- package/dist/__tests__/synced.test.d.ts +0 -2
- package/dist/__tests__/synced.test.d.ts.map +0 -1
- package/dist/__tests__/synced.test.js +0 -161
- package/dist/__tests__/synced.test.js.map +0 -1
- package/dist/__tests__/until.test.d.ts +0 -2
- package/dist/__tests__/until.test.d.ts.map +0 -1
- package/dist/__tests__/until.test.js +0 -88
- package/dist/__tests__/until.test.js.map +0 -1
- package/dist/create-accessor-decorator.d.ts +0 -14
- package/dist/create-accessor-decorator.d.ts.map +0 -1
- package/dist/create-accessor-decorator.js +0 -27
- package/dist/create-accessor-decorator.js.map +0 -1
- package/src/__tests__/component.test.ts +0 -167
- package/src/__tests__/compose.test.ts +0 -202
- package/src/__tests__/deep-state.test.ts +0 -185
- package/src/__tests__/events.test.ts +0 -366
- package/src/__tests__/functions.test.ts +0 -803
- package/src/__tests__/lazy.test.ts +0 -296
- package/src/__tests__/memo.test.ts +0 -208
- package/src/__tests__/properties-browser.test.ts +0 -234
- package/src/__tests__/properties.test.ts +0 -1526
- package/src/__tests__/ref.test.ts +0 -131
- package/src/__tests__/resource.test.ts +0 -246
- package/src/__tests__/slot.test.ts +0 -179
- package/src/__tests__/synced.test.ts +0 -193
- package/src/__tests__/until.test.ts +0 -113
- package/src/create-accessor-decorator.ts +0 -45
- package/tsconfig.json +0 -9
|
@@ -1,296 +0,0 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
|
-
import { beforeEach, afterEach, describe, it, expect, vi } from "vitest";
|
|
3
|
-
|
|
4
|
-
import { StatefulComponent } from "@praxisjs/core";
|
|
5
|
-
import { setComponentAnchor } from "@praxisjs/core/internal";
|
|
6
|
-
import { Lazy } from "../component/lazy";
|
|
7
|
-
import type { LazyOptions } from "../component/lazy";
|
|
8
|
-
|
|
9
|
-
type AnyConstructor = new (...args: unknown[]) => StatefulComponent;
|
|
10
|
-
|
|
11
|
-
function applyLazy<T extends AnyConstructor>(cls: T, placeholderOrOptions?: number | LazyOptions): T {
|
|
12
|
-
if (placeholderOrOptions === undefined) return Lazy()(cls, {} as ClassDecoratorContext);
|
|
13
|
-
return Lazy(placeholderOrOptions)(cls, {} as ClassDecoratorContext);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/** render() now returns a reactive thunk — resolve it to get the actual DOM output. */
|
|
17
|
-
function resolveRender(instance: StatefulComponent): Node | Node[] | null {
|
|
18
|
-
const result = instance.render();
|
|
19
|
-
if (typeof result === "function") return (result as () => Node | Node[] | null)();
|
|
20
|
-
return result;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
class BaseComp extends StatefulComponent {
|
|
24
|
-
render() {
|
|
25
|
-
return document.createTextNode("content");
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
describe("Lazy decorator", () => {
|
|
30
|
-
let intersectionCallback: IntersectionObserverCallback = () => {};
|
|
31
|
-
let mockObserve: ReturnType<typeof vi.fn>;
|
|
32
|
-
let mockDisconnect: ReturnType<typeof vi.fn>;
|
|
33
|
-
|
|
34
|
-
beforeEach(() => {
|
|
35
|
-
intersectionCallback = () => {};
|
|
36
|
-
mockObserve = vi.fn();
|
|
37
|
-
mockDisconnect = vi.fn();
|
|
38
|
-
|
|
39
|
-
globalThis.IntersectionObserver = class {
|
|
40
|
-
constructor(cb: IntersectionObserverCallback) {
|
|
41
|
-
intersectionCallback = cb;
|
|
42
|
-
}
|
|
43
|
-
observe = mockObserve;
|
|
44
|
-
disconnect = mockDisconnect;
|
|
45
|
-
} as unknown as typeof IntersectionObserver;
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
afterEach(() => {
|
|
49
|
-
// @ts-expect-error – removing test stub
|
|
50
|
-
delete globalThis.IntersectionObserver;
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it("returns null from render before visibility", () => {
|
|
54
|
-
const Wrapped = applyLazy(BaseComp as AnyConstructor);
|
|
55
|
-
const instance = new Wrapped();
|
|
56
|
-
expect(resolveRender(instance)).toBeNull();
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it("sets lazy visibility to true when IntersectionObserver is not supported", () => {
|
|
60
|
-
// Remove IntersectionObserver to simulate unsupported environment
|
|
61
|
-
// @ts-expect-error – removing test stub
|
|
62
|
-
delete globalThis.IntersectionObserver;
|
|
63
|
-
|
|
64
|
-
const Wrapped = applyLazy(BaseComp as AnyConstructor);
|
|
65
|
-
const instance = new Wrapped();
|
|
66
|
-
|
|
67
|
-
// Simulate anchor with parent element
|
|
68
|
-
const parent = document.createElement("div");
|
|
69
|
-
document.body.appendChild(parent);
|
|
70
|
-
const anchor = document.createComment("end");
|
|
71
|
-
parent.appendChild(anchor);
|
|
72
|
-
setComponentAnchor(instance, anchor);
|
|
73
|
-
|
|
74
|
-
instance.onMount?.();
|
|
75
|
-
// Without IntersectionObserver, component should immediately become visible
|
|
76
|
-
expect(instance.render()).not.toBeNull();
|
|
77
|
-
document.body.removeChild(parent);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it("sets minHeight placeholder on mount", () => {
|
|
81
|
-
const Wrapped = applyLazy(BaseComp as AnyConstructor, 300);
|
|
82
|
-
const instance = new Wrapped();
|
|
83
|
-
|
|
84
|
-
const parent = document.createElement("div");
|
|
85
|
-
document.body.appendChild(parent);
|
|
86
|
-
const anchor = document.createComment("end");
|
|
87
|
-
parent.appendChild(anchor);
|
|
88
|
-
setComponentAnchor(instance, anchor);
|
|
89
|
-
|
|
90
|
-
instance.onMount?.();
|
|
91
|
-
expect(parent.style.minHeight).toBe("300px");
|
|
92
|
-
document.body.removeChild(parent);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it("sets _lazyVisible and clears minHeight when intersecting", () => {
|
|
96
|
-
const Wrapped = applyLazy(BaseComp as AnyConstructor, 200);
|
|
97
|
-
const instance = new Wrapped();
|
|
98
|
-
|
|
99
|
-
const parent = document.createElement("div");
|
|
100
|
-
document.body.appendChild(parent);
|
|
101
|
-
const anchor = document.createComment("end");
|
|
102
|
-
parent.appendChild(anchor);
|
|
103
|
-
setComponentAnchor(instance, anchor);
|
|
104
|
-
|
|
105
|
-
instance.onMount?.();
|
|
106
|
-
expect(resolveRender(instance)).toBeNull();
|
|
107
|
-
|
|
108
|
-
intersectionCallback(
|
|
109
|
-
[{ isIntersecting: true } as IntersectionObserverEntry],
|
|
110
|
-
null as unknown as IntersectionObserver,
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
expect(parent.style.minHeight).toBe("");
|
|
114
|
-
expect(resolveRender(instance)).not.toBeNull(); // component became visible after intersection
|
|
115
|
-
expect(mockDisconnect).toHaveBeenCalled();
|
|
116
|
-
document.body.removeChild(parent);
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
it("does nothing when the anchor has no parent", () => {
|
|
120
|
-
const Wrapped = applyLazy(BaseComp as AnyConstructor);
|
|
121
|
-
const instance = new Wrapped();
|
|
122
|
-
|
|
123
|
-
const anchor = document.createComment("end");
|
|
124
|
-
setComponentAnchor(instance, anchor);
|
|
125
|
-
|
|
126
|
-
// Should not throw even though anchor has no parent
|
|
127
|
-
expect(() => instance.onMount?.()).not.toThrow();
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
it("disconnects observer on unmount", () => {
|
|
131
|
-
const Wrapped = applyLazy(BaseComp as AnyConstructor);
|
|
132
|
-
const instance = new Wrapped();
|
|
133
|
-
|
|
134
|
-
const parent = document.createElement("div");
|
|
135
|
-
document.body.appendChild(parent);
|
|
136
|
-
const anchor = document.createComment("end");
|
|
137
|
-
parent.appendChild(anchor);
|
|
138
|
-
setComponentAnchor(instance, anchor);
|
|
139
|
-
|
|
140
|
-
instance.onMount?.();
|
|
141
|
-
instance.onUnmount?.();
|
|
142
|
-
expect(mockDisconnect).toHaveBeenCalled();
|
|
143
|
-
document.body.removeChild(parent);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it("onMount does nothing when anchor is undefined", () => {
|
|
147
|
-
const Wrapped = applyLazy(BaseComp as AnyConstructor);
|
|
148
|
-
const instance = new Wrapped();
|
|
149
|
-
expect(() => instance.onMount?.()).not.toThrow();
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it("render() returns original content after becoming visible", () => {
|
|
153
|
-
const Wrapped = applyLazy(BaseComp as AnyConstructor, 200);
|
|
154
|
-
const instance = new Wrapped();
|
|
155
|
-
|
|
156
|
-
const parent = document.createElement("div");
|
|
157
|
-
document.body.appendChild(parent);
|
|
158
|
-
const anchor = document.createComment("end");
|
|
159
|
-
parent.appendChild(anchor);
|
|
160
|
-
setComponentAnchor(instance, anchor);
|
|
161
|
-
|
|
162
|
-
instance.onMount?.();
|
|
163
|
-
expect(resolveRender(instance)).toBeNull(); // not yet visible
|
|
164
|
-
|
|
165
|
-
intersectionCallback(
|
|
166
|
-
[{ isIntersecting: true } as IntersectionObserverEntry],
|
|
167
|
-
null as unknown as IntersectionObserver,
|
|
168
|
-
);
|
|
169
|
-
|
|
170
|
-
// Now visible: render() must delegate to the original render()
|
|
171
|
-
const result = resolveRender(instance);
|
|
172
|
-
expect(result).not.toBeNull();
|
|
173
|
-
document.body.removeChild(parent);
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
it("observer stays active and minHeight is preserved when entry is not intersecting", () => {
|
|
177
|
-
const Wrapped = applyLazy(BaseComp as AnyConstructor, 200);
|
|
178
|
-
const instance = new Wrapped();
|
|
179
|
-
|
|
180
|
-
const parent = document.createElement("div");
|
|
181
|
-
document.body.appendChild(parent);
|
|
182
|
-
const anchor = document.createComment("end");
|
|
183
|
-
parent.appendChild(anchor);
|
|
184
|
-
setComponentAnchor(instance, anchor);
|
|
185
|
-
|
|
186
|
-
instance.onMount?.();
|
|
187
|
-
|
|
188
|
-
// Fire callback with isIntersecting=false — should be a no-op
|
|
189
|
-
intersectionCallback(
|
|
190
|
-
[{ isIntersecting: false } as IntersectionObserverEntry],
|
|
191
|
-
null as unknown as IntersectionObserver,
|
|
192
|
-
);
|
|
193
|
-
|
|
194
|
-
expect(resolveRender(instance)).toBeNull();
|
|
195
|
-
expect(mockDisconnect).not.toHaveBeenCalled();
|
|
196
|
-
expect(parent.style.minHeight).toBe("200px");
|
|
197
|
-
document.body.removeChild(parent);
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
it("accepts a LazyOptions object instead of a number (line 74 false branch)", () => {
|
|
201
|
-
const Wrapped = applyLazy(BaseComp as AnyConstructor, { placeholder: 150, rootMargin: "50px" });
|
|
202
|
-
const instance = new Wrapped();
|
|
203
|
-
|
|
204
|
-
const parent = document.createElement("div");
|
|
205
|
-
document.body.appendChild(parent);
|
|
206
|
-
const anchor = document.createComment("end");
|
|
207
|
-
parent.appendChild(anchor);
|
|
208
|
-
setComponentAnchor(instance, anchor);
|
|
209
|
-
|
|
210
|
-
instance.onMount?.();
|
|
211
|
-
expect(parent.style.minHeight).toBe("150px");
|
|
212
|
-
document.body.removeChild(parent);
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
it("uses root.current as the IntersectionObserver root when provided (line 42 false branch)", () => {
|
|
216
|
-
const rootEl = document.createElement("div");
|
|
217
|
-
document.body.appendChild(rootEl);
|
|
218
|
-
|
|
219
|
-
let capturedInit: IntersectionObserverInit | undefined;
|
|
220
|
-
globalThis.IntersectionObserver = class {
|
|
221
|
-
constructor(cb: IntersectionObserverCallback, init?: IntersectionObserverInit) {
|
|
222
|
-
intersectionCallback = cb;
|
|
223
|
-
capturedInit = init;
|
|
224
|
-
}
|
|
225
|
-
observe = mockObserve;
|
|
226
|
-
disconnect = mockDisconnect;
|
|
227
|
-
} as unknown as typeof IntersectionObserver;
|
|
228
|
-
|
|
229
|
-
const Wrapped = applyLazy(BaseComp as AnyConstructor, { root: { current: rootEl } });
|
|
230
|
-
const instance = new Wrapped();
|
|
231
|
-
|
|
232
|
-
const parent = document.createElement("div");
|
|
233
|
-
document.body.appendChild(parent);
|
|
234
|
-
const anchor = document.createComment("end");
|
|
235
|
-
parent.appendChild(anchor);
|
|
236
|
-
setComponentAnchor(instance, anchor);
|
|
237
|
-
|
|
238
|
-
instance.onMount?.();
|
|
239
|
-
expect(capturedInit?.root).toBe(rootEl);
|
|
240
|
-
|
|
241
|
-
document.body.removeChild(parent);
|
|
242
|
-
document.body.removeChild(rootEl);
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
it("falls back to null root when root.current is null (line 42 ?? null branch)", () => {
|
|
246
|
-
let capturedInit: IntersectionObserverInit | undefined;
|
|
247
|
-
globalThis.IntersectionObserver = class {
|
|
248
|
-
constructor(cb: IntersectionObserverCallback, init?: IntersectionObserverInit) {
|
|
249
|
-
intersectionCallback = cb;
|
|
250
|
-
capturedInit = init;
|
|
251
|
-
}
|
|
252
|
-
observe = mockObserve;
|
|
253
|
-
disconnect = mockDisconnect;
|
|
254
|
-
} as unknown as typeof IntersectionObserver;
|
|
255
|
-
|
|
256
|
-
const Wrapped = applyLazy(BaseComp as AnyConstructor, { root: { current: null } });
|
|
257
|
-
const instance = new Wrapped();
|
|
258
|
-
|
|
259
|
-
const parent = document.createElement("div");
|
|
260
|
-
document.body.appendChild(parent);
|
|
261
|
-
const anchor = document.createComment("end");
|
|
262
|
-
parent.appendChild(anchor);
|
|
263
|
-
setComponentAnchor(instance, anchor);
|
|
264
|
-
|
|
265
|
-
instance.onMount?.();
|
|
266
|
-
expect(capturedInit?.root).toBeNull();
|
|
267
|
-
|
|
268
|
-
document.body.removeChild(parent);
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
it("does not re-apply minHeight on remount when already visible", () => {
|
|
272
|
-
const Wrapped = applyLazy(BaseComp as AnyConstructor, 300);
|
|
273
|
-
const instance = new Wrapped();
|
|
274
|
-
|
|
275
|
-
const parent = document.createElement("div");
|
|
276
|
-
document.body.appendChild(parent);
|
|
277
|
-
const anchor = document.createComment("end");
|
|
278
|
-
parent.appendChild(anchor);
|
|
279
|
-
setComponentAnchor(instance, anchor);
|
|
280
|
-
|
|
281
|
-
// First mount — become visible
|
|
282
|
-
instance.onMount?.();
|
|
283
|
-
intersectionCallback(
|
|
284
|
-
[{ isIntersecting: true } as IntersectionObserverEntry],
|
|
285
|
-
null as unknown as IntersectionObserver,
|
|
286
|
-
);
|
|
287
|
-
instance.onUnmount?.();
|
|
288
|
-
|
|
289
|
-
// Second mount — lazy visibility is already true
|
|
290
|
-
instance.onMount?.();
|
|
291
|
-
|
|
292
|
-
// minHeight must NOT be re-applied because lazy visibility is true
|
|
293
|
-
expect(parent.style.minHeight).toBe("");
|
|
294
|
-
document.body.removeChild(parent);
|
|
295
|
-
});
|
|
296
|
-
});
|
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from "vitest";
|
|
2
|
-
|
|
3
|
-
import { signal } from "@praxisjs/core/internal";
|
|
4
|
-
import { Memo } from "../functions/memo";
|
|
5
|
-
|
|
6
|
-
function mockCtx(name: string) {
|
|
7
|
-
const initializers: Array<(this: object) => void> = [];
|
|
8
|
-
return {
|
|
9
|
-
name,
|
|
10
|
-
kind: "method" as const,
|
|
11
|
-
addInitializer(fn: (this: object) => void) {
|
|
12
|
-
initializers.push(fn);
|
|
13
|
-
},
|
|
14
|
-
runInitializers(instance: object) {
|
|
15
|
-
initializers.forEach((fn) => { fn.call(instance); });
|
|
16
|
-
},
|
|
17
|
-
} as unknown as ClassMethodDecoratorContext & { runInitializers(instance: object): void };
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
describe("Memo", () => {
|
|
21
|
-
it("returns the computed value", () => {
|
|
22
|
-
const fn = vi.fn((x: unknown) => (x as number) * 2);
|
|
23
|
-
const ctx = mockCtx("double");
|
|
24
|
-
Memo()(fn, ctx);
|
|
25
|
-
|
|
26
|
-
const obj = {};
|
|
27
|
-
ctx.runInitializers(obj);
|
|
28
|
-
const method = (obj as Record<string, (...a: unknown[]) => unknown>).double;
|
|
29
|
-
|
|
30
|
-
expect(method(5)).toBe(10);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it("caches result for same args — calls original only once", () => {
|
|
34
|
-
const fn = vi.fn((x: unknown) => (x as number) + 1);
|
|
35
|
-
const ctx = mockCtx("inc");
|
|
36
|
-
Memo()(fn, ctx);
|
|
37
|
-
|
|
38
|
-
const obj = {};
|
|
39
|
-
ctx.runInitializers(obj);
|
|
40
|
-
const method = (obj as Record<string, (...a: unknown[]) => unknown>).inc;
|
|
41
|
-
|
|
42
|
-
method(3);
|
|
43
|
-
method(3);
|
|
44
|
-
method(3);
|
|
45
|
-
expect(fn).toHaveBeenCalledTimes(1);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it("computes separately for different args", () => {
|
|
49
|
-
const fn = vi.fn((x: unknown) => (x as number) * 10);
|
|
50
|
-
const ctx = mockCtx("mul");
|
|
51
|
-
Memo()(fn, ctx);
|
|
52
|
-
|
|
53
|
-
const obj = {};
|
|
54
|
-
ctx.runInitializers(obj);
|
|
55
|
-
const method = (obj as Record<string, (...a: unknown[]) => unknown>).mul;
|
|
56
|
-
|
|
57
|
-
expect(method(2)).toBe(20);
|
|
58
|
-
expect(method(3)).toBe(30);
|
|
59
|
-
expect(fn).toHaveBeenCalledTimes(2);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it("is per-instance — different instances have separate caches", () => {
|
|
63
|
-
const fn = vi.fn(() => Math.random());
|
|
64
|
-
const ctx = mockCtx("rand");
|
|
65
|
-
Memo()(fn, ctx);
|
|
66
|
-
|
|
67
|
-
const a = {};
|
|
68
|
-
const b = {};
|
|
69
|
-
ctx.runInitializers(a);
|
|
70
|
-
ctx.runInitializers(b);
|
|
71
|
-
|
|
72
|
-
const methodA = (a as Record<string, () => unknown>).rand;
|
|
73
|
-
const methodB = (b as Record<string, () => unknown>).rand;
|
|
74
|
-
const v1 = methodA();
|
|
75
|
-
const v2 = methodB();
|
|
76
|
-
expect(fn).toHaveBeenCalledTimes(2);
|
|
77
|
-
expect(v1).not.toBe(v2); // different random values per instance
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it("serializes no-args calls with __no_args__ key", () => {
|
|
81
|
-
const fn = vi.fn(() => 42);
|
|
82
|
-
const ctx = mockCtx("noop");
|
|
83
|
-
Memo()(fn, ctx);
|
|
84
|
-
|
|
85
|
-
const obj = {};
|
|
86
|
-
ctx.runInitializers(obj);
|
|
87
|
-
const method = (obj as Record<string, () => unknown>).noop;
|
|
88
|
-
|
|
89
|
-
method();
|
|
90
|
-
method();
|
|
91
|
-
expect(fn).toHaveBeenCalledTimes(1);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it("serializes object args via JSON.stringify", () => {
|
|
95
|
-
const fn = vi.fn((o: unknown) => JSON.stringify(o));
|
|
96
|
-
const ctx = mockCtx("obj");
|
|
97
|
-
Memo()(fn, ctx);
|
|
98
|
-
|
|
99
|
-
const obj = {};
|
|
100
|
-
ctx.runInitializers(obj);
|
|
101
|
-
const method = (obj as Record<string, (...a: unknown[]) => unknown>).obj;
|
|
102
|
-
|
|
103
|
-
method({ a: 1 });
|
|
104
|
-
method({ a: 1 }); // same key
|
|
105
|
-
expect(fn).toHaveBeenCalledTimes(1);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it("re-evaluates when a reactive signal read inside the method changes", () => {
|
|
109
|
-
const s = signal(1);
|
|
110
|
-
const fn = vi.fn(() => s() * 10);
|
|
111
|
-
const ctx = mockCtx("reactive");
|
|
112
|
-
Memo()(fn, ctx);
|
|
113
|
-
|
|
114
|
-
const obj = {};
|
|
115
|
-
ctx.runInitializers(obj);
|
|
116
|
-
const method = (obj as Record<string, () => unknown>).reactive;
|
|
117
|
-
|
|
118
|
-
expect(method()).toBe(10);
|
|
119
|
-
expect(fn).toHaveBeenCalledTimes(1);
|
|
120
|
-
|
|
121
|
-
s.set(5);
|
|
122
|
-
// The computed tracks s() — next read triggers recomputation
|
|
123
|
-
expect(method()).toBe(50);
|
|
124
|
-
expect(fn).toHaveBeenCalledTimes(2);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it("serializes symbol args via toString()", () => {
|
|
128
|
-
const fn = vi.fn((s: unknown) => String(s));
|
|
129
|
-
const ctx = mockCtx("sym");
|
|
130
|
-
Memo()(fn, ctx);
|
|
131
|
-
|
|
132
|
-
const obj = {};
|
|
133
|
-
ctx.runInitializers(obj);
|
|
134
|
-
const method = (obj as Record<string, (...a: unknown[]) => unknown>).sym;
|
|
135
|
-
|
|
136
|
-
const sym = Symbol("test");
|
|
137
|
-
method(sym);
|
|
138
|
-
method(sym);
|
|
139
|
-
expect(fn).toHaveBeenCalledTimes(1);
|
|
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
|
-
});
|
|
208
|
-
});
|