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