@praxisjs/core 2.2.0 → 2.3.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 (116) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/component/internals.d.ts +12 -2
  3. package/dist/component/internals.d.ts.map +1 -1
  4. package/dist/component/internals.js +25 -7
  5. package/dist/component/internals.js.map +1 -1
  6. package/dist/internal.d.ts +3 -2
  7. package/dist/internal.d.ts.map +1 -1
  8. package/dist/internal.js +3 -2
  9. package/dist/internal.js.map +1 -1
  10. package/dist/signal/batch.d.ts +1 -3
  11. package/dist/signal/batch.d.ts.map +1 -1
  12. package/dist/signal/batch.js +4 -30
  13. package/dist/signal/batch.js.map +1 -1
  14. package/dist/signal/computed.d.ts.map +1 -1
  15. package/dist/signal/computed.js +13 -66
  16. package/dist/signal/computed.js.map +1 -1
  17. package/dist/signal/effect.d.ts +6 -14
  18. package/dist/signal/effect.d.ts.map +1 -1
  19. package/dist/signal/effect.js +11 -83
  20. package/dist/signal/effect.js.map +1 -1
  21. package/dist/signal/graph.d.ts +111 -0
  22. package/dist/signal/graph.d.ts.map +1 -0
  23. package/dist/signal/graph.js +513 -0
  24. package/dist/signal/graph.js.map +1 -0
  25. package/dist/signal/index.d.ts +1 -0
  26. package/dist/signal/index.d.ts.map +1 -1
  27. package/dist/signal/index.js +1 -0
  28. package/dist/signal/index.js.map +1 -1
  29. package/dist/signal/peek.d.ts.map +1 -1
  30. package/dist/signal/peek.js +2 -9
  31. package/dist/signal/peek.js.map +1 -1
  32. package/dist/signal/selector.d.ts +5 -0
  33. package/dist/signal/selector.d.ts.map +1 -0
  34. package/dist/signal/selector.js +128 -0
  35. package/dist/signal/selector.js.map +1 -0
  36. package/dist/signal/signal.d.ts +0 -5
  37. package/dist/signal/signal.d.ts.map +1 -1
  38. package/dist/signal/signal.js +11 -70
  39. package/dist/signal/signal.js.map +1 -1
  40. package/package.json +13 -4
  41. package/src/component/internals.ts +35 -10
  42. package/src/internal.ts +26 -0
  43. package/src/signal/batch.ts +5 -33
  44. package/src/signal/computed.ts +13 -66
  45. package/src/signal/effect.ts +12 -92
  46. package/src/signal/graph.ts +562 -0
  47. package/src/signal/index.ts +1 -0
  48. package/src/signal/peek.ts +2 -8
  49. package/src/signal/selector.ts +140 -0
  50. package/src/signal/signal.ts +11 -74
  51. package/dist/__tests__/batch.test.d.ts +0 -2
  52. package/dist/__tests__/batch.test.d.ts.map +0 -1
  53. package/dist/__tests__/batch.test.js +0 -124
  54. package/dist/__tests__/batch.test.js.map +0 -1
  55. package/dist/__tests__/component.test.d.ts +0 -2
  56. package/dist/__tests__/component.test.d.ts.map +0 -1
  57. package/dist/__tests__/component.test.js +0 -124
  58. package/dist/__tests__/component.test.js.map +0 -1
  59. package/dist/__tests__/computed.test.d.ts +0 -2
  60. package/dist/__tests__/computed.test.d.ts.map +0 -1
  61. package/dist/__tests__/computed.test.js +0 -289
  62. package/dist/__tests__/computed.test.js.map +0 -1
  63. package/dist/__tests__/effect.test.d.ts +0 -2
  64. package/dist/__tests__/effect.test.d.ts.map +0 -1
  65. package/dist/__tests__/effect.test.js +0 -200
  66. package/dist/__tests__/effect.test.js.map +0 -1
  67. package/dist/__tests__/peek.test.d.ts +0 -2
  68. package/dist/__tests__/peek.test.d.ts.map +0 -1
  69. package/dist/__tests__/peek.test.js +0 -26
  70. package/dist/__tests__/peek.test.js.map +0 -1
  71. package/dist/__tests__/persisted-signal.test.d.ts +0 -2
  72. package/dist/__tests__/persisted-signal.test.d.ts.map +0 -1
  73. package/dist/__tests__/persisted-signal.test.js +0 -252
  74. package/dist/__tests__/persisted-signal.test.js.map +0 -1
  75. package/dist/__tests__/reactive.test.d.ts +0 -2
  76. package/dist/__tests__/reactive.test.d.ts.map +0 -1
  77. package/dist/__tests__/reactive.test.js +0 -299
  78. package/dist/__tests__/reactive.test.js.map +0 -1
  79. package/dist/__tests__/resource-focus.test.d.ts +0 -2
  80. package/dist/__tests__/resource-focus.test.d.ts.map +0 -1
  81. package/dist/__tests__/resource-focus.test.js +0 -50
  82. package/dist/__tests__/resource-focus.test.js.map +0 -1
  83. package/dist/__tests__/resource-server-pass.test.d.ts +0 -2
  84. package/dist/__tests__/resource-server-pass.test.d.ts.map +0 -1
  85. package/dist/__tests__/resource-server-pass.test.js +0 -78
  86. package/dist/__tests__/resource-server-pass.test.js.map +0 -1
  87. package/dist/__tests__/resource.test.d.ts +0 -2
  88. package/dist/__tests__/resource.test.d.ts.map +0 -1
  89. package/dist/__tests__/resource.test.js +0 -336
  90. package/dist/__tests__/resource.test.js.map +0 -1
  91. package/dist/__tests__/signal.test.d.ts +0 -2
  92. package/dist/__tests__/signal.test.d.ts.map +0 -1
  93. package/dist/__tests__/signal.test.js +0 -254
  94. package/dist/__tests__/signal.test.js.map +0 -1
  95. package/dist/__tests__/synced.test.d.ts +0 -2
  96. package/dist/__tests__/synced.test.d.ts.map +0 -1
  97. package/dist/__tests__/synced.test.js +0 -142
  98. package/dist/__tests__/synced.test.js.map +0 -1
  99. package/dist/__tests__/track.test.d.ts +0 -2
  100. package/dist/__tests__/track.test.d.ts.map +0 -1
  101. package/dist/__tests__/track.test.js +0 -54
  102. package/dist/__tests__/track.test.js.map +0 -1
  103. package/src/__tests__/batch.test.ts +0 -138
  104. package/src/__tests__/component.test.ts +0 -165
  105. package/src/__tests__/computed.test.ts +0 -333
  106. package/src/__tests__/effect.test.ts +0 -241
  107. package/src/__tests__/peek.test.ts +0 -31
  108. package/src/__tests__/persisted-signal.test.ts +0 -305
  109. package/src/__tests__/reactive.test.ts +0 -343
  110. package/src/__tests__/resource-focus.test.ts +0 -61
  111. package/src/__tests__/resource-server-pass.test.ts +0 -90
  112. package/src/__tests__/resource.test.ts +0 -421
  113. package/src/__tests__/signal.test.ts +0 -291
  114. package/src/__tests__/synced.test.ts +0 -173
  115. package/src/__tests__/track.test.ts +0 -61
  116. package/tsconfig.json +0 -8
