rask-ui 0.28.3 → 0.29.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 (65) hide show
  1. package/README.md +1 -1
  2. package/dist/component.d.ts +4 -3
  3. package/dist/component.d.ts.map +1 -1
  4. package/dist/component.js +37 -57
  5. package/dist/index.d.ts +0 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +0 -1
  8. package/dist/render.js +2 -2
  9. package/dist/scheduler.d.ts +2 -3
  10. package/dist/scheduler.d.ts.map +1 -1
  11. package/dist/scheduler.js +31 -104
  12. package/dist/tests/batch.test.js +202 -12
  13. package/dist/tests/createContext.test.js +50 -37
  14. package/dist/tests/error.test.js +25 -12
  15. package/dist/tests/renderCount.test.d.ts +2 -0
  16. package/dist/tests/renderCount.test.d.ts.map +1 -0
  17. package/dist/tests/renderCount.test.js +95 -0
  18. package/dist/tests/scopeEnforcement.test.d.ts +2 -0
  19. package/dist/tests/scopeEnforcement.test.d.ts.map +1 -0
  20. package/dist/tests/scopeEnforcement.test.js +157 -0
  21. package/dist/tests/useAction.test.d.ts +2 -0
  22. package/dist/tests/useAction.test.d.ts.map +1 -0
  23. package/dist/tests/useAction.test.js +132 -0
  24. package/dist/tests/useAsync.test.d.ts +2 -0
  25. package/dist/tests/useAsync.test.d.ts.map +1 -0
  26. package/dist/tests/useAsync.test.js +499 -0
  27. package/dist/tests/useDerived.test.d.ts +2 -0
  28. package/dist/tests/useDerived.test.d.ts.map +1 -0
  29. package/dist/tests/useDerived.test.js +407 -0
  30. package/dist/tests/useEffect.test.d.ts +2 -0
  31. package/dist/tests/useEffect.test.d.ts.map +1 -0
  32. package/dist/tests/useEffect.test.js +600 -0
  33. package/dist/tests/useLookup.test.d.ts +2 -0
  34. package/dist/tests/useLookup.test.d.ts.map +1 -0
  35. package/dist/tests/useLookup.test.js +299 -0
  36. package/dist/tests/useRef.test.d.ts +2 -0
  37. package/dist/tests/useRef.test.d.ts.map +1 -0
  38. package/dist/tests/useRef.test.js +189 -0
  39. package/dist/tests/useState.test.d.ts +2 -0
  40. package/dist/tests/useState.test.d.ts.map +1 -0
  41. package/dist/tests/useState.test.js +178 -0
  42. package/dist/tests/useSuspend.test.d.ts +2 -0
  43. package/dist/tests/useSuspend.test.d.ts.map +1 -0
  44. package/dist/tests/useSuspend.test.js +752 -0
  45. package/dist/tests/useView.test.d.ts +2 -0
  46. package/dist/tests/useView.test.d.ts.map +1 -0
  47. package/dist/tests/useView.test.js +305 -0
  48. package/dist/useAsync.d.ts.map +1 -1
  49. package/dist/useAsync.js +12 -11
  50. package/dist/useDerived.d.ts +1 -1
  51. package/dist/useDerived.d.ts.map +1 -1
  52. package/dist/useDerived.js +9 -63
  53. package/dist/useEffect.d.ts.map +1 -1
  54. package/dist/useEffect.js +4 -19
  55. package/dist/useLookup.d.ts.map +1 -1
  56. package/dist/useLookup.js +9 -14
  57. package/dist/useRef.d.ts.map +1 -1
  58. package/dist/useRef.js +4 -8
  59. package/dist/useRouter.d.ts.map +1 -1
  60. package/dist/useRouter.js +4 -8
  61. package/dist/useState.d.ts +0 -1
  62. package/dist/useState.d.ts.map +1 -1
  63. package/dist/useState.js +2 -100
  64. package/dist/useSuspend.d.ts.map +1 -1
  65. package/package.json +1 -1
