@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,131 +0,0 @@
1
- // @vitest-environment jsdom
2
- import { describe, it, expect } from "vitest";
3
-
4
- import { StatefulComponent } from "@praxisjs/core";
5
- import { Ref, createRef } from "../properties/ref";
6
-
7
- function fieldCtx(name: string) {
8
- const initializers: Array<(this: unknown) => void> = [];
9
- return {
10
- ctx: {
11
- name,
12
- kind: "field" as const,
13
- addInitializer(fn: (this: unknown) => void) {
14
- initializers.push(fn);
15
- },
16
- } as ClassFieldDecoratorContext,
17
- run(instance: unknown) {
18
- initializers.forEach((fn) => { fn.call(instance); });
19
- },
20
- };
21
- }
22
-
23
- class TestComponent extends StatefulComponent {
24
- render() { return null; }
25
- }
26
-
27
- describe("Ref decorator", () => {
28
- it("initializes .current to null", () => {
29
- const { ctx, run } = fieldCtx("elRef");
30
- Ref()(undefined, ctx);
31
-
32
- const instance = new TestComponent();
33
- run(instance);
34
-
35
- const ref = (instance as unknown as Record<string, unknown>).elRef as { current: unknown };
36
- expect(ref.current).toBeNull();
37
- });
38
-
39
- it("is callable and sets .current", () => {
40
- const { ctx, run } = fieldCtx("elRef");
41
- Ref<HTMLDivElement>()(undefined, ctx);
42
-
43
- const instance = new TestComponent();
44
- run(instance);
45
-
46
- const ref = (instance as unknown as Record<string, unknown>).elRef as ((el: HTMLDivElement | null) => void) & { current: HTMLDivElement | null };
47
- const fakeEl = document.createElement("div") as HTMLDivElement;
48
- ref(fakeEl);
49
-
50
- expect(ref.current).toBe(fakeEl);
51
- });
52
-
53
- it("calling with null clears .current", () => {
54
- const { ctx, run } = fieldCtx("elRef");
55
- Ref<HTMLDivElement>()(undefined, ctx);
56
-
57
- const instance = new TestComponent();
58
- run(instance);
59
-
60
- const ref = (instance as unknown as Record<string, unknown>).elRef as ((el: HTMLDivElement | null) => void) & { current: HTMLDivElement | null };
61
- const fakeEl = document.createElement("div") as HTMLDivElement;
62
- ref(fakeEl);
63
- expect(ref.current).toBe(fakeEl);
64
-
65
- ref(null);
66
- expect(ref.current).toBeNull();
67
- });
68
-
69
- it("returns the same ref object on every access", () => {
70
- const { ctx, run } = fieldCtx("elRef");
71
- Ref()(undefined, ctx);
72
-
73
- const instance = new TestComponent();
74
- run(instance);
75
-
76
- const inst = instance as unknown as Record<string, unknown>;
77
- expect(inst.elRef).toBe(inst.elRef);
78
- });
79
-
80
- it("each instance gets its own independent ref", () => {
81
- const { ctx: ctx1, run: run1 } = fieldCtx("elRef");
82
- const { ctx: ctx2, run: run2 } = fieldCtx("elRef");
83
- Ref<HTMLDivElement>()(undefined, ctx1);
84
- Ref<HTMLDivElement>()(undefined, ctx2);
85
-
86
- const a = new TestComponent();
87
- const b = new TestComponent();
88
- run1(a);
89
- run2(b);
90
-
91
- const refA = (a as unknown as Record<string, unknown>).elRef as ((el: HTMLDivElement | null) => void) & { current: HTMLDivElement | null };
92
- const refB = (b as unknown as Record<string, unknown>).elRef as ((el: HTMLDivElement | null) => void) & { current: HTMLDivElement | null };
93
-
94
- const el = document.createElement("div") as HTMLDivElement;
95
- refA(el);
96
-
97
- expect(refA.current).toBe(el);
98
- expect(refB.current).toBeNull();
99
- });
100
- });
101
-
102
- describe("createRef", () => {
103
- it("initializes .current to null", () => {
104
- const ref = createRef<HTMLDivElement>();
105
- expect(ref.current).toBeNull();
106
- });
107
-
108
- it("is callable and sets .current", () => {
109
- const ref = createRef<HTMLDivElement>();
110
- const el = document.createElement("div") as HTMLDivElement;
111
- ref(el);
112
- expect(ref.current).toBe(el);
113
- });
114
-
115
- it("calling with null clears .current", () => {
116
- const ref = createRef<HTMLDivElement>();
117
- const el = document.createElement("div") as HTMLDivElement;
118
- ref(el);
119
- ref(null);
120
- expect(ref.current).toBeNull();
121
- });
122
-
123
- it("each call creates an independent ref", () => {
124
- const a = createRef<HTMLDivElement>();
125
- const b = createRef<HTMLDivElement>();
126
- const el = document.createElement("div") as HTMLDivElement;
127
- a(el);
128
- expect(a.current).toBe(el);
129
- expect(b.current).toBeNull();
130
- });
131
- });
@@ -1,246 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from "vitest";
2
-
3
- import { invalidateResource, _clearCache } from "@praxisjs/core/internal";
4
-
5
- import { Resource } from "../properties/resource";
6
-
7
- function makeFieldCtx(name: string) {
8
- const initializers: Array<(this: unknown) => void> = [];
9
- return {
10
- ctx: {
11
- name,
12
- kind: "field" as const,
13
- addInitializer(fn: (this: unknown) => void) { initializers.push(fn); },
14
- } as ClassFieldDecoratorContext,
15
- run(instance: unknown) { initializers.forEach((fn) => { fn.call(instance); }); },
16
- };
17
- }
18
-
19
- describe("@Resource", () => {
20
- it("binds a Resource object to the field", () => {
21
- const { ctx, run } = makeFieldCtx("users");
22
- Resource(() => Promise.resolve([]))(undefined, ctx);
23
- const instance: Record<string, unknown> = {};
24
- run(instance);
25
- const r = instance.users as Record<string, unknown>;
26
- expect(typeof r.data).toBe("function");
27
- expect(typeof r.pending).toBe("function");
28
- expect(typeof r.error).toBe("function");
29
- expect(typeof r.status).toBe("function");
30
- expect(typeof r.refetch).toBe("function");
31
- expect(typeof r.cancel).toBe("function");
32
- expect(typeof r.mutate).toBe("function");
33
- });
34
-
35
- it("starts in pending state when immediate=true (default)", () => {
36
- const { ctx, run } = makeFieldCtx("data");
37
- Resource(() => new Promise(() => {}))(undefined, ctx);
38
- const instance: Record<string, unknown> = {};
39
- run(instance);
40
- const r = instance.data as { pending: () => boolean; status: () => string };
41
- expect(r.pending()).toBe(true);
42
- expect(r.status()).toBe("pending");
43
- });
44
-
45
- it("starts idle when immediate=false", () => {
46
- const { ctx, run } = makeFieldCtx("data");
47
- Resource(() => Promise.resolve(42), { immediate: false })(undefined, ctx);
48
- const instance: Record<string, unknown> = {};
49
- run(instance);
50
- const r = instance.data as { status: () => string; pending: () => boolean };
51
- expect(r.status()).toBe("idle");
52
- expect(r.pending()).toBe(false);
53
- });
54
-
55
- it("resolves data asynchronously", async () => {
56
- const { ctx, run } = makeFieldCtx("items");
57
- Resource(() => Promise.resolve([1, 2, 3]))(undefined, ctx);
58
- const instance: Record<string, unknown> = {};
59
- run(instance);
60
- const r = instance.items as { data: () => unknown; status: () => string };
61
- await vi.waitFor(() => r.status() === "success");
62
- expect(r.data()).toEqual([1, 2, 3]);
63
- });
64
-
65
- it("sets error state on rejection", async () => {
66
- const { ctx, run } = makeFieldCtx("items");
67
- Resource(() => Promise.reject(new Error("fetch failed")))(undefined, ctx);
68
- const instance: Record<string, unknown> = {};
69
- run(instance);
70
- const r = instance.items as { error: () => Error | null; status: () => string };
71
- await new Promise((res) => setTimeout(res, 0));
72
- expect(r.status()).toBe("error");
73
- expect((r.error() as Error).message).toBe("fetch failed");
74
- });
75
-
76
- it("each instance gets its own resource", () => {
77
- const { ctx, run } = makeFieldCtx("r");
78
- Resource(() => Promise.resolve(0))(undefined, ctx);
79
- const a: Record<string, unknown> = {};
80
- const b: Record<string, unknown> = {};
81
- run(a);
82
- run(b);
83
- expect(a.r).not.toBe(b.r);
84
- });
85
-
86
- it("mutate() updates data immediately", async () => {
87
- const { ctx, run } = makeFieldCtx("r");
88
- Resource(() => Promise.resolve(1))(undefined, ctx);
89
- const instance: Record<string, unknown> = {};
90
- run(instance);
91
- const r = instance.r as { mutate: (v: number) => void; data: () => number; status: () => string };
92
- r.mutate(99);
93
- expect(r.data()).toBe(99);
94
- expect(r.status()).toBe("success");
95
- });
96
-
97
- it("cancel() stops a pending fetch", async () => {
98
- const { ctx, run } = makeFieldCtx("r");
99
- Resource(() => new Promise(() => {}))(undefined, ctx);
100
- const instance: Record<string, unknown> = {};
101
- run(instance);
102
- const r = instance.r as { cancel: () => void; status: () => string };
103
- r.cancel();
104
- expect(r.status()).toBe("idle");
105
- });
106
-
107
- it("uses initialData option", () => {
108
- const { ctx, run } = makeFieldCtx("r");
109
- Resource(() => Promise.resolve("new"), { initialData: "default", immediate: false })(undefined, ctx);
110
- const instance: Record<string, unknown> = {};
111
- run(instance);
112
- const r = instance.r as { data: () => string };
113
- expect(r.data()).toBe("default");
114
- });
115
-
116
- it("cancel() called when not pending — no-op, no crash", () => {
117
- const { ctx, run } = makeFieldCtx("r");
118
- Resource(() => Promise.resolve(42), { immediate: false })(undefined, ctx);
119
- const instance: Record<string, unknown> = {};
120
- run(instance);
121
- const r = instance.r as { cancel: () => void; status: () => string };
122
- // Not pending (immediate=false → idle)
123
- expect(r.status()).toBe("idle");
124
- expect(() => r.cancel()).not.toThrow();
125
- expect(r.status()).toBe("idle");
126
- });
127
-
128
- it("refetch() called after successful load — re-fetches", async () => {
129
- let callCount = 0;
130
- const { ctx, run } = makeFieldCtx("r");
131
- Resource(() => Promise.resolve(++callCount))(undefined, ctx);
132
- const instance: Record<string, unknown> = {};
133
- run(instance);
134
- const r = instance.r as { refetch: () => void; data: () => number; status: () => string };
135
-
136
- await vi.waitFor(() => r.status() === "success");
137
- expect(r.data()).toBe(1);
138
-
139
- r.refetch();
140
- await vi.waitFor(() => r.status() === "success" && r.data() === 2);
141
- expect(callCount).toBe(2);
142
- });
143
-
144
- it("passes instance as self when fetcher has arity > 0", async () => {
145
- const { ctx, run } = makeFieldCtx("r");
146
- const fetcher = vi.fn((self: Record<string, unknown>) =>
147
- Promise.resolve(self.value as number),
148
- );
149
- Resource(fetcher)(undefined, ctx);
150
- const inst: Record<string, unknown> = { value: 42 };
151
- run(inst);
152
- const r = inst.r as { status: () => string; data: () => number };
153
- await vi.waitFor(() => r.status() === "success");
154
- expect(r.data()).toBe(42);
155
- expect(fetcher).toHaveBeenCalledWith(inst);
156
- });
157
-
158
- it("setter is a no-op — assigning to the decorated field has no effect", () => {
159
- const { ctx, run } = makeFieldCtx("r");
160
- Resource(() => Promise.resolve(42), { immediate: false })(undefined, ctx);
161
- const instance: Record<string, unknown> = {};
162
- run(instance);
163
- const original = instance.r;
164
- instance.r = "overwrite-attempt"; // triggers set(): void {} (no-op)
165
- expect(instance.r).toBe(original);
166
- });
167
-
168
- it("onUnmount calls destroy() on the resource", async () => {
169
- const { ctx, run } = makeFieldCtx("r");
170
- Resource(() => Promise.resolve(1))(undefined, ctx);
171
- const inst: Record<string, unknown> = {};
172
- run(inst);
173
- const r = inst.r as { destroy: () => void };
174
- const spy = vi.spyOn(r, "destroy");
175
- (inst as { onUnmount?: () => void }).onUnmount?.();
176
- expect(spy).toHaveBeenCalledOnce();
177
- });
178
- });
179
-
180
- describe("@Resource — cache / SWR / invalidation", () => {
181
- beforeEach(() => { _clearCache(); });
182
-
183
- function makeFieldCtx(name: string) {
184
- const initializers: Array<(this: unknown) => void> = [];
185
- return {
186
- ctx: {
187
- name,
188
- kind: "field" as const,
189
- addInitializer(fn: (this: unknown) => void) { initializers.push(fn); },
190
- } as ClassFieldDecoratorContext,
191
- run(instance: unknown) { initializers.forEach((fn) => { fn.call(instance); }); },
192
- };
193
- }
194
-
195
- it("key: serves stale cached data immediately while refetching (SWR)", async () => {
196
- const { ctx: ctx1, run: run1 } = makeFieldCtx("r1");
197
- Resource(() => Promise.resolve("first"), { key: "dec-swr" })(undefined, ctx1);
198
- const inst1: Record<string, unknown> = {};
199
- run1(inst1);
200
- const r1 = inst1.r1 as { status: () => string; data: () => string; destroy: () => void };
201
- await vi.waitFor(() => r1.status() === "success");
202
-
203
- // Second decorator instance — should see cached data straight away
204
- const { ctx: ctx2, run: run2 } = makeFieldCtx("r2");
205
- Resource(() => Promise.resolve("second"), { key: "dec-swr" })(undefined, ctx2);
206
- const inst2: Record<string, unknown> = {};
207
- run2(inst2);
208
- const r2 = inst2.r2 as { status: () => string; data: () => string; destroy: () => void };
209
-
210
- expect(r2.data()).toBe("first"); // stale data shown immediately
211
- await vi.waitFor(() => r2.data() === "second");
212
- r1.destroy();
213
- r2.destroy();
214
- });
215
-
216
- it("invalidateResource() triggers refetch on decorated field", async () => {
217
- let call = 0;
218
- const { ctx, run } = makeFieldCtx("r");
219
- Resource(() => Promise.resolve(++call), { key: "dec-inv" })(undefined, ctx);
220
- const inst: Record<string, unknown> = {};
221
- run(inst);
222
- const r = inst.r as { status: () => string; data: () => number; destroy: () => void };
223
- await vi.waitFor(() => r.status() === "success");
224
- expect(r.data()).toBe(1);
225
-
226
- invalidateResource("dec-inv");
227
- await vi.waitFor(() => r.data() === 2);
228
- r.destroy();
229
- });
230
-
231
- it("destroy() via onUnmount unregisters from cache — no refetch after unmount", async () => {
232
- let call = 0;
233
- const { ctx, run } = makeFieldCtx("r");
234
- Resource(() => Promise.resolve(++call), { key: "dec-unreg" })(undefined, ctx);
235
- const inst: Record<string, unknown> = {};
236
- run(inst);
237
- const r = inst.r as { status: () => string; data: () => number; destroy: () => void };
238
- await vi.waitFor(() => r.status() === "success");
239
-
240
- r.destroy();
241
- const before = call;
242
- invalidateResource("dec-unreg");
243
- await new Promise((res) => setTimeout(res, 10));
244
- expect(call).toBe(before);
245
- });
246
- });
@@ -1,179 +0,0 @@
1
- // @vitest-environment jsdom
2
- import { describe, it, expect, vi } from "vitest";
3
-
4
- import { initSlots, getSlot, Slot } from "../properties/slot";
5
- import { StatefulComponent } from "@praxisjs/core";
6
-
7
- // ── initSlots / getSlot ──────────────────────────────────────────────────────
8
-
9
- describe("initSlots / getSlot", () => {
10
- it("returns empty default slot when children is null", () => {
11
- const instance = {};
12
- initSlots(instance, null);
13
- expect(getSlot(instance, "default")).toEqual([]);
14
- });
15
-
16
- it("returns empty array for unknown slot", () => {
17
- const instance = {};
18
- initSlots(instance, null);
19
- expect(getSlot(instance, "unknown")).toEqual([]);
20
- });
21
-
22
- it("puts plain children into the default slot", () => {
23
- const instance = {};
24
- const child = document.createElement("span");
25
- initSlots(instance, child);
26
- expect(getSlot(instance, "default")).toEqual([child]);
27
- });
28
-
29
- it("flattens array children into the default slot", () => {
30
- const instance = {};
31
- const a = document.createElement("p");
32
- const b = document.createElement("div");
33
- initSlots(instance, [a, b]);
34
- expect(getSlot(instance, "default")).toEqual([a, b]);
35
- });
36
-
37
- it("routes element with slot attribute to named slot", () => {
38
- const instance = {};
39
- const el = document.createElement("div");
40
- el.setAttribute("slot", "header");
41
- initSlots(instance, el);
42
- expect(getSlot(instance, "header")).toEqual([el]);
43
- expect(getSlot(instance, "default")).toEqual([]);
44
- // slot attribute should be removed
45
- expect(el.hasAttribute("slot")).toBe(false);
46
- });
47
-
48
- it("handles mixed named and default children", () => {
49
- const instance = {};
50
- const named = document.createElement("aside");
51
- named.setAttribute("slot", "sidebar");
52
- const plain = document.createElement("main");
53
- initSlots(instance, [named, plain]);
54
- expect(getSlot(instance, "sidebar")).toEqual([named]);
55
- expect(getSlot(instance, "default")).toEqual([plain]);
56
- });
57
-
58
- it("skips null children in array", () => {
59
- const instance = {};
60
- const el = document.createElement("span");
61
- initSlots(instance, [null, el, undefined]);
62
- expect(getSlot(instance, "default")).toEqual([el]);
63
- });
64
-
65
- it("appends to existing named slot when same slot name appears twice", () => {
66
- const instance = {};
67
- const a = document.createElement("div");
68
- a.setAttribute("slot", "header");
69
- const b = document.createElement("div");
70
- b.setAttribute("slot", "header");
71
- initSlots(instance, [a, b]);
72
- const headerSlot = getSlot(instance, "header");
73
- expect(headerSlot).toHaveLength(2);
74
- expect(headerSlot[0]).toBe(a);
75
- expect(headerSlot[1]).toBe(b);
76
- });
77
- });
78
-
79
- // ── Slot decorator ───────────────────────────────────────────────────────────
80
-
81
- describe("Slot decorator", () => {
82
- it("provides the default slot via field decorator", () => {
83
- class MyComp extends StatefulComponent {
84
- render() { return null; }
85
- }
86
-
87
- const initializers: Array<(this: unknown) => void> = [];
88
- const ctx = {
89
- name: "default",
90
- kind: "field" as const,
91
- addInitializer(fn: (this: unknown) => void) { initializers.push(fn); },
92
- } as ClassFieldDecoratorContext<StatefulComponent>;
93
-
94
- Slot()(undefined, ctx);
95
-
96
- const instance = new MyComp() as unknown as Record<string, unknown>;
97
- initializers.forEach((fn) => { fn.call(instance); });
98
-
99
- // No children initialized — slot should be empty
100
- const children = document.createElement("p");
101
- initSlots(instance as object, children);
102
-
103
- expect(Array.isArray((instance as Record<string, unknown>).default)).toBe(true);
104
- });
105
-
106
- it("uses custom slot name when provided", () => {
107
- class MyComp extends StatefulComponent {
108
- render() { return null; }
109
- }
110
-
111
- const initializers: Array<(this: unknown) => void> = [];
112
- const ctx = {
113
- name: "content",
114
- kind: "field" as const,
115
- addInitializer(fn: (this: unknown) => void) { initializers.push(fn); },
116
- } as ClassFieldDecoratorContext<StatefulComponent>;
117
-
118
- Slot("header")(undefined, ctx);
119
-
120
- const instance = new MyComp() as unknown as Record<string, unknown>;
121
- initializers.forEach((fn) => { fn.call(instance); });
122
-
123
- const el = document.createElement("nav");
124
- el.setAttribute("slot", "header");
125
- initSlots(instance as object, el);
126
-
127
- const slot = (instance as Record<string, unknown>).content as unknown[];
128
- expect(slot).toHaveLength(1);
129
- expect(slot[0]).toBe(el);
130
- });
131
-
132
- it("skips warn on direct assignment in production mode", () => {
133
- class MyComp extends StatefulComponent {
134
- render() { return null; }
135
- }
136
-
137
- const initializers: Array<(this: unknown) => void> = [];
138
- const ctx = {
139
- name: "default",
140
- kind: "field" as const,
141
- addInitializer(fn: (this: unknown) => void) { initializers.push(fn); },
142
- } as ClassFieldDecoratorContext<StatefulComponent>;
143
-
144
- Slot()(undefined, ctx);
145
-
146
- const instance = new MyComp() as unknown as Record<string, unknown>;
147
- initializers.forEach((fn) => { fn.call(instance); });
148
-
149
- const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
150
- vi.stubEnv("NODE_ENV", "production");
151
- (instance as Record<string, unknown>).default = "bad";
152
- expect(warn).not.toHaveBeenCalled();
153
- vi.unstubAllEnvs();
154
- warn.mockRestore();
155
- });
156
-
157
- it("warns on direct assignment in non-production", () => {
158
- class MyComp extends StatefulComponent {
159
- render() { return null; }
160
- }
161
-
162
- const initializers: Array<(this: unknown) => void> = [];
163
- const ctx = {
164
- name: "default",
165
- kind: "field" as const,
166
- addInitializer(fn: (this: unknown) => void) { initializers.push(fn); },
167
- } as ClassFieldDecoratorContext<StatefulComponent>;
168
-
169
- Slot()(undefined, ctx);
170
-
171
- const instance = new MyComp() as unknown as Record<string, unknown>;
172
- initializers.forEach((fn) => { fn.call(instance); });
173
-
174
- const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
175
- (instance as Record<string, unknown>).default = "bad";
176
- expect(warn).toHaveBeenCalledWith(expect.stringContaining("[Slot]"));
177
- warn.mockRestore();
178
- });
179
- });