rask-ui 0.28.2 → 0.28.4

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 (43) hide show
  1. package/dist/component.d.ts +1 -0
  2. package/dist/component.d.ts.map +1 -1
  3. package/dist/component.js +7 -2
  4. package/dist/tests/batch.test.js +202 -12
  5. package/dist/tests/createContext.test.js +50 -37
  6. package/dist/tests/error.test.js +25 -12
  7. package/dist/tests/renderCount.test.d.ts +2 -0
  8. package/dist/tests/renderCount.test.d.ts.map +1 -0
  9. package/dist/tests/renderCount.test.js +95 -0
  10. package/dist/tests/scopeEnforcement.test.d.ts +2 -0
  11. package/dist/tests/scopeEnforcement.test.d.ts.map +1 -0
  12. package/dist/tests/scopeEnforcement.test.js +157 -0
  13. package/dist/tests/useAction.test.d.ts +2 -0
  14. package/dist/tests/useAction.test.d.ts.map +1 -0
  15. package/dist/tests/useAction.test.js +132 -0
  16. package/dist/tests/useAsync.test.d.ts +2 -0
  17. package/dist/tests/useAsync.test.d.ts.map +1 -0
  18. package/dist/tests/useAsync.test.js +499 -0
  19. package/dist/tests/useDerived.test.d.ts +2 -0
  20. package/dist/tests/useDerived.test.d.ts.map +1 -0
  21. package/dist/tests/useDerived.test.js +407 -0
  22. package/dist/tests/useEffect.test.d.ts +2 -0
  23. package/dist/tests/useEffect.test.d.ts.map +1 -0
  24. package/dist/tests/useEffect.test.js +600 -0
  25. package/dist/tests/useLookup.test.d.ts +2 -0
  26. package/dist/tests/useLookup.test.d.ts.map +1 -0
  27. package/dist/tests/useLookup.test.js +299 -0
  28. package/dist/tests/useRef.test.d.ts +2 -0
  29. package/dist/tests/useRef.test.d.ts.map +1 -0
  30. package/dist/tests/useRef.test.js +189 -0
  31. package/dist/tests/useState.test.d.ts +2 -0
  32. package/dist/tests/useState.test.d.ts.map +1 -0
  33. package/dist/tests/useState.test.js +178 -0
  34. package/dist/tests/useSuspend.test.d.ts +2 -0
  35. package/dist/tests/useSuspend.test.d.ts.map +1 -0
  36. package/dist/tests/useSuspend.test.js +752 -0
  37. package/dist/tests/useView.test.d.ts +2 -0
  38. package/dist/tests/useView.test.d.ts.map +1 -0
  39. package/dist/tests/useView.test.js +305 -0
  40. package/dist/transformer.d.ts.map +1 -1
  41. package/dist/transformer.js +1 -5
  42. package/dist/useState.js +4 -2
  43. package/package.json +1 -1