@@ -0,0 +1,132 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { useAction } from "../useAction";
3
+ describe("useAction", () => {
4
+ it("should initialize with idle state", () => {
5
+ const [state] = useAction(async () => "result");
6
+ expect(state.isPending).toBe(false);
7
+ expect(state.params).toBe(null);
8
+ expect(state.result).toBe(null);
9
+ expect(state.error).toBe(null);
10
+ });
11
+ it("should handle successful action without parameters", async () => {
12
+ const fn = vi.fn(async () => "success");
13
+ const [state, run] = useAction(fn);
14
+ run();
15
+ expect(state.isPending).toBe(true);
16
+ expect(state.params).toBe(null);
17
+ expect(state.result).toBe(null);
18
+ expect(state.error).toBe(null);
19
+ await new Promise((resolve) => setTimeout(resolve, 10));
20
+ expect(state.isPending).toBe(false);
21
+ expect(state.result).toBe("success");
22
+ expect(state.error).toBe(null);
23
+ expect(fn).toHaveBeenCalledTimes(1);
24
+ });
25
+ it("should handle successful action with parameters", async () => {
26
+ const fn = vi.fn(async (x) => x * 2);
27
+ const [state, run] = useAction(fn);
28
+ run(5);
29
+ expect(state.isPending).toBe(true);
30
+ expect(state.params).toBe(5);
31
+ expect(state.result).toBe(null);
32
+ await new Promise((resolve) => setTimeout(resolve, 10));
33
+ expect(state.isPending).toBe(false);
34
+ expect(state.params).toBe(5);
35
+ expect(state.result).toBe(10);
36
+ expect(state.error).toBe(null);
37
+ });
38
+ it("should handle errors", async () => {
39
+ const fn = vi.fn(async () => {
40
+ throw new Error("Test error");
41
+ });
42
+ const [state, run] = useAction(fn);
43
+ run();
44
+ expect(state.isPending).toBe(true);
45
+ await new Promise((resolve) => setTimeout(resolve, 10));
46
+ expect(state.isPending).toBe(false);
47
+ expect(state.result).toBe(null);
48
+ expect(state.error?.message).toBe("Test error");
49
+ });
50
+ it("should abort previous action when new action starts", async () => {
51
+ let resolveFirst = null;
52
+ let resolveSecond = null;
53
+ const fn = vi.fn(async (id) => {
54
+ if (id === 1) {
55
+ return new Promise((resolve) => {
56
+ resolveFirst = resolve;
57
+ });
58
+ }
59
+ else {
60
+ return new Promise((resolve) => {
61
+ resolveSecond = resolve;
62
+ });
63
+ }
64
+ });
65
+ const [state, run] = useAction(fn);
66
+ // Start first action
67
+ run(1);
68
+ expect(state.isPending).toBe(true);
69
+ expect(state.params).toBe(1);
70
+ await new Promise((resolve) => setTimeout(resolve, 10));
71
+ // Start second action (should abort first)
72
+ run(2);
73
+ expect(state.isPending).toBe(true);
74
+ expect(state.params).toBe(2);
75
+ // Resolve first action (should be ignored due to abort)
76
+ resolveFirst?.("first");
77
+ await new Promise((resolve) => setTimeout(resolve, 10));
78
+ // State should not reflect first action
79
+ expect(state.result).toBe(null);
80
+ // Resolve second action
81
+ resolveSecond?.("second");
82
+ await new Promise((resolve) => setTimeout(resolve, 10));
83
+ // State should reflect second action
84
+ expect(state.isPending).toBe(false);
85
+ expect(state.params).toBe(2);
86
+ expect(state.result).toBe("second");
87
+ });
88
+ it("should track params even on error", async () => {
89
+ const fn = vi.fn(async (x) => {
90
+ throw new Error("Failed");
91
+ });
92
+ const [state, run] = useAction(fn);
93
+ run(42);
94
+ await new Promise((resolve) => setTimeout(resolve, 10));
95
+ expect(state.isPending).toBe(false);
96
+ expect(state.params).toBe(42);
97
+ expect(state.error?.message).toBe("Failed");
98
+ expect(state.result).toBe(null);
99
+ });
100
+ it("should handle rapid consecutive calls", async () => {
101
+ let callCount = 0;
102
+ const fn = vi.fn(async (x) => {
103
+ callCount++;
104
+ await new Promise((resolve) => setTimeout(resolve, 5));
105
+ return x;
106
+ });
107
+ const [state, run] = useAction(fn);
108
+ // Fire off multiple calls
109
+ run(1);
110
+ run(2);
111
+ run(3);
112
+ // Wait for all to settle
113
+ await new Promise((resolve) => setTimeout(resolve, 50));
114
+ // Should have called function 3 times (not aborted, just ignored results)
115
+ expect(callCount).toBe(3);
116
+ // State should reflect the last call
117
+ expect(state.isPending).toBe(false);
118
+ expect(state.params).toBe(3);
119
+ expect(state.result).toBe(3);
120
+ });
121
+ it("should handle null as valid param value", async () => {
122
+ const fn = vi.fn(async (x) => {
123
+ return x === null ? "was null" : x;
124
+ });
125
+ const [state, run] = useAction(fn);
126
+ run(null);
127
+ await new Promise((resolve) => setTimeout(resolve, 10));
128
+ expect(state.isPending).toBe(false);
129
+ expect(state.params).toBe(null);
130
+ expect(state.result).toBe("was null");
131
+ });
132
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=useAsync.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAsync.test.d.ts","sourceRoot":"","sources":["../../src/tests/useAsync.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,499 @@
1
+ import { jsx as _jsx } from "rask-ui/jsx-runtime";
2
+ import { describe, it, expect, vi } from "vitest";
3
+ import { useAsync, isAsync } from "../useAsync";
4
+ import { render } from "../index";
5
+ describe("useAsync", () => {
6
+ describe("basic functionality", () => {
7
+ it("should auto-run on mount and start in loading state", () => {
8
+ let state, refresh;
9
+ function Component() {
10
+ [state, refresh] = useAsync(() => new Promise(() => { }));
11
+ return () => _jsx("div", { children: "test" });
12
+ }
13
+ const container = document.createElement("div");
14
+ render(_jsx(Component, {}), container);
15
+ expect(state.isLoading).toBe(true);
16
+ expect(state.isRefreshing).toBe(false);
17
+ expect(state.value).toBeNull();
18
+ expect(state.error).toBeNull();
19
+ });
20
+ it("should resolve to success state", async () => {
21
+ let state, refresh;
22
+ function Component() {
23
+ [state, refresh] = useAsync(() => Promise.resolve("success"));
24
+ return () => _jsx("div", { children: "test" });
25
+ }
26
+ const container = document.createElement("div");
27
+ render(_jsx(Component, {}), container);
28
+ expect(state.isLoading).toBe(true);
29
+ await new Promise((resolve) => setTimeout(resolve, 10));
30
+ expect(state.isLoading).toBe(false);
31
+ expect(state.isRefreshing).toBe(false);
32
+ expect(state.value).toBe("success");
33
+ expect(state.error).toBeNull();
34
+ });
35
+ it("should resolve to error state on rejection", async () => {
36
+ let state, refresh;
37
+ const error = new Error("failed");
38
+ function Component() {
39
+ [state, refresh] = useAsync(() => Promise.reject(error));
40
+ return () => _jsx("div", { children: "test" });
41
+ }
42
+ const container = document.createElement("div");
43
+ render(_jsx(Component, {}), container);
44
+ await new Promise((resolve) => setTimeout(resolve, 10));
45
+ expect(state.isLoading).toBe(true);
46
+ expect(state.isRefreshing).toBe(false);
47
+ expect(state.value).toBeNull();
48
+ expect(state.error).toBeInstanceOf(Error);
49
+ expect(state.error.message).toBe("failed");
50
+ });
51
+ it("should auto-run the async function on mount", async () => {
52
+ const fetcher = vi.fn(() => Promise.resolve("data"));
53
+ let state, refresh;
54
+ function Component() {
55
+ [state, refresh] = useAsync(fetcher);
56
+ return () => _jsx("div", { children: "test" });
57
+ }
58
+ const container = document.createElement("div");
59
+ render(_jsx(Component, {}), container);
60
+ expect(fetcher).toHaveBeenCalledTimes(1);
61
+ expect(state.isLoading).toBe(true);
62
+ await new Promise((resolve) => setTimeout(resolve, 10));
63
+ expect(state.value).toBe("data");
64
+ expect(state.isLoading).toBe(false);
65
+ });
66
+ });
67
+ describe("refresh functionality", () => {
68
+ it("should set isRefreshing when refresh is called with existing value", async () => {
69
+ let state, refresh;
70
+ let resolveFirst;
71
+ let resolveSecond;
72
+ const firstPromise = new Promise((resolve) => {
73
+ resolveFirst = resolve;
74
+ });
75
+ const secondPromise = new Promise((resolve) => {
76
+ resolveSecond = resolve;
77
+ });
78
+ const fetcher = vi
79
+ .fn()
80
+ .mockReturnValueOnce(firstPromise)
81
+ .mockReturnValueOnce(secondPromise);
82
+ function Component() {
83
+ [state, refresh] = useAsync(fetcher);
84
+ return () => _jsx("div", { children: "test" });
85
+ }
86
+ const container = document.createElement("div");
87
+ render(_jsx(Component, {}), container);
88
+ expect(state.isLoading).toBe(true);
89
+ expect(state.isRefreshing).toBe(false);
90
+ // Resolve first load
91
+ resolveFirst("first");
92
+ await new Promise((resolve) => setTimeout(resolve, 10));
93
+ expect(state.isLoading).toBe(false);
94
+ expect(state.isRefreshing).toBe(false);
95
+ expect(state.value).toBe("first");
96
+ // Refresh
97
+ refresh();
98
+ expect(state.isLoading).toBe(false);
99
+ expect(state.isRefreshing).toBe(true);
100
+ expect(state.value).toBe("first");
101
+ // Resolve refresh
102
+ resolveSecond("second");
103
+ await new Promise((resolve) => setTimeout(resolve, 10));
104
+ expect(state.isLoading).toBe(false);
105
+ expect(state.isRefreshing).toBe(false);
106
+ expect(state.value).toBe("second");
107
+ });
108
+ it("should set isLoading when refresh is called after error", async () => {
109
+ let state, refresh;
110
+ const error = new Error("failed");
111
+ const fetcher = vi
112
+ .fn()
113
+ .mockRejectedValueOnce(error)
114
+ .mockResolvedValueOnce("success");
115
+ function Component() {
116
+ [state, refresh] = useAsync(fetcher);
117
+ return () => _jsx("div", { children: "test" });
118
+ }
119
+ const container = document.createElement("div");
120
+ render(_jsx(Component, {}), container);
121
+ await new Promise((resolve) => setTimeout(resolve, 10));
122
+ expect(state.isLoading).toBe(true);
123
+ expect(state.isRefreshing).toBe(false);
124
+ expect(state.error).toBeInstanceOf(Error);
125
+ expect(state.error.message).toBe("failed");
126
+ expect(state.value).toBeNull();
127
+ // Refresh after error
128
+ refresh();
129
+ expect(state.isLoading).toBe(true);
130
+ expect(state.isRefreshing).toBe(false);
131
+ expect(state.error).toBeNull();
132
+ await new Promise((resolve) => setTimeout(resolve, 10));
133
+ expect(state.isLoading).toBe(false);
134
+ expect(state.isRefreshing).toBe(false);
135
+ expect(state.value).toBe("success");
136
+ expect(state.error).toBeNull();
137
+ });
138
+ it("should not trigger refresh if already loading", async () => {
139
+ let state, refresh;
140
+ const fetcher = vi.fn(() => new Promise(() => { }));
141
+ function Component() {
142
+ [state, refresh] = useAsync(fetcher);
143
+ return () => _jsx("div", { children: "test" });
144
+ }
145
+ const container = document.createElement("div");
146
+ render(_jsx(Component, {}), container);
147
+ expect(state.isLoading).toBe(true);
148
+ expect(fetcher).toHaveBeenCalledTimes(1);
149
+ // Try to refresh while loading
150
+ await refresh();
151
+ // Should not trigger another call
152
+ expect(fetcher).toHaveBeenCalledTimes(1);
153
+ });
154
+ it("should return a promise from refresh that resolves on success", async () => {
155
+ let state, refresh;
156
+ let resolvePromise;
157
+ const promise = new Promise((resolve) => {
158
+ resolvePromise = resolve;
159
+ });
160
+ const fetcher = vi.fn(() => promise);
161
+ function Component() {
162
+ [state, refresh] = useAsync(fetcher);
163
+ return () => _jsx("div", { children: "test" });
164
+ }
165
+ const container = document.createElement("div");
166
+ render(_jsx(Component, {}), container);
167
+ // Wait for initial load
168
+ resolvePromise("initial");
169
+ await new Promise((resolve) => setTimeout(resolve, 10));
170
+ // Create new promise for refresh
171
+ let resolveRefresh;
172
+ const refreshPromise = new Promise((resolve) => {
173
+ resolveRefresh = resolve;
174
+ });
175
+ fetcher.mockReturnValueOnce(refreshPromise);
176
+ let refreshResolved = false;
177
+ refresh().then(() => {
178
+ refreshResolved = true;
179
+ });
180
+ expect(refreshResolved).toBe(false);
181
+ resolveRefresh("refreshed");
182
+ await new Promise((resolve) => setTimeout(resolve, 10));
183
+ expect(refreshResolved).toBe(true);
184
+ expect(state.value).toBe("refreshed");
185
+ });
186
+ it("should return a promise from refresh that rejects on error", async () => {
187
+ let state, refresh;
188
+ let resolvePromise;
189
+ const promise = new Promise((resolve) => {
190
+ resolvePromise = resolve;
191
+ });
192
+ const fetcher = vi.fn(() => promise);
193
+ function Component() {
194
+ [state, refresh] = useAsync(fetcher);
195
+ return () => _jsx("div", { children: "test" });
196
+ }
197
+ const container = document.createElement("div");
198
+ render(_jsx(Component, {}), container);
199
+ // Wait for initial load
200
+ resolvePromise("initial");
201
+ await new Promise((resolve) => setTimeout(resolve, 10));
202
+ // Create new promise for refresh that rejects
203
+ const error = new Error("refresh failed");
204
+ fetcher.mockRejectedValueOnce(error);
205
+ let refreshRejected = false;
206
+ refresh().catch(() => {
207
+ refreshRejected = true;
208
+ });
209
+ await new Promise((resolve) => setTimeout(resolve, 10));
210
+ expect(refreshRejected).toBe(true);
211
+ expect(state.error).toBeInstanceOf(Error);
212
+ expect(state.error.message).toBe("refresh failed");
213
+ });
214
+ });
215
+ describe("cancellation with AbortSignal", () => {
216
+ it("should pass AbortSignal to the async function", async () => {
217
+ let receivedSignal;
218
+ const fetcher = vi.fn((signal) => {
219
+ receivedSignal = signal;
220
+ return Promise.resolve("data");
221
+ });
222
+ let state, refresh;
223
+ function Component() {
224
+ [state, refresh] = useAsync(fetcher);
225
+ return () => _jsx("div", { children: "test" });
226
+ }
227
+ const container = document.createElement("div");
228
+ render(_jsx(Component, {}), container);
229
+ await new Promise((resolve) => setTimeout(resolve, 10));
230
+ expect(receivedSignal).toBeInstanceOf(AbortSignal);
231
+ });
232
+ it("should abort previous request when refresh is called", async () => {
233
+ let firstSignal;
234
+ let secondSignal;
235
+ let resolveFirst;
236
+ let resolveSecond;
237
+ const firstPromise = new Promise((resolve) => {
238
+ resolveFirst = resolve;
239
+ });
240
+ const secondPromise = new Promise((resolve) => {
241
+ resolveSecond = resolve;
242
+ });
243
+ const fetcher = vi
244
+ .fn()
245
+ .mockImplementationOnce((signal) => {
246
+ firstSignal = signal;
247
+ return firstPromise;
248
+ })
249
+ .mockImplementationOnce((signal) => {
250
+ secondSignal = signal;
251
+ return secondPromise;
252
+ });
253
+ let state, refresh;
254
+ function Component() {
255
+ [state, refresh] = useAsync(fetcher);
256
+ return () => _jsx("div", { children: "test" });
257
+ }
258
+ const container = document.createElement("div");
259
+ render(_jsx(Component, {}), container);
260
+ expect(firstSignal?.aborted).toBe(false);
261
+ // Resolve first to get to refreshable state
262
+ resolveFirst("first");
263
+ await new Promise((resolve) => setTimeout(resolve, 10));
264
+ // Trigger refresh - should abort first signal
265
+ refresh();
266
+ expect(firstSignal?.aborted).toBe(true);
267
+ expect(secondSignal?.aborted).toBe(false);
268
+ // Resolve second
269
+ resolveSecond("second");
270
+ await new Promise((resolve) => setTimeout(resolve, 10));
271
+ expect(state.value).toBe("second");
272
+ });
273
+ it("should ignore aborted request results", async () => {
274
+ let state, refresh;
275
+ let resolveFirst;
276
+ let resolveSecond;
277
+ const firstPromise = new Promise((resolve) => {
278
+ resolveFirst = resolve;
279
+ });
280
+ const secondPromise = new Promise((resolve) => {
281
+ resolveSecond = resolve;
282
+ });
283
+ const fetcher = vi
284
+ .fn()
285
+ .mockReturnValueOnce(firstPromise)
286
+ .mockReturnValueOnce(secondPromise);
287
+ function Component() {
288
+ [state, refresh] = useAsync(fetcher);
289
+ return () => _jsx("div", { children: "test" });
290
+ }
291
+ const container = document.createElement("div");
292
+ render(_jsx(Component, {}), container);
293
+ // Resolve first to get to refreshable state
294
+ resolveFirst("first");
295
+ await new Promise((resolve) => setTimeout(resolve, 10));
296
+ // Trigger refresh (aborts previous)
297
+ refresh();
298
+ // Resolve second
299
+ resolveSecond("second");
300
+ await new Promise((resolve) => setTimeout(resolve, 10));
301
+ // Only second result should be in state
302
+ expect(state.value).toBe("second");
303
+ });
304
+ });
305
+ describe("type handling", () => {
306
+ it("should handle numeric values", async () => {
307
+ let state, refresh;
308
+ function Component() {
309
+ [state, refresh] = useAsync(() => Promise.resolve(42));
310
+ return () => _jsx("div", { children: "test" });
311
+ }
312
+ const container = document.createElement("div");
313
+ render(_jsx(Component, {}), container);
314
+ await new Promise((resolve) => setTimeout(resolve, 10));
315
+ expect(state.value).toBe(42);
316
+ });
317
+ it("should handle object values", async () => {
318
+ const data = { id: 1, name: "Test" };
319
+ let state, refresh;
320
+ function Component() {
321
+ [state, refresh] = useAsync(() => Promise.resolve(data));
322
+ return () => _jsx("div", { children: "test" });
323
+ }
324
+ const container = document.createElement("div");
325
+ render(_jsx(Component, {}), container);
326
+ await new Promise((resolve) => setTimeout(resolve, 10));
327
+ expect(state.value).toEqual(data);
328
+ });
329
+ it("should handle array values", async () => {
330
+ const data = [1, 2, 3, 4, 5];
331
+ let state, refresh;
332
+ function Component() {
333
+ [state, refresh] = useAsync(() => Promise.resolve(data));
334
+ return () => _jsx("div", { children: "test" });
335
+ }
336
+ const container = document.createElement("div");
337
+ render(_jsx(Component, {}), container);
338
+ await new Promise((resolve) => setTimeout(resolve, 10));
339
+ expect(state.value).toEqual(data);
340
+ });
341
+ it("should preserve Error objects", async () => {
342
+ const error = new Error("Something went wrong");
343
+ let state, refresh;
344
+ function Component() {
345
+ [state, refresh] = useAsync(() => Promise.reject(error));
346
+ return () => _jsx("div", { children: "test" });
347
+ }
348
+ const container = document.createElement("div");
349
+ render(_jsx(Component, {}), container);
350
+ await new Promise((resolve) => setTimeout(resolve, 10));
351
+ expect(state.error).toBeInstanceOf(Error);
352
+ expect(state.error.message).toBe("Something went wrong");
353
+ });
354
+ });
355
+ describe("edge cases", () => {
356
+ it("should handle immediate resolution", async () => {
357
+ let state, refresh;
358
+ function Component() {
359
+ [state, refresh] = useAsync(() => Promise.resolve("immediate"));
360
+ return () => _jsx("div", { children: "test" });
361
+ }
362
+ const container = document.createElement("div");
363
+ render(_jsx(Component, {}), container);
364
+ await new Promise((resolve) => setTimeout(resolve, 10));
365
+ expect(state.isLoading).toBe(false);
366
+ expect(state.isRefreshing).toBe(false);
367
+ expect(state.value).toBe("immediate");
368
+ });
369
+ it("should handle immediate rejection", async () => {
370
+ const error = new Error("immediate error");
371
+ let state, refresh;
372
+ function Component() {
373
+ [state, refresh] = useAsync(() => Promise.reject(error));
374
+ return () => _jsx("div", { children: "test" });
375
+ }
376
+ const container = document.createElement("div");
377
+ render(_jsx(Component, {}), container);
378
+ await new Promise((resolve) => setTimeout(resolve, 10));
379
+ expect(state.isLoading).toBe(true);
380
+ expect(state.isRefreshing).toBe(false);
381
+ expect(state.error).toBeInstanceOf(Error);
382
+ expect(state.error.message).toBe("immediate error");
383
+ });
384
+ it("should handle delayed resolution", async () => {
385
+ let state, refresh;
386
+ function Component() {
387
+ [state, refresh] = useAsync(() => new Promise((resolve) => {
388
+ setTimeout(() => resolve("delayed"), 20);
389
+ }));
390
+ return () => _jsx("div", { children: "test" });
391
+ }
392
+ const container = document.createElement("div");
393
+ render(_jsx(Component, {}), container);
394
+ expect(state.isLoading).toBe(true);
395
+ await new Promise((resolve) => setTimeout(resolve, 30));
396
+ expect(state.isLoading).toBe(false);
397
+ expect(state.value).toBe("delayed");
398
+ });
399
+ it("should clear error on successful retry", async () => {
400
+ const error = new Error("First error");
401
+ const fetcher = vi
402
+ .fn()
403
+ .mockRejectedValueOnce(error)
404
+ .mockResolvedValueOnce("success");
405
+ let state, refresh;
406
+ function Component() {
407
+ [state, refresh] = useAsync(fetcher);
408
+ return () => _jsx("div", { children: "test" });
409
+ }
410
+ const container = document.createElement("div");
411
+ render(_jsx(Component, {}), container);
412
+ await new Promise((resolve) => setTimeout(resolve, 10));
413
+ expect(state.error).toBeInstanceOf(Error);
414
+ expect(state.error.message).toBe("First error");
415
+ expect(state.value).toBeNull();
416
+ refresh();
417
+ await new Promise((resolve) => setTimeout(resolve, 10));
418
+ expect(state.error).toBeNull();
419
+ expect(state.value).toBe("success");
420
+ });
421
+ it("should handle rapid successive refreshes", async () => {
422
+ let counter = 0;
423
+ let resolvers = [];
424
+ const fetcher = vi.fn(() => {
425
+ const currentCount = ++counter;
426
+ return new Promise((resolve) => {
427
+ resolvers.push(() => resolve(`data-${currentCount}`));
428
+ });
429
+ });
430
+ let state, refresh;
431
+ function Component() {
432
+ [state, refresh] = useAsync(fetcher);
433
+ return () => _jsx("div", { children: "test" });
434
+ }
435
+ const container = document.createElement("div");
436
+ render(_jsx(Component, {}), container);
437
+ // Resolve initial load
438
+ resolvers[0]();
439
+ await new Promise((resolve) => setTimeout(resolve, 10));
440
+ expect(state.value).toBe("data-1");
441
+ // Rapid refreshes
442
+ refresh();
443
+ refresh();
444
+ refresh();
445
+ // Resolve in order
446
+ resolvers[1]();
447
+ await new Promise((resolve) => setTimeout(resolve, 10));
448
+ resolvers[2]();
449
+ await new Promise((resolve) => setTimeout(resolve, 10));
450
+ resolvers[3]();
451
+ await new Promise((resolve) => setTimeout(resolve, 10));
452
+ // Only the last result should be in state due to abortion
453
+ expect(state.value).toBe("data-4");
454
+ });
455
+ });
456
+ describe("cleanup", () => {
457
+ it("should abort request when component unmounts", async () => {
458
+ let signal;
459
+ const fetcher = vi.fn((s) => {
460
+ signal = s;
461
+ return new Promise(() => { });
462
+ });
463
+ let state, refresh;
464
+ function Component() {
465
+ [state, refresh] = useAsync(fetcher);
466
+ return () => _jsx("div", { children: "test" });
467
+ }
468
+ const container = document.createElement("div");
469
+ render(_jsx(Component, {}), container);
470
+ expect(signal?.aborted).toBe(false);
471
+ // Unmount
472
+ render(null, container);
473
+ expect(signal?.aborted).toBe(true);
474
+ });
475
+ });
476
+ describe("isAsync helper", () => {
477
+ it("should identify async state objects", () => {
478
+ expect(isAsync({
479
+ isLoading: true,
480
+ isRefreshing: false,
481
+ value: null,
482
+ error: null,
483
+ })).toBe(true);
484
+ expect(isAsync({
485
+ isLoading: false,
486
+ isRefreshing: true,
487
+ value: "data",
488
+ error: null,
489
+ })).toBe(true);
490
+ });
491
+ it("should reject non-async objects", () => {
492
+ expect(isAsync(null)).toBe(false);
493
+ expect(isAsync(undefined)).toBe(false);
494
+ expect(isAsync({})).toBe(false);
495
+ expect(isAsync({ isLoading: true })).toBe(false);
496
+ expect(isAsync({ value: "test" })).toBe(false);
497
+ });
498
+ });
499
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=useDerived.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useDerived.test.d.ts","sourceRoot":"","sources":["../../src/tests/useDerived.test.tsx"],"names":[],"mappings":""}