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