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.
- package/dist/component.d.ts +1 -0
- package/dist/component.d.ts.map +1 -1
- package/dist/component.js +7 -2
- package/dist/tests/batch.test.js +202 -12
- package/dist/tests/createContext.test.js +50 -37
- package/dist/tests/error.test.js +25 -12
- package/dist/tests/renderCount.test.d.ts +2 -0
- package/dist/tests/renderCount.test.d.ts.map +1 -0
- package/dist/tests/renderCount.test.js +95 -0
- package/dist/tests/scopeEnforcement.test.d.ts +2 -0
- package/dist/tests/scopeEnforcement.test.d.ts.map +1 -0
- package/dist/tests/scopeEnforcement.test.js +157 -0
- package/dist/tests/useAction.test.d.ts +2 -0
- package/dist/tests/useAction.test.d.ts.map +1 -0
- package/dist/tests/useAction.test.js +132 -0
- package/dist/tests/useAsync.test.d.ts +2 -0
- package/dist/tests/useAsync.test.d.ts.map +1 -0
- package/dist/tests/useAsync.test.js +499 -0
- package/dist/tests/useDerived.test.d.ts +2 -0
- package/dist/tests/useDerived.test.d.ts.map +1 -0
- package/dist/tests/useDerived.test.js +407 -0
- package/dist/tests/useEffect.test.d.ts +2 -0
- package/dist/tests/useEffect.test.d.ts.map +1 -0
- package/dist/tests/useEffect.test.js +600 -0
- package/dist/tests/useLookup.test.d.ts +2 -0
- package/dist/tests/useLookup.test.d.ts.map +1 -0
- package/dist/tests/useLookup.test.js +299 -0
- package/dist/tests/useRef.test.d.ts +2 -0
- package/dist/tests/useRef.test.d.ts.map +1 -0
- package/dist/tests/useRef.test.js +189 -0
- package/dist/tests/useState.test.d.ts +2 -0
- package/dist/tests/useState.test.d.ts.map +1 -0
- package/dist/tests/useState.test.js +178 -0
- package/dist/tests/useSuspend.test.d.ts +2 -0
- package/dist/tests/useSuspend.test.d.ts.map +1 -0
- package/dist/tests/useSuspend.test.js +752 -0
- package/dist/tests/useView.test.d.ts +2 -0
- package/dist/tests/useView.test.d.ts.map +1 -0
- package/dist/tests/useView.test.js +305 -0
- package/dist/transformer.d.ts.map +1 -1
- package/dist/transformer.js +1 -5
- package/dist/useState.js +4 -2
- package/package.json +1 -1
|
@@ -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 @@
|
|
|
1
|
+
{"version":3,"file":"useDerived.test.d.ts","sourceRoot":"","sources":["../../src/tests/useDerived.test.tsx"],"names":[],"mappings":""}
|