@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,1049 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it, test, vi } from 'vitest'
2
- import { waitFor } from '@testing-library/react'
3
- import { QueryObserver, dehydrate, hydrate, isCancelledError } from '..'
4
- import {
5
- createQueryClient,
6
- mockOnlineManagerIsOnline,
7
- mockVisibilityState,
8
- queryKey,
9
- setIsServer,
10
- sleep,
11
- } from './utils'
12
- import type {
13
- QueryCache,
14
- QueryClient,
15
- QueryFunctionContext,
16
- QueryObserverResult,
17
- } from '..'
18
-
19
- describe('query', () => {
20
- let queryClient: QueryClient
21
- let queryCache: QueryCache
22
-
23
- beforeEach(() => {
24
- queryClient = createQueryClient()
25
- queryCache = queryClient.getQueryCache()
26
- queryClient.mount()
27
- })
28
-
29
- afterEach(() => {
30
- queryClient.clear()
31
- })
32
-
33
- test('should use the longest garbage collection time it has seen', async () => {
34
- const key = queryKey()
35
- await queryClient.prefetchQuery({
36
- queryKey: key,
37
- queryFn: () => 'data',
38
- gcTime: 100,
39
- })
40
- await queryClient.prefetchQuery({
41
- queryKey: key,
42
- queryFn: () => 'data',
43
- gcTime: 200,
44
- })
45
- await queryClient.prefetchQuery({
46
- queryKey: key,
47
- queryFn: () => 'data',
48
- gcTime: 10,
49
- })
50
- const query = queryCache.find({ queryKey: key })!
51
- expect(query.gcTime).toBe(200)
52
- })
53
-
54
- it('should continue retry after focus regain and resolve all promises', async () => {
55
- const key = queryKey()
56
-
57
- // make page unfocused
58
- const visibilityMock = mockVisibilityState('hidden')
59
-
60
- let count = 0
61
- let result
62
-
63
- const promise = queryClient.fetchQuery({
64
- queryKey: key,
65
- queryFn: async () => {
66
- count++
67
-
68
- if (count === 3) {
69
- return `data${count}`
70
- }
71
-
72
- throw new Error(`error${count}`)
73
- },
74
- retry: 3,
75
- retryDelay: 1,
76
- })
77
-
78
- promise.then((data) => {
79
- result = data
80
- })
81
-
82
- // Check if we do not have a result
83
- expect(result).toBeUndefined()
84
-
85
- // Check if the query is really paused
86
- await sleep(50)
87
- expect(result).toBeUndefined()
88
-
89
- // Reset visibilityState to original value
90
- visibilityMock.mockRestore()
91
- window.dispatchEvent(new Event('visibilitychange'))
92
-
93
- // There should not be a result yet
94
- expect(result).toBeUndefined()
95
-
96
- // By now we should have a value
97
- await sleep(50)
98
- expect(result).toBe('data3')
99
- })
100
-
101
- it('should continue retry after reconnect and resolve all promises', async () => {
102
- const key = queryKey()
103
-
104
- const onlineMock = mockOnlineManagerIsOnline(false)
105
-
106
- let count = 0
107
- let result
108
-
109
- const promise = queryClient.fetchQuery({
110
- queryKey: key,
111
- queryFn: async () => {
112
- count++
113
-
114
- if (count === 3) {
115
- return `data${count}`
116
- }
117
-
118
- throw new Error(`error${count}`)
119
- },
120
- retry: 3,
121
- retryDelay: 1,
122
- })
123
-
124
- promise.then((data) => {
125
- result = data
126
- })
127
-
128
- // Check if we do not have a result
129
- expect(result).toBeUndefined()
130
-
131
- // Check if the query is really paused
132
- await sleep(50)
133
- expect(result).toBeUndefined()
134
-
135
- // Reset navigator to original value
136
- onlineMock.mockReturnValue(true)
137
- // trigger online event
138
- queryClient.getQueryCache().onOnline()
139
-
140
- // There should not be a result yet
141
- expect(result).toBeUndefined()
142
-
143
- // Promise should eventually be resolved
144
- await promise
145
-
146
- console.log('has finished')
147
- expect(result).toBe('data3')
148
- onlineMock.mockRestore()
149
- })
150
-
151
- it('should throw a CancelledError when a paused query is cancelled', async () => {
152
- const key = queryKey()
153
-
154
- // make page unfocused
155
- const visibilityMock = mockVisibilityState('hidden')
156
-
157
- let count = 0
158
- let result: unknown
159
-
160
- const promise = queryClient.fetchQuery({
161
- queryKey: key,
162
- queryFn: async (): Promise<unknown> => {
163
- count++
164
- throw new Error(`error${count}`)
165
- },
166
- retry: 3,
167
- retryDelay: 1,
168
- })
169
-
170
- promise.catch((data) => {
171
- result = data
172
- })
173
-
174
- const query = queryCache.find({ queryKey: key })!
175
-
176
- // Check if the query is really paused
177
- await sleep(50)
178
- expect(result).toBeUndefined()
179
-
180
- // Cancel query
181
- query.cancel()
182
-
183
- // Check if the error is set to the cancelled error
184
- try {
185
- await promise
186
- expect.unreachable()
187
- } catch {
188
- expect(isCancelledError(result)).toBe(true)
189
- expect(result instanceof Error).toBe(true)
190
- } finally {
191
- // Reset visibilityState to original value
192
- visibilityMock.mockRestore()
193
- }
194
- })
195
-
196
- test('should provide context to queryFn', async () => {
197
- const key = queryKey()
198
-
199
- const queryFn = vi
200
- .fn<
201
- (
202
- context: QueryFunctionContext<ReturnType<typeof queryKey>>,
203
- ) => Promise<'data'>
204
- >()
205
- .mockResolvedValue('data')
206
-
207
- queryClient.prefetchQuery({ queryKey: key, queryFn })
208
-
209
- await sleep(10)
210
-
211
- expect(queryFn).toHaveBeenCalledTimes(1)
212
- const args = queryFn.mock.calls[0]![0]
213
- expect(args).toBeDefined()
214
- expect(args.pageParam).toBeUndefined()
215
- expect(args.queryKey).toEqual(key)
216
- expect(args.signal).toBeInstanceOf(AbortSignal)
217
- })
218
-
219
- test('should continue if cancellation is not supported and signal is not consumed', async () => {
220
- const key = queryKey()
221
-
222
- queryClient.prefetchQuery({
223
- queryKey: key,
224
- queryFn: async () => {
225
- await sleep(100)
226
- return 'data'
227
- },
228
- })
229
-
230
- await sleep(10)
231
-
232
- // Subscribe and unsubscribe to simulate cancellation because the last observer unsubscribed
233
- const observer = new QueryObserver(queryClient, {
234
- queryKey: key,
235
- enabled: false,
236
- })
237
- const unsubscribe = observer.subscribe(() => undefined)
238
- unsubscribe()
239
-
240
- await sleep(100)
241
-
242
- const query = queryCache.find({ queryKey: key })!
243
-
244
- expect(query.state).toMatchObject({
245
- data: 'data',
246
- status: 'success',
247
- dataUpdateCount: 1,
248
- })
249
- })
250
-
251
- test('should not continue when last observer unsubscribed if the signal was consumed', async () => {
252
- const key = queryKey()
253
-
254
- queryClient.prefetchQuery({
255
- queryKey: key,
256
- queryFn: async ({ signal }) => {
257
- await sleep(100)
258
- return signal.aborted ? 'aborted' : 'data'
259
- },
260
- })
261
-
262
- await sleep(10)
263
-
264
- // Subscribe and unsubscribe to simulate cancellation because the last observer unsubscribed
265
- const observer = new QueryObserver(queryClient, {
266
- queryKey: key,
267
- enabled: false,
268
- })
269
- const unsubscribe = observer.subscribe(() => undefined)
270
- unsubscribe()
271
-
272
- await sleep(100)
273
-
274
- const query = queryCache.find({ queryKey: key })!
275
-
276
- expect(query.state).toMatchObject({
277
- data: undefined,
278
- status: 'pending',
279
- fetchStatus: 'idle',
280
- })
281
- })
282
-
283
- test('should provide an AbortSignal to the queryFn that provides info about the cancellation state', async () => {
284
- const key = queryKey()
285
-
286
- const queryFn =
287
- vi.fn<
288
- (
289
- context: QueryFunctionContext<ReturnType<typeof queryKey>>,
290
- ) => Promise<unknown>
291
- >()
292
- const onAbort = vi.fn()
293
- const abortListener = vi.fn()
294
- let error
295
-
296
- queryFn.mockImplementation(async ({ signal }) => {
297
- signal.onabort = onAbort
298
- signal.addEventListener('abort', abortListener)
299
- await sleep(10)
300
- signal.onabort = null
301
- signal.removeEventListener('abort', abortListener)
302
- throw new Error()
303
- })
304
-
305
- const promise = queryClient.fetchQuery({
306
- queryKey: key,
307
- queryFn,
308
- retry: 3,
309
- retryDelay: 10,
310
- })
311
-
312
- promise.catch((e) => {
313
- error = e
314
- })
315
-
316
- const query = queryCache.find({ queryKey: key })!
317
-
318
- expect(queryFn).toHaveBeenCalledTimes(1)
319
-
320
- const signal = queryFn.mock.calls[0]![0].signal
321
- expect(signal.aborted).toBe(false)
322
- expect(onAbort).not.toHaveBeenCalled()
323
- expect(abortListener).not.toHaveBeenCalled()
324
-
325
- query.cancel()
326
-
327
- await sleep(100)
328
-
329
- expect(signal.aborted).toBe(true)
330
- expect(onAbort).toHaveBeenCalledTimes(1)
331
- expect(abortListener).toHaveBeenCalledTimes(1)
332
- expect(isCancelledError(error)).toBe(true)
333
- })
334
-
335
- test('should not continue if explicitly cancelled', async () => {
336
- const key = queryKey()
337
-
338
- const queryFn = vi.fn<(...args: Array<unknown>) => unknown>()
339
-
340
- queryFn.mockImplementation(async () => {
341
- await sleep(10)
342
- throw new Error()
343
- })
344
-
345
- let error
346
-
347
- const promise = queryClient.fetchQuery({
348
- queryKey: key,
349
- queryFn,
350
- retry: 3,
351
- retryDelay: 10,
352
- })
353
-
354
- promise.catch((e) => {
355
- error = e
356
- })
357
-
358
- const query = queryCache.find({ queryKey: key })!
359
- query.cancel()
360
-
361
- await sleep(100)
362
-
363
- expect(queryFn).toHaveBeenCalledTimes(1)
364
- expect(isCancelledError(error)).toBe(true)
365
- })
366
-
367
- test('should not error if reset while pending', async () => {
368
- const key = queryKey()
369
-
370
- const queryFn = vi.fn<(...args: Array<unknown>) => unknown>()
371
-
372
- queryFn.mockImplementation(async () => {
373
- await sleep(10)
374
- throw new Error()
375
- })
376
-
377
- queryClient.fetchQuery({ queryKey: key, queryFn, retry: 3, retryDelay: 10 })
378
-
379
- // Ensure the query is pending
380
- const query = queryCache.find({ queryKey: key })!
381
- expect(query.state.status).toBe('pending')
382
-
383
- // Reset the query while it is pending
384
- query.reset()
385
-
386
- await sleep(100)
387
-
388
- // The query should
389
- expect(queryFn).toHaveBeenCalledTimes(1) // have been called,
390
- expect(query.state.error).toBe(null) // not have an error, and
391
- expect(query.state.fetchStatus).toBe('idle') // not be loading any longer
392
- })
393
-
394
- test('should reset to default state when created from hydration', async () => {
395
- const client = createQueryClient()
396
- await client.prefetchQuery({
397
- queryKey: ['string'],
398
- queryFn: () => Promise.resolve('string'),
399
- })
400
-
401
- const dehydrated = dehydrate(client)
402
-
403
- const hydrationClient = createQueryClient()
404
- hydrate(hydrationClient, dehydrated)
405
-
406
- expect(hydrationClient.getQueryData(['string'])).toBe('string')
407
-
408
- const query = hydrationClient.getQueryCache().find({ queryKey: ['string'] })
409
- query?.reset()
410
-
411
- expect(hydrationClient.getQueryData(['string'])).toBe(undefined)
412
- })
413
-
414
- test('should be able to refetch a cancelled query', async () => {
415
- const key = queryKey()
416
-
417
- const queryFn = vi.fn<(...args: Array<unknown>) => unknown>()
418
-
419
- queryFn.mockImplementation(async () => {
420
- await sleep(50)
421
- return 'data'
422
- })
423
-
424
- queryClient.prefetchQuery({ queryKey: key, queryFn })
425
- const query = queryCache.find({ queryKey: key })!
426
- await sleep(10)
427
- query.cancel()
428
- await sleep(100)
429
-
430
- expect(queryFn).toHaveBeenCalledTimes(1)
431
- expect(isCancelledError(query.state.error)).toBe(true)
432
- const result = await query.fetch()
433
- expect(result).toBe('data')
434
- expect(query.state.error).toBe(null)
435
- expect(queryFn).toHaveBeenCalledTimes(2)
436
- })
437
-
438
- test('cancelling a resolved query should not have any effect', async () => {
439
- const key = queryKey()
440
- await queryClient.prefetchQuery({
441
- queryKey: key,
442
- queryFn: async () => 'data',
443
- })
444
- const query = queryCache.find({ queryKey: key })!
445
- query.cancel()
446
- await sleep(10)
447
- expect(query.state.data).toBe('data')
448
- })
449
-
450
- test('cancelling a rejected query should not have any effect', async () => {
451
- const key = queryKey()
452
- const error = new Error('error')
453
-
454
- await queryClient.prefetchQuery({
455
- queryKey: key,
456
- queryFn: async (): Promise<unknown> => {
457
- throw error
458
- },
459
- })
460
- const query = queryCache.find({ queryKey: key })!
461
- query.cancel()
462
- await sleep(10)
463
-
464
- expect(query.state.error).toBe(error)
465
- expect(isCancelledError(query.state.error)).toBe(false)
466
- })
467
-
468
- test('the previous query status should be kept when refetching', async () => {
469
- const key = queryKey()
470
-
471
- await queryClient.prefetchQuery({ queryKey: key, queryFn: () => 'data' })
472
- const query = queryCache.find({ queryKey: key })!
473
- expect(query.state.status).toBe('success')
474
-
475
- await queryClient.prefetchQuery({
476
- queryKey: key,
477
- queryFn: () => Promise.reject<string>('reject'),
478
- retry: false,
479
- })
480
- expect(query.state.status).toBe('error')
481
-
482
- queryClient.prefetchQuery({
483
- queryKey: key,
484
- queryFn: async () => {
485
- await sleep(10)
486
- return Promise.reject<unknown>('reject')
487
- },
488
- retry: false,
489
- })
490
- expect(query.state.status).toBe('error')
491
-
492
- await sleep(100)
493
- expect(query.state.status).toBe('error')
494
- })
495
-
496
- test('queries with gcTime 0 should be removed immediately after unsubscribing', async () => {
497
- const key = queryKey()
498
- let count = 0
499
- const observer = new QueryObserver(queryClient, {
500
- queryKey: key,
501
- queryFn: () => {
502
- count++
503
- return 'data'
504
- },
505
- gcTime: 0,
506
- staleTime: Infinity,
507
- })
508
- const unsubscribe1 = observer.subscribe(() => undefined)
509
- unsubscribe1()
510
- await waitFor(() =>
511
- expect(queryCache.find({ queryKey: key })).toBeUndefined(),
512
- )
513
- const unsubscribe2 = observer.subscribe(() => undefined)
514
- unsubscribe2()
515
-
516
- await waitFor(() =>
517
- expect(queryCache.find({ queryKey: key })).toBeUndefined(),
518
- )
519
- expect(count).toBe(1)
520
- })
521
-
522
- test('should be garbage collected when unsubscribed to', async () => {
523
- const key = queryKey()
524
- const observer = new QueryObserver(queryClient, {
525
- queryKey: key,
526
- queryFn: async () => 'data',
527
- gcTime: 0,
528
- })
529
- expect(queryCache.find({ queryKey: key })).toBeDefined()
530
- const unsubscribe = observer.subscribe(() => undefined)
531
- expect(queryCache.find({ queryKey: key })).toBeDefined()
532
- unsubscribe()
533
- await waitFor(() =>
534
- expect(queryCache.find({ queryKey: key })).toBeUndefined(),
535
- )
536
- })
537
-
538
- test('should be garbage collected later when unsubscribed and query is fetching', async () => {
539
- const key = queryKey()
540
- const observer = new QueryObserver(queryClient, {
541
- queryKey: key,
542
- queryFn: async () => {
543
- await sleep(20)
544
- return 'data'
545
- },
546
- gcTime: 10,
547
- })
548
- const unsubscribe = observer.subscribe(() => undefined)
549
- await sleep(20)
550
- expect(queryCache.find({ queryKey: key })).toBeDefined()
551
- observer.refetch()
552
- unsubscribe()
553
- await sleep(10)
554
- // unsubscribe should not remove even though gcTime has elapsed b/c query is still fetching
555
- expect(queryCache.find({ queryKey: key })).toBeDefined()
556
- await sleep(10)
557
- // should be removed after an additional staleTime wait
558
- await waitFor(() =>
559
- expect(queryCache.find({ queryKey: key })).toBeUndefined(),
560
- )
561
- })
562
-
563
- test('should not be garbage collected unless there are no subscribers', async () => {
564
- const key = queryKey()
565
- const observer = new QueryObserver(queryClient, {
566
- queryKey: key,
567
- queryFn: async () => 'data',
568
- gcTime: 0,
569
- })
570
- expect(queryCache.find({ queryKey: key })).toBeDefined()
571
- const unsubscribe = observer.subscribe(() => undefined)
572
- await sleep(100)
573
- expect(queryCache.find({ queryKey: key })).toBeDefined()
574
- unsubscribe()
575
- await sleep(100)
576
- expect(queryCache.find({ queryKey: key })).toBeUndefined()
577
- queryClient.setQueryData(key, 'data')
578
- await sleep(100)
579
- expect(queryCache.find({ queryKey: key })).toBeDefined()
580
- })
581
-
582
- test('should return proper count of observers', async () => {
583
- const key = queryKey()
584
- const options = { queryKey: key, queryFn: async () => 'data' }
585
- const observer = new QueryObserver(queryClient, options)
586
- const observer2 = new QueryObserver(queryClient, options)
587
- const observer3 = new QueryObserver(queryClient, options)
588
- const query = queryCache.find({ queryKey: key })
589
-
590
- expect(query?.getObserversCount()).toEqual(0)
591
-
592
- const unsubscribe1 = observer.subscribe(() => undefined)
593
- const unsubscribe2 = observer2.subscribe(() => undefined)
594
- const unsubscribe3 = observer3.subscribe(() => undefined)
595
- expect(query?.getObserversCount()).toEqual(3)
596
-
597
- unsubscribe3()
598
- expect(query?.getObserversCount()).toEqual(2)
599
-
600
- unsubscribe2()
601
- expect(query?.getObserversCount()).toEqual(1)
602
-
603
- unsubscribe1()
604
- expect(query?.getObserversCount()).toEqual(0)
605
- })
606
-
607
- test('stores meta object in query', async () => {
608
- const meta = {
609
- it: 'works',
610
- }
611
-
612
- const key = queryKey()
613
-
614
- await queryClient.prefetchQuery({
615
- queryKey: key,
616
- queryFn: () => 'data',
617
- meta,
618
- })
619
-
620
- const query = queryCache.find({ queryKey: key })!
621
-
622
- expect(query.meta).toBe(meta)
623
- expect(query.options.meta).toBe(meta)
624
- })
625
-
626
- test('updates meta object on change', async () => {
627
- const meta = {
628
- it: 'works',
629
- }
630
-
631
- const key = queryKey()
632
- const queryFn = () => 'data'
633
-
634
- await queryClient.prefetchQuery({ queryKey: key, queryFn, meta })
635
-
636
- await queryClient.prefetchQuery({ queryKey: key, queryFn, meta: undefined })
637
-
638
- const query = queryCache.find({ queryKey: key })!
639
-
640
- expect(query.meta).toBeUndefined()
641
- expect(query.options.meta).toBeUndefined()
642
- })
643
-
644
- test('can use default meta', async () => {
645
- const meta = {
646
- it: 'works',
647
- }
648
-
649
- const key = queryKey()
650
- const queryFn = () => 'data'
651
-
652
- queryClient.setQueryDefaults(key, { meta })
653
-
654
- await queryClient.prefetchQuery({ queryKey: key, queryFn })
655
-
656
- const query = queryCache.find({ queryKey: key })!
657
-
658
- expect(query.meta).toBe(meta)
659
- })
660
-
661
- test('provides meta object inside query function', async () => {
662
- const meta = {
663
- it: 'works',
664
- }
665
-
666
- const queryFn = vi.fn(() => 'data')
667
-
668
- const key = queryKey()
669
-
670
- await queryClient.prefetchQuery({ queryKey: key, queryFn, meta })
671
-
672
- expect(queryFn).toBeCalledWith(
673
- expect.objectContaining({
674
- meta,
675
- }),
676
- )
677
- })
678
-
679
- test('should refetch the observer when online method is called', async () => {
680
- const key = queryKey()
681
-
682
- const observer = new QueryObserver(queryClient, {
683
- queryKey: key,
684
- queryFn: () => 'data',
685
- })
686
-
687
- const refetchSpy = vi.spyOn(observer, 'refetch')
688
- const unsubscribe = observer.subscribe(() => undefined)
689
- queryCache.onOnline()
690
-
691
- // Should refetch the observer
692
- expect(refetchSpy).toHaveBeenCalledTimes(1)
693
-
694
- unsubscribe()
695
- refetchSpy.mockRestore()
696
- })
697
-
698
- test('should not add an existing observer', async () => {
699
- const key = queryKey()
700
-
701
- await queryClient.prefetchQuery({ queryKey: key, queryFn: () => 'data' })
702
- const query = queryCache.find({ queryKey: key })!
703
- expect(query.getObserversCount()).toEqual(0)
704
-
705
- const observer = new QueryObserver(queryClient, {
706
- queryKey: key,
707
- })
708
- expect(query.getObserversCount()).toEqual(0)
709
-
710
- query.addObserver(observer)
711
- expect(query.getObserversCount()).toEqual(1)
712
-
713
- query.addObserver(observer)
714
- expect(query.getObserversCount()).toEqual(1)
715
- })
716
-
717
- test('should not try to remove an observer that does not exist', async () => {
718
- const key = queryKey()
719
-
720
- await queryClient.prefetchQuery({ queryKey: key, queryFn: () => 'data' })
721
- const query = queryCache.find({ queryKey: key })!
722
- const observer = new QueryObserver(queryClient, {
723
- queryKey: key,
724
- })
725
- expect(query.getObserversCount()).toEqual(0)
726
-
727
- const notifySpy = vi.spyOn(queryCache, 'notify')
728
- expect(() => query.removeObserver(observer)).not.toThrow()
729
- expect(notifySpy).not.toHaveBeenCalled()
730
-
731
- notifySpy.mockRestore()
732
- })
733
-
734
- test('should not change state on invalidate() if already invalidated', async () => {
735
- const key = queryKey()
736
-
737
- await queryClient.prefetchQuery({ queryKey: key, queryFn: () => 'data' })
738
- const query = queryCache.find({ queryKey: key })!
739
-
740
- query.invalidate()
741
- expect(query.state.isInvalidated).toBeTruthy()
742
-
743
- const previousState = query.state
744
-
745
- query.invalidate()
746
-
747
- expect(query.state).toBe(previousState)
748
- })
749
-
750
- test('fetch should not dispatch "fetch" query is already fetching', async () => {
751
- const key = queryKey()
752
-
753
- const queryFn = async () => {
754
- await sleep(10)
755
- return 'data'
756
- }
757
-
758
- const updates: Array<string> = []
759
-
760
- await queryClient.prefetchQuery({ queryKey: key, queryFn })
761
- const query = queryCache.find({ queryKey: key })!
762
-
763
- const unsubscribe = queryClient.getQueryCache().subscribe((event) => {
764
- updates.push(event.type)
765
- })
766
-
767
- void query.fetch({
768
- queryKey: key,
769
- queryFn,
770
- })
771
-
772
- await query.fetch({
773
- queryKey: key,
774
- queryFn,
775
- })
776
-
777
- expect(updates).toEqual([
778
- 'updated', // type: 'fetch'
779
- 'updated', // type: 'success'
780
- ])
781
-
782
- unsubscribe()
783
- })
784
-
785
- test('fetch should throw an error if the queryFn is not defined', async () => {
786
- const key = queryKey()
787
-
788
- const observer = new QueryObserver(queryClient, {
789
- queryKey: key,
790
- queryFn: undefined,
791
- retry: false,
792
- })
793
-
794
- const unsubscribe = observer.subscribe(() => undefined)
795
-
796
- await sleep(10)
797
- const query = queryCache.find({ queryKey: key })!
798
- expect(observer.getCurrentResult()).toMatchObject({
799
- status: 'error',
800
- error: new Error(`Missing queryFn: '${query.queryHash}'`),
801
- })
802
- unsubscribe()
803
- })
804
-
805
- test('fetch should dispatch an error if the queryFn returns undefined', async () => {
806
- const consoleMock = vi.spyOn(console, 'error')
807
- consoleMock.mockImplementation(() => undefined)
808
- const key = queryKey()
809
-
810
- const observer = new QueryObserver(queryClient, {
811
- queryKey: key,
812
- queryFn: () => undefined,
813
- retry: false,
814
- })
815
-
816
- let observerResult: QueryObserverResult<unknown, unknown> | undefined
817
-
818
- const unsubscribe = observer.subscribe((result) => {
819
- observerResult = result
820
- })
821
-
822
- await sleep(10)
823
-
824
- const error = new Error(`${JSON.stringify(key)} data is undefined`)
825
-
826
- expect(observerResult).toMatchObject({
827
- isError: true,
828
- error,
829
- })
830
-
831
- expect(consoleMock).toHaveBeenCalledWith(
832
- `Query data cannot be undefined. Please make sure to return a value other than undefined from your query function. Affected query key: ["${key}"]`,
833
- )
834
- unsubscribe()
835
- consoleMock.mockRestore()
836
- })
837
-
838
- it('should not retry on the server', async () => {
839
- const resetIsServer = setIsServer(true)
840
-
841
- const key = queryKey()
842
- let count = 0
843
-
844
- const observer = new QueryObserver(queryClient, {
845
- queryKey: key,
846
- queryFn: () => {
847
- count++
848
- return Promise.reject(new Error('error'))
849
- },
850
- })
851
-
852
- await observer.refetch()
853
-
854
- expect(count).toBe(1)
855
-
856
- resetIsServer()
857
- })
858
-
859
- test('constructor should call initialDataUpdatedAt if defined as a function', async () => {
860
- const key = queryKey()
861
-
862
- const initialDataUpdatedAtSpy = vi.fn()
863
-
864
- await queryClient.prefetchQuery({
865
- queryKey: key,
866
- queryFn: () => 'data',
867
- initialData: 'initial',
868
- initialDataUpdatedAt: initialDataUpdatedAtSpy,
869
- })
870
-
871
- expect(initialDataUpdatedAtSpy).toHaveBeenCalled()
872
- })
873
-
874
- test('should work with initialDataUpdatedAt set to zero', async () => {
875
- const key = queryKey()
876
-
877
- await queryClient.prefetchQuery({
878
- queryKey: key,
879
- queryFn: () => 'data',
880
- staleTime: Infinity,
881
- initialData: 'initial',
882
- initialDataUpdatedAt: 0,
883
- })
884
-
885
- expect(queryCache.find({ queryKey: key })?.state).toMatchObject({
886
- data: 'initial',
887
- status: 'success',
888
- dataUpdatedAt: 0,
889
- })
890
- })
891
-
892
- test('queries should be garbage collected even if they never fetched', async () => {
893
- const key = queryKey()
894
-
895
- queryClient.setQueryDefaults(key, { gcTime: 10 })
896
-
897
- const fn = vi.fn()
898
-
899
- const unsubscribe = queryClient.getQueryCache().subscribe(fn)
900
-
901
- queryClient.setQueryData(key, 'data')
902
-
903
- await waitFor(() =>
904
- expect(fn).toHaveBeenCalledWith(
905
- expect.objectContaining({
906
- type: 'removed',
907
- }),
908
- ),
909
- )
910
-
911
- expect(queryClient.getQueryCache().findAll()).toHaveLength(0)
912
-
913
- unsubscribe()
914
- })
915
-
916
- test('should always revert to idle state (#5958)', async () => {
917
- let mockedData = [1]
918
-
919
- const key = queryKey()
920
-
921
- const queryFn = vi
922
- .fn<
923
- (
924
- context: QueryFunctionContext<ReturnType<typeof queryKey>>,
925
- ) => Promise<unknown>
926
- >()
927
- .mockImplementation(({ signal }) => {
928
- return new Promise((resolve, reject) => {
929
- const abortListener = () => {
930
- clearTimeout(timerId)
931
- reject(signal.reason)
932
- }
933
- signal.addEventListener('abort', abortListener)
934
-
935
- const timerId = setTimeout(() => {
936
- signal.removeEventListener('abort', abortListener)
937
- resolve(mockedData.join(' - '))
938
- }, 50)
939
- })
940
- })
941
-
942
- const observer = new QueryObserver(queryClient, {
943
- queryKey: key,
944
- queryFn,
945
- })
946
- const unsubscribe = observer.subscribe(() => undefined)
947
- await sleep(60) // let it resolve
948
-
949
- mockedData = [1, 2] // update "server" state in the background
950
-
951
- queryClient.invalidateQueries({ queryKey: key })
952
- await sleep(1)
953
- queryClient.invalidateQueries({ queryKey: key })
954
- await sleep(1)
955
- unsubscribe() // unsubscribe to simulate unmount
956
-
957
- // set up a new observer to simulate a mount of new component
958
- const newObserver = new QueryObserver(queryClient, {
959
- queryKey: key,
960
- queryFn,
961
- })
962
-
963
- const spy = vi.fn()
964
- newObserver.subscribe(({ data }) => spy(data))
965
- await sleep(60) // let it resolve
966
- expect(spy).toHaveBeenCalledWith('1 - 2')
967
- })
968
-
969
- it('should have an error log when queryFn data is not serializable', async () => {
970
- const consoleMock = vi.spyOn(console, 'error')
971
-
972
- consoleMock.mockImplementation(() => undefined)
973
-
974
- const key = queryKey()
975
-
976
- const queryFn = vi.fn()
977
-
978
- const data: Array<{
979
- id: number
980
- name: string
981
- link: null | { id: number; name: string; link: unknown }
982
- }> = Array.from({ length: 5 })
983
- .fill(null)
984
- .map((_, index) => ({
985
- id: index,
986
- name: `name-${index}`,
987
- link: null,
988
- }))
989
-
990
- if (data[0] && data[1]) {
991
- data[0].link = data[1]
992
- data[1].link = data[0]
993
- }
994
-
995
- queryFn.mockImplementation(async () => {
996
- await sleep(10)
997
- return data
998
- })
999
-
1000
- await queryClient.prefetchQuery({
1001
- queryKey: key,
1002
- queryFn,
1003
- initialData: structuredClone(data),
1004
- })
1005
-
1006
- const query = queryCache.find({ queryKey: key })!
1007
-
1008
- expect(queryFn).toHaveBeenCalledTimes(1)
1009
-
1010
- expect(query.state.status).toBe('error')
1011
- expect(
1012
- query.state.error?.message.includes('Maximum call stack size exceeded'),
1013
- ).toBeTruthy()
1014
-
1015
- expect(consoleMock).toHaveBeenCalledWith(
1016
- expect.stringContaining(
1017
- 'Structural sharing requires data to be JSON serializable',
1018
- ),
1019
- )
1020
-
1021
- consoleMock.mockRestore()
1022
- })
1023
-
1024
- it('should have an error status when setData has any error inside', async () => {
1025
- const key = queryKey()
1026
-
1027
- const queryFn = vi.fn()
1028
-
1029
- queryFn.mockImplementation(async () => {
1030
- await sleep(10)
1031
-
1032
- return 'data'
1033
- })
1034
-
1035
- await queryClient.prefetchQuery({
1036
- queryKey: key,
1037
- queryFn,
1038
- structuralSharing: () => {
1039
- throw Error('Any error')
1040
- },
1041
- })
1042
-
1043
- const query = queryCache.find({ queryKey: key })!
1044
-
1045
- expect(queryFn).toHaveBeenCalledTimes(1)
1046
-
1047
- expect(query.state.status).toBe('error')
1048
- })
1049
- })