@tanstack/query-core 5.59.16 → 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.
- package/build/legacy/queriesObserver.cjs +8 -0
- package/build/legacy/queriesObserver.cjs.map +1 -1
- package/build/legacy/queriesObserver.js +8 -0
- package/build/legacy/queriesObserver.js.map +1 -1
- package/build/modern/queriesObserver.cjs +8 -0
- package/build/modern/queriesObserver.cjs.map +1 -1
- package/build/modern/queriesObserver.js +8 -0
- package/build/modern/queriesObserver.js.map +1 -1
- package/package.json +3 -2
- package/src/queriesObserver.ts +9 -0
- package/src/__tests__/OmitKeyof.test-d.ts +0 -175
- package/src/__tests__/focusManager.test.tsx +0 -163
- package/src/__tests__/hydration.test.tsx +0 -1069
- package/src/__tests__/infiniteQueryBehavior.test.tsx +0 -427
- package/src/__tests__/infiniteQueryObserver.test-d.tsx +0 -64
- package/src/__tests__/infiniteQueryObserver.test.tsx +0 -198
- package/src/__tests__/mutationCache.test.tsx +0 -376
- package/src/__tests__/mutationObserver.test.tsx +0 -326
- package/src/__tests__/mutations.test.tsx +0 -603
- package/src/__tests__/notifyManager.test.tsx +0 -85
- package/src/__tests__/onlineManager.test.tsx +0 -168
- package/src/__tests__/queriesObserver.test.tsx +0 -267
- package/src/__tests__/query.test.tsx +0 -1049
- package/src/__tests__/queryCache.test.tsx +0 -350
- package/src/__tests__/queryClient.test-d.tsx +0 -156
- package/src/__tests__/queryClient.test.tsx +0 -2031
- package/src/__tests__/queryObserver.test-d.tsx +0 -108
- package/src/__tests__/queryObserver.test.tsx +0 -1236
- package/src/__tests__/utils.test.tsx +0 -468
- 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
|
-
})
|