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,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
|
+
});
|