@sylphx/lens-react 2.0.2 → 2.1.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.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Tests for React Hooks (Operations-based API)
2
+ * Tests for React Hooks (Selector-based API)
3
3
  *
4
4
  * NOTE: These tests require DOM environment (happy-dom).
5
5
  * Run from packages/react directory: cd packages/react && bun test
@@ -12,8 +12,10 @@ import { test as bunTest, describe, expect } from "bun:test";
12
12
 
13
13
  const test = hasDom ? bunTest : bunTest.skip;
14
14
 
15
- import type { MutationResult, QueryResult } from "@sylphx/lens-client";
15
+ import type { LensClient, MutationResult, QueryResult } from "@sylphx/lens-client";
16
16
  import { act, renderHook, waitFor } from "@testing-library/react";
17
+ import type { ReactNode } from "react";
18
+ import { LensProvider } from "./context.js";
17
19
  import { useLazyQuery, useMutation, useQuery } from "./hooks.js";
18
20
 
19
21
  // =============================================================================
@@ -87,15 +89,32 @@ function createMockQueryResult<T>(initialValue: T | null = null): QueryResult<T>
87
89
  };
88
90
  }
89
91
 
92
+ // =============================================================================
93
+ // Test Wrapper with Mock Client
94
+ // =============================================================================
95
+
96
+ function createMockClient() {
97
+ return {} as LensClient<any, any>;
98
+ }
99
+
100
+ function createWrapper(mockClient: LensClient<any, any>) {
101
+ return function Wrapper({ children }: { children: ReactNode }) {
102
+ return <LensProvider client={mockClient}>{children}</LensProvider>;
103
+ };
104
+ }
105
+
90
106
  // =============================================================================
91
107
  // Tests: useQuery (Accessor + Deps pattern)
92
108
  // =============================================================================
93
109
 
