@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.
Files changed (270) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/dist/component/component.d.ts +1 -1
  3. package/dist/component/component.d.ts.map +1 -1
  4. package/dist/component/component.js +8 -15
  5. package/dist/component/component.js.map +1 -1
  6. package/dist/component/lazy.d.ts +1 -1
  7. package/dist/component/lazy.d.ts.map +1 -1
  8. package/dist/component/lazy.js +44 -36
  9. package/dist/component/lazy.js.map +1 -1
  10. package/dist/create-class-decorator.d.ts +27 -5
  11. package/dist/create-class-decorator.d.ts.map +1 -1
  12. package/dist/create-class-decorator.js +53 -18
  13. package/dist/create-class-decorator.js.map +1 -1
  14. package/dist/create-field-decorator.d.ts +42 -3
  15. package/dist/create-field-decorator.d.ts.map +1 -1
  16. package/dist/create-field-decorator.js +63 -27
  17. package/dist/create-field-decorator.js.map +1 -1
  18. package/dist/create-getter-decorator.d.ts +81 -16
  19. package/dist/create-getter-decorator.d.ts.map +1 -1
  20. package/dist/create-getter-decorator.js +127 -15
  21. package/dist/create-getter-decorator.js.map +1 -1
  22. package/dist/create-lifecycle-method-decorator.d.ts +21 -3
  23. package/dist/create-lifecycle-method-decorator.d.ts.map +1 -1
  24. package/dist/create-lifecycle-method-decorator.js +25 -18
  25. package/dist/create-lifecycle-method-decorator.js.map +1 -1
  26. package/dist/create-method-decorator.d.ts +43 -3
  27. package/dist/create-method-decorator.d.ts.map +1 -1
  28. package/dist/create-method-decorator.js +49 -8
  29. package/dist/create-method-decorator.js.map +1 -1
  30. package/dist/events/command.d.ts +2 -1
  31. package/dist/events/command.d.ts.map +1 -1
  32. package/dist/events/command.js +10 -8
  33. package/dist/events/command.js.map +1 -1
  34. package/dist/events/emit.d.ts +2 -2
  35. package/dist/events/emit.d.ts.map +1 -1
  36. package/dist/events/emit.js +18 -24
  37. package/dist/events/emit.js.map +1 -1
  38. package/dist/events/on-command.d.ts +2 -1
  39. package/dist/events/on-command.d.ts.map +1 -1
  40. package/dist/events/on-command.js +12 -14
  41. package/dist/events/on-command.js.map +1 -1
  42. package/dist/field.d.ts +51 -0
  43. package/dist/field.d.ts.map +1 -0
  44. package/dist/field.js +82 -0
  45. package/dist/field.js.map +1 -0
  46. package/dist/functions/bind.d.ts +2 -1
  47. package/dist/functions/bind.d.ts.map +1 -1
  48. package/dist/functions/bind.js +4 -4
  49. package/dist/functions/bind.js.map +1 -1
  50. package/dist/functions/debounce.d.ts +2 -2
  51. package/dist/functions/debounce.d.ts.map +1 -1
  52. package/dist/functions/debounce.js +25 -23
  53. package/dist/functions/debounce.js.map +1 -1
  54. package/dist/functions/index.d.ts +1 -1
  55. package/dist/functions/index.d.ts.map +1 -1
  56. package/dist/functions/index.js.map +1 -1
  57. package/dist/functions/log.d.ts +2 -1
  58. package/dist/functions/log.d.ts.map +1 -1
  59. package/dist/functions/log.js +38 -40
  60. package/dist/functions/log.js.map +1 -1
  61. package/dist/functions/memo.d.ts +9 -1
  62. package/dist/functions/memo.d.ts.map +1 -1
  63. package/dist/functions/memo.js +44 -30
  64. package/dist/functions/memo.js.map +1 -1
  65. package/dist/functions/once.d.ts +2 -1
  66. package/dist/functions/once.d.ts.map +1 -1
  67. package/dist/functions/once.js +10 -14
  68. package/dist/functions/once.js.map +1 -1
  69. package/dist/functions/retry.d.ts +2 -1
  70. package/dist/functions/retry.d.ts.map +1 -1
  71. package/dist/functions/retry.js +21 -23
  72. package/dist/functions/retry.js.map +1 -1
  73. package/dist/functions/throttle.d.ts +2 -1
  74. package/dist/functions/throttle.d.ts.map +1 -1
  75. package/dist/functions/throttle.js +12 -14
  76. package/dist/functions/throttle.js.map +1 -1
  77. package/dist/functions/until.d.ts +2 -1
  78. package/dist/functions/until.d.ts.map +1 -1
  79. package/dist/functions/until.js +13 -14
  80. package/dist/functions/until.js.map +1 -1
  81. package/dist/functions/watch.d.ts +1 -1
  82. package/dist/functions/watch.d.ts.map +1 -1
  83. package/dist/functions/watch.js +36 -34
  84. package/dist/functions/watch.js.map +1 -1
  85. package/dist/functions/when.d.ts +1 -1
  86. package/dist/functions/when.d.ts.map +1 -1
  87. package/dist/functions/when.js +19 -21
  88. package/dist/functions/when.js.map +1 -1
  89. package/dist/index.d.ts +6 -6
  90. package/dist/index.d.ts.map +1 -1
  91. package/dist/index.js +2 -2
  92. package/dist/index.js.map +1 -1
  93. package/dist/lifecycle.d.ts +24 -0
  94. package/dist/lifecycle.d.ts.map +1 -0
  95. package/dist/lifecycle.js +55 -0
  96. package/dist/lifecycle.js.map +1 -0
  97. package/dist/properties/compose.d.ts +3 -2
  98. package/dist/properties/compose.d.ts.map +1 -1
  99. package/dist/properties/compose.js +33 -19
  100. package/dist/properties/compose.js.map +1 -1
  101. package/dist/properties/computed.d.ts +12 -10
  102. package/dist/properties/computed.d.ts.map +1 -1
  103. package/dist/properties/computed.js +26 -21
  104. package/dist/properties/computed.js.map +1 -1
  105. package/dist/properties/deep-state.d.ts +2 -1
  106. package/dist/properties/deep-state.d.ts.map +1 -1
  107. package/dist/properties/deep-state.js +66 -49
  108. package/dist/properties/deep-state.js.map +1 -1
  109. package/dist/properties/function-prop.d.ts +2 -1
  110. package/dist/properties/function-prop.d.ts.map +1 -1
  111. package/dist/properties/function-prop.js +20 -22
  112. package/dist/properties/function-prop.js.map +1 -1
  113. package/dist/properties/history.d.ts +2 -2
  114. package/dist/properties/history.d.ts.map +1 -1
  115. package/dist/properties/history.js +34 -31
  116. package/dist/properties/history.js.map +1 -1
  117. package/dist/properties/index.d.ts +1 -0
  118. package/dist/properties/index.d.ts.map +1 -1
  119. package/dist/properties/index.js +1 -0
  120. package/dist/properties/index.js.map +1 -1
  121. package/dist/properties/persisted.d.ts +2 -1
  122. package/dist/properties/persisted.d.ts.map +1 -1
  123. package/dist/properties/persisted.js +30 -19
  124. package/dist/properties/persisted.js.map +1 -1
  125. package/dist/properties/prop.d.ts +2 -1
  126. package/dist/properties/prop.d.ts.map +1 -1
  127. package/dist/properties/prop.js +26 -22
  128. package/dist/properties/prop.js.map +1 -1
  129. package/dist/properties/ref.d.ts +2 -1
  130. package/dist/properties/ref.d.ts.map +1 -1
  131. package/dist/properties/ref.js +11 -8
  132. package/dist/properties/ref.js.map +1 -1
  133. package/dist/properties/resource.d.ts.map +1 -1
  134. package/dist/properties/resource.js +21 -16
  135. package/dist/properties/resource.js.map +1 -1
  136. package/dist/properties/selector.d.ts +12 -0
  137. package/dist/properties/selector.d.ts.map +1 -0
  138. package/dist/properties/selector.js +25 -0
  139. package/dist/properties/selector.js.map +1 -0
  140. package/dist/properties/slot.d.ts +2 -1
  141. package/dist/properties/slot.d.ts.map +1 -1
  142. package/dist/properties/slot.js +22 -20
  143. package/dist/properties/slot.js.map +1 -1
  144. package/dist/properties/state.d.ts.map +1 -1
  145. package/dist/properties/state.js +27 -16
  146. package/dist/properties/state.js.map +1 -1
  147. package/dist/properties/synced.d.ts +2 -1
  148. package/dist/properties/synced.d.ts.map +1 -1
  149. package/dist/properties/synced.js +27 -19
  150. package/dist/properties/synced.js.map +1 -1
  151. package/dist/shared-method.d.ts +14 -0
  152. package/dist/shared-method.d.ts.map +1 -0
  153. package/dist/shared-method.js +87 -0
  154. package/dist/shared-method.js.map +1 -0
  155. package/package.json +14 -5
  156. package/src/component/component.ts +8 -27
  157. package/src/component/lazy.ts +51 -41
  158. package/src/create-class-decorator.ts +90 -31
  159. package/src/create-field-decorator.ts +120 -43
  160. package/src/create-getter-decorator.ts +263 -66
  161. package/src/create-lifecycle-method-decorator.ts +50 -28
  162. package/src/create-method-decorator.ts +119 -13
  163. package/src/events/command.ts +11 -9
  164. package/src/events/emit.ts +16 -22
  165. package/src/events/on-command.ts +17 -18
  166. package/src/field.ts +116 -0
  167. package/src/functions/bind.ts +4 -4
  168. package/src/functions/debounce.ts +25 -23
  169. package/src/functions/index.ts +1 -1
  170. package/src/functions/log.ts +44 -46
  171. package/src/functions/memo.ts +54 -28
  172. package/src/functions/once.ts +10 -13
  173. package/src/functions/retry.ts +19 -21
  174. package/src/functions/throttle.ts +12 -13
  175. package/src/functions/until.ts +14 -14
  176. package/src/functions/watch.ts +37 -36
  177. package/src/functions/when.ts +18 -20
  178. package/src/index.ts +13 -5
  179. package/src/lifecycle.ts +74 -0
  180. package/src/properties/compose.ts +33 -20
  181. package/src/properties/computed.ts +35 -45
  182. package/src/properties/deep-state.ts +68 -53
  183. package/src/properties/function-prop.ts +25 -27
  184. package/src/properties/history.ts +34 -33
  185. package/src/properties/index.ts +1 -0
  186. package/src/properties/persisted.ts +41 -27
  187. package/src/properties/prop.ts +30 -22
  188. package/src/properties/ref.ts +12 -10
  189. package/src/properties/resource.ts +22 -17
  190. package/src/properties/selector.ts +45 -0
  191. package/src/properties/slot.ts +28 -22
  192. package/src/properties/state.ts +32 -16
  193. package/src/properties/synced.ts +31 -19
  194. package/src/shared-method.ts +101 -0
  195. package/dist/__tests__/component.test.d.ts +0 -2
  196. package/dist/__tests__/component.test.d.ts.map +0 -1
  197. package/dist/__tests__/component.test.js +0 -147
  198. package/dist/__tests__/component.test.js.map +0 -1
  199. package/dist/__tests__/compose.test.d.ts +0 -2
  200. package/dist/__tests__/compose.test.d.ts.map +0 -1
  201. package/dist/__tests__/compose.test.js +0 -164
  202. package/dist/__tests__/compose.test.js.map +0 -1
  203. package/dist/__tests__/deep-state.test.d.ts +0 -2
  204. package/dist/__tests__/deep-state.test.d.ts.map +0 -1
  205. package/dist/__tests__/deep-state.test.js +0 -150
  206. package/dist/__tests__/deep-state.test.js.map +0 -1
  207. package/dist/__tests__/events.test.d.ts +0 -2
  208. package/dist/__tests__/events.test.d.ts.map +0 -1
  209. package/dist/__tests__/events.test.js +0 -295
  210. package/dist/__tests__/events.test.js.map +0 -1
  211. package/dist/__tests__/functions.test.d.ts +0 -2
  212. package/dist/__tests__/functions.test.d.ts.map +0 -1
  213. package/dist/__tests__/functions.test.js +0 -618
  214. package/dist/__tests__/functions.test.js.map +0 -1
  215. package/dist/__tests__/lazy.test.d.ts +0 -2
  216. package/dist/__tests__/lazy.test.d.ts.map +0 -1
  217. package/dist/__tests__/lazy.test.js +0 -227
  218. package/dist/__tests__/lazy.test.js.map +0 -1
  219. package/dist/__tests__/memo.test.d.ts +0 -2
  220. package/dist/__tests__/memo.test.d.ts.map +0 -1
  221. package/dist/__tests__/memo.test.js +0 -168
  222. package/dist/__tests__/memo.test.js.map +0 -1
  223. package/dist/__tests__/properties-browser.test.d.ts +0 -2
  224. package/dist/__tests__/properties-browser.test.d.ts.map +0 -1
  225. package/dist/__tests__/properties-browser.test.js +0 -178
  226. package/dist/__tests__/properties-browser.test.js.map +0 -1
  227. package/dist/__tests__/properties.test.d.ts +0 -2
  228. package/dist/__tests__/properties.test.d.ts.map +0 -1
  229. package/dist/__tests__/properties.test.js +0 -1226
  230. package/dist/__tests__/properties.test.js.map +0 -1
  231. package/dist/__tests__/ref.test.d.ts +0 -2
  232. package/dist/__tests__/ref.test.d.ts.map +0 -1
  233. package/dist/__tests__/ref.test.js +0 -106
  234. package/dist/__tests__/ref.test.js.map +0 -1
  235. package/dist/__tests__/resource.test.d.ts +0 -2
  236. package/dist/__tests__/resource.test.d.ts.map +0 -1
  237. package/dist/__tests__/resource.test.js +0 -217
  238. package/dist/__tests__/resource.test.js.map +0 -1
  239. package/dist/__tests__/slot.test.d.ts +0 -2
  240. package/dist/__tests__/slot.test.d.ts.map +0 -1
  241. package/dist/__tests__/slot.test.js +0 -147
  242. package/dist/__tests__/slot.test.js.map +0 -1
  243. package/dist/__tests__/synced.test.d.ts +0 -2
  244. package/dist/__tests__/synced.test.d.ts.map +0 -1
  245. package/dist/__tests__/synced.test.js +0 -161
  246. package/dist/__tests__/synced.test.js.map +0 -1
  247. package/dist/__tests__/until.test.d.ts +0 -2
  248. package/dist/__tests__/until.test.d.ts.map +0 -1
  249. package/dist/__tests__/until.test.js +0 -88
  250. package/dist/__tests__/until.test.js.map +0 -1
  251. package/dist/create-accessor-decorator.d.ts +0 -14
  252. package/dist/create-accessor-decorator.d.ts.map +0 -1
  253. package/dist/create-accessor-decorator.js +0 -27
  254. package/dist/create-accessor-decorator.js.map +0 -1
  255. package/src/__tests__/component.test.ts +0 -167
  256. package/src/__tests__/compose.test.ts +0 -202
  257. package/src/__tests__/deep-state.test.ts +0 -185
  258. package/src/__tests__/events.test.ts +0 -366
  259. package/src/__tests__/functions.test.ts +0 -803
  260. package/src/__tests__/lazy.test.ts +0 -296
  261. package/src/__tests__/memo.test.ts +0 -208
  262. package/src/__tests__/properties-browser.test.ts +0 -234
  263. package/src/__tests__/properties.test.ts +0 -1526
  264. package/src/__tests__/ref.test.ts +0 -131
  265. package/src/__tests__/resource.test.ts +0 -246
  266. package/src/__tests__/slot.test.ts +0 -179
  267. package/src/__tests__/synced.test.ts +0 -193
  268. package/src/__tests__/until.test.ts +0 -113
  269. package/src/create-accessor-decorator.ts +0 -45
  270. 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
- });