@@ -1,291 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
-
3
- import { batch } from "../signal/batch";
4
- import { SUBS_SET_THRESHOLD } from "../signal/effect";
5
- import { effect } from "../signal/effect";
6
- import { signal, addSub, removeSub, type SubList } from "../signal/signal";
7
- import type { Effect } from "../signal/effect";
8
-
9
- describe("signal", () => {
10
- it("returns initial value", () => {
11
- const s = signal(42);
12
- expect(s()).toBe(42);
13
- });
14
-
15
- it("updates value with set", () => {
16
- const s = signal(0);
17
- s.set(10);
18
- expect(s()).toBe(10);
19
- });
20
-
21
- it("updates value with update fn", () => {
22
- const s = signal(5);
23
- s.update((v) => v * 2);
24
- expect(s()).toBe(10);
25
- });
26
-
27
- it("skips update when value is identical", () => {
28
- const s = signal(1);
29
- const calls: number[] = [];
30
- s.subscribe((v) => calls.push(v));
31
- const before = calls.length;
32
- s.set(1); // same value
33
- expect(calls.length).toBe(before);
34
- });
35
-
36
- it("subscribe fires immediately with current value", () => {
37
- const s = signal("hello");
38
- const received: string[] = [];
39
- s.subscribe((v) => received.push(v));
40
- expect(received).toEqual(["hello"]);
41
- });
42
-
43
- it("subscribe fires on subsequent updates", () => {
44
- const s = signal(0);
45
- const received: number[] = [];
46
- s.subscribe((v) => received.push(v));
47
- s.set(1);
48
- s.set(2);
49
- expect(received).toEqual([0, 1, 2]);
50
- });
51
-
52
- it("unsubscribe stops receiving updates", () => {
53
- const s = signal(0);
54
- const received: number[] = [];
55
- const unsub = s.subscribe((v) => received.push(v));
56
- unsub();
57
- s.set(99);
58
- expect(received).toEqual([0]);
59
- });
60
-
61
- it("marks __isSignal = true", () => {
62
- const s = signal(0);
63
- expect(s.__isSignal).toBe(true);
64
- });
65
-
66
- it("supports object values", () => {
67
- const s = signal<{ name: string }>({ name: "Alice" });
68
- s.set({ name: "Bob" });
69
- expect(s().name).toBe("Bob");
70
- });
71
-
72
- it("supports null and undefined", () => {
73
- const s = signal<null | number>(null);
74
- expect(s()).toBeNull();
75
- s.set(5);
76
- expect(s()).toBe(5);
77
- });
78
-
79
- it("NaN is considered equal to NaN — no notification", () => {
80
- const s = signal(NaN);
81
- const calls: unknown[] = [];
82
- s.subscribe((v) => calls.push(v));
83
- const before = calls.length;
84
- s.set(NaN);
85
- expect(calls.length).toBe(before);
86
- });
87
-
88
- it("-0 and +0 are considered different — notification fires", () => {
89
- const s = signal<number>(-0);
90
- const calls: number[] = [];
91
- s.subscribe((v) => calls.push(v));
92
- const before = calls.length;
93
- s.set(+0);
94
- expect(calls.length).toBe(before + 1);
95
- });
96
-
97
- it("update() fn that throws does not update the value", () => {
98
- const s = signal(5);
99
- expect(() => s.update(() => { throw new Error("fn error"); })).toThrow("fn error");
100
- expect(s()).toBe(5);
101
- });
102
-
103
- it("multiple subscribers all receive the update", () => {
104
- const s = signal(0);
105
- const a: number[] = [];
106
- const b: number[] = [];
107
- s.subscribe((v) => a.push(v));
108
- s.subscribe((v) => b.push(v));
109
- s.set(7);
110
- expect(a).toContain(7);
111
- expect(b).toContain(7);
112
- });
113
-
114
- it("subscriber B still fires when subscriber A throws", () => {
115
- const s = signal(0);
116
- const received: number[] = [];
117
- s.subscribe((v) => { if (v !== 0) throw new Error("sub A throws"); });
118
- s.subscribe((v) => received.push(v));
119
- expect(() => s.set(1)).toThrow("sub A throws");
120
- expect(received).toContain(1);
121
- });
122
-
123
- it("unsubscribe during notification does not crash", () => {
124
- const s = signal(0);
125
- let unsub: (() => void) | undefined;
126
- unsub = s.subscribe(() => {
127
- unsub?.();
128
- });
129
- expect(() => s.set(1)).not.toThrow();
130
- });
131
-
132
- it("unsubscribing one of multiple subscribers removes only that subscriber", () => {
133
- const s = signal(0);
134
- const a: number[] = [];
135
- const b: number[] = [];
136
- const unsubA = s.subscribe((v) => a.push(v));
137
- s.subscribe((v) => b.push(v));
138
- // subs is now an array — hits removeSub else branch
139
- unsubA();
140
- s.set(1);
141
- expect(a).toEqual([0]); // A stopped after initial fire
142
- expect(b).toEqual([0, 1]); // B still receives
143
- });
144
-
145
- it("unsubscribing a non-existent subscriber from array subs is a no-op", () => {
146
- const s = signal(0);
147
- const a: number[] = [];
148
- const b: number[] = [];
149
- s.subscribe((v) => a.push(v));
150
- const unsubB = s.subscribe((v) => b.push(v));
151
- unsubB(); // remove B, subs becomes array without B
152
- unsubB(); // call again — idx will be -1, splice not called
153
- expect(() => s.set(1)).not.toThrow();
154
- expect(a).toContain(1);
155
- });
156
-
157
- it("batch defers notifications for multiple array subscribers", () => {
158
- const s = signal(0);
159
- const a: number[] = [];
160
- const b: number[] = [];
161
- s.subscribe((v) => a.push(v));
162
- s.subscribe((v) => b.push(v));
163
- // subs is an array; inside batch, notifySubs hits the batching=true array path
164
- batch(() => {
165
- s.set(1);
166
- expect(a).toEqual([0]); // not yet fired
167
- expect(b).toEqual([0]);
168
- });
169
- expect(a).toContain(1);
170
- expect(b).toContain(1);
171
- });
172
-
173
- it("removeSub with non-matching single-function subscriber is a no-op", () => {
174
- // When subs is a single function and we try to remove a different function,
175
- // the holder must remain unchanged (subs !== eff → false branch).
176
- const holder: { subs: SubList } = { subs: null };
177
- const fn1: Effect = () => {};
178
- const fn2: Effect = () => {};
179
- addSub(holder, fn1); // subs = fn1 (single function)
180
- removeSub(holder, fn2); // fn2 !== fn1 → no-op
181
- expect(holder.subs).toBe(fn1);
182
- });
183
-
184
- it("addSub does not add a duplicate subscriber to an array holder", () => {
185
- const fn1: Effect = () => {};
186
- const fn2: Effect = () => {};
187
- const holder: { subs: SubList } = { subs: [fn1, fn2] };
188
-
189
- addSub(holder, fn2);
190
-
191
- expect(holder.subs).toEqual([fn1, fn2]);
192
- });
193
-
194
- it("removeSub normalizes an emptied array holder back to null", () => {
195
- const fn: Effect = () => {};
196
- const holder: { subs: SubList } = { subs: [fn] };
197
-
198
- removeSub(holder, fn);
199
-
200
- expect(holder.subs).toBeNull();
201
- });
202
-
203
- it("set() with mutated object reference (same ref) does NOT notify — Object.is semantics", () => {
204
- const obj = { count: 0 };
205
- const s = signal(obj);
206
- const calls: unknown[] = [];
207
- s.subscribe((v) => calls.push(v));
208
- const before = calls.length;
209
- // Mutate the object in-place and set the same reference
210
- obj.count = 99;
211
- s.set(obj);
212
- // Object.is(obj, obj) === true, so no notification
213
- expect(calls.length).toBe(before);
214
- });
215
- });
216
-
217
- describe("large subscriber lists", () => {
218
- function fns(count: number): Array<() => void> {
219
- return Array.from({ length: count }, () => () => {});
220
- }
221
-
222
- it("keeps an array up to the threshold and promotes to a Set past it, preserving order", () => {
223
- const holder: { subs: SubList } = { subs: null };
224
- const list = fns(SUBS_SET_THRESHOLD);
225
- for (const fn of list) addSub(holder, fn);
226
- expect(Array.isArray(holder.subs)).toBe(true);
227
-
228
- const extra = () => {};
229
- addSub(holder, extra);
230
- expect(holder.subs).toBeInstanceOf(Set);
231
- expect([...(holder.subs as Set<() => void>)]).toEqual([...list, extra]);
232
- });
233
-
234
- it("addSub on a Set holder adds new subscribers and ignores duplicates", () => {
235
- const list = fns(SUBS_SET_THRESHOLD + 1);
236
- const holder: { subs: SubList } = { subs: new Set(list) };
237
- addSub(holder, list[0]);
238
- expect((holder.subs as Set<() => void>).size).toBe(list.length);
239
- const extra = () => {};
240
- addSub(holder, extra);
241
- expect((holder.subs as Set<() => void>).size).toBe(list.length + 1);
242
- });
243
-
244
- it("removeSub on a Set holder removes one subscriber, ignores unknown ones, and normalizes to null when emptied", () => {
245
- const [a, b, stranger] = fns(3);
246
- const holder: { subs: SubList } = { subs: new Set([a, b]) };
247
- removeSub(holder, stranger);
248
- expect((holder.subs as Set<() => void>).size).toBe(2);
249
- removeSub(holder, a);
250
- expect([...(holder.subs as Set<() => void>)]).toEqual([b]);
251
- removeSub(holder, b);
252
- expect(holder.subs).toBeNull();
253
- });
254
-
255
- it("many effects on one signal all re-run once per change, in subscription order", () => {
256
- const count = SUBS_SET_THRESHOLD * 3;
257
- const s = signal(0);
258
- const order: number[] = [];
259
- const stops = Array.from({ length: count }, (_, i) =>
260
- effect(() => {
261
- void s();
262
- order.push(i);
263
- }),
264
- );
265
- order.length = 0;
266
- s.set(1);
267
- expect(order).toEqual(Array.from({ length: count }, (_, i) => i));
268
- order.length = 0;
269
- s.set(2);
270
- expect(order).toEqual(Array.from({ length: count }, (_, i) => i));
271
- stops.forEach((stop) => { stop(); });
272
- order.length = 0;
273
- s.set(3);
274
- expect(order).toEqual([]);
275
- });
276
-
277
- it("an effect stopped while the signal holds a Set no longer runs, the others still do", () => {
278
- const s = signal(0);
279
- const runs = new Map<number, number>();
280
- const stops = Array.from({ length: SUBS_SET_THRESHOLD + 4 }, (_, i) =>
281
- effect(() => {
282
- void s();
283
- runs.set(i, (runs.get(i) ?? 0) + 1);
284
- }),
285
- );
286
- stops[2]();
287
- s.set(1);
288
- expect(runs.get(2)).toBe(1);
289
- expect(runs.get(3)).toBe(2);
290
- });
291
- });
@@ -1,173 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from "vitest";
2
-
3
- import { syncedSignal } from "../signal/synced";
4
- import { effect } from "../signal/effect";
5
-
6
- // BroadcastChannel mock
7
- class MockBroadcastChannel {
8
- static instances: MockBroadcastChannel[] = [];
9
- static byChannel: Map<string, MockBroadcastChannel[]> = new Map();
10
-
11
- onmessage: ((event: MessageEvent) => void) | null = null;
12
- readonly name: string;
13
- closed = false;
14
-
15
- constructor(name: string) {
16
- this.name = name;
17
- MockBroadcastChannel.instances.push(this);
18
- if (!MockBroadcastChannel.byChannel.has(name)) {
19
- MockBroadcastChannel.byChannel.set(name, []);
20
- }
21
- MockBroadcastChannel.byChannel.get(name)!.push(this);
22
- }
23
-
24
- postMessage(data: unknown) {
25
- // Deliver to all OTHER instances on same channel
26
- MockBroadcastChannel.byChannel.get(this.name)?.forEach((ch) => {
27
- if (ch !== this && ch.onmessage) {
28
- ch.onmessage(new MessageEvent("message", { data }));
29
- }
30
- });
31
- }
32
-
33
- close() {
34
- this.closed = true;
35
- const list = MockBroadcastChannel.byChannel.get(this.name);
36
- if (list) {
37
- const idx = list.indexOf(this);
38
- if (idx !== -1) list.splice(idx, 1);
39
- }
40
- MockBroadcastChannel.instances.splice(
41
- MockBroadcastChannel.instances.indexOf(this),
42
- 1,
43
- );
44
- }
45
- }
46
-
47
- beforeEach(() => {
48
- MockBroadcastChannel.instances = [];
49
- MockBroadcastChannel.byChannel = new Map();
50
- vi.stubGlobal("BroadcastChannel", MockBroadcastChannel);
51
- });
52
-
53
- describe("syncedSignal", () => {
54
- it("reads initial value", () => {
55
- const s = syncedSignal("ch", 42);
56
- expect(s()).toBe(42);
57
- s.close();
58
- });
59
-
60
- it("set() updates the signal value", () => {
61
- const s = syncedSignal("ch", 0);
62
- s.set(10);
63
- expect(s()).toBe(10);
64
- s.close();
65
- });
66
-
67
- it("update() applies a transform", () => {
68
- const s = syncedSignal("ch", 5);
69
- s.update((v) => v * 2);
70
- expect(s()).toBe(10);
71
- s.close();
72
- });
73
-
74
- it("set() posts a JSON message to the channel", () => {
75
- const s = syncedSignal("ch", 0);
76
- const postSpy = vi.spyOn(
77
- MockBroadcastChannel.instances[0],
78
- "postMessage",
79
- );
80
- s.set(99);
81
- expect(postSpy).toHaveBeenCalledWith(JSON.stringify(99));
82
- s.close();
83
- });
84
-
85
- it("receiving a message from another tab updates the signal", () => {
86
- const s = syncedSignal("ch", 0);
87
- const channel = MockBroadcastChannel.instances[0];
88
-
89
- channel.onmessage!(new MessageEvent("message", { data: JSON.stringify(7) }));
90
- expect(s()).toBe(7);
91
- s.close();
92
- });
93
-
94
- it("does not echo own set() back to itself", () => {
95
- const s = syncedSignal("ch", 0);
96
-
97
- // Simulate two instances on the same channel
98
- const s2 = syncedSignal("ch", 0);
99
- const received: number[] = [];
100
- effect(() => { received.push(s2()); });
101
-
102
- s.set(5);
103
- // s2 should receive the message because it came from a different instance
104
- expect(s2()).toBe(5);
105
-
106
- // s itself should NOT trigger its own onmessage handler (no echo)
107
- const channel = MockBroadcastChannel.instances[0];
108
- const receivedBySelf: number[] = [];
109
- effect(() => { receivedBySelf.push(s()); });
110
- const beforeCount = receivedBySelf.length;
111
- // Manually simulate echo — should be ignored because posting=false after set
112
- channel.onmessage!(new MessageEvent("message", { data: JSON.stringify(5) }));
113
- // The value stays 5 (no spurious extra notification tested via subscribe count)
114
- expect(s()).toBe(5);
115
- expect(receivedBySelf.length).toBe(beforeCount); // no extra effect run (Object.is equality)
116
-
117
- s.close();
118
- s2.close();
119
- });
120
-
121
- it("subscribe fires immediately then on each remote change", () => {
122
- const s = syncedSignal("ch", 0);
123
- const channel = MockBroadcastChannel.instances[0];
124
- const received: number[] = [];
125
- s.subscribe((v) => received.push(v));
126
-
127
- expect(received).toEqual([0]);
128
-
129
- channel.onmessage!(new MessageEvent("message", { data: JSON.stringify(3) }));
130
- expect(received).toEqual([0, 3]);
131
- s.close();
132
- });
133
-
134
- it("close() shuts down the BroadcastChannel", () => {
135
- const s = syncedSignal("ch", 0);
136
- const channel = MockBroadcastChannel.instances[0];
137
- expect(channel.closed).toBe(false);
138
- s.close();
139
- expect(channel.closed).toBe(true);
140
- });
141
-
142
- it("has __isSignal marker", () => {
143
- const s = syncedSignal("ch", 0);
144
- expect(s.__isSignal).toBe(true);
145
- s.close();
146
- });
147
-
148
- it("does not update value when onmessage fires while posting (posting guard)", () => {
149
- // Use a self-delivering channel that echoes to itself during postMessage.
150
- // This covers the `if (posting) return;` branch in synced.ts.
151
- class SelfEchoChannel {
152
- onmessage: ((e: MessageEvent) => void) | null = null;
153
- postMessage(data: unknown) {
154
- this.onmessage?.(new MessageEvent("message", { data }));
155
- }
156
- close() {}
157
- }
158
- vi.stubGlobal("BroadcastChannel", SelfEchoChannel);
159
-
160
- const s = syncedSignal("echo-ch", 0);
161
- const received: number[] = [];
162
- s.subscribe((v) => received.push(v));
163
-
164
- // set() triggers postMessage → onmessage fires → posting=true → early return
165
- // Inner value should not be double-set
166
- s.set(42);
167
- expect(s()).toBe(42);
168
- // Only two notifications: initial (0) + the set (42), no echo re-set
169
- expect(received).toEqual([0, 42]);
170
-
171
- s.close();
172
- });
173
- });
@@ -1,61 +0,0 @@
1
- import { describe, it, expect, vi } from "vitest";
2
-
3
- import { track, runEffect, activeEffect } from "../signal/effect";
4
- import { signal } from "../signal/signal";
5
-
6
- describe("track", () => {
7
- it("runs the effect immediately", () => {
8
- const fn = vi.fn();
9
- track(fn);
10
- expect(fn).toHaveBeenCalledOnce();
11
- });
12
-
13
- it("sets activeEffect during execution, resets after", () => {
14
- let during: unknown = "not-set";
15
- track(() => {
16
- during = activeEffect;
17
- });
18
- expect(during).toBeTypeOf("function");
19
- expect(activeEffect).toBeNull();
20
- });
21
-
22
- it("supports nested track calls — restores outer effect", () => {
23
- const outer = vi.fn();
24
- const inner = vi.fn();
25
- let outerActive: unknown;
26
- let innerActive: unknown;
27
-
28
- track(() => {
29
- outer();
30
- outerActive = activeEffect;
31
- track(() => {
32
- inner();
33
- innerActive = activeEffect;
34
- });
35
- // after inner track, activeEffect should be back to outer
36
- expect(activeEffect).toBe(outerActive);
37
- });
38
-
39
- expect(outer).toHaveBeenCalledOnce();
40
- expect(inner).toHaveBeenCalledOnce();
41
- expect(innerActive).not.toBe(outerActive);
42
- });
43
-
44
- it("subscribes the tracked effect to signals read inside it", () => {
45
- const s = signal(1);
46
- const values: number[] = [];
47
- track(() => values.push(s()));
48
- s.set(2);
49
- expect(values).toEqual([1, 2]);
50
- });
51
- });
52
-
53
- describe("runEffect", () => {
54
- it("sets activeEffect to the given value", () => {
55
- const fn = () => {};
56
- runEffect(fn);
57
- expect(activeEffect).toBe(fn);
58
- runEffect(null);
59
- expect(activeEffect).toBeNull();
60
- });
61
- });
package/tsconfig.json DELETED
@@ -1,8 +0,0 @@
1
- {
2
- "extends": "../../../tsconfig.base.json",
3
- "compilerOptions": {
4
- "outDir": "./dist",
5
- "rootDir": "./src"
6
- },
7
- "include": ["src"]
8
- }