@tanstack/query-core 5.59.17 → 5.60.5

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.
Files changed (30) hide show
  1. package/build/legacy/queriesObserver.cjs +16 -10
  2. package/build/legacy/queriesObserver.cjs.map +1 -1
  3. package/build/legacy/queriesObserver.js +16 -10
  4. package/build/legacy/queriesObserver.js.map +1 -1
  5. package/build/modern/queriesObserver.cjs +13 -9
  6. package/build/modern/queriesObserver.cjs.map +1 -1
  7. package/build/modern/queriesObserver.js +13 -9
  8. package/build/modern/queriesObserver.js.map +1 -1
  9. package/package.json +3 -2
  10. package/src/queriesObserver.ts +21 -12
  11. package/src/__tests__/OmitKeyof.test-d.ts +0 -175
  12. package/src/__tests__/focusManager.test.tsx +0 -163
  13. package/src/__tests__/hydration.test.tsx +0 -1069
  14. package/src/__tests__/infiniteQueryBehavior.test.tsx +0 -427
  15. package/src/__tests__/infiniteQueryObserver.test-d.tsx +0 -64
  16. package/src/__tests__/infiniteQueryObserver.test.tsx +0 -198
  17. package/src/__tests__/mutationCache.test.tsx +0 -376
  18. package/src/__tests__/mutationObserver.test.tsx +0 -326
  19. package/src/__tests__/mutations.test.tsx +0 -603
  20. package/src/__tests__/notifyManager.test.tsx +0 -85
  21. package/src/__tests__/onlineManager.test.tsx +0 -168
  22. package/src/__tests__/queriesObserver.test.tsx +0 -267
  23. package/src/__tests__/query.test.tsx +0 -1049
  24. package/src/__tests__/queryCache.test.tsx +0 -350
  25. package/src/__tests__/queryClient.test-d.tsx +0 -156
  26. package/src/__tests__/queryClient.test.tsx +0 -2031
  27. package/src/__tests__/queryObserver.test-d.tsx +0 -108
  28. package/src/__tests__/queryObserver.test.tsx +0 -1236
  29. package/src/__tests__/utils.test.tsx +0 -468
  30. package/src/__tests__/utils.ts +0 -59