@@ -0,0 +1,752 @@
1
+ import { jsx as _jsx } from "rask-ui/jsx-runtime";
2
+ import { describe, it, expect, vi } from "vitest";
3
+ import { useSuspend } from "../useSuspend";
4
+ import { useAsync } from "../useAsync";
5
+ import { render } from "../index";
6
+ describe("useSuspend", () => {
7
+ describe("basic functionality", () => {
8
+ it("should initialize with isLoading true when any async is loading", () => {
9
+ let state;
10
+ function Component() {
11
+ const [async1] = useAsync(() => new Promise(() => { }));
12
+ state = useSuspend({
13
+ data: () => async1,
14
+ });
15
+ return () => _jsx("div", { children: "test" });
16
+ }
17
+ const container = document.createElement("div");
18
+ render(_jsx(Component, {}), container);
19
+ expect(state.isLoading).toBe(true);
20
+ expect(state.isRefreshing).toBe(false);
21
+ expect(state.error).toBeNull();
22
+ expect(state.data).toBeNull();
23
+ });
24
+ it("should resolve to success state when all asyncs resolve", async () => {
25
+ let state;
26
+ function Component() {
27
+ const [async1] = useAsync(() => Promise.resolve("value1"));
28
+ const [async2] = useAsync(() => Promise.resolve("value2"));
29
+ state = useSuspend({
30
+ data1: () => async1,
31
+ data2: () => async2,
32
+ });
33
+ return () => _jsx("div", { children: "test" });
34
+ }
35
+ const container = document.createElement("div");
36
+ render(_jsx(Component, {}), container);
37
+ expect(state.isLoading).toBe(true);
38
+ await new Promise((resolve) => setTimeout(resolve, 10));
39
+ expect(state.isLoading).toBe(false);
40
+ expect(state.isRefreshing).toBe(false);
41
+ expect(state.error).toBeNull();
42
+ expect(state.data1).toBe("value1");
43
+ expect(state.data2).toBe("value2");
44
+ });
45
+ it("should set error when any async fails", async () => {
46
+ const error = new Error("failed");
47
+ let state;
48
+ function Component() {
49
+ const [async1] = useAsync(() => Promise.resolve("success"));
50
+ const [async2] = useAsync(() => Promise.reject(error));
51
+ state = useSuspend({
52
+ data1: () => async1,
53
+ data2: () => async2,
54
+ });
55
+ return () => _jsx("div", { children: "test" });
56
+ }
57
+ const container = document.createElement("div");
58
+ render(_jsx(Component, {}), container);
59
+ await new Promise((resolve) => setTimeout(resolve, 10));
60
+ expect(state.isLoading).toBe(true);
61
+ expect(state.isRefreshing).toBe(false);
62
+ expect(state.error).toBeInstanceOf(Error);
63
+ expect(state.error.message).toBe("failed");
64
+ });
65
+ it("should handle mixed async and non-async values", async () => {
66
+ let state;
67
+ function Component() {
68
+ const [async1] = useAsync(() => Promise.resolve("async-value"));
69
+ state = useSuspend({
70
+ asyncData: () => async1,
71
+ syncData: () => "sync-value",
72
+ });
73
+ return () => _jsx("div", { children: "test" });
74
+ }
75
+ const container = document.createElement("div");
76
+ render(_jsx(Component, {}), container);
77
+ expect(state.isLoading).toBe(true);
78
+ expect(state.syncData).toBe("sync-value");
79
+ expect(state.asyncData).toBeNull();
80
+ await new Promise((resolve) => setTimeout(resolve, 10));
81
+ expect(state.isLoading).toBe(false);
82
+ expect(state.asyncData).toBe("async-value");
83
+ expect(state.syncData).toBe("sync-value");
84
+ });
85
+ });
86
+ describe("loading and refreshing states", () => {
87
+ it("should set isLoading true when any async is loading", async () => {
88
+ let state;
89
+ let resolveFirst;
90
+ let resolveSecond;
91
+ const firstPromise = new Promise((resolve) => {
92
+ resolveFirst = resolve;
93
+ });
94
+ const secondPromise = new Promise((resolve) => {
95
+ resolveSecond = resolve;
96
+ });
97
+ function Component() {
98
+ const [async1] = useAsync(() => firstPromise);
99
+ const [async2] = useAsync(() => secondPromise);
100
+ state = useSuspend({
101
+ data1: () => async1,
102
+ data2: () => async2,
103
+ });
104
+ return () => _jsx("div", { children: "test" });
105
+ }
106
+ const container = document.createElement("div");
107
+ render(_jsx(Component, {}), container);
108
+ expect(state.isLoading).toBe(true);
109
+ expect(state.isRefreshing).toBe(false);
110
+ // Resolve first
111
+ resolveFirst("first");
112
+ await new Promise((resolve) => setTimeout(resolve, 10));
113
+ // Still loading because second is not resolved
114
+ expect(state.isLoading).toBe(true);
115
+ expect(state.isRefreshing).toBe(false);
116
+ // Resolve second
117
+ resolveSecond("second");
118
+ await new Promise((resolve) => setTimeout(resolve, 10));
119
+ expect(state.isLoading).toBe(false);
120
+ expect(state.isRefreshing).toBe(false);
121
+ expect(state.data1).toBe("first");
122
+ expect(state.data2).toBe("second");
123
+ });
124
+ it("should set isRefreshing true when any async is refreshing", async () => {
125
+ let state, refresh2;
126
+ let resolveFirst;
127
+ let resolveSecond;
128
+ let resolveRefresh;
129
+ const firstPromise = new Promise((resolve) => {
130
+ resolveFirst = resolve;
131
+ });
132
+ const secondPromise = new Promise((resolve) => {
133
+ resolveSecond = resolve;
134
+ });
135
+ const refreshPromise = new Promise((resolve) => {
136
+ resolveRefresh = resolve;
137
+ });
138
+ const fetcher1 = vi.fn().mockReturnValueOnce(firstPromise);
139
+ const fetcher2 = vi
140
+ .fn()
141
+ .mockReturnValueOnce(secondPromise)
142
+ .mockReturnValueOnce(refreshPromise);
143
+ function Component() {
144
+ const [async1] = useAsync(fetcher1);
145
+ const [async2, refresh2Fn] = useAsync(fetcher2);
146
+ refresh2 = refresh2Fn;
147
+ state = useSuspend({
148
+ data1: () => async1,
149
+ data2: () => async2,
150
+ });
151
+ return () => _jsx("div", { children: "test" });
152
+ }
153
+ const container = document.createElement("div");
154
+ render(_jsx(Component, {}), container);
155
+ // Resolve initial loads
156
+ resolveFirst("first");
157
+ resolveSecond("second");
158
+ await new Promise((resolve) => setTimeout(resolve, 10));
159
+ expect(state.isLoading).toBe(false);
160
+ expect(state.isRefreshing).toBe(false);
161
+ // Refresh one async
162
+ refresh2();
163
+ expect(state.isLoading).toBe(false);
164
+ expect(state.isRefreshing).toBe(true);
165
+ expect(state.data1).toBe("first");
166
+ expect(state.data2).toBe("second");
167
+ // Resolve refresh
168
+ resolveRefresh("refreshed");
169
+ await new Promise((resolve) => setTimeout(resolve, 10));
170
+ expect(state.isLoading).toBe(false);
171
+ expect(state.isRefreshing).toBe(false);
172
+ expect(state.data2).toBe("refreshed");
173
+ });
174
+ it("should prioritize isLoading over isRefreshing", async () => {
175
+ let state, refresh;
176
+ let resolveFirst;
177
+ let resolveSecond;
178
+ let resolveRefresh;
179
+ const firstPromise = new Promise((resolve) => {
180
+ resolveFirst = resolve;
181
+ });
182
+ const secondPromise = new Promise((resolve) => {
183
+ resolveSecond = resolve;
184
+ });
185
+ const refreshPromise = new Promise((resolve) => {
186
+ resolveRefresh = resolve;
187
+ });
188
+ const fetcher1 = vi
189
+ .fn()
190
+ .mockReturnValueOnce(firstPromise)
191
+ .mockReturnValueOnce(refreshPromise);
192
+ const fetcher2 = vi.fn().mockReturnValueOnce(secondPromise);
193
+ function Component() {
194
+ const [async1, refreshFn] = useAsync(fetcher1);
195
+ refresh = refreshFn;
196
+ const [async2] = useAsync(fetcher2);
197
+ state = useSuspend({
198
+ data1: () => async1,
199
+ data2: () => async2,
200
+ });
201
+ return () => _jsx("div", { children: "test" });
202
+ }
203
+ const container = document.createElement("div");
204
+ render(_jsx(Component, {}), container);
205
+ // Resolve first async
206
+ resolveFirst("first");
207
+ await new Promise((resolve) => setTimeout(resolve, 10));
208
+ expect(state.isLoading).toBe(true); // Second is still loading
209
+ expect(state.isRefreshing).toBe(false);
210
+ // Start refresh on first while second is still loading
211
+ refresh();
212
+ // isLoading should be true, isRefreshing should be false (loading takes priority)
213
+ expect(state.isLoading).toBe(true);
214
+ expect(state.isRefreshing).toBe(false);
215
+ // Resolve second
216
+ resolveSecond("second");
217
+ await new Promise((resolve) => setTimeout(resolve, 10));
218
+ // Now first is refreshing and second is done
219
+ expect(state.isLoading).toBe(false);
220
+ expect(state.isRefreshing).toBe(true);
221
+ // Resolve refresh
222
+ resolveRefresh("refreshed");
223
+ await new Promise((resolve) => setTimeout(resolve, 10));
224
+ expect(state.isLoading).toBe(false);
225
+ expect(state.isRefreshing).toBe(false);
226
+ expect(state.data1).toBe("refreshed");
227
+ expect(state.data2).toBe("second");
228
+ });
229
+ });
230
+ describe("error handling", () => {
231
+ it("should report error from first failing async", async () => {
232
+ const error1 = new Error("error1");
233
+ const error2 = new Error("error2");
234
+ let state;
235
+ function Component() {
236
+ const [async1] = useAsync(() => Promise.reject(error1));
237
+ const [async2] = useAsync(() => Promise.reject(error2));
238
+ state = useSuspend({
239
+ data1: () => async1,
240
+ data2: () => async2,
241
+ });
242
+ return () => _jsx("div", { children: "test" });
243
+ }
244
+ const container = document.createElement("div");
245
+ render(_jsx(Component, {}), container);
246
+ await new Promise((resolve) => setTimeout(resolve, 10));
247
+ expect(state.error).toBeInstanceOf(Error);
248
+ // The error should be one of them (depends on iteration order)
249
+ expect([error1.message, error2.message]).toContain(state.error.message);
250
+ });
251
+ it("should clear error when all asyncs succeed after retry", async () => {
252
+ const error = new Error("failed");
253
+ let state, refresh2;
254
+ const fetcher1 = vi
255
+ .fn()
256
+ .mockResolvedValueOnce("value1")
257
+ .mockResolvedValueOnce("value1-refreshed");
258
+ const fetcher2 = vi
259
+ .fn()
260
+ .mockRejectedValueOnce(error)
261
+ .mockResolvedValueOnce("value2");
262
+ function Component() {
263
+ const [async1] = useAsync(fetcher1);
264
+ const [async2, refresh2Fn] = useAsync(fetcher2);
265
+ refresh2 = refresh2Fn;
266
+ state = useSuspend({
267
+ data1: () => async1,
268
+ data2: () => async2,
269
+ });
270
+ return () => _jsx("div", { children: "test" });
271
+ }
272
+ const container = document.createElement("div");
273
+ render(_jsx(Component, {}), container);
274
+ await new Promise((resolve) => setTimeout(resolve, 10));
275
+ expect(state.error).toBeInstanceOf(Error);
276
+ expect(state.error.message).toBe("failed");
277
+ expect(state.isLoading).toBe(true);
278
+ // Retry the failed async
279
+ refresh2();
280
+ await new Promise((resolve) => setTimeout(resolve, 10));
281
+ expect(state.error).toBeNull();
282
+ expect(state.isLoading).toBe(false);
283
+ expect(state.isRefreshing).toBe(false);
284
+ expect(state.data1).toBe("value1");
285
+ expect(state.data2).toBe("value2");
286
+ });
287
+ it("should maintain error state when refreshing", async () => {
288
+ const error = new Error("failed");
289
+ let state, refresh1;
290
+ const fetcher1 = vi
291
+ .fn()
292
+ .mockResolvedValueOnce("value1")
293
+ .mockResolvedValueOnce("value1-refreshed");
294
+ const fetcher2 = vi.fn().mockRejectedValueOnce(error);
295
+ function Component() {
296
+ const [async1, refresh1Fn] = useAsync(fetcher1);
297
+ refresh1 = refresh1Fn;
298
+ const [async2] = useAsync(fetcher2);
299
+ state = useSuspend({
300
+ data1: () => async1,
301
+ data2: () => async2,
302
+ });
303
+ return () => _jsx("div", { children: "test" });
304
+ }
305
+ const container = document.createElement("div");
306
+ render(_jsx(Component, {}), container);
307
+ await new Promise((resolve) => setTimeout(resolve, 10));
308
+ expect(state.error).toBeInstanceOf(Error);
309
+ // Refresh a successful async while another has an error
310
+ refresh1();
311
+ expect(state.error).toBeInstanceOf(Error);
312
+ expect(state.isLoading).toBe(true); // Error keeps it loading
313
+ expect(state.isRefreshing).toBe(false);
314
+ });
315
+ });
316
+ describe("values update behavior", () => {
317
+ it("should not update values while loading", async () => {
318
+ let state;
319
+ let resolveFirst;
320
+ let resolveSecond;
321
+ const firstPromise = new Promise((resolve) => {
322
+ resolveFirst = resolve;
323
+ });
324
+ const secondPromise = new Promise((resolve) => {
325
+ resolveSecond = resolve;
326
+ });
327
+ function Component() {
328
+ const [async1] = useAsync(() => firstPromise);
329
+ const [async2] = useAsync(() => secondPromise);
330
+ state = useSuspend({
331
+ data1: () => async1,
332
+ data2: () => async2,
333
+ });
334
+ return () => _jsx("div", { children: "test" });
335
+ }
336
+ const container = document.createElement("div");
337
+ render(_jsx(Component, {}), container);
338
+ const initialData1 = state.data1;
339
+ const initialData2 = state.data2;
340
+ // Resolve first
341
+ resolveFirst("first");
342
+ await new Promise((resolve) => setTimeout(resolve, 10));
343
+ // Values should not update yet (second is still loading)
344
+ expect(state.data1).toBe(initialData1);
345
+ expect(state.data1).toBeNull();
346
+ // Resolve second
347
+ resolveSecond("second");
348
+ await new Promise((resolve) => setTimeout(resolve, 10));
349
+ // Now values should update
350
+ expect(state.data1).toBe("first");
351
+ expect(state.data2).toBe("second");
352
+ });
353
+ it("should not update values while refreshing", async () => {
354
+ let state, refresh;
355
+ let resolveFirst;
356
+ let resolveSecond;
357
+ let resolveRefresh;
358
+ const firstPromise = new Promise((resolve) => {
359
+ resolveFirst = resolve;
360
+ });
361
+ const secondPromise = new Promise((resolve) => {
362
+ resolveSecond = resolve;
363
+ });
364
+ const refreshPromise = new Promise((resolve) => {
365
+ resolveRefresh = resolve;
366
+ });
367
+ const fetcher1 = vi
368
+ .fn()
369
+ .mockReturnValueOnce(firstPromise)
370
+ .mockReturnValueOnce(refreshPromise);
371
+ const fetcher2 = vi.fn().mockReturnValueOnce(secondPromise);
372
+ function Component() {
373
+ const [async1, refreshFn] = useAsync(fetcher1);
374
+ refresh = refreshFn;
375
+ const [async2] = useAsync(fetcher2);
376
+ state = useSuspend({
377
+ data1: () => async1,
378
+ data2: () => async2,
379
+ });
380
+ return () => _jsx("div", { children: "test" });
381
+ }
382
+ const container = document.createElement("div");
383
+ render(_jsx(Component, {}), container);
384
+ // Resolve initial loads
385
+ resolveFirst("first");
386
+ resolveSecond("second");
387
+ await new Promise((resolve) => setTimeout(resolve, 10));
388
+ expect(state.data1).toBe("first");
389
+ expect(state.data2).toBe("second");
390
+ // Start refresh
391
+ refresh();
392
+ const data1BeforeRefresh = state.data1;
393
+ // Values should not change during refresh
394
+ expect(state.data1).toBe(data1BeforeRefresh);
395
+ expect(state.data1).toBe("first");
396
+ // Resolve refresh
397
+ resolveRefresh("refreshed");
398
+ await new Promise((resolve) => setTimeout(resolve, 10));
399
+ // Now values should update
400
+ expect(state.data1).toBe("refreshed");
401
+ expect(state.data2).toBe("second");
402
+ });
403
+ it("should not update values when there is an error", async () => {
404
+ const error = new Error("failed");
405
+ let state;
406
+ function Component() {
407
+ const [async1] = useAsync(() => Promise.resolve("success"));
408
+ const [async2] = useAsync(() => Promise.reject(error));
409
+ state = useSuspend({
410
+ data1: () => async1,
411
+ data2: () => async2,
412
+ });
413
+ return () => _jsx("div", { children: "test" });
414
+ }
415
+ const container = document.createElement("div");
416
+ render(_jsx(Component, {}), container);
417
+ const initialData1 = state.data1;
418
+ const initialData2 = state.data2;
419
+ await new Promise((resolve) => setTimeout(resolve, 10));
420
+ // Values should not update because there's an error
421
+ expect(state.data1).toBe(initialData1);
422
+ expect(state.data2).toBe(initialData2);
423
+ expect(state.error).toBeInstanceOf(Error);
424
+ });
425
+ it("should update values only when all conditions are met", async () => {
426
+ let state;
427
+ let resolveFirst;
428
+ let resolveSecond;
429
+ const firstPromise = new Promise((resolve) => {
430
+ resolveFirst = resolve;
431
+ });
432
+ const secondPromise = new Promise((resolve) => {
433
+ resolveSecond = resolve;
434
+ });
435
+ function Component() {
436
+ const [async1] = useAsync(() => firstPromise);
437
+ const [async2] = useAsync(() => secondPromise);
438
+ state = useSuspend({
439
+ data1: () => async1,
440
+ data2: () => async2,
441
+ });
442
+ return () => _jsx("div", { children: "test" });
443
+ }
444
+ const container = document.createElement("div");
445
+ render(_jsx(Component, {}), container);
446
+ // Resolve first
447
+ resolveFirst("first");
448
+ await new Promise((resolve) => setTimeout(resolve, 10));
449
+ // Values not updated yet (second still loading)
450
+ expect(state.data1).toBe(null);
451
+ expect(state.data2).toBe(null);
452
+ // Resolve second
453
+ resolveSecond("second");
454
+ await new Promise((resolve) => setTimeout(resolve, 10));
455
+ // Values should now be updated
456
+ expect(state.data1).toBe("first");
457
+ expect(state.data2).toBe("second");
458
+ });
459
+ });
460
+ describe("edge cases", () => {
461
+ it("should handle empty async object", () => {
462
+ let state;
463
+ function Component() {
464
+ state = useSuspend({});
465
+ return () => _jsx("div", { children: "test" });
466
+ }
467
+ const container = document.createElement("div");
468
+ render(_jsx(Component, {}), container);
469
+ expect(state.isLoading).toBe(false);
470
+ expect(state.isRefreshing).toBe(false);
471
+ expect(state.error).toBeNull();
472
+ // No values to check for empty object
473
+ });
474
+ it("should handle only sync values", () => {
475
+ let state;
476
+ function Component() {
477
+ state = useSuspend({
478
+ value1: () => "sync1",
479
+ value2: () => "sync2",
480
+ value3: () => 42,
481
+ });
482
+ return () => _jsx("div", { children: "test" });
483
+ }
484
+ const container = document.createElement("div");
485
+ render(_jsx(Component, {}), container);
486
+ expect(state.isLoading).toBe(false);
487
+ expect(state.isRefreshing).toBe(false);
488
+ expect(state.error).toBeNull();
489
+ expect(state.value1).toBe("sync1");
490
+ expect(state.value2).toBe("sync2");
491
+ expect(state.value3).toBe(42);
492
+ });
493
+ it("should handle single async value", async () => {
494
+ let state;
495
+ function Component() {
496
+ const [async1] = useAsync(() => Promise.resolve("value"));
497
+ state = useSuspend({
498
+ data: () => async1,
499
+ });
500
+ return () => _jsx("div", { children: "test" });
501
+ }
502
+ const container = document.createElement("div");
503
+ render(_jsx(Component, {}), container);
504
+ expect(state.isLoading).toBe(true);
505
+ await new Promise((resolve) => setTimeout(resolve, 10));
506
+ expect(state.isLoading).toBe(false);
507
+ expect(state.data).toBe("value");
508
+ });
509
+ it("should handle many async values", async () => {
510
+ let state;
511
+ function Component() {
512
+ const [async1] = useAsync(() => Promise.resolve("value1"));
513
+ const [async2] = useAsync(() => Promise.resolve("value2"));
514
+ const [async3] = useAsync(() => Promise.resolve("value3"));
515
+ const [async4] = useAsync(() => Promise.resolve("value4"));
516
+ const [async5] = useAsync(() => Promise.resolve("value5"));
517
+ state = useSuspend({
518
+ data1: () => async1,
519
+ data2: () => async2,
520
+ data3: () => async3,
521
+ data4: () => async4,
522
+ data5: () => async5,
523
+ });
524
+ return () => _jsx("div", { children: "test" });
525
+ }
526
+ const container = document.createElement("div");
527
+ render(_jsx(Component, {}), container);
528
+ expect(state.isLoading).toBe(true);
529
+ await new Promise((resolve) => setTimeout(resolve, 10));
530
+ expect(state.isLoading).toBe(false);
531
+ expect(state.data1).toBe("value1");
532
+ expect(state.data2).toBe("value2");
533
+ expect(state.data3).toBe("value3");
534
+ expect(state.data4).toBe("value4");
535
+ expect(state.data5).toBe("value5");
536
+ });
537
+ it("should handle immediate resolution", async () => {
538
+ let state;
539
+ function Component() {
540
+ const [async1] = useAsync(() => Promise.resolve("immediate"));
541
+ state = useSuspend({
542
+ data: () => async1,
543
+ });
544
+ return () => _jsx("div", { children: "test" });
545
+ }
546
+ const container = document.createElement("div");
547
+ render(_jsx(Component, {}), container);
548
+ await new Promise((resolve) => setTimeout(resolve, 10));
549
+ expect(state.isLoading).toBe(false);
550
+ expect(state.data).toBe("immediate");
551
+ });
552
+ it("should handle immediate rejection", async () => {
553
+ const error = new Error("immediate error");
554
+ let state;
555
+ function Component() {
556
+ const [async1] = useAsync(() => Promise.reject(error));
557
+ state = useSuspend({
558
+ data: () => async1,
559
+ });
560
+ return () => _jsx("div", { children: "test" });
561
+ }
562
+ const container = document.createElement("div");
563
+ render(_jsx(Component, {}), container);
564
+ await new Promise((resolve) => setTimeout(resolve, 10));
565
+ expect(state.isLoading).toBe(true);
566
+ expect(state.error).toBeInstanceOf(Error);
567
+ expect(state.error.message).toBe("immediate error");
568
+ });
569
+ });
570
+ describe("complex scenarios", () => {
571
+ it("should handle multiple refresh operations", async () => {
572
+ let state, refresh1, refresh2;
573
+ let resolveFirst;
574
+ let resolveSecond;
575
+ let resolveRefresh1;
576
+ let resolveRefresh2;
577
+ const firstPromise = new Promise((resolve) => {
578
+ resolveFirst = resolve;
579
+ });
580
+ const secondPromise = new Promise((resolve) => {
581
+ resolveSecond = resolve;
582
+ });
583
+ const refreshPromise1 = new Promise((resolve) => {
584
+ resolveRefresh1 = resolve;
585
+ });
586
+ const refreshPromise2 = new Promise((resolve) => {
587
+ resolveRefresh2 = resolve;
588
+ });
589
+ const fetcher1 = vi
590
+ .fn()
591
+ .mockReturnValueOnce(firstPromise)
592
+ .mockReturnValueOnce(refreshPromise1);
593
+ const fetcher2 = vi
594
+ .fn()
595
+ .mockReturnValueOnce(secondPromise)
596
+ .mockReturnValueOnce(refreshPromise2);
597
+ function Component() {
598
+ const [async1, refresh1Fn] = useAsync(fetcher1);
599
+ refresh1 = refresh1Fn;
600
+ const [async2, refresh2Fn] = useAsync(fetcher2);
601
+ refresh2 = refresh2Fn;
602
+ state = useSuspend({
603
+ data1: () => async1,
604
+ data2: () => async2,
605
+ });
606
+ return () => _jsx("div", { children: "test" });
607
+ }
608
+ const container = document.createElement("div");
609
+ render(_jsx(Component, {}), container);
610
+ // Resolve initial loads
611
+ resolveFirst("first");
612
+ resolveSecond("second");
613
+ await new Promise((resolve) => setTimeout(resolve, 10));
614
+ expect(state.data1).toBe("first");
615
+ expect(state.data2).toBe("second");
616
+ // Start both refreshes
617
+ refresh1();
618
+ refresh2();
619
+ expect(state.isRefreshing).toBe(true);
620
+ expect(state.data1).toBe("first");
621
+ expect(state.data2).toBe("second");
622
+ // Resolve first refresh
623
+ resolveRefresh1("refreshed1");
624
+ await new Promise((resolve) => setTimeout(resolve, 10));
625
+ // Still refreshing because second is not done
626
+ expect(state.isRefreshing).toBe(true);
627
+ expect(state.data1).toBe("first"); // Not updated yet
628
+ // Resolve second refresh
629
+ resolveRefresh2("refreshed2");
630
+ await new Promise((resolve) => setTimeout(resolve, 10));
631
+ // Now all done
632
+ expect(state.isRefreshing).toBe(false);
633
+ expect(state.data1).toBe("refreshed1");
634
+ expect(state.data2).toBe("refreshed2");
635
+ });
636
+ it("should handle transition from error to success", async () => {
637
+ const error = new Error("failed");
638
+ let state, refresh;
639
+ const fetcher = vi
640
+ .fn()
641
+ .mockRejectedValueOnce(error)
642
+ .mockResolvedValueOnce("success");
643
+ function Component() {
644
+ const [async1, refreshFn] = useAsync(fetcher);
645
+ refresh = refreshFn;
646
+ const [async2] = useAsync(() => Promise.resolve("value2"));
647
+ state = useSuspend({
648
+ data1: () => async1,
649
+ data2: () => async2,
650
+ });
651
+ return () => _jsx("div", { children: "test" });
652
+ }
653
+ const container = document.createElement("div");
654
+ render(_jsx(Component, {}), container);
655
+ await new Promise((resolve) => setTimeout(resolve, 10));
656
+ expect(state.error).toBeInstanceOf(Error);
657
+ expect(state.isLoading).toBe(true);
658
+ // Refresh the failed async
659
+ refresh();
660
+ await new Promise((resolve) => setTimeout(resolve, 10));
661
+ expect(state.error).toBeNull();
662
+ expect(state.isLoading).toBe(false);
663
+ expect(state.isRefreshing).toBe(false);
664
+ expect(state.data1).toBe("success");
665
+ expect(state.data2).toBe("value2");
666
+ });
667
+ it("should handle alternating async resolutions", async () => {
668
+ let state;
669
+ let resolveFirst;
670
+ let resolveSecond;
671
+ let resolveThird;
672
+ const firstPromise = new Promise((resolve) => {
673
+ resolveFirst = resolve;
674
+ });
675
+ const secondPromise = new Promise((resolve) => {
676
+ resolveSecond = resolve;
677
+ });
678
+ const thirdPromise = new Promise((resolve) => {
679
+ resolveThird = resolve;
680
+ });
681
+ function Component() {
682
+ const [async1] = useAsync(() => firstPromise);
683
+ const [async2] = useAsync(() => secondPromise);
684
+ const [async3] = useAsync(() => thirdPromise);
685
+ state = useSuspend({
686
+ data1: () => async1,
687
+ data2: () => async2,
688
+ data3: () => async3,
689
+ });
690
+ return () => _jsx("div", { children: "test" });
691
+ }
692
+ const container = document.createElement("div");
693
+ render(_jsx(Component, {}), container);
694
+ expect(state.isLoading).toBe(true);
695
+ // Resolve in non-sequential order
696
+ resolveSecond("second");
697
+ await new Promise((resolve) => setTimeout(resolve, 10));
698
+ expect(state.isLoading).toBe(true); // Still loading
699
+ resolveThird("third");
700
+ await new Promise((resolve) => setTimeout(resolve, 10));
701
+ expect(state.isLoading).toBe(true); // Still loading
702
+ resolveFirst("first");
703
+ await new Promise((resolve) => setTimeout(resolve, 10));
704
+ expect(state.isLoading).toBe(false);
705
+ expect(state.data1).toBe("first");
706
+ expect(state.data2).toBe("second");
707
+ expect(state.data3).toBe("third");
708
+ });
709
+ });
710
+ describe("type handling", () => {
711
+ it("should handle different value types", async () => {
712
+ let state;
713
+ function Component() {
714
+ const [async1] = useAsync(() => Promise.resolve(42));
715
+ const [async2] = useAsync(() => Promise.resolve({ id: 1, name: "test" }));
716
+ const [async3] = useAsync(() => Promise.resolve([1, 2, 3]));
717
+ const [async4] = useAsync(() => Promise.resolve(true));
718
+ state = useSuspend({
719
+ number: () => async1,
720
+ object: () => async2,
721
+ array: () => async3,
722
+ boolean: () => async4,
723
+ });
724
+ return () => _jsx("div", { children: "test" });
725
+ }
726
+ const container = document.createElement("div");
727
+ render(_jsx(Component, {}), container);
728
+ await new Promise((resolve) => setTimeout(resolve, 10));
729
+ expect(state.number).toBe(42);
730
+ expect(state.object).toEqual({ id: 1, name: "test" });
731
+ expect(state.array).toEqual([1, 2, 3]);
732
+ expect(state.boolean).toBe(true);
733
+ });
734
+ it("should handle null and undefined values", async () => {
735
+ let state;
736
+ function Component() {
737
+ const [async1] = useAsync(() => Promise.resolve(null));
738
+ const [async2] = useAsync(() => Promise.resolve(undefined));
739
+ state = useSuspend({
740
+ nullValue: () => async1,
741
+ undefinedValue: () => async2,
742
+ });
743
+ return () => _jsx("div", { children: "test" });
744
+ }
745
+ const container = document.createElement("div");
746
+ render(_jsx(Component, {}), container);
747
+ await new Promise((resolve) => setTimeout(resolve, 10));
748
+ expect(state.nullValue).toBeNull();
749
+ expect(state.undefinedValue).toBeUndefined();
750
+ });
751
+ });
752
+ });