@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.
- package/CHANGELOG.md +18 -0
- package/dist/component/internals.d.ts +12 -2
- package/dist/component/internals.d.ts.map +1 -1
- package/dist/component/internals.js +25 -7
- package/dist/component/internals.js.map +1 -1
- package/dist/internal.d.ts +3 -2
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +3 -2
- package/dist/internal.js.map +1 -1
- package/dist/signal/batch.d.ts +1 -3
- package/dist/signal/batch.d.ts.map +1 -1
- package/dist/signal/batch.js +4 -30
- package/dist/signal/batch.js.map +1 -1
- package/dist/signal/computed.d.ts.map +1 -1
- package/dist/signal/computed.js +13 -66
- package/dist/signal/computed.js.map +1 -1
- package/dist/signal/effect.d.ts +6 -14
- package/dist/signal/effect.d.ts.map +1 -1
- package/dist/signal/effect.js +11 -83
- package/dist/signal/effect.js.map +1 -1
- package/dist/signal/graph.d.ts +111 -0
- package/dist/signal/graph.d.ts.map +1 -0
- package/dist/signal/graph.js +513 -0
- package/dist/signal/graph.js.map +1 -0
- package/dist/signal/index.d.ts +1 -0
- package/dist/signal/index.d.ts.map +1 -1
- package/dist/signal/index.js +1 -0
- package/dist/signal/index.js.map +1 -1
- package/dist/signal/peek.d.ts.map +1 -1
- package/dist/signal/peek.js +2 -9
- package/dist/signal/peek.js.map +1 -1
- package/dist/signal/selector.d.ts +5 -0
- package/dist/signal/selector.d.ts.map +1 -0
- package/dist/signal/selector.js +128 -0
- package/dist/signal/selector.js.map +1 -0
- package/dist/signal/signal.d.ts +0 -5
- package/dist/signal/signal.d.ts.map +1 -1
- package/dist/signal/signal.js +11 -70
- package/dist/signal/signal.js.map +1 -1
- package/package.json +13 -4
- package/src/component/internals.ts +35 -10
- package/src/internal.ts +26 -0
- package/src/signal/batch.ts +5 -33
- package/src/signal/computed.ts +13 -66
- package/src/signal/effect.ts +12 -92
- package/src/signal/graph.ts +562 -0
- package/src/signal/index.ts +1 -0
- package/src/signal/peek.ts +2 -8
- package/src/signal/selector.ts +140 -0
- package/src/signal/signal.ts +11 -74
- package/dist/__tests__/batch.test.d.ts +0 -2
- package/dist/__tests__/batch.test.d.ts.map +0 -1
- package/dist/__tests__/batch.test.js +0 -124
- package/dist/__tests__/batch.test.js.map +0 -1
- package/dist/__tests__/component.test.d.ts +0 -2
- package/dist/__tests__/component.test.d.ts.map +0 -1
- package/dist/__tests__/component.test.js +0 -124
- package/dist/__tests__/component.test.js.map +0 -1
- package/dist/__tests__/computed.test.d.ts +0 -2
- package/dist/__tests__/computed.test.d.ts.map +0 -1
- package/dist/__tests__/computed.test.js +0 -289
- package/dist/__tests__/computed.test.js.map +0 -1
- package/dist/__tests__/effect.test.d.ts +0 -2
- package/dist/__tests__/effect.test.d.ts.map +0 -1
- package/dist/__tests__/effect.test.js +0 -200
- package/dist/__tests__/effect.test.js.map +0 -1
- package/dist/__tests__/peek.test.d.ts +0 -2
- package/dist/__tests__/peek.test.d.ts.map +0 -1
- package/dist/__tests__/peek.test.js +0 -26
- package/dist/__tests__/peek.test.js.map +0 -1
- package/dist/__tests__/persisted-signal.test.d.ts +0 -2
- package/dist/__tests__/persisted-signal.test.d.ts.map +0 -1
- package/dist/__tests__/persisted-signal.test.js +0 -252
- package/dist/__tests__/persisted-signal.test.js.map +0 -1
- package/dist/__tests__/reactive.test.d.ts +0 -2
- package/dist/__tests__/reactive.test.d.ts.map +0 -1
- package/dist/__tests__/reactive.test.js +0 -299
- package/dist/__tests__/reactive.test.js.map +0 -1
- package/dist/__tests__/resource-focus.test.d.ts +0 -2
- package/dist/__tests__/resource-focus.test.d.ts.map +0 -1
- package/dist/__tests__/resource-focus.test.js +0 -50
- package/dist/__tests__/resource-focus.test.js.map +0 -1
- package/dist/__tests__/resource-server-pass.test.d.ts +0 -2
- package/dist/__tests__/resource-server-pass.test.d.ts.map +0 -1
- package/dist/__tests__/resource-server-pass.test.js +0 -78
- package/dist/__tests__/resource-server-pass.test.js.map +0 -1
- package/dist/__tests__/resource.test.d.ts +0 -2
- package/dist/__tests__/resource.test.d.ts.map +0 -1
- package/dist/__tests__/resource.test.js +0 -336
- package/dist/__tests__/resource.test.js.map +0 -1
- package/dist/__tests__/signal.test.d.ts +0 -2
- package/dist/__tests__/signal.test.d.ts.map +0 -1
- package/dist/__tests__/signal.test.js +0 -254
- package/dist/__tests__/signal.test.js.map +0 -1
- package/dist/__tests__/synced.test.d.ts +0 -2
- package/dist/__tests__/synced.test.d.ts.map +0 -1
- package/dist/__tests__/synced.test.js +0 -142
- package/dist/__tests__/synced.test.js.map +0 -1
- package/dist/__tests__/track.test.d.ts +0 -2
- package/dist/__tests__/track.test.d.ts.map +0 -1
- package/dist/__tests__/track.test.js +0 -54
- package/dist/__tests__/track.test.js.map +0 -1
- package/src/__tests__/batch.test.ts +0 -138
- package/src/__tests__/component.test.ts +0 -165
- package/src/__tests__/computed.test.ts +0 -333
- package/src/__tests__/effect.test.ts +0 -241
- package/src/__tests__/peek.test.ts +0 -31
- package/src/__tests__/persisted-signal.test.ts +0 -305
- package/src/__tests__/reactive.test.ts +0 -343
- package/src/__tests__/resource-focus.test.ts +0 -61
- package/src/__tests__/resource-server-pass.test.ts +0 -90
- package/src/__tests__/resource.test.ts +0 -421
- package/src/__tests__/signal.test.ts +0 -291
- package/src/__tests__/synced.test.ts +0 -173
- package/src/__tests__/track.test.ts +0 -61
- package/tsconfig.json +0 -8
|
@@ -1,343 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from "vitest";
|
|
2
|
-
|
|
3
|
-
import { history } from "../reactive/history";
|
|
4
|
-
import { when, until, debounced } from "../reactive/reactive";
|
|
5
|
-
import { computed } from "../signal/computed";
|
|
6
|
-
import { signal } from "../signal/signal";
|
|
7
|
-
|
|
8
|
-
// ---------- when ----------
|
|
9
|
-
|
|
10
|
-
describe("when", () => {
|
|
11
|
-
it("calls fn immediately when source is already truthy", () => {
|
|
12
|
-
const s = signal<number>(5);
|
|
13
|
-
const fn = vi.fn();
|
|
14
|
-
when(s, fn);
|
|
15
|
-
expect(fn).toHaveBeenCalledWith(5);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it("calls fn once the source becomes truthy", () => {
|
|
19
|
-
const s = signal<number>(0);
|
|
20
|
-
const fn = vi.fn();
|
|
21
|
-
when(s, fn);
|
|
22
|
-
expect(fn).not.toHaveBeenCalled();
|
|
23
|
-
s.set(1);
|
|
24
|
-
expect(fn).toHaveBeenCalledWith(1);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it("only fires once, even if source changes again", () => {
|
|
28
|
-
const s = signal(0);
|
|
29
|
-
const fn = vi.fn();
|
|
30
|
-
when(s, fn);
|
|
31
|
-
s.set(1);
|
|
32
|
-
s.set(2);
|
|
33
|
-
expect(fn).toHaveBeenCalledTimes(1);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it("does not re-fire after immediate truthy source changes again", () => {
|
|
37
|
-
// Regression: when source is truthy on first run, stop?.() was a no-op
|
|
38
|
-
// because `stop` hadn't been assigned yet. The effect must be cancelled
|
|
39
|
-
// after firing so further source changes do not re-trigger fn.
|
|
40
|
-
const s = signal(1); // immediately truthy
|
|
41
|
-
const fn = vi.fn();
|
|
42
|
-
when(s, fn);
|
|
43
|
-
expect(fn).toHaveBeenCalledTimes(1);
|
|
44
|
-
s.set(2);
|
|
45
|
-
s.set(3);
|
|
46
|
-
expect(fn).toHaveBeenCalledTimes(1); // must still be 1
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it("does not call fn when source stays falsy", () => {
|
|
50
|
-
const s = signal<number>(0);
|
|
51
|
-
const fn = vi.fn();
|
|
52
|
-
when(s, fn);
|
|
53
|
-
s.set(0);
|
|
54
|
-
expect(fn).not.toHaveBeenCalled();
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it("with source that fires immediately on subscription does not crash", () => {
|
|
58
|
-
const s = signal(1); // immediately truthy
|
|
59
|
-
const fn = vi.fn();
|
|
60
|
-
expect(() => when(s, fn)).not.toThrow();
|
|
61
|
-
expect(fn).toHaveBeenCalledWith(1);
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
// ---------- until ----------
|
|
66
|
-
|
|
67
|
-
describe("until", () => {
|
|
68
|
-
it("resolves immediately when source is already truthy", async () => {
|
|
69
|
-
const s = signal(42);
|
|
70
|
-
const value = await until(s);
|
|
71
|
-
expect(value).toBe(42);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it("resolves when source becomes truthy", async () => {
|
|
75
|
-
const s = signal<number>(0);
|
|
76
|
-
const promise = until(s);
|
|
77
|
-
s.set(7);
|
|
78
|
-
expect(await promise).toBe(7);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it("the promise remains pending if source is always falsy (no-timeout behavior)", async () => {
|
|
82
|
-
const s = signal<number>(0);
|
|
83
|
-
let resolved = false;
|
|
84
|
-
until(s).then(() => { resolved = true; });
|
|
85
|
-
// Never set a truthy value
|
|
86
|
-
await new Promise((res) => setTimeout(res, 10));
|
|
87
|
-
expect(resolved).toBe(false);
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
// ---------- debounced ----------
|
|
92
|
-
|
|
93
|
-
describe("debounced", () => {
|
|
94
|
-
it("returns initial value immediately", () => {
|
|
95
|
-
vi.useFakeTimers();
|
|
96
|
-
const s = signal(1);
|
|
97
|
-
const d = debounced(s, 100);
|
|
98
|
-
expect(d()).toBe(1);
|
|
99
|
-
vi.useRealTimers();
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it("debounces rapid updates", () => {
|
|
103
|
-
vi.useFakeTimers();
|
|
104
|
-
const s = signal(1);
|
|
105
|
-
const d = debounced(s, 100);
|
|
106
|
-
|
|
107
|
-
s.set(2);
|
|
108
|
-
s.set(3);
|
|
109
|
-
s.set(4);
|
|
110
|
-
|
|
111
|
-
expect(d()).toBe(1); // not yet updated
|
|
112
|
-
|
|
113
|
-
vi.advanceTimersByTime(100);
|
|
114
|
-
expect(d()).toBe(4); // settled on last value
|
|
115
|
-
vi.useRealTimers();
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it("emits each value when updates are spaced apart", () => {
|
|
119
|
-
vi.useFakeTimers();
|
|
120
|
-
const s = signal(0);
|
|
121
|
-
const d = debounced(s, 50);
|
|
122
|
-
|
|
123
|
-
s.set(1);
|
|
124
|
-
vi.advanceTimersByTime(50);
|
|
125
|
-
expect(d()).toBe(1);
|
|
126
|
-
|
|
127
|
-
s.set(2);
|
|
128
|
-
vi.advanceTimersByTime(50);
|
|
129
|
-
expect(d()).toBe(2);
|
|
130
|
-
vi.useRealTimers();
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it("delay=0 fires synchronously after setTimeout(0)", () => {
|
|
134
|
-
vi.useFakeTimers();
|
|
135
|
-
const s = signal("a");
|
|
136
|
-
const d = debounced(s, 0);
|
|
137
|
-
|
|
138
|
-
s.set("b");
|
|
139
|
-
expect(d()).toBe("a"); // not yet
|
|
140
|
-
vi.advanceTimersByTime(0);
|
|
141
|
-
expect(d()).toBe("b");
|
|
142
|
-
vi.useRealTimers();
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
it("the inner effect is cleaned up when stop is called (no leak)", () => {
|
|
146
|
-
vi.useFakeTimers();
|
|
147
|
-
const s = signal(0);
|
|
148
|
-
const d = debounced(s, 100);
|
|
149
|
-
|
|
150
|
-
s.set(1);
|
|
151
|
-
// Stop the debounced effect before the timer fires
|
|
152
|
-
d.stop();
|
|
153
|
-
|
|
154
|
-
// Advance time — the timer should not fire and update the signal
|
|
155
|
-
vi.advanceTimersByTime(200);
|
|
156
|
-
expect(d()).toBe(0); // still initial value, effect was stopped
|
|
157
|
-
|
|
158
|
-
vi.useRealTimers();
|
|
159
|
-
});
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
// ---------- history ----------
|
|
163
|
-
|
|
164
|
-
describe("history", () => {
|
|
165
|
-
it("current reflects signal value", () => {
|
|
166
|
-
const s = signal("a");
|
|
167
|
-
const h = history(s);
|
|
168
|
-
expect(h.current()).toBe("a");
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it("canUndo is false initially", () => {
|
|
172
|
-
const s = signal(0);
|
|
173
|
-
const h = history(s);
|
|
174
|
-
expect(h.canUndo()).toBe(false);
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
it("canUndo becomes true after a change", () => {
|
|
178
|
-
const s = signal(0);
|
|
179
|
-
const h = history(s);
|
|
180
|
-
s.set(1);
|
|
181
|
-
expect(h.canUndo()).toBe(true);
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
it("undo reverts to previous value", () => {
|
|
185
|
-
const s = signal(0);
|
|
186
|
-
const h = history(s);
|
|
187
|
-
s.set(1);
|
|
188
|
-
s.set(2);
|
|
189
|
-
h.undo();
|
|
190
|
-
expect(h.current()).toBe(1);
|
|
191
|
-
expect(s()).toBe(1);
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
it("redo re-applies reverted value", () => {
|
|
195
|
-
const s = signal(0);
|
|
196
|
-
const h = history(s);
|
|
197
|
-
s.set(1);
|
|
198
|
-
h.undo();
|
|
199
|
-
expect(h.canRedo()).toBe(true);
|
|
200
|
-
h.redo();
|
|
201
|
-
expect(h.current()).toBe(1);
|
|
202
|
-
expect(s()).toBe(1);
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
it("values() returns full timeline", () => {
|
|
206
|
-
const s = signal("a");
|
|
207
|
-
const h = history(s);
|
|
208
|
-
s.set("b");
|
|
209
|
-
s.set("c");
|
|
210
|
-
expect(h.values()).toEqual(["a", "b", "c"]);
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
it("clear removes past and future", () => {
|
|
214
|
-
const s = signal(0);
|
|
215
|
-
const h = history(s);
|
|
216
|
-
s.set(1);
|
|
217
|
-
s.set(2);
|
|
218
|
-
h.clear();
|
|
219
|
-
expect(h.canUndo()).toBe(false);
|
|
220
|
-
expect(h.canRedo()).toBe(false);
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
it("undo does nothing when there is no history", () => {
|
|
224
|
-
const s = signal(5);
|
|
225
|
-
const h = history(s);
|
|
226
|
-
h.undo();
|
|
227
|
-
expect(h.current()).toBe(5);
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
it("redo does nothing when there is no future", () => {
|
|
231
|
-
const s = signal(5);
|
|
232
|
-
const h = history(s);
|
|
233
|
-
s.set(10);
|
|
234
|
-
h.redo();
|
|
235
|
-
expect(h.current()).toBe(10);
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
it("works with computed source (read-only — no signal.set)", async () => {
|
|
239
|
-
const s = signal(1);
|
|
240
|
-
const c = computed(() => s() * 2);
|
|
241
|
-
const h = history(c);
|
|
242
|
-
s.set(2);
|
|
243
|
-
await Promise.resolve();
|
|
244
|
-
expect(h.current()).toBe(4);
|
|
245
|
-
expect(h.canUndo()).toBe(true);
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
it("respects limit — oldest entries are dropped", () => {
|
|
249
|
-
const s = signal(0);
|
|
250
|
-
const h = history(s, 3);
|
|
251
|
-
for (let i = 1; i <= 5; i++) s.set(i);
|
|
252
|
-
// past holds at most 3, plus current = 4 values total
|
|
253
|
-
expect(h.values().length).toBeLessThanOrEqual(4);
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
it("limit=1 allows only one past entry", () => {
|
|
257
|
-
const s = signal(0);
|
|
258
|
-
const h = history(s, 1);
|
|
259
|
-
s.set(1);
|
|
260
|
-
s.set(2);
|
|
261
|
-
s.set(3);
|
|
262
|
-
// values = [past(1 entry max), current] → at most 2 entries
|
|
263
|
-
expect(h.values().length).toBeLessThanOrEqual(2);
|
|
264
|
-
expect(h.current()).toBe(3);
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
it("clear() after undo removes both past and future", () => {
|
|
268
|
-
const s = signal(0);
|
|
269
|
-
const h = history(s);
|
|
270
|
-
s.set(1);
|
|
271
|
-
s.set(2);
|
|
272
|
-
h.undo();
|
|
273
|
-
expect(h.canRedo()).toBe(true);
|
|
274
|
-
h.clear();
|
|
275
|
-
expect(h.canUndo()).toBe(false);
|
|
276
|
-
expect(h.canRedo()).toBe(false);
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
it("undo() called more times than history entries — state stays consistent", () => {
|
|
280
|
-
const s = signal(0);
|
|
281
|
-
const h = history(s);
|
|
282
|
-
s.set(1);
|
|
283
|
-
h.undo();
|
|
284
|
-
// No more history — undo is a no-op
|
|
285
|
-
h.undo();
|
|
286
|
-
h.undo();
|
|
287
|
-
expect(h.current()).toBe(0);
|
|
288
|
-
expect(h.canUndo()).toBe(false);
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
it("values() returns a snapshot — each call after source change yields a fresh array", () => {
|
|
292
|
-
const s = signal("a");
|
|
293
|
-
const h = history(s);
|
|
294
|
-
s.set("b");
|
|
295
|
-
s.set("c");
|
|
296
|
-
const snapshot1 = h.values();
|
|
297
|
-
expect(snapshot1).toEqual(["a", "b", "c"]);
|
|
298
|
-
// Trigger a change so the computed re-evaluates on next read
|
|
299
|
-
s.set("d");
|
|
300
|
-
const snapshot2 = h.values();
|
|
301
|
-
expect(snapshot2).toEqual(["a", "b", "c", "d"]);
|
|
302
|
-
// snapshot1 and snapshot2 are different array instances
|
|
303
|
-
expect(snapshot1).not.toBe(snapshot2);
|
|
304
|
-
});
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
describe("history.destroy", () => {
|
|
308
|
-
it("stops recording changes but keeps what was recorded", () => {
|
|
309
|
-
const s = signal(0);
|
|
310
|
-
const h = history(s);
|
|
311
|
-
s.set(1);
|
|
312
|
-
h.destroy();
|
|
313
|
-
s.set(2);
|
|
314
|
-
s.set(3);
|
|
315
|
-
expect(h.values()).toEqual([0, 1]);
|
|
316
|
-
expect(h.current()).toBe(1);
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
it("can still undo recorded entries after destroy", () => {
|
|
320
|
-
const s = signal(0);
|
|
321
|
-
const h = history(s);
|
|
322
|
-
s.set(1);
|
|
323
|
-
h.destroy();
|
|
324
|
-
h.undo();
|
|
325
|
-
expect(s()).toBe(0);
|
|
326
|
-
});
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
describe("history with a computed source", () => {
|
|
330
|
-
it("undo and redo move the tracked value without writing back to the read-only source", async () => {
|
|
331
|
-
const s = signal(0);
|
|
332
|
-
const source = computed(() => s());
|
|
333
|
-
const h = history(source);
|
|
334
|
-
s.set(1);
|
|
335
|
-
await Promise.resolve();
|
|
336
|
-
h.undo();
|
|
337
|
-
expect(h.current()).toBe(0);
|
|
338
|
-
expect(s()).toBe(1);
|
|
339
|
-
h.redo();
|
|
340
|
-
expect(h.current()).toBe(1);
|
|
341
|
-
expect(s()).toBe(1);
|
|
342
|
-
});
|
|
343
|
-
});
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
|
-
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
3
|
-
|
|
4
|
-
import { resource } from "../async/resource";
|
|
5
|
-
import { _clearCache } from "../async/resource-cache";
|
|
6
|
-
|
|
7
|
-
describe("resource — refetchOnFocus", () => {
|
|
8
|
-
beforeEach(() => { _clearCache(); });
|
|
9
|
-
|
|
10
|
-
it("refetches when document becomes visible", async () => {
|
|
11
|
-
let call = 0;
|
|
12
|
-
const fetcher = vi.fn(() => Promise.resolve(++call));
|
|
13
|
-
const r = resource(fetcher, { refetchOnFocus: true });
|
|
14
|
-
await vi.waitFor(() => r.status() === "success");
|
|
15
|
-
expect(fetcher).toHaveBeenCalledTimes(1);
|
|
16
|
-
|
|
17
|
-
Object.defineProperty(document, "visibilityState", {
|
|
18
|
-
configurable: true,
|
|
19
|
-
get: () => "visible",
|
|
20
|
-
});
|
|
21
|
-
document.dispatchEvent(new Event("visibilitychange"));
|
|
22
|
-
|
|
23
|
-
await vi.waitFor(() => r.data() === 2);
|
|
24
|
-
expect(fetcher).toHaveBeenCalledTimes(2);
|
|
25
|
-
r.destroy();
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it("does not refetch when document becomes hidden", async () => {
|
|
29
|
-
const fetcher = vi.fn(() => Promise.resolve(1));
|
|
30
|
-
const r = resource(fetcher, { refetchOnFocus: true });
|
|
31
|
-
await vi.waitFor(() => r.status() === "success");
|
|
32
|
-
|
|
33
|
-
Object.defineProperty(document, "visibilityState", {
|
|
34
|
-
configurable: true,
|
|
35
|
-
get: () => "hidden",
|
|
36
|
-
});
|
|
37
|
-
document.dispatchEvent(new Event("visibilitychange"));
|
|
38
|
-
await new Promise((res) => setTimeout(res, 10));
|
|
39
|
-
|
|
40
|
-
expect(fetcher).toHaveBeenCalledTimes(1);
|
|
41
|
-
r.destroy();
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it("destroy() removes the visibilitychange listener", async () => {
|
|
45
|
-
let call = 0;
|
|
46
|
-
const fetcher = vi.fn(() => Promise.resolve(++call));
|
|
47
|
-
const r = resource(fetcher, { refetchOnFocus: true });
|
|
48
|
-
await vi.waitFor(() => r.status() === "success");
|
|
49
|
-
|
|
50
|
-
r.destroy();
|
|
51
|
-
|
|
52
|
-
Object.defineProperty(document, "visibilityState", {
|
|
53
|
-
configurable: true,
|
|
54
|
-
get: () => "visible",
|
|
55
|
-
});
|
|
56
|
-
document.dispatchEvent(new Event("visibilitychange"));
|
|
57
|
-
await new Promise((res) => setTimeout(res, 10));
|
|
58
|
-
|
|
59
|
-
expect(fetcher).toHaveBeenCalledTimes(1); // no refetch after destroy
|
|
60
|
-
});
|
|
61
|
-
});
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
-
|
|
3
|
-
import { flushPendingResources, resource } from "../async/resource";
|
|
4
|
-
import { _clearCache, getPendingResources } from "../async/resource-cache";
|
|
5
|
-
import { setServerRenderPass, isServerRenderPass } from "../async/server-mode";
|
|
6
|
-
|
|
7
|
-
describe("server render pass — flushPendingResources", () => {
|
|
8
|
-
beforeEach(() => { _clearCache(); });
|
|
9
|
-
afterEach(() => { setServerRenderPass(false); });
|
|
10
|
-
|
|
11
|
-
it("is a no-op when no server pass is active", async () => {
|
|
12
|
-
setServerRenderPass(false);
|
|
13
|
-
const r = resource(() => new Promise<string>(() => {}));
|
|
14
|
-
expect(getPendingResources().size).toBe(0);
|
|
15
|
-
await flushPendingResources();
|
|
16
|
-
// Untouched — resource() never settled and flush didn't wait for it.
|
|
17
|
-
expect(r.status()).toBe("pending");
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it("resolves immediately when nothing is pending", async () => {
|
|
21
|
-
setServerRenderPass(true);
|
|
22
|
-
await expect(flushPendingResources()).resolves.toBeUndefined();
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it("tracks isServerRenderPass() state via the setter", () => {
|
|
26
|
-
expect(isServerRenderPass()).toBe(false);
|
|
27
|
-
setServerRenderPass(true);
|
|
28
|
-
expect(isServerRenderPass()).toBe(true);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it("awaits an unkeyed resource() (e.g. @Collection) before returning", async () => {
|
|
32
|
-
setServerRenderPass(true);
|
|
33
|
-
const r = resource(() => new Promise<string>((res) => { setTimeout(() => { res("content"); }, 5); }));
|
|
34
|
-
await flushPendingResources();
|
|
35
|
-
expect(r.status()).toBe("success");
|
|
36
|
-
expect(r.data()).toBe("content");
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it("awaits a keyed resource() too", async () => {
|
|
40
|
-
setServerRenderPass(true);
|
|
41
|
-
const r = resource(() => Promise.resolve("keyed-value"), { key: "ssg-key" });
|
|
42
|
-
await flushPendingResources();
|
|
43
|
-
expect(r.status()).toBe("success");
|
|
44
|
-
expect(r.data()).toBe("keyed-value");
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it("awaits multiple concurrently pending resources", async () => {
|
|
48
|
-
setServerRenderPass(true);
|
|
49
|
-
const r1 = resource(() => new Promise<number>((res) => { setTimeout(() => { res(1); }, 5); }));
|
|
50
|
-
const r2 = resource(() => new Promise<number>((res) => { setTimeout(() => { res(2); }, 10); }));
|
|
51
|
-
await flushPendingResources();
|
|
52
|
-
expect(r1.data()).toBe(1);
|
|
53
|
-
expect(r2.data()).toBe(2);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it("fixed-point loop: awaits a resource created while an earlier one settles", async () => {
|
|
57
|
-
setServerRenderPass(true);
|
|
58
|
-
let second: ReturnType<typeof resource> | undefined;
|
|
59
|
-
|
|
60
|
-
// Simulates a component that, upon its first resource resolving, creates a
|
|
61
|
-
// second resource (e.g. a detail page fetching related content) — the new
|
|
62
|
-
// promise must still be picked up even though it didn't exist yet when
|
|
63
|
-
// flushPendingResources() started its first pass.
|
|
64
|
-
const first = resource(() => new Promise<string>((res) => {
|
|
65
|
-
setTimeout(() => {
|
|
66
|
-
res("first");
|
|
67
|
-
second = resource(() => new Promise<string>((res2) => { setTimeout(() => { res2("second"); }, 5); }));
|
|
68
|
-
}, 5);
|
|
69
|
-
}));
|
|
70
|
-
|
|
71
|
-
await flushPendingResources();
|
|
72
|
-
expect(first.status()).toBe("success");
|
|
73
|
-
expect(second?.status()).toBe("success");
|
|
74
|
-
expect(second?.data()).toBe("second");
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it("does not leave stale entries after resources settle", async () => {
|
|
78
|
-
setServerRenderPass(true);
|
|
79
|
-
resource(() => Promise.resolve("done"));
|
|
80
|
-
await flushPendingResources();
|
|
81
|
-
expect(getPendingResources().size).toBe(0);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it("a rejected resource does not hang the flush", async () => {
|
|
85
|
-
setServerRenderPass(true);
|
|
86
|
-
const r = resource(() => Promise.reject(new Error("boom")));
|
|
87
|
-
await expect(flushPendingResources()).resolves.toBeUndefined();
|
|
88
|
-
expect(r.status()).toBe("error");
|
|
89
|
-
});
|
|
90
|
-
});
|