@@ -1,1236 +0,0 @@
1
- import {
2
- afterEach,
3
- beforeEach,
4
- describe,
5
- expect,
6
- expectTypeOf,
7
- test,
8
- vi,
9
- } from 'vitest'
10
- import { waitFor } from '@testing-library/react'
11
- import { QueryObserver, focusManager } from '..'
12
- import { createQueryClient, queryKey, sleep } from './utils'
13
- import type { QueryClient, QueryObserverResult } from '..'
14
-
15
- describe('queryObserver', () => {
16
- let queryClient: QueryClient
17
-
18
- beforeEach(() => {
19
- queryClient = createQueryClient({
20
- defaultOptions: {
21
- queries: {
22
- experimental_prefetchInRender: true,
23
- },
24
- },
25
- })
26
- queryClient.mount()
27
- })
28
-
29
- afterEach(() => {
30
- queryClient.clear()
31
- })
32
-
33
- test('should trigger a fetch when subscribed', async () => {
34
- const key = queryKey()
35
- const queryFn = vi
36
- .fn<(...args: Array<unknown>) => string>()
37
- .mockReturnValue('data')
38
- const observer = new QueryObserver(queryClient, { queryKey: key, queryFn })
39
- const unsubscribe = observer.subscribe(() => undefined)
40
- await sleep(1)
41
- unsubscribe()
42
- expect(queryFn).toHaveBeenCalledTimes(1)
43
- })
44
-
45
- test('should be able to read latest data after subscribing', async () => {
46
- const key = queryKey()
47
- queryClient.setQueryData(key, 'data')
48
- const observer = new QueryObserver(queryClient, {
49
- queryKey: key,
50
- enabled: false,
51
- })
52
-
53
- const unsubscribe = observer.subscribe(vi.fn())
54
-
55
- expect(observer.getCurrentResult()).toMatchObject({
56
- status: 'success',
57
- data: 'data',
58
- })
59
-
60
- unsubscribe()
61
- })
62
-
63
- describe('enabled is a callback that initially returns false', () => {
64
- let observer: QueryObserver<string, Error, string, string, Array<string>>
65
- let enabled: boolean
66
- let count: number
67
- let key: Array<string>
68
-
69
- beforeEach(() => {
70
- key = queryKey()
71
- count = 0
72
- enabled = false
73
-
74
- observer = new QueryObserver(queryClient, {
75
- queryKey: key,
76
- staleTime: Infinity,
77
- enabled: () => enabled,
78
- queryFn: async () => {
79
- await sleep(10)
80
- count++
81
- return 'data'
82
- },
83
- })
84
- })
85
-
86
- test('should not fetch on mount', () => {
87
- const unsubscribe = observer.subscribe(vi.fn())
88
-
89
- // Has not fetched and is not fetching since its disabled
90
- expect(count).toBe(0)
91
- expect(observer.getCurrentResult()).toMatchObject({
92
- status: 'pending',
93
- fetchStatus: 'idle',
94
- data: undefined,
95
- })
96
-
97
- unsubscribe()
98
- })
99
-
100
- test('should not be re-fetched when invalidated with refetchType: all', async () => {
101
- const unsubscribe = observer.subscribe(vi.fn())
102
-
103
- queryClient.invalidateQueries({ queryKey: key, refetchType: 'all' })
104
-
105
- // So we still expect it to not have fetched and not be fetching
106
- expect(count).toBe(0)
107
- expect(observer.getCurrentResult()).toMatchObject({
108
- status: 'pending',
109
- fetchStatus: 'idle',
110
- data: undefined,
111
- })
112
- await waitFor(() => expect(count).toBe(0))
113
-
114
- unsubscribe()
115
- })
116
-
117
- test('should still trigger a fetch when refetch is called', async () => {
118
- const unsubscribe = observer.subscribe(vi.fn())
119
-
120
- expect(enabled).toBe(false)
121
-
122
- // Not the same with explicit refetch, this will override enabled and trigger a fetch anyway
123
- observer.refetch()
124
-
125
- expect(observer.getCurrentResult()).toMatchObject({
126
- status: 'pending',
127
- fetchStatus: 'fetching',
128
- data: undefined,
129
- })
130
-
131
- await waitFor(() => expect(count).toBe(1))
132
- expect(observer.getCurrentResult()).toMatchObject({
133
- status: 'success',
134
- fetchStatus: 'idle',
135
- data: 'data',
136
- })
137
-
138
- unsubscribe()
139
- })
140
-
141
- test('should fetch if unsubscribed, then enabled returns true, and then re-subscribed', async () => {
142
- let unsubscribe = observer.subscribe(vi.fn())
143
- expect(observer.getCurrentResult()).toMatchObject({
144
- status: 'pending',
145
- fetchStatus: 'idle',
146
- data: undefined,
147
- })
148
-
149
- unsubscribe()
150
-
151
- enabled = true
152
-
153
- unsubscribe = observer.subscribe(vi.fn())
154
-
155
- expect(observer.getCurrentResult()).toMatchObject({
156
- status: 'pending',
157
- fetchStatus: 'fetching',
158
- data: undefined,
159
- })
160
-
161
- await waitFor(() => expect(count).toBe(1))
162
-
163
- unsubscribe()
164
- })
165
-
166
- test('should not be re-fetched if not subscribed to after enabled was toggled to true (fetchStatus: "idle")', async () => {
167
- const unsubscribe = observer.subscribe(vi.fn())
168
-
169
- // Toggle enabled
170
- enabled = true
171
-
172
- unsubscribe()
173
-
174
- queryClient.invalidateQueries({ queryKey: key, refetchType: 'active' })
175
-
176
- expect(observer.getCurrentResult()).toMatchObject({
177
- status: 'pending',
178
- fetchStatus: 'idle',
179
- data: undefined,
180
- })
181
- expect(count).toBe(0)
182
- })
183
-
184
- test('should not be re-fetched if not subscribed to after enabled was toggled to true (fetchStatus: "fetching")', async () => {
185
- const unsubscribe = observer.subscribe(vi.fn())
186
-
187
- // Toggle enabled
188
- enabled = true
189
-
190
- queryClient.invalidateQueries({ queryKey: key, refetchType: 'active' })
191
-
192
- expect(observer.getCurrentResult()).toMatchObject({
193
- status: 'pending',
194
- fetchStatus: 'fetching',
195
- data: undefined,
196
- })
197
- await waitFor(() => expect(count).toBe(1))
198
-
199
- unsubscribe()
200
- })
201
-
202
- test('should handle that the enabled callback updates the return value', async () => {
203
- const unsubscribe = observer.subscribe(vi.fn())
204
-
205
- // Toggle enabled
206
- enabled = true
207
-
208
- queryClient.invalidateQueries({ queryKey: key, refetchType: 'inactive' })
209
-
210
- // should not refetch since it was active and we only refetch inactive
211
- await waitFor(() => expect(count).toBe(0))
212
-
213
- queryClient.invalidateQueries({ queryKey: key, refetchType: 'active' })
214
-
215
- // should refetch since it was active and we refetch active
216
- await waitFor(() => expect(count).toBe(1))
217
-
218
- // Toggle enabled
219
- enabled = false
220
-
221
- // should not refetch since it is not active and we only refetch active
222
- queryClient.invalidateQueries({ queryKey: key, refetchType: 'active' })
223
-
224
- await waitFor(() => expect(count).toBe(1))
225
-
226
- unsubscribe()
227
- })
228
- })
229
-
230
- test('should be able to read latest data when re-subscribing (but not re-fetching)', async () => {
231
- const key = queryKey()
232
- let count = 0
233
- const observer = new QueryObserver(queryClient, {
234
- queryKey: key,
235
- staleTime: Infinity,
236
- queryFn: async () => {
237
- await sleep(10)
238
- count++
239
- return 'data'
240
- },
241
- })
242
-
243
- let unsubscribe = observer.subscribe(vi.fn())
244
-
245
- // unsubscribe before data comes in
246
- unsubscribe()
247
- expect(count).toBe(0)
248
- expect(observer.getCurrentResult()).toMatchObject({
249
- status: 'pending',
250
- fetchStatus: 'fetching',
251
- data: undefined,
252
- })
253
-
254
- await waitFor(() => expect(count).toBe(1))
255
-
256
- // re-subscribe after data comes in
257
- unsubscribe = observer.subscribe(vi.fn())
258
-
259
- expect(observer.getCurrentResult()).toMatchObject({
260
- status: 'success',
261
- data: 'data',
262
- })
263
-
264
- unsubscribe()
265
- })
266
-
267
- test('should notify when switching query', async () => {
268
- const key1 = queryKey()
269
- const key2 = queryKey()
270
- const results: Array<QueryObserverResult> = []
271
- const observer = new QueryObserver(queryClient, {
272
- queryKey: key1,
273
- queryFn: () => 1,
274
- })
275
- const unsubscribe = observer.subscribe((result) => {
276
- results.push(result)
277
- })
278
- await sleep(1)
279
- observer.setOptions({ queryKey: key2, queryFn: () => 2 })
280
- await sleep(1)
281
- unsubscribe()
282
- expect(results.length).toBe(4)
283
- expect(results[0]).toMatchObject({ data: undefined, status: 'pending' })
284
- expect(results[1]).toMatchObject({ data: 1, status: 'success' })
285
- expect(results[2]).toMatchObject({ data: undefined, status: 'pending' })
286
- expect(results[3]).toMatchObject({ data: 2, status: 'success' })
287
- })
288
-
289
- test('should be able to fetch with a selector', async () => {
290
- const key = queryKey()
291
- const observer = new QueryObserver(queryClient, {
292
- queryKey: key,
293
- queryFn: () => ({ count: 1 }),
294
- select: (data) => ({ myCount: data.count }),
295
- })
296
- let observerResult
297
- const unsubscribe = observer.subscribe((result) => {
298
- expectTypeOf(result).toEqualTypeOf<
299
- QueryObserverResult<{ myCount: number }>
300
- >()
301
- observerResult = result
302
- })
303
- await sleep(1)
304
- unsubscribe()
305
- expect(observerResult).toMatchObject({ data: { myCount: 1 } })
306
- })
307
-
308
- test('should be able to fetch with a selector using the fetch method', async () => {
309
- const key = queryKey()
310
- const observer = new QueryObserver(queryClient, {
311
- queryKey: key,
312
- queryFn: () => ({ count: 1 }),
313
- select: (data) => ({ myCount: data.count }),
314
- })
315
- const observerResult = await observer.refetch()
316
- expectTypeOf(observerResult.data).toEqualTypeOf<
317
- { myCount: number } | undefined
318
- >()
319
- expect(observerResult.data).toMatchObject({ myCount: 1 })
320
- })
321
-
322
- test('should be able to fetch with a selector and object syntax', async () => {
323
- const key = queryKey()
324
- const observer = new QueryObserver(queryClient, {
325
- queryKey: key,
326
- queryFn: () => ({ count: 1 }),
327
- select: (data) => ({ myCount: data.count }),
328
- })
329
- let observerResult
330
- const unsubscribe = observer.subscribe((result) => {
331
- observerResult = result
332
- })
333
- await sleep(1)
334
- unsubscribe()
335
- expect(observerResult).toMatchObject({ data: { myCount: 1 } })
336
- })
337
-
338
- test('should run the selector again if the data changed', async () => {
339
- const key = queryKey()
340
- let count = 0
341
- const observer = new QueryObserver(queryClient, {
342
- queryKey: key,
343
- queryFn: () => ({ count }),
344
- select: (data) => {
345
- count++
346
- return { myCount: data.count }
347
- },
348
- })
349
- const observerResult1 = await observer.refetch()
350
- const observerResult2 = await observer.refetch()
351
- expect(count).toBe(2)
352
- expect(observerResult1.data).toMatchObject({ myCount: 0 })
353
- expect(observerResult2.data).toMatchObject({ myCount: 1 })
354
- })
355
-
356
- test('should run the selector again if the selector changed', async () => {
357
- const key = queryKey()
358
- let count = 0
359
- const results: Array<QueryObserverResult> = []
360
- const queryFn = () => ({ count: 1 })
361
- const select1 = (data: ReturnType<typeof queryFn>) => {
362
- count++
363
- return { myCount: data.count }
364
- }
365
- const select2 = (_data: ReturnType<typeof queryFn>) => {
366
- count++
367
- return { myCount: 99 }
368
- }
369
- const observer = new QueryObserver(queryClient, {
370
- queryKey: key,
371
- queryFn,
372
- select: select1,
373
- })
374
- const unsubscribe = observer.subscribe((result) => {
375
- results.push(result)
376
- })
377
- await sleep(1)
378
- observer.setOptions({
379
- queryKey: key,
380
- queryFn,
381
- select: select2,
382
- })
383
- await sleep(1)
384
- await observer.refetch()
385
- unsubscribe()
386
- expect(count).toBe(2)
387
- expect(results.length).toBe(5)
388
- expect(results[0]).toMatchObject({
389
- status: 'pending',
390
- isFetching: true,
391
- data: undefined,
392
- })
393
- expect(results[1]).toMatchObject({
394
- status: 'success',
395
- isFetching: false,
396
- data: { myCount: 1 },
397
- })
398
- expect(results[2]).toMatchObject({
399
- status: 'success',
400
- isFetching: false,
401
- data: { myCount: 99 },
402
- })
403
- expect(results[3]).toMatchObject({
404
- status: 'success',
405
- isFetching: true,
406
- data: { myCount: 99 },
407
- })
408
- expect(results[4]).toMatchObject({
409
- status: 'success',
410
- isFetching: false,
411
- data: { myCount: 99 },
412
- })
413
- })
414
-
415
- test('should not run the selector again if the data and selector did not change', async () => {
416
- const key = queryKey()
417
- let count = 0
418
- const results: Array<QueryObserverResult> = []
419
- const queryFn = () => ({ count: 1 })
420
- const select = (data: ReturnType<typeof queryFn>) => {
421
- count++
422
- return { myCount: data.count }
423
- }
424
- const observer = new QueryObserver(queryClient, {
425
- queryKey: key,
426
- queryFn,
427
- select,
428
- })
429
- const unsubscribe = observer.subscribe((result) => {
430
- results.push(result)
431
- })
432
- await sleep(1)
433
- observer.setOptions({
434
- queryKey: key,
435
- queryFn,
436
- select,
437
- })
438
- await sleep(1)
439
- await observer.refetch()
440
- unsubscribe()
441
- expect(count).toBe(1)
442
- expect(results.length).toBe(4)
443
- expect(results[0]).toMatchObject({
444
- status: 'pending',
445
- isFetching: true,
446
- data: undefined,
447
- })
448
- expect(results[1]).toMatchObject({
449
- status: 'success',
450
- isFetching: false,
451
- data: { myCount: 1 },
452
- })
453
- expect(results[2]).toMatchObject({
454
- status: 'success',
455
- isFetching: true,
456
- data: { myCount: 1 },
457
- })
458
- expect(results[3]).toMatchObject({
459
- status: 'success',
460
- isFetching: false,
461
- data: { myCount: 1 },
462
- })
463
- })
464
-
465
- test('should not run the selector again if the data did not change', async () => {
466
- const key = queryKey()
467
- let count = 0
468
- const observer = new QueryObserver(queryClient, {
469
- queryKey: key,
470
- queryFn: () => ({ count: 1 }),
471
- select: (data) => {
472
- count++
473
- return { myCount: data.count }
474
- },
475
- })
476
- const observerResult1 = await observer.refetch()
477
- const observerResult2 = await observer.refetch()
478
- expect(count).toBe(1)
479
- expect(observerResult1.data).toMatchObject({ myCount: 1 })
480
- expect(observerResult2.data).toMatchObject({ myCount: 1 })
481
- })
482
-
483
- test('should always run the selector again if selector throws an error and selector is not referentially stable', async () => {
484
- const key = queryKey()
485
- const results: Array<QueryObserverResult> = []
486
- const queryFn = async () => {
487
- await sleep(10)
488
- return { count: 1 }
489
- }
490
- const observer = new QueryObserver(queryClient, {
491
- queryKey: key,
492
- queryFn,
493
- select: () => {
494
- throw new Error('selector error')
495
- },
496
- })
497
- const unsubscribe = observer.subscribe((result) => {
498
- results.push(result)
499
- })
500
- await sleep(50)
501
- await observer.refetch()
502
- unsubscribe()
503
- expect(results[0]).toMatchObject({
504
- status: 'pending',
505
- isFetching: true,
506
- data: undefined,
507
- })
508
- expect(results[1]).toMatchObject({
509
- status: 'error',
510
- isFetching: false,
511
- data: undefined,
512
- })
513
- expect(results[2]).toMatchObject({
514
- status: 'error',
515
- isFetching: true,
516
- data: undefined,
517
- })
518
- expect(results[3]).toMatchObject({
519
- status: 'error',
520
- isFetching: false,
521
- data: undefined,
522
- })
523
- })
524
-
525
- test('should return stale data if selector throws an error', async () => {
526
- const key = queryKey()
527
- const results: Array<QueryObserverResult> = []
528
- let shouldError = false
529
- const error = new Error('select error')
530
- const observer = new QueryObserver(queryClient, {
531
- queryKey: key,
532
- retry: 0,
533
- queryFn: async () => {
534
- await sleep(10)
535
- return shouldError ? 2 : 1
536
- },
537
- select: (num) => {
538
- if (shouldError) {
539
- throw error
540
- }
541
- shouldError = true
542
- return String(num)
543
- },
544
- })
545
-
546
- const unsubscribe = observer.subscribe((result) => {
547
- results.push(result)
548
- })
549
- await sleep(50)
550
- await observer.refetch()
551
- unsubscribe()
552
-
553
- expect(results[0]).toMatchObject({
554
- status: 'pending',
555
- isFetching: true,
556
- data: undefined,
557
- error: null,
558
- })
559
- expect(results[1]).toMatchObject({
560
- status: 'success',
561
- isFetching: false,
562
- data: '1',
563
- error: null,
564
- })
565
- expect(results[2]).toMatchObject({
566
- status: 'success',
567
- isFetching: true,
568
- data: '1',
569
- error: null,
570
- })
571
- expect(results[3]).toMatchObject({
572
- status: 'error',
573
- isFetching: false,
574
- data: '1',
575
- error,
576
- })
577
- })
578
-
579
- test('should structurally share the selector', async () => {
580
- const key = queryKey()
581
- let count = 0
582
- const observer = new QueryObserver(queryClient, {
583
- queryKey: key,
584
- queryFn: () => ({ count: ++count }),
585
- select: () => ({ myCount: 1 }),
586
- })
587
- const observerResult1 = await observer.refetch()
588
- const observerResult2 = await observer.refetch()
589
- expect(count).toBe(2)
590
- expect(observerResult1.data).toBe(observerResult2.data)
591
- })
592
-
593
- test('should not trigger a fetch when subscribed and disabled', async () => {
594
- const key = queryKey()
595
- const queryFn = vi
596
- .fn<(...args: Array<unknown>) => string>()
597
- .mockReturnValue('data')
598
- const observer = new QueryObserver(queryClient, {
599
- queryKey: key,
600
- queryFn,
601
- enabled: false,
602
- })
603
- const unsubscribe = observer.subscribe(() => undefined)
604
- await sleep(1)
605
- unsubscribe()
606
- expect(queryFn).toHaveBeenCalledTimes(0)
607
- })
608
-
609
- test('should not trigger a fetch when subscribed and disabled by callback', async () => {
610
- const key = queryKey()
611
- const queryFn = vi
612
- .fn<(...args: Array<unknown>) => string>()
613
- .mockReturnValue('data')
614
- const observer = new QueryObserver(queryClient, {
615
- queryKey: key,
616
- queryFn,
617
- enabled: () => false,
618
- })
619
- const unsubscribe = observer.subscribe(() => undefined)
620
- await sleep(1)
621
- unsubscribe()
622
- expect(queryFn).toHaveBeenCalledTimes(0)
623
- })
624
-
625
- test('should not trigger a fetch when not subscribed', async () => {
626
- const key = queryKey()
627
- const queryFn = vi
628
- .fn<(...args: Array<unknown>) => string>()
629
- .mockReturnValue('data')
630
- new QueryObserver(queryClient, { queryKey: key, queryFn })
631
- await sleep(1)
632
- expect(queryFn).toHaveBeenCalledTimes(0)
633
- })
634
-
635
- test('should be able to watch a query without defining a query function', async () => {
636
- const key = queryKey()
637
- const queryFn = vi
638
- .fn<(...args: Array<unknown>) => string>()
639
- .mockReturnValue('data')
640
- const callback = vi.fn()
641
- const observer = new QueryObserver(queryClient, {
642
- queryKey: key,
643
- enabled: false,
644
- })
645
- const unsubscribe = observer.subscribe(callback)
646
- await queryClient.fetchQuery({ queryKey: key, queryFn })
647
- unsubscribe()
648
- expect(queryFn).toHaveBeenCalledTimes(1)
649
- expect(callback).toHaveBeenCalledTimes(2)
650
- })
651
-
652
- test('should accept unresolved query config in update function', async () => {
653
- const key = queryKey()
654
- const queryFn = vi
655
- .fn<(...args: Array<unknown>) => string>()
656
- .mockReturnValue('data')
657
- const observer = new QueryObserver(queryClient, {
658
- queryKey: key,
659
- enabled: false,
660
- })
661
- const results: Array<QueryObserverResult<unknown>> = []
662
- const unsubscribe = observer.subscribe((x) => {
663
- results.push(x)
664
- })
665
- observer.setOptions({ queryKey: key, enabled: false, staleTime: 10 })
666
- await queryClient.fetchQuery({ queryKey: key, queryFn })
667
- await sleep(20)
668
- unsubscribe()
669
- expect(queryFn).toHaveBeenCalledTimes(1)
670
- expect(results.length).toBe(2)
671
- expect(results[0]).toMatchObject({ isStale: false, data: undefined })
672
- expect(results[1]).toMatchObject({ isStale: false, data: 'data' })
673
- })
674
-
675
- test('should be able to handle multiple subscribers', async () => {
676
- const key = queryKey()
677
- const queryFn = vi
678
- .fn<(...args: Array<unknown>) => string>()
679
- .mockReturnValue('data')
680
- const observer = new QueryObserver<string>(queryClient, {
681
- queryKey: key,
682
- enabled: false,
683
- })
684
- const results1: Array<QueryObserverResult<string>> = []
685
- const results2: Array<QueryObserverResult<string>> = []
686
- const unsubscribe1 = observer.subscribe((x) => {
687
- results1.push(x)
688
- })
689
- const unsubscribe2 = observer.subscribe((x) => {
690
- results2.push(x)
691
- })
692
- await queryClient.fetchQuery({ queryKey: key, queryFn })
693
- await sleep(50)
694
- unsubscribe1()
695
- unsubscribe2()
696
- expect(queryFn).toHaveBeenCalledTimes(1)
697
- expect(results1.length).toBe(2)
698
- expect(results2.length).toBe(2)
699
- expect(results1[0]).toMatchObject({ data: undefined })
700
- expect(results1[1]).toMatchObject({ data: 'data' })
701
- expect(results2[0]).toMatchObject({ data: undefined })
702
- expect(results2[1]).toMatchObject({ data: 'data' })
703
- })
704
-
705
- test('should stop retry when unsubscribing', async () => {
706
- const key = queryKey()
707
- let count = 0
708
- const observer = new QueryObserver(queryClient, {
709
- queryKey: key,
710
- queryFn: () => {
711
- count++
712
- return Promise.reject<unknown>('reject')
713
- },
714
- retry: 10,
715
- retryDelay: 50,
716
- })
717
- const unsubscribe = observer.subscribe(() => undefined)
718
- await sleep(70)
719
- unsubscribe()
720
- await sleep(200)
721
- expect(count).toBe(2)
722
- })
723
-
724
- test('should clear interval when unsubscribing to a refetchInterval query', async () => {
725
- const key = queryKey()
726
- let count = 0
727
-
728
- const fetchData = () => {
729
- count++
730
- return Promise.resolve('data')
731
- }
732
- const observer = new QueryObserver(queryClient, {
733
- queryKey: key,
734
- queryFn: fetchData,
735
- gcTime: 0,
736
- refetchInterval: 10,
737
- })
738
- const unsubscribe = observer.subscribe(() => undefined)
739
- expect(count).toBe(1)
740
- await sleep(15)
741
- expect(count).toBe(2)
742
- unsubscribe()
743
- await sleep(10)
744
- expect(queryClient.getQueryCache().find({ queryKey: key })).toBeUndefined()
745
- expect(count).toBe(2)
746
- })
747
-
748
- test('uses placeholderData as non-cache data when pending a query with no data', async () => {
749
- const key = queryKey()
750
- const observer = new QueryObserver(queryClient, {
751
- queryKey: key,
752
- queryFn: () => 'data',
753
- placeholderData: 'placeholder',
754
- })
755
-
756
- expect(observer.getCurrentResult()).toMatchObject({
757
- status: 'success',
758
- data: 'placeholder',
759
- })
760
-
761
- const results: Array<QueryObserverResult<unknown>> = []
762
-
763
- const unsubscribe = observer.subscribe((x) => {
764
- results.push(x)
765
- })
766
-
767
- await sleep(10)
768
- unsubscribe()
769
-
770
- expect(results.length).toBe(2)
771
- expect(results[0]).toMatchObject({ status: 'success', data: 'placeholder' })
772
- expect(results[1]).toMatchObject({ status: 'success', data: 'data' })
773
- })
774
-
775
- test('should structurally share placeholder data', async () => {
776
- const key = queryKey()
777
- const observer = new QueryObserver(queryClient, {
778
- queryKey: key,
779
- enabled: false,
780
- queryFn: () => 'data',
781
- placeholderData: {},
782
- })
783
-
784
- const firstData = observer.getCurrentResult().data
785
-
786
- observer.setOptions({ queryKey: key, placeholderData: {} })
787
-
788
- const secondData = observer.getCurrentResult().data
789
-
790
- expect(firstData).toBe(secondData)
791
- })
792
-
793
- test('should throw an error if enabled option type is not valid', async () => {
794
- const key = queryKey()
795
-
796
- expect(
797
- () =>
798
- new QueryObserver(queryClient, {
799
- queryKey: key,
800
- queryFn: () => 'data',
801
- // @ts-expect-error
802
- enabled: null,
803
- }),
804
- ).toThrowError('Expected enabled to be a boolean')
805
- })
806
-
807
- test('getCurrentQuery should return the current query', async () => {
808
- const key = queryKey()
809
-
810
- const observer = new QueryObserver(queryClient, {
811
- queryKey: key,
812
- queryFn: () => 'data',
813
- })
814
-
815
- expect(observer.getCurrentQuery().queryKey).toEqual(key)
816
- })
817
-
818
- test('should throw an error if throwOnError option is true', async () => {
819
- const key = queryKey()
820
-
821
- const observer = new QueryObserver(queryClient, {
822
- queryKey: key,
823
- queryFn: () => Promise.reject<unknown>('error'),
824
- retry: false,
825
- })
826
-
827
- let error: string | null = null
828
- try {
829
- await observer.refetch({ throwOnError: true })
830
- } catch (err) {
831
- error = err as string
832
- }
833
-
834
- expect(error).toEqual('error')
835
- })
836
-
837
- test('should not refetch in background if refetchIntervalInBackground is false', async () => {
838
- const key = queryKey()
839
- const queryFn = vi
840
- .fn<(...args: Array<unknown>) => string>()
841
- .mockReturnValue('data')
842
-
843
- focusManager.setFocused(false)
844
- const observer = new QueryObserver(queryClient, {
845
- queryKey: key,
846
- queryFn,
847
- refetchIntervalInBackground: false,
848
- refetchInterval: 10,
849
- })
850
-
851
- const unsubscribe = observer.subscribe(() => undefined)
852
- await sleep(30)
853
-
854
- expect(queryFn).toHaveBeenCalledTimes(1)
855
-
856
- // Clean-up
857
- unsubscribe()
858
- focusManager.setFocused(true)
859
- })
860
-
861
- test('should not use replaceEqualDeep for select value when structuralSharing option is true', async () => {
862
- const key = queryKey()
863
-
864
- const data = { value: 'data' }
865
- const selectedData = { value: 'data' }
866
-
867
- const observer = new QueryObserver(queryClient, {
868
- queryKey: key,
869
- queryFn: () => data,
870
- select: () => data,
871
- })
872
-
873
- const unsubscribe = observer.subscribe(() => undefined)
874
-
875
- await sleep(10)
876
- expect(observer.getCurrentResult().data).toBe(data)
877
-
878
- observer.setOptions({
879
- queryKey: key,
880
- queryFn: () => data,
881
- structuralSharing: false,
882
- select: () => selectedData,
883
- })
884
-
885
- await observer.refetch()
886
- expect(observer.getCurrentResult().data).toBe(selectedData)
887
-
888
- unsubscribe()
889
- })
890
-
891
- test('should not use replaceEqualDeep for select value when structuralSharing option is true and placeholderData is defined', () => {
892
- const key = queryKey()
893
-
894
- const data = { value: 'data' }
895
- const selectedData1 = { value: 'data' }
896
- const selectedData2 = { value: 'data' }
897
- const placeholderData1 = { value: 'data' }
898
- const placeholderData2 = { value: 'data' }
899
-
900
- const observer = new QueryObserver(queryClient, {
901
- queryKey: key,
902
- queryFn: () => data,
903
- select: () => data,
904
- })
905
-
906
- observer.setOptions({
907
- queryKey: key,
908
- queryFn: () => data,
909
- select: () => {
910
- return selectedData1
911
- },
912
- placeholderData: placeholderData1,
913
- })
914
-
915
- observer.setOptions({
916
- queryKey: key,
917
- queryFn: () => data,
918
- select: () => {
919
- return selectedData2
920
- },
921
- placeholderData: placeholderData2,
922
- structuralSharing: false,
923
- })
924
-
925
- expect(observer.getCurrentResult().data).toBe(selectedData2)
926
- })
927
-
928
- test('should not use an undefined value returned by select as placeholderData', () => {
929
- const key = queryKey()
930
-
931
- const data = { value: 'data' }
932
- const selectedData = { value: 'data' }
933
- const placeholderData1 = { value: 'data' }
934
- const placeholderData2 = { value: 'data' }
935
-
936
- const observer = new QueryObserver(queryClient, {
937
- queryKey: key,
938
- queryFn: () => data,
939
- select: () => data,
940
- })
941
-
942
- observer.setOptions({
943
- queryKey: key,
944
- queryFn: () => data,
945
- select: () => {
946
- return selectedData
947
- },
948
- placeholderData: placeholderData1,
949
- })
950
-
951
- expect(observer.getCurrentResult().isPlaceholderData).toBe(true)
952
-
953
- observer.setOptions({
954
- queryKey: key,
955
- queryFn: () => data,
956
- // @ts-expect-error
957
- select: () => undefined,
958
- placeholderData: placeholderData2,
959
- })
960
-
961
- expect(observer.getCurrentResult().isPlaceholderData).toBe(false)
962
- })
963
-
964
- test('should pass the correct previous queryKey (from prevQuery) to placeholderData function params with select', async () => {
965
- const results: Array<QueryObserverResult> = []
966
- const keys: Array<ReadonlyArray<unknown> | null> = []
967
-
968
- const key1 = queryKey()
969
- const key2 = queryKey()
970
-
971
- const data1 = { value: 'data1' }
972
- const data2 = { value: 'data2' }
973
-
974
- const observer = new QueryObserver(queryClient, {
975
- queryKey: key1,
976
- queryFn: () => data1,
977
- placeholderData: (prev, prevQuery) => {
978
- keys.push(prevQuery?.queryKey || null)
979
- return prev
980
- },
981
- select: (data) => data.value,
982
- })
983
-
984
- const unsubscribe = observer.subscribe((result) => {
985
- results.push(result)
986
- })
987
-
988
- await sleep(1)
989
-
990
- observer.setOptions({
991
- queryKey: key2,
992
- queryFn: () => data2,
993
- placeholderData: (prev, prevQuery) => {
994
- keys.push(prevQuery?.queryKey || null)
995
- return prev
996
- },
997
- select: (data) => data.value,
998
- })
999
-
1000
- await sleep(1)
1001
- unsubscribe()
1002
- expect(results.length).toBe(4)
1003
- expect(keys.length).toBe(3)
1004
- expect(keys[0]).toBe(null) // First Query - status: 'pending', fetchStatus: 'idle'
1005
- expect(keys[1]).toBe(null) // First Query - status: 'pending', fetchStatus: 'fetching'
1006
- expect(keys[2]).toBe(key1) // Second Query - status: 'pending', fetchStatus: 'fetching'
1007
-
1008
- expect(results[0]).toMatchObject({
1009
- data: undefined,
1010
- status: 'pending',
1011
- fetchStatus: 'fetching',
1012
- }) // Initial fetch
1013
- expect(results[1]).toMatchObject({
1014
- data: 'data1',
1015
- status: 'success',
1016
- fetchStatus: 'idle',
1017
- }) // Successful fetch
1018
- expect(results[2]).toMatchObject({
1019
- data: 'data1',
1020
- status: 'success',
1021
- fetchStatus: 'fetching',
1022
- }) // Fetch for new key, but using previous data as placeholder
1023
- expect(results[3]).toMatchObject({
1024
- data: 'data2',
1025
- status: 'success',
1026
- fetchStatus: 'idle',
1027
- }) // Successful fetch for new key
1028
- })
1029
-
1030
- test('should pass the correct previous data to placeholderData function params when select function is used in conjunction', async () => {
1031
- const results: Array<QueryObserverResult> = []
1032
-
1033
- const key1 = queryKey()
1034
- const key2 = queryKey()
1035
-
1036
- const data1 = { value: 'data1' }
1037
- const data2 = { value: 'data2' }
1038
-
1039
- const observer = new QueryObserver(queryClient, {
1040
- queryKey: key1,
1041
- queryFn: () => data1,
1042
- placeholderData: (prev) => prev,
1043
- select: (data) => data.value,
1044
- })
1045
-
1046
- const unsubscribe = observer.subscribe((result) => {
1047
- results.push(result)
1048
- })
1049
-
1050
- await sleep(1)
1051
-
1052
- observer.setOptions({
1053
- queryKey: key2,
1054
- queryFn: () => data2,
1055
- placeholderData: (prev) => prev,
1056
- select: (data) => data.value,
1057
- })
1058
-
1059
- await sleep(1)
1060
- unsubscribe()
1061
-
1062
- expect(results.length).toBe(4)
1063
- expect(results[0]).toMatchObject({
1064
- data: undefined,
1065
- status: 'pending',
1066
- fetchStatus: 'fetching',
1067
- }) // Initial fetch
1068
- expect(results[1]).toMatchObject({
1069
- data: 'data1',
1070
- status: 'success',
1071
- fetchStatus: 'idle',
1072
- }) // Successful fetch
1073
- expect(results[2]).toMatchObject({
1074
- data: 'data1',
1075
- status: 'success',
1076
- fetchStatus: 'fetching',
1077
- }) // Fetch for new key, but using previous data as placeholder
1078
- expect(results[3]).toMatchObject({
1079
- data: 'data2',
1080
- status: 'success',
1081
- fetchStatus: 'idle',
1082
- }) // Successful fetch for new key
1083
- })
1084
-
1085
- test('setOptions should notify cache listeners', async () => {
1086
- const key = queryKey()
1087
-
1088
- const observer = new QueryObserver(queryClient, {
1089
- queryKey: key,
1090
- enabled: false,
1091
- })
1092
-
1093
- const spy = vi.fn()
1094
- const unsubscribe = queryClient.getQueryCache().subscribe(spy)
1095
- observer.setOptions({ queryKey: key, enabled: false, refetchInterval: 10 })
1096
-
1097
- expect(spy).toHaveBeenCalledTimes(1)
1098
- expect(spy).toHaveBeenCalledWith(
1099
- expect.objectContaining({ type: 'observerOptionsUpdated' }),
1100
- )
1101
-
1102
- unsubscribe()
1103
- })
1104
-
1105
- test('disabled observers should not be stale', async () => {
1106
- const key = queryKey()
1107
-
1108
- const observer = new QueryObserver(queryClient, {
1109
- queryKey: key,
1110
- enabled: false,
1111
- })
1112
-
1113
- const result = observer.getCurrentResult()
1114
- expect(result.isStale).toBe(false)
1115
- })
1116
-
1117
- test('should allow staleTime as a function', async () => {
1118
- const key = queryKey()
1119
- const observer = new QueryObserver(queryClient, {
1120
- queryKey: key,
1121
- queryFn: async () => {
1122
- await sleep(5)
1123
- return {
1124
- data: 'data',
1125
- staleTime: 20,
1126
- }
1127
- },
1128
- staleTime: (query) => query.state.data?.staleTime ?? 0,
1129
- })
1130
- const results: Array<QueryObserverResult<unknown>> = []
1131
- const unsubscribe = observer.subscribe((x) => {
1132
- if (x.data) {
1133
- results.push(x)
1134
- }
1135
- })
1136
-
1137
- await waitFor(() => expect(results[0]?.isStale).toBe(false))
1138
- await waitFor(() => expect(results[1]?.isStale).toBe(true))
1139
-
1140
- unsubscribe()
1141
- })
1142
-
1143
- test('should return a promise that resolves when data is present', async () => {
1144
- const results: Array<QueryObserverResult> = []
1145
- const key = queryKey()
1146
- let count = 0
1147
- const observer = new QueryObserver(queryClient, {
1148
- queryKey: key,
1149
- queryFn: () => {
1150
- if (++count > 9) {
1151
- return Promise.resolve('data')
1152
- }
1153
- throw new Error('rejected')
1154
- },
1155
- retry: 10,
1156
- retryDelay: 0,
1157
- })
1158
- const unsubscribe = observer.subscribe(() => {
1159
- results.push(observer.getCurrentResult())
1160
- })
1161
-
1162
- await waitFor(() => {
1163
- expect(results.at(-1)?.data).toBe('data')
1164
- })
1165
-
1166
- const numberOfUniquePromises = new Set(
1167
- results.map((result) => result.promise),
1168
- ).size
1169
- expect(numberOfUniquePromises).toBe(1)
1170
-
1171
- unsubscribe()
1172
- })
1173
-
1174
- test('should return a new promise after recovering from an error', async () => {
1175
- const results: Array<QueryObserverResult> = []
1176
- const key = queryKey()
1177
-
1178
- let succeeds = false
1179
- let idx = 0
1180
- const observer = new QueryObserver(queryClient, {
1181
- queryKey: key,
1182
- queryFn: () => {
1183
- if (succeeds) {
1184
- return Promise.resolve('data')
1185
- }
1186
- throw new Error(`rejected #${++idx}`)
1187
- },
1188
- retry: 5,
1189
- retryDelay: 0,
1190
- })
1191
- const unsubscribe = observer.subscribe(() => {
1192
- results.push(observer.getCurrentResult())
1193
- })
1194
-
1195
- await waitFor(() => {
1196
- expect(results.at(-1)?.status).toBe('error')
1197
- })
1198
-
1199
- expect(
1200
- results.every((result) => result.promise === results[0]!.promise),
1201
- ).toBe(true)
1202
-
1203
- {
1204
- // fail again
1205
- const lengthBefore = results.length
1206
- observer.refetch()
1207
- await waitFor(() => {
1208
- expect(results.length).toBeGreaterThan(lengthBefore)
1209
- expect(results.at(-1)?.status).toBe('error')
1210
- })
1211
-
1212
- const numberOfUniquePromises = new Set(
1213
- results.map((result) => result.promise),
1214
- ).size
1215
-
1216
- expect(numberOfUniquePromises).toBe(2)
1217
- }
1218
- {
1219
- // succeed
1220
- succeeds = true
1221
- observer.refetch()
1222
-
1223
- await waitFor(() => {
1224
- results.at(-1)?.status === 'success'
1225
- })
1226
-
1227
- const numberOfUniquePromises = new Set(
1228
- results.map((result) => result.promise),
1229
- ).size
1230
-
1231
- expect(numberOfUniquePromises).toBe(3)
1232
- }
1233
-
1234
- unsubscribe()
1235
- })
1236
- })