@tanstack/query-core 5.59.17 → 5.59.20

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,1069 +0,0 @@
1
- import { describe, expect, test, vi } from 'vitest'
2
- import { waitFor } from '@testing-library/react'
3
- import { QueryCache } from '../queryCache'
4
- import { dehydrate, hydrate } from '../hydration'
5
- import { MutationCache } from '../mutationCache'
6
- import {
7
- createQueryClient,
8
- executeMutation,
9
- mockOnlineManagerIsOnline,
10
- sleep,
11
- } from './utils'
12
-
13
- async function fetchData<TData>(value: TData, ms?: number): Promise<TData> {
14
- await sleep(ms || 0)
15
- return value
16
- }
17
-
18
- async function fetchDate(value: string, ms?: number): Promise<Date> {
19
- await sleep(ms || 0)
20
- return new Date(value)
21
- }
22
-
23
- describe('dehydration and rehydration', () => {
24
- test('should work with serializable values', async () => {
25
- const queryCache = new QueryCache()
26
- const queryClient = createQueryClient({ queryCache })
27
- await queryClient.prefetchQuery({
28
- queryKey: ['string'],
29
- queryFn: () => fetchData('string'),
30
- })
31
- await queryClient.prefetchQuery({
32
- queryKey: ['number'],
33
- queryFn: () => fetchData(1),
34
- })
35
- await queryClient.prefetchQuery({
36
- queryKey: ['boolean'],
37
- queryFn: () => fetchData(true),
38
- })
39
- await queryClient.prefetchQuery({
40
- queryKey: ['null'],
41
- queryFn: () => fetchData(null),
42
- })
43
- await queryClient.prefetchQuery({
44
- queryKey: ['array'],
45
- queryFn: () => fetchData(['string', 0]),
46
- })
47
- await queryClient.prefetchQuery({
48
- queryKey: ['nested'],
49
- queryFn: () => fetchData({ key: [{ nestedKey: 1 }] }),
50
- })
51
- const dehydrated = dehydrate(queryClient)
52
- const stringified = JSON.stringify(dehydrated)
53
-
54
- // ---
55
-
56
- const parsed = JSON.parse(stringified)
57
- const hydrationCache = new QueryCache()
58
- const hydrationClient = createQueryClient({
59
- queryCache: hydrationCache,
60
- })
61
- hydrate(hydrationClient, parsed)
62
- expect(hydrationCache.find({ queryKey: ['string'] })?.state.data).toBe(
63
- 'string',
64
- )
65
- expect(hydrationCache.find({ queryKey: ['number'] })?.state.data).toBe(1)
66
- expect(hydrationCache.find({ queryKey: ['boolean'] })?.state.data).toBe(
67
- true,
68
- )
69
- expect(hydrationCache.find({ queryKey: ['null'] })?.state.data).toBe(null)
70
- expect(hydrationCache.find({ queryKey: ['array'] })?.state.data).toEqual([
71
- 'string',
72
- 0,
73
- ])
74
- expect(hydrationCache.find({ queryKey: ['nested'] })?.state.data).toEqual({
75
- key: [{ nestedKey: 1 }],
76
- })
77
-
78
- const fetchDataAfterHydration =
79
- vi.fn<(...args: Array<unknown>) => unknown>()
80
- await hydrationClient.prefetchQuery({
81
- queryKey: ['string'],
82
- queryFn: fetchDataAfterHydration,
83
- staleTime: 1000,
84
- })
85
- await hydrationClient.prefetchQuery({
86
- queryKey: ['number'],
87
- queryFn: fetchDataAfterHydration,
88
- staleTime: 1000,
89
- })
90
- await hydrationClient.prefetchQuery({
91
- queryKey: ['boolean'],
92
- queryFn: fetchDataAfterHydration,
93
- staleTime: 1000,
94
- })
95
- await hydrationClient.prefetchQuery({
96
- queryKey: ['null'],
97
- queryFn: fetchDataAfterHydration,
98
- staleTime: 1000,
99
- })
100
- await hydrationClient.prefetchQuery({
101
- queryKey: ['array'],
102
- queryFn: fetchDataAfterHydration,
103
- staleTime: 1000,
104
- })
105
- await hydrationClient.prefetchQuery({
106
- queryKey: ['nested'],
107
- queryFn: fetchDataAfterHydration,
108
- staleTime: 1000,
109
- })
110
- expect(fetchDataAfterHydration).toHaveBeenCalledTimes(0)
111
-
112
- queryClient.clear()
113
- hydrationClient.clear()
114
- })
115
-
116
- test('should not dehydrate queries if dehydrateQueries is set to false', async () => {
117
- const queryCache = new QueryCache()
118
- const queryClient = createQueryClient({ queryCache })
119
- await queryClient.prefetchQuery({
120
- queryKey: ['string'],
121
- queryFn: () => fetchData('string'),
122
- })
123
-
124
- const dehydrated = dehydrate(queryClient, {
125
- shouldDehydrateQuery: () => false,
126
- })
127
-
128
- expect(dehydrated.queries.length).toBe(0)
129
-
130
- queryClient.clear()
131
- })
132
-
133
- test('should use the garbage collection time from the client', async () => {
134
- const queryCache = new QueryCache()
135
- const queryClient = createQueryClient({ queryCache })
136
- await queryClient.prefetchQuery({
137
- queryKey: ['string'],
138
- queryFn: () => fetchData('string'),
139
- gcTime: 50,
140
- })
141
- const dehydrated = dehydrate(queryClient)
142
- const stringified = JSON.stringify(dehydrated)
143
-
144
- await sleep(20)
145
-
146
- // ---
147
-
148
- const parsed = JSON.parse(stringified)
149
- const hydrationCache = new QueryCache()
150
- const hydrationClient = createQueryClient({ queryCache: hydrationCache })
151
- hydrate(hydrationClient, parsed)
152
- expect(hydrationCache.find({ queryKey: ['string'] })?.state.data).toBe(
153
- 'string',
154
- )
155
- await sleep(100)
156
- expect(hydrationCache.find({ queryKey: ['string'] })).toBeTruthy()
157
-
158
- queryClient.clear()
159
- hydrationClient.clear()
160
- })
161
-
162
- test('should be able to provide default options for the hydrated queries', async () => {
163
- const queryCache = new QueryCache()
164
- const queryClient = createQueryClient({ queryCache })
165
- await queryClient.prefetchQuery({
166
- queryKey: ['string'],
167
- queryFn: () => fetchData('string'),
168
- })
169
- const dehydrated = dehydrate(queryClient)
170
- const stringified = JSON.stringify(dehydrated)
171
- const parsed = JSON.parse(stringified)
172
- const hydrationCache = new QueryCache()
173
- const hydrationClient = createQueryClient({ queryCache: hydrationCache })
174
- hydrate(hydrationClient, parsed, {
175
- defaultOptions: { queries: { retry: 10 } },
176
- })
177
- expect(hydrationCache.find({ queryKey: ['string'] })?.options.retry).toBe(
178
- 10,
179
- )
180
- queryClient.clear()
181
- hydrationClient.clear()
182
- })
183
-
184
- test('should respect query defaultOptions specified on the QueryClient', async () => {
185
- const queryCache = new QueryCache()
186
- const queryClient = createQueryClient({
187
- queryCache,
188
- defaultOptions: {
189
- dehydrate: { shouldDehydrateQuery: () => true },
190
- },
191
- })
192
- await queryClient.prefetchQuery({
193
- queryKey: ['string'],
194
- retry: 0,
195
- queryFn: () => Promise.reject(new Error('error')),
196
- })
197
- const dehydrated = dehydrate(queryClient)
198
- expect(dehydrated.queries.length).toBe(1)
199
- expect(dehydrated.queries[0]?.state.error).toStrictEqual(new Error('error'))
200
- const stringified = JSON.stringify(dehydrated)
201
- const parsed = JSON.parse(stringified)
202
- const hydrationCache = new QueryCache()
203
- const hydrationClient = createQueryClient({
204
- queryCache: hydrationCache,
205
- defaultOptions: { hydrate: { queries: { retry: 10 } } },
206
- })
207
- hydrate(hydrationClient, parsed, {
208
- defaultOptions: { queries: { gcTime: 10 } },
209
- })
210
- expect(hydrationCache.find({ queryKey: ['string'] })?.options.retry).toBe(
211
- 10,
212
- )
213
- expect(hydrationCache.find({ queryKey: ['string'] })?.options.gcTime).toBe(
214
- 10,
215
- )
216
- queryClient.clear()
217
- hydrationClient.clear()
218
- })
219
-
220
- test('should respect mutation defaultOptions specified on the QueryClient', async () => {
221
- const mutationCache = new MutationCache()
222
- const queryClient = createQueryClient({
223
- mutationCache,
224
- defaultOptions: {
225
- dehydrate: {
226
- shouldDehydrateMutation: (mutation) => mutation.state.data === 'done',
227
- },
228
- },
229
- })
230
- await executeMutation(
231
- queryClient,
232
- {
233
- mutationKey: ['string'],
234
- mutationFn: () => Promise.resolve('done'),
235
- },
236
- undefined,
237
- )
238
-
239
- const dehydrated = dehydrate(queryClient)
240
- expect(dehydrated.mutations.length).toBe(1)
241
- expect(dehydrated.mutations[0]?.state.data).toBe('done')
242
- const stringified = JSON.stringify(dehydrated)
243
- const parsed = JSON.parse(stringified)
244
- const hydrationCache = new MutationCache()
245
- const hydrationClient = createQueryClient({
246
- mutationCache: hydrationCache,
247
- defaultOptions: { hydrate: { mutations: { retry: 10 } } },
248
- })
249
- hydrate(hydrationClient, parsed, {
250
- defaultOptions: { mutations: { gcTime: 10 } },
251
- })
252
- expect(
253
- hydrationCache.find({ mutationKey: ['string'] })?.options.retry,
254
- ).toBe(10)
255
- expect(
256
- hydrationCache.find({ mutationKey: ['string'] })?.options.gcTime,
257
- ).toBe(10)
258
- queryClient.clear()
259
- hydrationClient.clear()
260
- })
261
-
262
- test('should work with complex keys', async () => {
263
- const queryCache = new QueryCache()
264
- const queryClient = createQueryClient({ queryCache })
265
- await queryClient.prefetchQuery({
266
- queryKey: ['string', { key: ['string'], key2: 0 }],
267
- queryFn: () => fetchData('string'),
268
- })
269
- const dehydrated = dehydrate(queryClient)
270
- const stringified = JSON.stringify(dehydrated)
271
-
272
- // ---
273
-
274
- const parsed = JSON.parse(stringified)
275
- const hydrationCache = new QueryCache()
276
- const hydrationClient = createQueryClient({ queryCache: hydrationCache })
277
- hydrate(hydrationClient, parsed)
278
- expect(
279
- hydrationCache.find({
280
- queryKey: ['string', { key: ['string'], key2: 0 }],
281
- })?.state.data,
282
- ).toBe('string')
283
-
284
- const fetchDataAfterHydration =
285
- vi.fn<(...args: Array<unknown>) => unknown>()
286
- await hydrationClient.prefetchQuery({
287
- queryKey: ['string', { key: ['string'], key2: 0 }],
288
- queryFn: fetchDataAfterHydration,
289
- staleTime: 100,
290
- })
291
- expect(fetchDataAfterHydration).toHaveBeenCalledTimes(0)
292
-
293
- queryClient.clear()
294
- hydrationClient.clear()
295
- })
296
-
297
- test('should only hydrate successful queries by default', async () => {
298
- const consoleMock = vi.spyOn(console, 'error')
299
- consoleMock.mockImplementation(() => undefined)
300
-
301
- const queryCache = new QueryCache()
302
- const queryClient = createQueryClient({ queryCache })
303
- await queryClient.prefetchQuery({
304
- queryKey: ['success'],
305
- queryFn: () => fetchData('success'),
306
- })
307
- queryClient.prefetchQuery({
308
- queryKey: ['loading'],
309
- queryFn: () => fetchData('loading', 10000),
310
- })
311
- await queryClient.prefetchQuery({
312
- queryKey: ['error'],
313
- queryFn: () => {
314
- throw new Error()
315
- },
316
- })
317
- const dehydrated = dehydrate(queryClient)
318
- const stringified = JSON.stringify(dehydrated)
319
-
320
- // ---
321
-
322
- const parsed = JSON.parse(stringified)
323
- const hydrationCache = new QueryCache()
324
- const hydrationClient = createQueryClient({ queryCache: hydrationCache })
325
- hydrate(hydrationClient, parsed)
326
-
327
- expect(hydrationCache.find({ queryKey: ['success'] })).toBeTruthy()
328
- expect(hydrationCache.find({ queryKey: ['loading'] })).toBeFalsy()
329
- expect(hydrationCache.find({ queryKey: ['error'] })).toBeFalsy()
330
-
331
- queryClient.clear()
332
- hydrationClient.clear()
333
- consoleMock.mockRestore()
334
- })
335
-
336
- test('should filter queries via dehydrateQuery', async () => {
337
- const queryCache = new QueryCache()
338
- const queryClient = createQueryClient({ queryCache })
339
- await queryClient.prefetchQuery({
340
- queryKey: ['string'],
341
- queryFn: () => fetchData('string'),
342
- })
343
- await queryClient.prefetchQuery({
344
- queryKey: ['number'],
345
- queryFn: () => fetchData(1),
346
- })
347
- const dehydrated = dehydrate(queryClient, {
348
- shouldDehydrateQuery: (query) => query.queryKey[0] !== 'string',
349
- })
350
-
351
- // This is testing implementation details that can change and are not
352
- // part of the public API, but is important for keeping the payload small
353
- const dehydratedQuery = dehydrated.queries.find(
354
- (query) => query.queryKey[0] === 'string',
355
- )
356
- expect(dehydratedQuery).toBeUndefined()
357
-
358
- const stringified = JSON.stringify(dehydrated)
359
-
360
- // ---
361
-
362
- const parsed = JSON.parse(stringified)
363
- const hydrationCache = new QueryCache()
364
- const hydrationClient = createQueryClient({ queryCache: hydrationCache })
365
- hydrate(hydrationClient, parsed)
366
- expect(hydrationCache.find({ queryKey: ['string'] })).toBeUndefined()
367
- expect(hydrationCache.find({ queryKey: ['number'] })?.state.data).toBe(1)
368
-
369
- queryClient.clear()
370
- hydrationClient.clear()
371
- })
372
-
373
- test('should not overwrite query in cache if hydrated query is older', async () => {
374
- const queryCache = new QueryCache()
375
- const queryClient = createQueryClient({ queryCache })
376
- await queryClient.prefetchQuery({
377
- queryKey: ['string'],
378
- queryFn: () => fetchData('string-older', 5),
379
- })
380
- const dehydrated = dehydrate(queryClient)
381
- const stringified = JSON.stringify(dehydrated)
382
-
383
- // ---
384
-
385
- const parsed = JSON.parse(stringified)
386
- const hydrationCache = new QueryCache()
387
- const hydrationClient = createQueryClient({ queryCache: hydrationCache })
388
- await hydrationClient.prefetchQuery({
389
- queryKey: ['string'],
390
- queryFn: () => fetchData('string-newer', 5),
391
- })
392
-
393
- hydrate(hydrationClient, parsed)
394
- expect(hydrationCache.find({ queryKey: ['string'] })?.state.data).toBe(
395
- 'string-newer',
396
- )
397
-
398
- queryClient.clear()
399
- hydrationClient.clear()
400
- })
401
-
402
- test('should overwrite query in cache if hydrated query is newer', async () => {
403
- const hydrationCache = new QueryCache()
404
- const hydrationClient = createQueryClient({ queryCache: hydrationCache })
405
- await hydrationClient.prefetchQuery({
406
- queryKey: ['string'],
407
- queryFn: () => fetchData('string-older', 5),
408
- })
409
-
410
- // ---
411
-
412
- const queryCache = new QueryCache()
413
- const queryClient = createQueryClient({ queryCache })
414
- await queryClient.prefetchQuery({
415
- queryKey: ['string'],
416
- queryFn: () => fetchData('string-newer', 5),
417
- })
418
- const dehydrated = dehydrate(queryClient)
419
- const stringified = JSON.stringify(dehydrated)
420
-
421
- // ---
422
-
423
- const parsed = JSON.parse(stringified)
424
- hydrate(hydrationClient, parsed)
425
- expect(hydrationCache.find({ queryKey: ['string'] })?.state.data).toBe(
426
- 'string-newer',
427
- )
428
-
429
- queryClient.clear()
430
- hydrationClient.clear()
431
- })
432
-
433
- test('should be able to dehydrate mutations and continue on hydration', async () => {
434
- const consoleMock = vi.spyOn(console, 'error')
435
- consoleMock.mockImplementation(() => undefined)
436
- const onlineMock = mockOnlineManagerIsOnline(false)
437
-
438
- const serverAddTodo = vi
439
- .fn()
440
- .mockImplementation(() => Promise.reject(new Error('offline')))
441
- const serverOnMutate = vi.fn().mockImplementation((variables) => {
442
- const optimisticTodo = { id: 1, text: variables.text }
443
- return { optimisticTodo }
444
- })
445
- const serverOnSuccess = vi.fn()
446
-
447
- const serverClient = createQueryClient()
448
-
449
- serverClient.setMutationDefaults(['addTodo'], {
450
- mutationFn: serverAddTodo,
451
- onMutate: serverOnMutate,
452
- onSuccess: serverOnSuccess,
453
- retry: 3,
454
- retryDelay: 10,
455
- })
456
-
457
- executeMutation(
458
- serverClient,
459
- {
460
- mutationKey: ['addTodo'],
461
- },
462
- { text: 'text' },
463
- ).catch(() => undefined)
464
-
465
- await sleep(50)
466
-
467
- const dehydrated = dehydrate(serverClient)
468
- const stringified = JSON.stringify(dehydrated)
469
-
470
- serverClient.clear()
471
-
472
- // ---
473
-
474
- onlineMock.mockReturnValue(true)
475
-
476
- const parsed = JSON.parse(stringified)
477
- const client = createQueryClient()
478
-
479
- const clientAddTodo = vi.fn().mockImplementation((variables) => {
480
- return { id: 2, text: variables.text }
481
- })
482
- const clientOnMutate = vi.fn().mockImplementation((variables) => {
483
- const optimisticTodo = { id: 1, text: variables.text }
484
- return { optimisticTodo }
485
- })
486
- const clientOnSuccess = vi.fn()
487
-
488
- client.setMutationDefaults(['addTodo'], {
489
- mutationFn: clientAddTodo,
490
- onMutate: clientOnMutate,
491
- onSuccess: clientOnSuccess,
492
- retry: 3,
493
- retryDelay: 10,
494
- })
495
-
496
- hydrate(client, parsed)
497
-
498
- await client.resumePausedMutations()
499
-
500
- expect(clientAddTodo).toHaveBeenCalledTimes(1)
501
- expect(clientOnMutate).not.toHaveBeenCalled()
502
- expect(clientOnSuccess).toHaveBeenCalledTimes(1)
503
- expect(clientOnSuccess).toHaveBeenCalledWith(
504
- { id: 2, text: 'text' },
505
- { text: 'text' },
506
- { optimisticTodo: { id: 1, text: 'text' } },
507
- )
508
-
509
- client.clear()
510
- consoleMock.mockRestore()
511
- onlineMock.mockRestore()
512
- })
513
-
514
- test('should not dehydrate mutations if dehydrateMutations is set to false', async () => {
515
- const consoleMock = vi.spyOn(console, 'error')
516
- consoleMock.mockImplementation(() => undefined)
517
-
518
- const serverAddTodo = vi
519
- .fn()
520
- .mockImplementation(() => Promise.reject(new Error('offline')))
521
-
522
- const queryClient = createQueryClient()
523
-
524
- queryClient.setMutationDefaults(['addTodo'], {
525
- mutationFn: serverAddTodo,
526
- retry: false,
527
- })
528
-
529
- executeMutation(
530
- queryClient,
531
- {
532
- mutationKey: ['addTodo'],
533
- },
534
- { text: 'text' },
535
- ).catch(() => undefined)
536
-
537
- await sleep(1)
538
- const dehydrated = dehydrate(queryClient, {
539
- shouldDehydrateMutation: () => false,
540
- })
541
-
542
- expect(dehydrated.mutations.length).toBe(0)
543
-
544
- queryClient.clear()
545
- consoleMock.mockRestore()
546
- })
547
-
548
- test('should not dehydrate mutation if mutation state is set to pause', async () => {
549
- const consoleMock = vi.spyOn(console, 'error')
550
- consoleMock.mockImplementation(() => undefined)
551
-
552
- const serverAddTodo = vi
553
- .fn()
554
- .mockImplementation(() => Promise.reject(new Error('offline')))
555
-
556
- const queryClient = createQueryClient()
557
-
558
- queryClient.setMutationDefaults(['addTodo'], {
559
- mutationFn: serverAddTodo,
560
- retry: 1,
561
- retryDelay: 20,
562
- })
563
-
564
- executeMutation(
565
- queryClient,
566
- {
567
- mutationKey: ['addTodo'],
568
- },
569
- { text: 'text' },
570
- ).catch(() => undefined)
571
-
572
- // Dehydrate mutation between retries
573
- await sleep(1)
574
- const dehydrated = dehydrate(queryClient)
575
-
576
- expect(dehydrated.mutations.length).toBe(0)
577
-
578
- await sleep(30)
579
- queryClient.clear()
580
- consoleMock.mockRestore()
581
- })
582
-
583
- test('should not hydrate if the hydratedState is null or is not an object', async () => {
584
- const queryCache = new QueryCache()
585
- const queryClient = createQueryClient({ queryCache })
586
-
587
- expect(() => hydrate(queryClient, null)).not.toThrow()
588
- expect(() => hydrate(queryClient, 'invalid')).not.toThrow()
589
-
590
- queryClient.clear()
591
- })
592
-
593
- test('should support hydratedState with undefined queries and mutations', async () => {
594
- const queryCache = new QueryCache()
595
- const queryClient = createQueryClient({ queryCache })
596
-
597
- expect(() => hydrate(queryClient, {})).not.toThrow()
598
- expect(() => hydrate(queryClient, {})).not.toThrow()
599
-
600
- queryClient.clear()
601
- })
602
-
603
- test('should set the fetchStatus to idle when creating a query with dehydrate', async () => {
604
- const queryCache = new QueryCache()
605
- const queryClient = createQueryClient({ queryCache })
606
-
607
- let isInitialFetch = true
608
- let resolvePromise: (value: unknown) => void = () => undefined
609
-
610
- const customFetchData = () => {
611
- const promise = new Promise((resolve) => {
612
- resolvePromise = resolve
613
- })
614
- // Resolve the promise in initial fetch
615
- // because we are awaiting the query first time
616
- if (isInitialFetch) {
617
- resolvePromise('string')
618
- }
619
- isInitialFetch = false
620
- return promise
621
- }
622
-
623
- await queryClient.prefetchQuery({
624
- queryKey: ['string'],
625
- queryFn: () => customFetchData(),
626
- })
627
-
628
- queryClient.refetchQueries({ queryKey: ['string'] })
629
-
630
- const dehydrated = dehydrate(queryClient)
631
- resolvePromise('string')
632
- expect(
633
- dehydrated.queries.find((q) => q.queryHash === '["string"]')?.state
634
- .fetchStatus,
635
- ).toBe('fetching')
636
- const stringified = JSON.stringify(dehydrated)
637
-
638
- // ---
639
- const parsed = JSON.parse(stringified)
640
- const hydrationCache = new QueryCache()
641
- const hydrationClient = createQueryClient({ queryCache: hydrationCache })
642
- hydrate(hydrationClient, parsed)
643
- expect(
644
- hydrationCache.find({ queryKey: ['string'] })?.state.fetchStatus,
645
- ).toBe('idle')
646
- })
647
-
648
- test('should dehydrate and hydrate meta for queries', async () => {
649
- const queryCache = new QueryCache()
650
- const queryClient = createQueryClient({ queryCache })
651
- await queryClient.prefetchQuery({
652
- queryKey: ['meta'],
653
- queryFn: () => Promise.resolve('meta'),
654
- meta: {
655
- some: 'meta',
656
- },
657
- })
658
- await queryClient.prefetchQuery({
659
- queryKey: ['no-meta'],
660
- queryFn: () => Promise.resolve('no-meta'),
661
- })
662
-
663
- const dehydrated = dehydrate(queryClient)
664
-
665
- expect(
666
- dehydrated.queries.find((q) => q.queryHash === '["meta"]')?.meta,
667
- ).toEqual({
668
- some: 'meta',
669
- })
670
-
671
- expect(
672
- dehydrated.queries.find((q) => q.queryHash === '["no-meta"]')?.meta,
673
- ).toEqual(undefined)
674
-
675
- expect(
676
- Object.keys(
677
- dehydrated.queries.find((q) => q.queryHash === '["no-meta"]')!,
678
- ),
679
- ).not.toEqual(expect.arrayContaining(['meta']))
680
-
681
- const stringified = JSON.stringify(dehydrated)
682
-
683
- // ---
684
-
685
- const parsed = JSON.parse(stringified)
686
- const hydrationCache = new QueryCache()
687
- const hydrationClient = createQueryClient({
688
- queryCache: hydrationCache,
689
- })
690
- hydrate(hydrationClient, parsed)
691
- expect(hydrationCache.find({ queryKey: ['meta'] })?.meta).toEqual({
692
- some: 'meta',
693
- })
694
- expect(hydrationCache.find({ queryKey: ['no-meta'] })?.meta).toEqual(
695
- undefined,
696
- )
697
- })
698
-
699
- test('should dehydrate and hydrate meta for mutations', async () => {
700
- const mutationCache = new MutationCache()
701
- const queryClient = createQueryClient({ mutationCache })
702
-
703
- await executeMutation(
704
- queryClient,
705
- {
706
- mutationKey: ['meta'],
707
- mutationFn: () => Promise.resolve('meta'),
708
- meta: {
709
- some: 'meta',
710
- },
711
- },
712
- undefined,
713
- )
714
-
715
- await executeMutation(
716
- queryClient,
717
- {
718
- mutationKey: ['no-meta'],
719
- mutationFn: () => Promise.resolve('no-meta'),
720
- },
721
- undefined,
722
- )
723
-
724
- const dehydrated = dehydrate(queryClient, {
725
- shouldDehydrateMutation: () => true,
726
- })
727
-
728
- expect(Object.keys(dehydrated.mutations[0]!)).toEqual(
729
- expect.arrayContaining(['meta']),
730
- )
731
- expect(dehydrated.mutations[0]?.meta).toEqual({
732
- some: 'meta',
733
- })
734
-
735
- expect(Object.keys(dehydrated.mutations[1]!)).not.toEqual(
736
- expect.arrayContaining(['meta']),
737
- )
738
- expect(dehydrated.mutations[1]?.meta).toEqual(undefined)
739
-
740
- const stringified = JSON.stringify(dehydrated)
741
-
742
- // ---
743
-
744
- const parsed = JSON.parse(stringified)
745
- const hydrationCache = new MutationCache()
746
- const hydrationClient = createQueryClient({
747
- mutationCache: hydrationCache,
748
- })
749
- hydrate(hydrationClient, parsed)
750
- expect(hydrationCache.find({ mutationKey: ['meta'] })?.meta).toEqual({
751
- some: 'meta',
752
- })
753
- expect(hydrationCache.find({ mutationKey: ['no-meta'] })?.meta).toEqual(
754
- undefined,
755
- )
756
- })
757
-
758
- test('should not change fetchStatus when updating a query with dehydrate', async () => {
759
- const queryClient = createQueryClient()
760
-
761
- const options = {
762
- queryKey: ['string'],
763
- queryFn: async () => {
764
- await sleep(10)
765
- return 'string'
766
- },
767
- } as const
768
-
769
- await queryClient.prefetchQuery(options)
770
-
771
- const dehydrated = dehydrate(queryClient)
772
- expect(
773
- dehydrated.queries.find((q) => q.queryHash === '["string"]')?.state
774
- .fetchStatus,
775
- ).toBe('idle')
776
- const stringified = JSON.stringify(dehydrated)
777
-
778
- // ---
779
- const parsed = JSON.parse(stringified)
780
- const hydrationCache = new QueryCache()
781
- const hydrationClient = createQueryClient({ queryCache: hydrationCache })
782
-
783
- const promise = hydrationClient.prefetchQuery(options)
784
- hydrate(hydrationClient, parsed)
785
- expect(
786
- hydrationCache.find({ queryKey: ['string'] })?.state.fetchStatus,
787
- ).toBe('fetching')
788
- await promise
789
- expect(
790
- hydrationCache.find({ queryKey: ['string'] })?.state.fetchStatus,
791
- ).toBe('idle')
792
- })
793
-
794
- test('should dehydrate and hydrate mutation scopes', async () => {
795
- const queryClient = createQueryClient()
796
- const onlineMock = mockOnlineManagerIsOnline(false)
797
-
798
- void executeMutation(
799
- queryClient,
800
- {
801
- mutationKey: ['mutation'],
802
- mutationFn: async () => {
803
- return 'mutation'
804
- },
805
- scope: {
806
- id: 'scope',
807
- },
808
- },
809
- 'vars',
810
- )
811
-
812
- const dehydrated = dehydrate(queryClient)
813
- expect(dehydrated.mutations[0]?.scope?.id).toBe('scope')
814
- const stringified = JSON.stringify(dehydrated)
815
-
816
- // ---
817
- const parsed = JSON.parse(stringified)
818
- const hydrationCache = new MutationCache()
819
- const hydrationClient = createQueryClient({ mutationCache: hydrationCache })
820
-
821
- hydrate(hydrationClient, parsed)
822
-
823
- expect(dehydrated.mutations[0]?.scope?.id).toBe('scope')
824
-
825
- onlineMock.mockRestore()
826
- })
827
-
828
- test('should dehydrate promises for pending queries', async () => {
829
- const queryCache = new QueryCache()
830
- const queryClient = createQueryClient({
831
- queryCache,
832
- defaultOptions: { dehydrate: { shouldDehydrateQuery: () => true } },
833
- })
834
- await queryClient.prefetchQuery({
835
- queryKey: ['success'],
836
- queryFn: () => fetchData('success'),
837
- })
838
-
839
- const promise = queryClient.prefetchQuery({
840
- queryKey: ['pending'],
841
- queryFn: () => fetchData('pending', 10),
842
- })
843
- const dehydrated = dehydrate(queryClient)
844
-
845
- expect(dehydrated.queries[0]?.promise).toBeUndefined()
846
- expect(dehydrated.queries[1]?.promise).toBeInstanceOf(Promise)
847
-
848
- await promise
849
- queryClient.clear()
850
- })
851
-
852
- test('should hydrate promises even without observers', async () => {
853
- const queryCache = new QueryCache()
854
- const queryClient = createQueryClient({
855
- queryCache,
856
- defaultOptions: { dehydrate: { shouldDehydrateQuery: () => true } },
857
- })
858
- await queryClient.prefetchQuery({
859
- queryKey: ['success'],
860
- queryFn: () => fetchData('success'),
861
- })
862
-
863
- void queryClient.prefetchQuery({
864
- queryKey: ['pending'],
865
- queryFn: () => fetchData('pending', 20),
866
- })
867
- const dehydrated = dehydrate(queryClient)
868
- // no stringify/parse here because promises can't be serialized to json
869
- // but nextJs still can do it
870
-
871
- const hydrationCache = new QueryCache()
872
- const hydrationClient = createQueryClient({
873
- queryCache: hydrationCache,
874
- })
875
-
876
- hydrate(hydrationClient, dehydrated)
877
-
878
- expect(hydrationCache.find({ queryKey: ['success'] })?.state.data).toBe(
879
- 'success',
880
- )
881
-
882
- expect(hydrationCache.find({ queryKey: ['pending'] })?.state).toMatchObject(
883
- {
884
- data: undefined,
885
- dataUpdateCount: 0,
886
- dataUpdatedAt: 0,
887
- error: null,
888
- errorUpdateCount: 0,
889
- errorUpdatedAt: 0,
890
- fetchFailureCount: 0,
891
- fetchFailureReason: null,
892
- fetchMeta: null,
893
- fetchStatus: 'fetching',
894
- isInvalidated: false,
895
- status: 'pending',
896
- },
897
- )
898
-
899
- await waitFor(() =>
900
- expect(
901
- hydrationCache.find({ queryKey: ['pending'] })?.state,
902
- ).toMatchObject({
903
- data: 'pending',
904
- dataUpdateCount: 1,
905
- dataUpdatedAt: expect.any(Number),
906
- error: null,
907
- errorUpdateCount: 0,
908
- errorUpdatedAt: 0,
909
- fetchFailureCount: 0,
910
- fetchFailureReason: null,
911
- fetchMeta: null,
912
- fetchStatus: 'idle',
913
- isInvalidated: false,
914
- status: 'success',
915
- }),
916
- )
917
- })
918
-
919
- test('should transform promise result', async () => {
920
- const queryClient = createQueryClient({
921
- defaultOptions: {
922
- dehydrate: {
923
- shouldDehydrateQuery: () => true,
924
- serializeData: (data) => data.toISOString(),
925
- },
926
- },
927
- })
928
-
929
- const promise = queryClient.prefetchQuery({
930
- queryKey: ['transformedStringToDate'],
931
- queryFn: () => fetchDate('2024-01-01T00:00:00.000Z', 20),
932
- })
933
- const dehydrated = dehydrate(queryClient)
934
- expect(dehydrated.queries[0]?.promise).toBeInstanceOf(Promise)
935
-
936
- const hydrationClient = createQueryClient({
937
- defaultOptions: {
938
- hydrate: {
939
- deserializeData: (data) => new Date(data),
940
- },
941
- },
942
- })
943
-
944
- hydrate(hydrationClient, dehydrated)
945
- await promise
946
- await waitFor(() =>
947
- expect(
948
- hydrationClient.getQueryData(['transformedStringToDate']),
949
- ).toBeInstanceOf(Date),
950
- )
951
-
952
- queryClient.clear()
953
- })
954
-
955
- test('should transform query data if promise is already resolved', async () => {
956
- const queryClient = createQueryClient({
957
- defaultOptions: {
958
- dehydrate: {
959
- shouldDehydrateQuery: () => true,
960
- serializeData: (data) => data.toISOString(),
961
- },
962
- },
963
- })
964
-
965
- const promise = queryClient.prefetchQuery({
966
- queryKey: ['transformedStringToDate'],
967
- queryFn: () => fetchDate('2024-01-01T00:00:00.000Z', 0),
968
- })
969
- await sleep(20)
970
- const dehydrated = dehydrate(queryClient)
971
-
972
- const hydrationClient = createQueryClient({
973
- defaultOptions: {
974
- hydrate: {
975
- deserializeData: (data) => new Date(data),
976
- },
977
- },
978
- })
979
-
980
- hydrate(hydrationClient, dehydrated)
981
- await promise
982
- await waitFor(() =>
983
- expect(
984
- hydrationClient.getQueryData(['transformedStringToDate']),
985
- ).toBeInstanceOf(Date),
986
- )
987
-
988
- queryClient.clear()
989
- })
990
-
991
- test('should overwrite query in cache if hydrated query is newer (with transformation)', async () => {
992
- const hydrationClient = createQueryClient({
993
- defaultOptions: {
994
- hydrate: {
995
- deserializeData: (data) => new Date(data),
996
- },
997
- },
998
- })
999
- await hydrationClient.prefetchQuery({
1000
- queryKey: ['date'],
1001
- queryFn: () => fetchDate('2024-01-01T00:00:00.000Z', 5),
1002
- })
1003
-
1004
- // ---
1005
-
1006
- const queryClient = createQueryClient({
1007
- defaultOptions: {
1008
- dehydrate: {
1009
- shouldDehydrateQuery: () => true,
1010
- serializeData: (data) => data.toISOString(),
1011
- },
1012
- },
1013
- })
1014
- await queryClient.prefetchQuery({
1015
- queryKey: ['date'],
1016
- queryFn: () => fetchDate('2024-01-02T00:00:00.000Z', 10),
1017
- })
1018
- const dehydrated = dehydrate(queryClient)
1019
-
1020
- // ---
1021
-
1022
- hydrate(hydrationClient, dehydrated)
1023
-
1024
- expect(hydrationClient.getQueryData(['date'])).toStrictEqual(
1025
- new Date('2024-01-02T00:00:00.000Z'),
1026
- )
1027
-
1028
- queryClient.clear()
1029
- hydrationClient.clear()
1030
- })
1031
-
1032
- test('should overwrite query in cache if hydrated query is newer (with promise)', async () => {
1033
- // --- server ---
1034
-
1035
- const serverQueryClient = createQueryClient({
1036
- defaultOptions: {
1037
- dehydrate: {
1038
- shouldDehydrateQuery: () => true,
1039
- },
1040
- },
1041
- })
1042
-
1043
- const promise = serverQueryClient.prefetchQuery({
1044
- queryKey: ['data'],
1045
- queryFn: async () => {
1046
- await sleep(10)
1047
- return 'server data'
1048
- },
1049
- })
1050
-
1051
- const dehydrated = dehydrate(serverQueryClient)
1052
-
1053
- // --- client ---
1054
-
1055
- const clientQueryClient = createQueryClient()
1056
-
1057
- clientQueryClient.setQueryData(['data'], 'old data', { updatedAt: 10 })
1058
-
1059
- hydrate(clientQueryClient, dehydrated)
1060
-
1061
- await promise
1062
- await waitFor(() =>
1063
- expect(clientQueryClient.getQueryData(['data'])).toBe('server data'),
1064
- )
1065
-
1066
- clientQueryClient.clear()
1067
- serverQueryClient.clear()
1068
- })
1069
- })