94
110
  describe("useQuery", () => {
95
111
  test("returns loading state initially", () => {
112
+ const mockClient = createMockClient();
96
113
  const mockQuery = createMockQueryResult<{ id: string; name: string }>();
97
114
 
98
- const { result } = renderHook(() => useQuery(() => mockQuery, []));
115
+ const { result } = renderHook(() => useQuery(() => mockQuery, []), {
116
+ wrapper: createWrapper(mockClient),
117
+ });
99
118
 
100
119
  expect(result.current.loading).toBe(true);
101
120
  expect(result.current.data).toBe(null);
@@ -103,9 +122,12 @@ describe("useQuery", () => {
103
122
  });
104
123
 
105
124
  test("returns data when query resolves", async () => {
125
+ const mockClient = createMockClient();
106
126
  const mockQuery = createMockQueryResult<{ id: string; name: string }>();
107
127
 
108
- const { result } = renderHook(() => useQuery(() => mockQuery, []));
128
+ const { result } = renderHook(() => useQuery(() => mockQuery, []), {
129
+ wrapper: createWrapper(mockClient),
130
+ });
109
131
 
110
132
  // Simulate data loading
111
133
  act(() => {
@@ -121,9 +143,12 @@ describe("useQuery", () => {
121
143
  });
122
144
 
123
145
  test("returns error when query fails", async () => {
146
+ const mockClient = createMockClient();
124
147
  const mockQuery = createMockQueryResult<{ id: string; name: string }>();
125
148
 
126
- const { result } = renderHook(() => useQuery(() => mockQuery, []));
149
+ const { result } = renderHook(() => useQuery(() => mockQuery, []), {
150
+ wrapper: createWrapper(mockClient),
151
+ });
127
152
 
128
153
  // Simulate error
129
154
  act(() => {
@@ -139,6 +164,7 @@ describe("useQuery", () => {
139
164
  });
140
165
 
141
166
  test("handles non-Error rejection", async () => {
167
+ const mockClient = createMockClient();
142
168
  const mockQuery = {
143
169
  subscribe: () => () => {},
144
170
  then: (onFulfilled: any, onRejected: any) => {
@@ -147,24 +173,36 @@ describe("useQuery", () => {
147
173
  },
148
174
  } as unknown as QueryResult<{ id: string }>;
149
175
 
150
- const { result } = renderHook(() => useQuery(() => mockQuery, []));
176
+ const { result } = renderHook(() => useQuery(() => mockQuery, []), {
177
+ wrapper: createWrapper(mockClient),
178
+ });
151
179
 
152
180
  await waitFor(() => {
153
- expect(result.current.error?.message).toBe("String error");
181
+ expect(result.current.loading).toBe(false);
154
182
  });
183
+
184
+ expect(result.current.error?.message).toBe("String error");
155
185
  });
156
186
 
157
187
  test("skips query when skip option is true", () => {
158
- const mockQuery = createMockQueryResult<{ id: string; name: string }>();
188
+ const mockClient = createMockClient();
189
+ const mockQuery = createMockQueryResult<{ id: string }>();
159
190
 
160
- const { result } = renderHook(() => useQuery(() => mockQuery, [], { skip: true }));
191
+ const { result } = renderHook(() => useQuery(() => mockQuery, [], { skip: true }), {
192
+ wrapper: createWrapper(mockClient),
193
+ });
161
194
 
162
195
  expect(result.current.loading).toBe(false);
163
196
  expect(result.current.data).toBe(null);
197
+ expect(result.current.error).toBe(null);
164
198
  });
165
199
 
166
200
  test("handles null query from accessor", () => {
167
- const { result } = renderHook(() => useQuery(() => null, []));
201
+ const mockClient = createMockClient();
202
+
203
+ const { result } = renderHook(() => useQuery(() => null, []), {
204
+ wrapper: createWrapper(mockClient),
205
+ });
168
206
 
169
207
  expect(result.current.loading).toBe(false);
170
208
  expect(result.current.data).toBe(null);
@@ -172,7 +210,11 @@ describe("useQuery", () => {
172
210
  });
173
211
 
174
212
  test("handles undefined query from accessor", () => {
175
- const { result } = renderHook(() => useQuery(() => undefined, []));
213
+ const mockClient = createMockClient();
214
+
215
+ const { result } = renderHook(() => useQuery(() => undefined, []), {
216
+ wrapper: createWrapper(mockClient),
217
+ });
176
218
 
177
219
  expect(result.current.loading).toBe(false);
178
220
  expect(result.current.data).toBe(null);
@@ -180,156 +222,171 @@ describe("useQuery", () => {
180
222
  });
181
223
 
182
224
  test("updates when query subscription emits", async () => {
183
- const mockQuery = createMockQueryResult<{ id: string; name: string }>();
225
+ const mockClient = createMockClient();
226
+ const mockQuery = createMockQueryResult<{ count: number }>();
184
227
 
185
- const { result } = renderHook(() => useQuery(() => mockQuery, []));
228
+ const { result } = renderHook(() => useQuery(() => mockQuery, []), {
229
+ wrapper: createWrapper(mockClient),
230
+ });
186
231
 
187
- // First value
232
+ // Initial value
188
233
  act(() => {
189
- mockQuery._setValue({ id: "123", name: "John" });
234
+ mockQuery._setValue({ count: 1 });
190
235
  });
191
236
 
192
237
  await waitFor(() => {
193
- expect(result.current.data?.name).toBe("John");
238
+ expect(result.current.data?.count).toBe(1);
194
239
  });
195
240
 
196
241
  // Update value via subscription
197
242
  act(() => {
198
- mockQuery._setValue({ id: "123", name: "Jane" });
243
+ mockQuery._setValue({ count: 2 });
199
244
  });
200
245
 
201
246
  await waitFor(() => {
202
- expect(result.current.data?.name).toBe("Jane");
247
+ expect(result.current.data?.count).toBe(2);
203
248
  });
204
249
  });
205
250
 
206
251
  test("refetch reloads the query", async () => {
207
- const mockQuery = createMockQueryResult<{ id: string; name: string }>();
208
-
209
- const { result } = renderHook(() => useQuery(() => mockQuery, []));
252
+ const mockClient = createMockClient();
253
+ const mockQuery = createMockQueryResult<{ id: string }>({ id: "initial" });
210
254
 
211
- // Initial load
212
- act(() => {
213
- mockQuery._setValue({ id: "123", name: "John" });
255
+ const { result } = renderHook(() => useQuery(() => mockQuery, []), {
256
+ wrapper: createWrapper(mockClient),
214
257
  });
215
258
 
216
259
  await waitFor(() => {
217
- expect(result.current.data?.name).toBe("John");
260
+ expect(result.current.loading).toBe(false);
218
261
  });
219
262
 
220
- // Refetch should trigger loading state and reload
263
+ expect(result.current.data?.id).toBe("initial");
264
+
265
+ // Refetch
221
266
  act(() => {
222
267
  result.current.refetch();
223
268
  });
224
269
 
225
- // Note: In this mock, refetch will resolve with the same data
226
- // In a real scenario, it would trigger a new network request
270
+ expect(result.current.loading).toBe(true);
271
+
227
272
  await waitFor(() => {
228
273
  expect(result.current.loading).toBe(false);
229
274
  });
230
-
231
- expect(result.current.data).toEqual({ id: "123", name: "John" });
232
275
  });
233
276
 
234
277
  test("refetch handles errors", async () => {
235
- const mockQuery = createMockQueryResult<{ id: string; name: string }>();
236
-
237
- const { result } = renderHook(() => useQuery(() => mockQuery, []));
278
+ const mockClient = createMockClient();
279
+ let shouldFail = false;
280
+ const mockQuery = {
281
+ subscribe: () => () => {},
282
+ then: (onFulfilled: any, onRejected: any) => {
283
+ if (shouldFail) {
284
+ return Promise.reject(new Error("Refetch failed")).then(onFulfilled, onRejected);
285
+ }
286
+ return Promise.resolve({ id: "test" }).then(onFulfilled, onRejected);
287
+ },
288
+ } as unknown as QueryResult<{ id: string }>;
238
289
 
239
- // Initial load succeeds
240
- act(() => {
241
- mockQuery._setValue({ id: "123", name: "John" });
290
+ const { result } = renderHook(() => useQuery(() => mockQuery, []), {
291
+ wrapper: createWrapper(mockClient),
242
292
  });
243
293
 
244
294
  await waitFor(() => {
245
- expect(result.current.data?.name).toBe("John");
295
+ expect(result.current.loading).toBe(false);
246
296
  });
247
297
 
248
- // Create a new query that will fail
249
- const failingQuery = {
250
- subscribe: () => () => {},
251
- then: (onFulfilled: any, onRejected: any) => {
252
- return Promise.reject(new Error("Refetch failed")).then(onFulfilled, onRejected);
253
- },
254
- } as unknown as QueryResult<{ id: string; name: string }>;
298
+ shouldFail = true;
255
299
 
256
- // Update the query to use failing query
257
- const { result: result2 } = renderHook(() => useQuery(() => failingQuery, []));
300
+ act(() => {
301
+ result.current.refetch();
302
+ });
258
303
 
259
304
  await waitFor(() => {
260
- expect(result2.current.error?.message).toBe("Refetch failed");
305
+ expect(result.current.loading).toBe(false);
261
306
  });
307
+
308
+ expect(result.current.error?.message).toBe("Refetch failed");
262
309
  });
263
310
 
264
- test("refetch does nothing when query is null", () => {
265
- const { result } = renderHook(() => useQuery(() => null, []));
311
+ test("refetch does nothing when query is null", async () => {
312
+ const mockClient = createMockClient();
266
313
 
314
+ const { result } = renderHook(() => useQuery(() => null, []), {
315
+ wrapper: createWrapper(mockClient),
316
+ });
317
+
318
+ // Should not throw
267
319
  act(() => {
268
320
  result.current.refetch();
269
321
  });
270
322
 
271
323
  expect(result.current.loading).toBe(false);
272
- expect(result.current.data).toBe(null);
273
324
  });
274
325
 
275
- test("refetch does nothing when skip is true", () => {
276
- const mockQuery = createMockQueryResult<{ id: string; name: string }>();
326
+ test("refetch does nothing when skip is true", async () => {
327
+ const mockClient = createMockClient();
328
+ const mockQuery = createMockQueryResult<{ id: string }>();
277
329
 
278
- const { result } = renderHook(() => useQuery(() => mockQuery, [], { skip: true }));
330
+ const { result } = renderHook(() => useQuery(() => mockQuery, [], { skip: true }), {
331
+ wrapper: createWrapper(mockClient),
332
+ });
279
333
 
334
+ // Should not throw
280
335
  act(() => {
281
336
  result.current.refetch();
282
337
  });
283
338
 
284
339
  expect(result.current.loading).toBe(false);
285
- expect(result.current.data).toBe(null);
286
340
  });
287
341
 
288
342
  test("refetch with non-Error rejection", async () => {
289
- let shouldFail = false;
343
+ const mockClient = createMockClient();
344
+ let callCount = 0;
290
345
  const mockQuery = {
291
346
  subscribe: () => () => {},
292
347
  then: (onFulfilled: any, onRejected: any) => {
293
- if (shouldFail) {
294
- return Promise.reject("Refetch string error").then(onFulfilled, onRejected);
348
+ callCount++;
349
+ if (callCount > 1) {
350
+ return Promise.reject("String error on refetch").then(onFulfilled, onRejected);
295
351
  }
296
- return Promise.resolve({ id: "123", name: "John" } as any).then(onFulfilled, onRejected);
352
+ return Promise.resolve({ id: "test" }).then(onFulfilled, onRejected);
297
353
  },
298
- } as unknown as QueryResult<{ id: string; name: string }>;
354
+ } as unknown as QueryResult<{ id: string }>;
299
355
 
300
- const { result } = renderHook(() => useQuery(() => mockQuery, []));
356
+ const { result } = renderHook(() => useQuery(() => mockQuery, []), {
357
+ wrapper: createWrapper(mockClient),
358
+ });
301
359
 
302
360
  await waitFor(() => {
303
- expect(result.current.data).toEqual({ id: "123", name: "John" });
361
+ expect(result.current.loading).toBe(false);
304
362
  });
305
363
 
306
- // Make it fail on refetch
307
- shouldFail = true;
308
-
309
364
  act(() => {
310
365
  result.current.refetch();
311
366
  });
312
367
 
313
368
  await waitFor(() => {
314
- expect(result.current.error?.message).toBe("Refetch string error");
369
+ expect(result.current.loading).toBe(false);
315
370
  });
371
+
372
+ expect(result.current.error?.message).toBe("String error on refetch");
316
373
  });
317
374
 
318
375
  test("cleans up subscription on unmount", async () => {
319
- const mockQuery = createMockQueryResult<{ id: string; name: string }>();
376
+ const mockClient = createMockClient();
320
377
  let unsubscribeCalled = false;
378
+ const mockQuery = {
379
+ subscribe: () => {
380
+ return () => {
381
+ unsubscribeCalled = true;
382
+ };
383
+ },
384
+ then: (onFulfilled: any) => Promise.resolve({ id: "test" }).then(onFulfilled),
385
+ } as unknown as QueryResult<{ id: string }>;
321
386
 
322
- // Override subscribe to track unsubscribe
323
- const originalSubscribe = mockQuery.subscribe;
324
- mockQuery.subscribe = (callback?: (data: { id: string; name: string }) => void) => {
325
- const originalUnsubscribe = originalSubscribe.call(mockQuery, callback);
326
- return () => {
327
- unsubscribeCalled = true;
328
- originalUnsubscribe();
329
- };
330
- };
331
-
332
- const { unmount } = renderHook(() => useQuery(() => mockQuery, []));
387
+ const { unmount } = renderHook(() => useQuery(() => mockQuery, []), {
388
+ wrapper: createWrapper(mockClient),
389
+ });
333
390
 
334
391
  unmount();
335
392
 
@@ -337,136 +394,136 @@ describe("useQuery", () => {
337
394
  });
338
395
 
339
396
  test("does not update state after unmount", async () => {
340
- const mockQuery = createMockQueryResult<{ id: string; name: string }>();
397
+ const mockClient = createMockClient();
398
+ const mockQuery = createMockQueryResult<{ id: string }>();
341
399
 
342
- const { unmount } = renderHook(() => useQuery(() => mockQuery, []));
400
+ const { result, unmount } = renderHook(() => useQuery(() => mockQuery, []), {
401
+ wrapper: createWrapper(mockClient),
402
+ });
343
403
 
344
- // Unmount before query resolves
345
404
  unmount();
346
405
 
347
- // Try to set value after unmount
406
+ // This should not cause errors or state updates
348
407
  act(() => {
349
- mockQuery._setValue({ id: "123", name: "John" });
408
+ mockQuery._setValue({ id: "after-unmount" });
350
409
  });
351
410
 
352
- // State should not be updated (we can't really assert this directly,
353
- // but the test passing without errors shows no state update occurred)
354
- expect(true).toBe(true);
411
+ // Result should still be from before unmount
412
+ expect(result.current.data).toBe(null);
355
413
  });
356
414
 
357
415
  test("handles query change via deps", async () => {
358
- const mockQuery1 = createMockQueryResult<{ id: string; name: string }>();
359
- const mockQuery2 = createMockQueryResult<{ id: string; name: string }>();
416
+ const mockClient = createMockClient();
417
+ const mockQuery1 = createMockQueryResult<{ id: string }>({ id: "query1" });
418
+ const mockQuery2 = createMockQueryResult<{ id: string }>({ id: "query2" });
360
419
 
361
- let queryId = 1;
362
- const { result, rerender } = renderHook(() => useQuery(() => (queryId === 1 ? mockQuery1 : mockQuery2), [queryId]));
363
-
364
- // Load first query
365
- act(() => {
366
- mockQuery1._setValue({ id: "1", name: "First" });
420
+ let useQuery1 = true;
421
+ const { result, rerender } = renderHook(() => useQuery(() => (useQuery1 ? mockQuery1 : mockQuery2), [useQuery1]), {
422
+ wrapper: createWrapper(mockClient),
367
423
  });
368
424
 
369
425
  await waitFor(() => {
370
- expect(result.current.data?.name).toBe("First");
426
+ expect(result.current.data?.id).toBe("query1");
371
427
  });
372
428
 
373
- // Change to second query
374
- queryId = 2;
429
+ // Change to query2
430
+ useQuery1 = false;
375
431
  rerender();
376
432
 
377
- expect(result.current.loading).toBe(true);
378
-
379
- // Load second query
380
- act(() => {
381
- mockQuery2._setValue({ id: "2", name: "Second" });
382
- });
383
-
384
433
  await waitFor(() => {
385
- expect(result.current.data?.name).toBe("Second");
434
+ expect(result.current.data?.id).toBe("query2");
386
435
  });
387
436
  });
388
437
 
389
438
  test("handles skip option change from true to false", async () => {
390
- const mockQuery = createMockQueryResult<{ id: string; name: string }>();
439
+ const mockClient = createMockClient();
440
+ const mockQuery = createMockQueryResult<{ id: string }>({ id: "test" });
391
441
 
392
442
  let skip = true;
393
- const { result, rerender } = renderHook(() => useQuery(() => mockQuery, [], { skip }));
443
+ const { result, rerender } = renderHook(() => useQuery(() => mockQuery, [], { skip }), {
444
+ wrapper: createWrapper(mockClient),
445
+ });
394
446
 
395
447
  expect(result.current.loading).toBe(false);
448
+ expect(result.current.data).toBe(null);
396
449
 
397
- // Change skip to false
450
+ // Enable query
398
451
  skip = false;
399
452
  rerender();
400
453
 
401
- expect(result.current.loading).toBe(true);
402
-
403
- act(() => {
404
- mockQuery._setValue({ id: "123", name: "John" });
405
- });
406
-
407
454
  await waitFor(() => {
408
- expect(result.current.data).toEqual({ id: "123", name: "John" });
455
+ expect(result.current.data?.id).toBe("test");
409
456
  });
410
457
  });
411
458
 
412
459
  test("handles skip option change from false to true", async () => {
413
- const mockQuery = createMockQueryResult<{ id: string; name: string }>();
460
+ const mockClient = createMockClient();
461
+ const mockQuery = createMockQueryResult<{ id: string }>({ id: "test" });
414
462
 
415
463
  let skip = false;
416
- const { result, rerender } = renderHook(() => useQuery(() => mockQuery, [], { skip }));
417
-
418
- act(() => {
419
- mockQuery._setValue({ id: "123", name: "John" });
464
+ const { result, rerender } = renderHook(() => useQuery(() => mockQuery, [], { skip }), {
465
+ wrapper: createWrapper(mockClient),
420
466
  });
421
467
 
422
468
  await waitFor(() => {
423
- expect(result.current.data).toEqual({ id: "123", name: "John" });
469
+ expect(result.current.data?.id).toBe("test");
424
470
  });
425
471
 
426
- // Change skip to true
472
+ // Disable query
427
473
  skip = true;
428
474
  rerender();
429
475
 
476
+ await waitFor(() => {
477
+ expect(result.current.data).toBe(null);
478
+ });
479
+
430
480
  expect(result.current.loading).toBe(false);
431
- expect(result.current.data).toBe(null);
432
- expect(result.current.error).toBe(null);
433
481
  });
434
482
 
435
483
  test("select transforms the data", async () => {
436
- const mockQuery = createMockQueryResult<{ id: string; name: string }>();
484
+ const mockClient = createMockClient();
485
+ const mockQuery = createMockQueryResult<{ id: string; name: string }>({
486
+ id: "123",
487
+ name: "John",
488
+ });
437
489
 
438
- const { result } = renderHook(() =>
439
- useQuery(() => mockQuery, [], {
440
- select: (data) => data.name.toUpperCase(),
441
- }),
490
+ const { result } = renderHook(
491
+ () =>
492
+ useQuery(() => mockQuery, [], {
493
+ select: (data) => data.name.toUpperCase(),
494
+ }),
495
+ { wrapper: createWrapper(mockClient) },
442
496
  );
443
497
 
444
- act(() => {
445
- mockQuery._setValue({ id: "123", name: "John" });
446
- });
447
-
448
498
  await waitFor(() => {
449
- expect(result.current.data).toBe("JOHN");
499
+ expect(result.current.loading).toBe(false);
450
500
  });
501
+
502
+ expect(result.current.data).toBe("JOHN");
451
503
  });
452
504
 
453
505
  test("Route + Params pattern works", async () => {
454
- const mockQuery = createMockQueryResult<{ id: string; name: string }>();
506
+ const mockClient = createMockClient();
507
+ const mockQuery = createMockQueryResult<{ id: string }>({ id: "user-123" });
455
508
  const route = (_params: { id: string }) => mockQuery;
456
509
 
457
- const { result } = renderHook(() => useQuery(route, { id: "123" }));
458
-
459
- act(() => {
460
- mockQuery._setValue({ id: "123", name: "John" });
510
+ const { result } = renderHook(() => useQuery(() => route, { id: "123" }), {
511
+ wrapper: createWrapper(mockClient),
461
512
  });
462
513
 
463
514
  await waitFor(() => {
464
- expect(result.current.data).toEqual({ id: "123", name: "John" });
515
+ expect(result.current.loading).toBe(false);
465
516
  });
517
+
518
+ expect(result.current.data?.id).toBe("user-123");
466
519
  });
467
520
 
468
- test("Route + Params with null route", () => {
469
- const { result } = renderHook(() => useQuery(null, { id: "123" }));
521
+ test("Route + Params with null route", async () => {
522
+ const mockClient = createMockClient();
523
+
524
+ const { result } = renderHook(() => useQuery(() => null, { id: "123" }), {
525
+ wrapper: createWrapper(mockClient),
526
+ });
470
527
 
471
528
  expect(result.current.loading).toBe(false);
472
529
  expect(result.current.data).toBe(null);
@@ -479,39 +536,40 @@ describe("useQuery", () => {
479
536
 
480
537
  describe("useMutation", () => {
481
538
  test("executes mutation and returns result", async () => {
482
- const mutationFn = async (input: { name: string }): Promise<MutationResult<{ id: string; name: string }>> => {
483
- return {
484
- data: { id: "new-id", name: input.name },
485
- };
539
+ const mockClient = createMockClient();
540
+ const mockMutation = async (_input: { title: string }): Promise<MutationResult<{ id: string }>> => {
541
+ return { data: { id: "new-123" } };
486
542
  };
487
543
 
488
- const { result } = renderHook(() => useMutation(mutationFn));
489
-
490
- expect(result.current.loading).toBe(false);
491
- expect(result.current.data).toBe(null);
544
+ const { result } = renderHook(() => useMutation(() => mockMutation), {
545
+ wrapper: createWrapper(mockClient),
546
+ });
492
547
 
493
- let mutationResult: MutationResult<{ id: string; name: string }> | undefined;
548
+ let mutationResult: MutationResult<{ id: string }> | undefined;
494
549
  await act(async () => {
495
- mutationResult = await result.current.mutate({ name: "New User" });
550
+ mutationResult = await result.current.mutate({ title: "Test" });
496
551
  });
497
552
 
498
- expect(mutationResult?.data).toEqual({ id: "new-id", name: "New User" });
499
- expect(result.current.data).toEqual({ id: "new-id", name: "New User" });
553
+ expect(mutationResult?.data?.id).toBe("new-123");
554
+ expect(result.current.data?.id).toBe("new-123");
500
555
  expect(result.current.loading).toBe(false);
501
556
  });
502
557
 
503
558
  test("handles mutation error", async () => {
504
- const mutationFn = async (_input: { name: string }): Promise<MutationResult<{ id: string; name: string }>> => {
559
+ const mockClient = createMockClient();
560
+ const mockMutation = async (): Promise<MutationResult<{ id: string }>> => {
505
561
  throw new Error("Mutation failed");
506
562
  };
507
563
 
508
- const { result } = renderHook(() => useMutation(mutationFn));
564
+ const { result } = renderHook(() => useMutation(() => mockMutation), {
565
+ wrapper: createWrapper(mockClient),
566
+ });
509
567
 
510
568
  await act(async () => {
511
569
  try {
512
- await result.current.mutate({ name: "New User" });
570
+ await result.current.mutate({ title: "Test" });
513
571
  } catch {
514
- // Expected error
572
+ // Expected
515
573
  }
516
574
  });
517
575
 
@@ -520,46 +578,49 @@ describe("useMutation", () => {
520
578
  });
521
579
 
522
580
  test("handles non-Error exception in mutation", async () => {
523
- const mutationFn = async (_input: { name: string }): Promise<MutationResult<{ id: string; name: string }>> => {
581
+ const mockClient = createMockClient();
582
+ const mockMutation = async (): Promise<MutationResult<{ id: string }>> => {
524
583
  throw "String error";
525
584
  };
526
585
 
527
- const { result } = renderHook(() => useMutation(mutationFn));
586
+ const { result } = renderHook(() => useMutation(() => mockMutation), {
587
+ wrapper: createWrapper(mockClient),
588
+ });
528
589
 
529
590
  await act(async () => {
530
591
  try {
531
- await result.current.mutate({ name: "New User" });
592
+ await result.current.mutate({ title: "Test" });
532
593
  } catch {
533
- // Expected error
594
+ // Expected
534
595
  }
535
596
  });
536
597
 
537
598
  expect(result.current.error?.message).toBe("String error");
538
- expect(result.current.loading).toBe(false);
539
599
  });
540
600
 
541
601
  test("shows loading state during mutation", async () => {
542
- let resolveMutation: ((value: MutationResult<{ id: string }>) => void) | null = null;
543
- const mutationFn = async (_input: { name: string }): Promise<MutationResult<{ id: string }>> => {
544
- return new Promise((resolve) => {
545
- resolveMutation = resolve;
602
+ const mockClient = createMockClient();
603
+ let resolvePromise: () => void;
604
+ const mockMutation = async (): Promise<MutationResult<{ id: string }>> => {
605
+ await new Promise<void>((resolve) => {
606
+ resolvePromise = resolve;
546
607
  });
608
+ return { data: { id: "test" } };
547
609
  };
548
610
 
549
- const { result } = renderHook(() => useMutation(mutationFn));
611
+ const { result } = renderHook(() => useMutation(() => mockMutation), {
612
+ wrapper: createWrapper(mockClient),
613
+ });
550
614
 
551
- // Start mutation (don't await)
552
- let mutationPromise: Promise<MutationResult<{ id: string }>> | undefined;
615
+ let mutationPromise: Promise<any>;
553
616
  act(() => {
554
- mutationPromise = result.current.mutate({ name: "New User" });
617
+ mutationPromise = result.current.mutate({ title: "Test" });
555
618
  });
556
619
 
557
- // Should be loading
558
620
  expect(result.current.loading).toBe(true);
559
621
 
560
- // Resolve mutation
561
622
  await act(async () => {
562
- resolveMutation?.({ data: { id: "new-id" } });
623
+ resolvePromise!();
563
624
  await mutationPromise;
564
625
  });
565
626
 
@@ -567,14 +628,17 @@ describe("useMutation", () => {
567
628
  });
568
629
 
569
630
  test("reset clears mutation state", async () => {
570
- const mutationFn = async (input: { name: string }): Promise<MutationResult<{ id: string; name: string }>> => {
571
- return { data: { id: "new-id", name: input.name } };
631
+ const mockClient = createMockClient();
632
+ const mockMutation = async (): Promise<MutationResult<{ id: string }>> => {
633
+ return { data: { id: "test" } };
572
634
  };
573
635
 
574
- const { result } = renderHook(() => useMutation(mutationFn));
636
+ const { result } = renderHook(() => useMutation(() => mockMutation), {
637
+ wrapper: createWrapper(mockClient),
638
+ });
575
639
 
576
640
  await act(async () => {
577
- await result.current.mutate({ name: "New User" });
641
+ await result.current.mutate({ title: "Test" });
578
642
  });
579
643
 
580
644
  expect(result.current.data).not.toBe(null);
@@ -589,128 +653,142 @@ describe("useMutation", () => {
589
653
  });
590
654
 
591
655
  test("handles multiple mutations in sequence", async () => {
592
- let counter = 0;
593
- const mutationFn = async (input: { name: string }): Promise<MutationResult<{ id: string; name: string }>> => {
594
- counter++;
595
- return { data: { id: `id-${counter}`, name: input.name } };
656
+ const mockClient = createMockClient();
657
+ let callCount = 0;
658
+ const mockMutation = async (_input: { title: string }): Promise<MutationResult<{ count: number }>> => {
659
+ callCount++;
660
+ return { data: { count: callCount } };
596
661
  };
597
662
 
598
- const { result } = renderHook(() => useMutation(mutationFn));
663
+ const { result } = renderHook(() => useMutation(() => mockMutation), {
664
+ wrapper: createWrapper(mockClient),
665
+ });
599
666
 
600
- // First mutation
601
667
  await act(async () => {
602
- await result.current.mutate({ name: "First" });
668
+ await result.current.mutate({ title: "First" });
603
669
  });
670
+ expect(result.current.data?.count).toBe(1);
604
671
 
605
- expect(result.current.data).toEqual({ id: "id-1", name: "First" });
606
-
607
- // Second mutation
608
672
  await act(async () => {
609
- await result.current.mutate({ name: "Second" });
673
+ await result.current.mutate({ title: "Second" });
610
674
  });
611
-
612
- expect(result.current.data).toEqual({ id: "id-2", name: "Second" });
675
+ expect(result.current.data?.count).toBe(2);
613
676
  });
614
677
 
615
678
  test("clears error on successful mutation after previous error", async () => {
679
+ const mockClient = createMockClient();
616
680
  let shouldFail = true;
617
- const mutationFn = async (input: { name: string }): Promise<MutationResult<{ id: string; name: string }>> => {
681
+ const mockMutation = async (): Promise<MutationResult<{ id: string }>> => {
618
682
  if (shouldFail) {
619
- throw new Error("Mutation failed");
683
+ throw new Error("Failed");
620
684
  }
621
- return { data: { id: "new-id", name: input.name } };
685
+ return { data: { id: "success" } };
622
686
  };
623
687
 
624
- const { result } = renderHook(() => useMutation(mutationFn));
688
+ const { result } = renderHook(() => useMutation(() => mockMutation), {
689
+ wrapper: createWrapper(mockClient),
690
+ });
625
691
 
626
692
  // First mutation fails
627
693
  await act(async () => {
628
694
  try {
629
- await result.current.mutate({ name: "New User" });
695
+ await result.current.mutate({ title: "Test" });
630
696
  } catch {
631
- // Expected error
697
+ // Expected
632
698
  }
633
699
  });
634
700
 
635
- expect(result.current.error?.message).toBe("Mutation failed");
701
+ expect(result.current.error).not.toBe(null);
636
702
 
637
703
  // Second mutation succeeds
638
704
  shouldFail = false;
639
705
  await act(async () => {
640
- await result.current.mutate({ name: "New User" });
706
+ await result.current.mutate({ title: "Test" });
641
707
  });
642
708
 
643
709
  expect(result.current.error).toBe(null);
644
- expect(result.current.data).toEqual({ id: "new-id", name: "New User" });
710
+ expect(result.current.data?.id).toBe("success");
645
711
  });
646
712
 
647
713
  test("does not update state after unmount", async () => {
648
- const mutationFn = async (input: { name: string }): Promise<MutationResult<{ id: string; name: string }>> => {
649
- return { data: { id: "new-id", name: input.name } };
714
+ const mockClient = createMockClient();
715
+ let resolvePromise: () => void;
716
+ const mockMutation = async (): Promise<MutationResult<{ id: string }>> => {
717
+ await new Promise<void>((resolve) => {
718
+ resolvePromise = resolve;
719
+ });
720
+ return { data: { id: "test" } };
650
721
  };
651
722
 
652
- const { result, unmount } = renderHook(() => useMutation(mutationFn));
723
+ const { result, unmount } = renderHook(() => useMutation(() => mockMutation), {
724
+ wrapper: createWrapper(mockClient),
725
+ });
653
726
 
654
- // Start mutation but unmount before it completes
655
- const mutationPromise = result.current.mutate({ name: "New User" });
656
- unmount();
727
+ let mutationPromise: Promise<any>;
728
+ act(() => {
729
+ mutationPromise = result.current.mutate({ title: "Test" });
730
+ });
657
731
 
658
- // Wait for mutation to complete
659
- await mutationPromise;
732
+ unmount();
660
733
 
661
- // Test passes if no error is thrown (state update after unmount would cause error)
662
- expect(true).toBe(true);
734
+ // Resolve after unmount - should not cause errors
735
+ await act(async () => {
736
+ resolvePromise!();
737
+ await mutationPromise;
738
+ });
663
739
  });
664
740
  });
665
741
 
666
742
  // =============================================================================
667
- // Tests: useLazyQuery (Accessor + Deps pattern)
743
+ // Tests: useLazyQuery
668
744
  // =============================================================================
669
745
 
670
746
  describe("useLazyQuery", () => {
671
747
  test("does not execute query on mount", () => {
672
- const mockQuery = createMockQueryResult<{ id: string; name: string }>();
748
+ const mockClient = createMockClient();
749
+ const mockQuery = createMockQueryResult<{ id: string }>();
673
750
 
674
- const { result } = renderHook(() => useLazyQuery(() => mockQuery, []));
751
+ const { result } = renderHook(() => useLazyQuery(() => mockQuery, []), {
752
+ wrapper: createWrapper(mockClient),
753
+ });
675
754
 
676
755
  expect(result.current.loading).toBe(false);
677
756
  expect(result.current.data).toBe(null);
678
757
  });
679
758
 
680
759
  test("executes query when execute is called", async () => {
681
- const mockQuery = createMockQueryResult<{ id: string; name: string }>({
682
- id: "123",
683
- name: "John",
684
- });
760
+ const mockClient = createMockClient();
761
+ const mockQuery = createMockQueryResult<{ id: string }>({ id: "lazy-123" });
685
762
 
686
- const { result } = renderHook(() => useLazyQuery(() => mockQuery, []));
763
+ const { result } = renderHook(() => useLazyQuery(() => mockQuery, []), {
764
+ wrapper: createWrapper(mockClient),
765
+ });
687
766
 
688
- let queryResult: { id: string; name: string } | undefined;
689
767
  await act(async () => {
690
- queryResult = await result.current.execute();
768
+ await result.current.execute();
691
769
  });
692
770
 
693
- expect(queryResult).toEqual({ id: "123", name: "John" });
694
- expect(result.current.data).toEqual({ id: "123", name: "John" });
771
+ expect(result.current.data?.id).toBe("lazy-123");
772
+ expect(result.current.loading).toBe(false);
695
773
  });
696
774
 
697
775
  test("handles query error", async () => {
698
- // Create a mock query that rejects
699
- const mockQuery = createMockQueryResult<{ id: string; name: string }>();
700
-
701
- const { result } = renderHook(() => useLazyQuery(() => mockQuery, []));
776
+ const mockClient = createMockClient();
777
+ const mockQuery = {
778
+ then: (_: any, onRejected: any) => {
779
+ return Promise.reject(new Error("Query failed")).then(null, onRejected);
780
+ },
781
+ } as unknown as QueryResult<{ id: string }>;
702
782
 
703
- // Set error before execute
704
- act(() => {
705
- mockQuery._setError(new Error("Query failed"));
783
+ const { result } = renderHook(() => useLazyQuery(() => mockQuery, []), {
784
+ wrapper: createWrapper(mockClient),
706
785
  });
707
786
 
708
- // Execute should throw
709
787
  await act(async () => {
710
788
  try {
711
789
  await result.current.execute();
712
790
  } catch {
713
- // Expected error
791
+ // Expected
714
792
  }
715
793
  });
716
794
 
@@ -718,19 +796,22 @@ describe("useLazyQuery", () => {
718
796
  });
719
797
 
720
798
  test("handles non-Error rejection", async () => {
799
+ const mockClient = createMockClient();
721
800
  const mockQuery = {
722
- then: (onFulfilled: any, onRejected: any) => {
723
- return Promise.reject("String error").then(onFulfilled, onRejected);
801
+ then: (_: any, onRejected: any) => {
802
+ return Promise.reject("String error").then(null, onRejected);
724
803
  },
725
804
  } as unknown as QueryResult<{ id: string }>;
726
805
 
727
- const { result } = renderHook(() => useLazyQuery(() => mockQuery, []));
806
+ const { result } = renderHook(() => useLazyQuery(() => mockQuery, []), {
807
+ wrapper: createWrapper(mockClient),
808
+ });
728
809
 
729
810
  await act(async () => {
730
811
  try {
731
812
  await result.current.execute();
732
813
  } catch {
733
- // Expected error
814
+ // Expected
734
815
  }
735
816
  });
736
817
 
@@ -738,12 +819,12 @@ describe("useLazyQuery", () => {
738
819
  });
739
820
 
740
821
  test("reset clears query state", async () => {
741
- const mockQuery = createMockQueryResult<{ id: string; name: string }>({
742
- id: "123",
743
- name: "John",
744
- });
822
+ const mockClient = createMockClient();
823
+ const mockQuery = createMockQueryResult<{ id: string }>({ id: "test" });
745
824
 
746
- const { result } = renderHook(() => useLazyQuery(() => mockQuery, []));
825
+ const { result } = renderHook(() => useLazyQuery(() => mockQuery, []), {
826
+ wrapper: createWrapper(mockClient),
827
+ });
747
828
 
748
829
  await act(async () => {
749
830
  await result.current.execute();
@@ -756,165 +837,195 @@ describe("useLazyQuery", () => {
756
837
  });
757
838
 
758
839
  expect(result.current.data).toBe(null);
759
- expect(result.current.error).toBe(null);
760
- expect(result.current.loading).toBe(false);
761
840
  });
762
841
 
763
842
  test("handles null query from accessor", async () => {
764
- const { result } = renderHook(() => useLazyQuery(() => null, []));
843
+ const mockClient = createMockClient();
765
844
 
766
- let queryResult: any;
845
+ const { result } = renderHook(() => useLazyQuery(() => null, []), {
846
+ wrapper: createWrapper(mockClient),
847
+ });
848
+
849
+ let executeResult: any;
767
850
  await act(async () => {
768
- queryResult = await result.current.execute();
851
+ executeResult = await result.current.execute();
769
852
  });
770
853
 
771
- expect(queryResult).toBe(null);
854
+ expect(executeResult).toBe(null);
772
855
  expect(result.current.data).toBe(null);
773
- expect(result.current.loading).toBe(false);
774
856
  });
775
857
 
776
858
  test("handles undefined query from accessor", async () => {
777
- const { result } = renderHook(() => useLazyQuery(() => undefined, []));
859
+ const mockClient = createMockClient();
860
+
861
+ const { result } = renderHook(() => useLazyQuery(() => undefined, []), {
862
+ wrapper: createWrapper(mockClient),
863
+ });
778
864
 
779
- let queryResult: any;
865
+ let executeResult: any;
780
866
  await act(async () => {
781
- queryResult = await result.current.execute();
867
+ executeResult = await result.current.execute();
782
868
  });
783
869
 
784
- expect(queryResult).toBe(null);
870
+ expect(executeResult).toBe(null);
785
871
  expect(result.current.data).toBe(null);
786
- expect(result.current.loading).toBe(false);
787
872
  });
788
873
 
789
874
  test("uses latest query value from accessor on execute", async () => {
790
- let currentValue = "first";
791
- const mockQuery1 = createMockQueryResult<string>("first");
792
- const mockQuery2 = createMockQueryResult<string>("second");
793
-
794
- const accessor = () => (currentValue === "first" ? mockQuery1 : mockQuery2);
875
+ const mockClient = createMockClient();
876
+ const mockQuery1 = createMockQueryResult<{ id: string }>({ id: "query1" });
877
+ const mockQuery2 = createMockQueryResult<{ id: string }>({ id: "query2" });
878
+
879
+ let useQuery1 = true;
880
+ const { result, rerender } = renderHook(
881
+ () => useLazyQuery(() => (useQuery1 ? mockQuery1 : mockQuery2), [useQuery1]),
882
+ { wrapper: createWrapper(mockClient) },
883
+ );
795
884
 
796
- const { result } = renderHook(() => useLazyQuery(accessor, []));
885
+ // Change to query2 before executing
886
+ useQuery1 = false;
887
+ rerender();
797
888
 
798
- // First execute
799
- let queryResult1: string | undefined;
800
889
  await act(async () => {
801
- queryResult1 = await result.current.execute();
890
+ await result.current.execute();
802
891
  });
803
892
 
804
- expect(queryResult1).toBe("first");
805
-
806
- // Change accessor to return different query
807
- currentValue = "second";
893
+ expect(result.current.data?.id).toBe("query2");
894
+ });
808
895
 
809
- // Second execute should use new query
810
- let queryResult2: string | undefined;
811
- await act(async () => {
812
- queryResult2 = await result.current.execute();
896
+ test("shows loading state during execution", async () => {
897
+ const mockClient = createMockClient();
898
+ // Create the pending promise upfront so resolvePromise is assigned immediately
899
+ let resolvePromise!: (value: { id: string }) => void;
900
+ const pendingPromise = new Promise<{ id: string }>((resolve) => {
901
+ resolvePromise = resolve;
813
902
  });
903
+ const mockQuery = {
904
+ then: (onFulfilled: any, onRejected?: any) => {
905
+ return pendingPromise.then(onFulfilled, onRejected);
906
+ },
907
+ } as unknown as QueryResult<{ id: string }>;
814
908
 
815
- expect(queryResult2).toBe("second");
816
- });
909
+ const { result } = renderHook(() => useLazyQuery(() => mockQuery, []), {
910
+ wrapper: createWrapper(mockClient),
911
+ });
817
912
 
818
- test("shows loading state during execution", async () => {
819
- const mockQuery = createMockQueryResult<{ id: string }>();
913
+ let executePromise: Promise<any>;
914
+ act(() => {
915
+ executePromise = result.current.execute();
916
+ });
820
917
 
821
- const { result } = renderHook(() => useLazyQuery(() => mockQuery, []));
918
+ expect(result.current.loading).toBe(true);
822
919
 
823
- // Execute and set value
824
- let executePromise: Promise<{ id: string }>;
825
920
  await act(async () => {
826
- executePromise = result.current.execute();
827
- mockQuery._setValue({ id: "123" });
921
+ resolvePromise({ id: "test" });
828
922
  await executePromise;
829
923
  });
830
924
 
831
925
  expect(result.current.loading).toBe(false);
832
- expect(result.current.data).toEqual({ id: "123" });
833
926
  });
834
927
 
835
928
  test("does not update state after unmount", async () => {
836
- const mockQuery = createMockQueryResult<{ id: string; name: string }>();
929
+ const mockClient = createMockClient();
930
+ // Create the pending promise upfront so resolvePromise is assigned immediately
931
+ let resolvePromise!: (value: { id: string }) => void;
932
+ const pendingPromise = new Promise<{ id: string }>((resolve) => {
933
+ resolvePromise = resolve;
934
+ });
935
+ const mockQuery = {
936
+ then: (onFulfilled: any, onRejected?: any) => {
937
+ return pendingPromise.then(onFulfilled, onRejected);
938
+ },
939
+ } as unknown as QueryResult<{ id: string }>;
837
940
 
838
- const { result, unmount } = renderHook(() => useLazyQuery(() => mockQuery, []));
941
+ const { result, unmount } = renderHook(() => useLazyQuery(() => mockQuery, []), {
942
+ wrapper: createWrapper(mockClient),
943
+ });
944
+
945
+ let executePromise: Promise<any>;
946
+ act(() => {
947
+ executePromise = result.current.execute();
948
+ });
839
949
 
840
- // Start execution, unmount, then resolve
841
- const executePromise = result.current.execute();
842
950
  unmount();
843
951
 
844
- // Resolve after unmount
952
+ // Resolve after unmount - should not cause errors
845
953
  await act(async () => {
846
- mockQuery._setValue({ id: "123", name: "John" });
847
- try {
848
- await executePromise;
849
- } catch {
850
- // May reject due to unmount
851
- }
954
+ resolvePromise({ id: "test" });
955
+ await executePromise;
852
956
  });
853
-
854
- // Test passes if no error is thrown (state update after unmount would cause error)
855
- expect(true).toBe(true);
856
957
  });
857
958
 
858
959
  test("can execute multiple times", async () => {
859
- let count = 0;
860
- const createQuery = () => {
861
- count++;
862
- return createMockQueryResult<{ count: number }>({ count });
863
- };
960
+ const mockClient = createMockClient();
961
+ let callCount = 0;
962
+ const mockQuery = {
963
+ then: (onFulfilled: any) => {
964
+ callCount++;
965
+ return Promise.resolve({ count: callCount }).then(onFulfilled);
966
+ },
967
+ } as unknown as QueryResult<{ count: number }>;
864
968
 
865
- const { result } = renderHook(() => useLazyQuery(() => createQuery(), []));
969
+ const { result } = renderHook(() => useLazyQuery(() => mockQuery, []), {
970
+ wrapper: createWrapper(mockClient),
971
+ });
866
972
 
867
- // First execution
868
973
  await act(async () => {
869
974
  await result.current.execute();
870
975
  });
871
-
872
976
  expect(result.current.data?.count).toBe(1);
873
977
 
874
- // Second execution
875
978
  await act(async () => {
876
979
  await result.current.execute();
877
980
  });
878
-
879
981
  expect(result.current.data?.count).toBe(2);
880
982
  });
881
983
 
882
984
  test("Route + Params pattern works", async () => {
883
- const mockQuery = createMockQueryResult<{ id: string; name: string }>({
884
- id: "123",
885
- name: "John",
886
- });
985
+ const mockClient = createMockClient();
986
+ const mockQuery = createMockQueryResult<{ id: string }>({ id: "user-123" });
887
987
  const route = (_params: { id: string }) => mockQuery;
888
988
 
889
- const { result } = renderHook(() => useLazyQuery(route, { id: "123" }));
989
+ const { result } = renderHook(() => useLazyQuery(() => route, { id: "123" }), {
990
+ wrapper: createWrapper(mockClient),
991
+ });
890
992
 
891
993
  await act(async () => {
892
994
  await result.current.execute();
893
995
  });
894
996
 
895
- expect(result.current.data).toEqual({ id: "123", name: "John" });
997
+ expect(result.current.data?.id).toBe("user-123");
896
998
  });
897
999
 
898
1000
  test("Route + Params with null route", async () => {
899
- const { result } = renderHook(() => useLazyQuery(null, { id: "123" }));
1001
+ const mockClient = createMockClient();
900
1002
 
1003
+ const { result } = renderHook(() => useLazyQuery(() => null, { id: "123" }), {
1004
+ wrapper: createWrapper(mockClient),
1005
+ });
1006
+
1007
+ let executeResult: any;
901
1008
  await act(async () => {
902
- await result.current.execute();
1009
+ executeResult = await result.current.execute();
903
1010
  });
904
1011
 
1012
+ expect(executeResult).toBe(null);
905
1013
  expect(result.current.data).toBe(null);
906
1014
  });
907
1015
 
908
1016
  test("select transforms the data", async () => {
1017
+ const mockClient = createMockClient();
909
1018
  const mockQuery = createMockQueryResult<{ id: string; name: string }>({
910
1019
  id: "123",
911
1020
  name: "John",
912
1021
  });
913
1022
 
914
- const { result } = renderHook(() =>
915
- useLazyQuery(() => mockQuery, [], {
916
- select: (data) => data.name.toUpperCase(),
917
- }),
1023
+ const { result } = renderHook(
1024
+ () =>
1025
+ useLazyQuery(() => mockQuery, [], {
1026
+ select: (data) => data.name.toUpperCase(),
1027
+ }),
1028
+ { wrapper: createWrapper(mockClient) },
918
1029
  );
919
1030
 
920
1031
  await act(async () => {