@tanstack/query-core 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cjs/focusManager.js +101 -0
- package/build/cjs/focusManager.js.map +1 -0
- package/build/cjs/hydration.js +112 -0
- package/build/cjs/hydration.js.map +1 -0
- package/build/cjs/index.js +51 -0
- package/build/cjs/index.js.map +1 -0
- package/build/cjs/infiniteQueryBehavior.js +160 -0
- package/build/cjs/infiniteQueryBehavior.js.map +1 -0
- package/build/cjs/infiniteQueryObserver.js +92 -0
- package/build/cjs/infiniteQueryObserver.js.map +1 -0
- package/build/cjs/logger.js +18 -0
- package/build/cjs/logger.js.map +1 -0
- package/build/cjs/mutation.js +258 -0
- package/build/cjs/mutation.js.map +1 -0
- package/build/cjs/mutationCache.js +99 -0
- package/build/cjs/mutationCache.js.map +1 -0
- package/build/cjs/mutationObserver.js +130 -0
- package/build/cjs/mutationObserver.js.map +1 -0
- package/build/cjs/notifyManager.js +114 -0
- package/build/cjs/notifyManager.js.map +1 -0
- package/build/cjs/onlineManager.js +100 -0
- package/build/cjs/onlineManager.js.map +1 -0
- package/build/cjs/queriesObserver.js +170 -0
- package/build/cjs/queriesObserver.js.map +1 -0
- package/build/cjs/query.js +474 -0
- package/build/cjs/query.js.map +1 -0
- package/build/cjs/queryCache.js +140 -0
- package/build/cjs/queryCache.js.map +1 -0
- package/build/cjs/queryClient.js +357 -0
- package/build/cjs/queryClient.js.map +1 -0
- package/build/cjs/queryObserver.js +521 -0
- package/build/cjs/queryObserver.js.map +1 -0
- package/build/cjs/removable.js +47 -0
- package/build/cjs/removable.js.map +1 -0
- package/build/cjs/retryer.js +177 -0
- package/build/cjs/retryer.js.map +1 -0
- package/build/cjs/subscribable.js +43 -0
- package/build/cjs/subscribable.js.map +1 -0
- package/build/cjs/utils.js +356 -0
- package/build/cjs/utils.js.map +1 -0
- package/build/esm/index.js +3077 -0
- package/build/esm/index.js.map +1 -0
- package/build/stats-html.html +2689 -0
- package/build/umd/index.development.js +3106 -0
- package/build/umd/index.development.js.map +1 -0
- package/build/umd/index.production.js +12 -0
- package/build/umd/index.production.js.map +1 -0
- package/package.json +25 -0
- package/src/focusManager.ts +89 -0
- package/src/hydration.ts +164 -0
- package/src/index.ts +35 -0
- package/src/infiniteQueryBehavior.ts +214 -0
- package/src/infiniteQueryObserver.ts +159 -0
- package/src/logger.native.ts +11 -0
- package/src/logger.ts +9 -0
- package/src/mutation.ts +349 -0
- package/src/mutationCache.ts +157 -0
- package/src/mutationObserver.ts +195 -0
- package/src/notifyManager.ts +96 -0
- package/src/onlineManager.ts +89 -0
- package/src/queriesObserver.ts +211 -0
- package/src/query.ts +612 -0
- package/src/queryCache.ts +206 -0
- package/src/queryClient.ts +716 -0
- package/src/queryObserver.ts +748 -0
- package/src/removable.ts +37 -0
- package/src/retryer.ts +215 -0
- package/src/subscribable.ts +33 -0
- package/src/tests/focusManager.test.tsx +155 -0
- package/src/tests/hydration.test.tsx +429 -0
- package/src/tests/infiniteQueryBehavior.test.tsx +124 -0
- package/src/tests/infiniteQueryObserver.test.tsx +64 -0
- package/src/tests/mutationCache.test.tsx +260 -0
- package/src/tests/mutationObserver.test.tsx +75 -0
- package/src/tests/mutations.test.tsx +363 -0
- package/src/tests/notifyManager.test.tsx +51 -0
- package/src/tests/onlineManager.test.tsx +148 -0
- package/src/tests/queriesObserver.test.tsx +330 -0
- package/src/tests/query.test.tsx +888 -0
- package/src/tests/queryCache.test.tsx +236 -0
- package/src/tests/queryClient.test.tsx +1435 -0
- package/src/tests/queryObserver.test.tsx +802 -0
- package/src/tests/utils.test.tsx +360 -0
- package/src/types.ts +705 -0
- package/src/utils.ts +435 -0
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createQueryClient,
|
|
3
|
+
executeMutation,
|
|
4
|
+
mockNavigatorOnLine,
|
|
5
|
+
sleep,
|
|
6
|
+
} from '../../../../tests/utils'
|
|
7
|
+
import { QueryCache } from '../queryCache'
|
|
8
|
+
import { dehydrate, hydrate } from '../hydration'
|
|
9
|
+
|
|
10
|
+
async function fetchData<TData>(value: TData, ms?: number): Promise<TData> {
|
|
11
|
+
await sleep(ms || 0)
|
|
12
|
+
return value
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe('dehydration and rehydration', () => {
|
|
16
|
+
test('should work with serializeable values', async () => {
|
|
17
|
+
const queryCache = new QueryCache()
|
|
18
|
+
const queryClient = createQueryClient({ queryCache })
|
|
19
|
+
await queryClient.prefetchQuery(['string'], () => fetchData('string'))
|
|
20
|
+
await queryClient.prefetchQuery(['number'], () => fetchData(1))
|
|
21
|
+
await queryClient.prefetchQuery(['boolean'], () => fetchData(true))
|
|
22
|
+
await queryClient.prefetchQuery(['null'], () => fetchData(null))
|
|
23
|
+
await queryClient.prefetchQuery(['array'], () => fetchData(['string', 0]))
|
|
24
|
+
await queryClient.prefetchQuery(['nested'], () =>
|
|
25
|
+
fetchData({ key: [{ nestedKey: 1 }] }),
|
|
26
|
+
)
|
|
27
|
+
const dehydrated = dehydrate(queryClient)
|
|
28
|
+
const stringified = JSON.stringify(dehydrated)
|
|
29
|
+
|
|
30
|
+
// ---
|
|
31
|
+
|
|
32
|
+
const parsed = JSON.parse(stringified)
|
|
33
|
+
const hydrationCache = new QueryCache()
|
|
34
|
+
const hydrationClient = createQueryClient({
|
|
35
|
+
queryCache: hydrationCache,
|
|
36
|
+
})
|
|
37
|
+
hydrate(hydrationClient, parsed)
|
|
38
|
+
expect(hydrationCache.find(['string'])?.state.data).toBe('string')
|
|
39
|
+
expect(hydrationCache.find(['number'])?.state.data).toBe(1)
|
|
40
|
+
expect(hydrationCache.find(['boolean'])?.state.data).toBe(true)
|
|
41
|
+
expect(hydrationCache.find(['null'])?.state.data).toBe(null)
|
|
42
|
+
expect(hydrationCache.find(['array'])?.state.data).toEqual(['string', 0])
|
|
43
|
+
expect(hydrationCache.find(['nested'])?.state.data).toEqual({
|
|
44
|
+
key: [{ nestedKey: 1 }],
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const fetchDataAfterHydration = jest.fn<unknown, unknown[]>()
|
|
48
|
+
await hydrationClient.prefetchQuery(['string'], fetchDataAfterHydration, {
|
|
49
|
+
staleTime: 1000,
|
|
50
|
+
})
|
|
51
|
+
await hydrationClient.prefetchQuery(['number'], fetchDataAfterHydration, {
|
|
52
|
+
staleTime: 1000,
|
|
53
|
+
})
|
|
54
|
+
await hydrationClient.prefetchQuery(['boolean'], fetchDataAfterHydration, {
|
|
55
|
+
staleTime: 1000,
|
|
56
|
+
})
|
|
57
|
+
await hydrationClient.prefetchQuery(['null'], fetchDataAfterHydration, {
|
|
58
|
+
staleTime: 1000,
|
|
59
|
+
})
|
|
60
|
+
await hydrationClient.prefetchQuery(['array'], fetchDataAfterHydration, {
|
|
61
|
+
staleTime: 1000,
|
|
62
|
+
})
|
|
63
|
+
await hydrationClient.prefetchQuery(['nested'], fetchDataAfterHydration, {
|
|
64
|
+
staleTime: 1000,
|
|
65
|
+
})
|
|
66
|
+
expect(fetchDataAfterHydration).toHaveBeenCalledTimes(0)
|
|
67
|
+
|
|
68
|
+
queryClient.clear()
|
|
69
|
+
hydrationClient.clear()
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test('should not dehydrate queries if dehydrateQueries is set to false', async () => {
|
|
73
|
+
const queryCache = new QueryCache()
|
|
74
|
+
const queryClient = createQueryClient({ queryCache })
|
|
75
|
+
await queryClient.prefetchQuery(['string'], () => fetchData('string'))
|
|
76
|
+
|
|
77
|
+
const dehydrated = dehydrate(queryClient, { dehydrateQueries: false })
|
|
78
|
+
|
|
79
|
+
expect(dehydrated.queries.length).toBe(0)
|
|
80
|
+
|
|
81
|
+
queryClient.clear()
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
test('should use the cache time from the client', async () => {
|
|
85
|
+
const queryCache = new QueryCache()
|
|
86
|
+
const queryClient = createQueryClient({ queryCache })
|
|
87
|
+
await queryClient.prefetchQuery(['string'], () => fetchData('string'), {
|
|
88
|
+
cacheTime: 50,
|
|
89
|
+
})
|
|
90
|
+
const dehydrated = dehydrate(queryClient)
|
|
91
|
+
const stringified = JSON.stringify(dehydrated)
|
|
92
|
+
|
|
93
|
+
await sleep(20)
|
|
94
|
+
|
|
95
|
+
// ---
|
|
96
|
+
|
|
97
|
+
const parsed = JSON.parse(stringified)
|
|
98
|
+
const hydrationCache = new QueryCache()
|
|
99
|
+
const hydrationClient = createQueryClient({ queryCache: hydrationCache })
|
|
100
|
+
hydrate(hydrationClient, parsed)
|
|
101
|
+
expect(hydrationCache.find(['string'])?.state.data).toBe('string')
|
|
102
|
+
await sleep(100)
|
|
103
|
+
expect(hydrationCache.find(['string'])).toBeTruthy()
|
|
104
|
+
|
|
105
|
+
queryClient.clear()
|
|
106
|
+
hydrationClient.clear()
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
test('should be able to provide default options for the hydrated queries', async () => {
|
|
110
|
+
const queryCache = new QueryCache()
|
|
111
|
+
const queryClient = createQueryClient({ queryCache })
|
|
112
|
+
await queryClient.prefetchQuery(['string'], () => fetchData('string'))
|
|
113
|
+
const dehydrated = dehydrate(queryClient)
|
|
114
|
+
const stringified = JSON.stringify(dehydrated)
|
|
115
|
+
const parsed = JSON.parse(stringified)
|
|
116
|
+
const hydrationCache = new QueryCache()
|
|
117
|
+
const hydrationClient = createQueryClient({ queryCache: hydrationCache })
|
|
118
|
+
hydrate(hydrationClient, parsed, {
|
|
119
|
+
defaultOptions: { queries: { retry: 10 } },
|
|
120
|
+
})
|
|
121
|
+
expect(hydrationCache.find(['string'])?.options.retry).toBe(10)
|
|
122
|
+
queryClient.clear()
|
|
123
|
+
hydrationClient.clear()
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
test('should work with complex keys', async () => {
|
|
127
|
+
const queryCache = new QueryCache()
|
|
128
|
+
const queryClient = createQueryClient({ queryCache })
|
|
129
|
+
await queryClient.prefetchQuery(
|
|
130
|
+
['string', { key: ['string'], key2: 0 }],
|
|
131
|
+
() => fetchData('string'),
|
|
132
|
+
)
|
|
133
|
+
const dehydrated = dehydrate(queryClient)
|
|
134
|
+
const stringified = JSON.stringify(dehydrated)
|
|
135
|
+
|
|
136
|
+
// ---
|
|
137
|
+
|
|
138
|
+
const parsed = JSON.parse(stringified)
|
|
139
|
+
const hydrationCache = new QueryCache()
|
|
140
|
+
const hydrationClient = createQueryClient({ queryCache: hydrationCache })
|
|
141
|
+
hydrate(hydrationClient, parsed)
|
|
142
|
+
expect(
|
|
143
|
+
hydrationCache.find(['string', { key: ['string'], key2: 0 }])?.state.data,
|
|
144
|
+
).toBe('string')
|
|
145
|
+
|
|
146
|
+
const fetchDataAfterHydration = jest.fn<unknown, unknown[]>()
|
|
147
|
+
await hydrationClient.prefetchQuery(
|
|
148
|
+
['string', { key: ['string'], key2: 0 }],
|
|
149
|
+
fetchDataAfterHydration,
|
|
150
|
+
{ staleTime: 100 },
|
|
151
|
+
)
|
|
152
|
+
expect(fetchDataAfterHydration).toHaveBeenCalledTimes(0)
|
|
153
|
+
|
|
154
|
+
queryClient.clear()
|
|
155
|
+
hydrationClient.clear()
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
test('should only hydrate successful queries by default', async () => {
|
|
159
|
+
const consoleMock = jest.spyOn(console, 'error')
|
|
160
|
+
consoleMock.mockImplementation(() => undefined)
|
|
161
|
+
|
|
162
|
+
const queryCache = new QueryCache()
|
|
163
|
+
const queryClient = createQueryClient({ queryCache })
|
|
164
|
+
await queryClient.prefetchQuery(['success'], () => fetchData('success'))
|
|
165
|
+
queryClient.prefetchQuery(['loading'], () => fetchData('loading', 10000))
|
|
166
|
+
await queryClient.prefetchQuery(['error'], () => {
|
|
167
|
+
throw new Error()
|
|
168
|
+
})
|
|
169
|
+
const dehydrated = dehydrate(queryClient)
|
|
170
|
+
const stringified = JSON.stringify(dehydrated)
|
|
171
|
+
|
|
172
|
+
// ---
|
|
173
|
+
|
|
174
|
+
const parsed = JSON.parse(stringified)
|
|
175
|
+
const hydrationCache = new QueryCache()
|
|
176
|
+
const hydrationClient = createQueryClient({ queryCache: hydrationCache })
|
|
177
|
+
hydrate(hydrationClient, parsed)
|
|
178
|
+
|
|
179
|
+
expect(hydrationCache.find(['success'])).toBeTruthy()
|
|
180
|
+
expect(hydrationCache.find(['loading'])).toBeFalsy()
|
|
181
|
+
expect(hydrationCache.find(['error'])).toBeFalsy()
|
|
182
|
+
|
|
183
|
+
queryClient.clear()
|
|
184
|
+
hydrationClient.clear()
|
|
185
|
+
consoleMock.mockRestore()
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
test('should filter queries via shouldDehydrateQuery', async () => {
|
|
189
|
+
const queryCache = new QueryCache()
|
|
190
|
+
const queryClient = createQueryClient({ queryCache })
|
|
191
|
+
await queryClient.prefetchQuery(['string'], () => fetchData('string'))
|
|
192
|
+
await queryClient.prefetchQuery(['number'], () => fetchData(1))
|
|
193
|
+
const dehydrated = dehydrate(queryClient, {
|
|
194
|
+
shouldDehydrateQuery: (query) => query.queryKey[0] !== 'string',
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
// This is testing implementation details that can change and are not
|
|
198
|
+
// part of the public API, but is important for keeping the payload small
|
|
199
|
+
const dehydratedQuery = dehydrated.queries.find(
|
|
200
|
+
(query) => query.queryKey[0] === 'string',
|
|
201
|
+
)
|
|
202
|
+
expect(dehydratedQuery).toBeUndefined()
|
|
203
|
+
|
|
204
|
+
const stringified = JSON.stringify(dehydrated)
|
|
205
|
+
|
|
206
|
+
// ---
|
|
207
|
+
|
|
208
|
+
const parsed = JSON.parse(stringified)
|
|
209
|
+
const hydrationCache = new QueryCache()
|
|
210
|
+
const hydrationClient = createQueryClient({ queryCache: hydrationCache })
|
|
211
|
+
hydrate(hydrationClient, parsed)
|
|
212
|
+
expect(hydrationCache.find(['string'])).toBeUndefined()
|
|
213
|
+
expect(hydrationCache.find(['number'])?.state.data).toBe(1)
|
|
214
|
+
|
|
215
|
+
queryClient.clear()
|
|
216
|
+
hydrationClient.clear()
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
test('should not overwrite query in cache if hydrated query is older', async () => {
|
|
220
|
+
const queryCache = new QueryCache()
|
|
221
|
+
const queryClient = createQueryClient({ queryCache })
|
|
222
|
+
await queryClient.prefetchQuery(['string'], () =>
|
|
223
|
+
fetchData('string-older', 5),
|
|
224
|
+
)
|
|
225
|
+
const dehydrated = dehydrate(queryClient)
|
|
226
|
+
const stringified = JSON.stringify(dehydrated)
|
|
227
|
+
|
|
228
|
+
// ---
|
|
229
|
+
|
|
230
|
+
const parsed = JSON.parse(stringified)
|
|
231
|
+
const hydrationCache = new QueryCache()
|
|
232
|
+
const hydrationClient = createQueryClient({ queryCache: hydrationCache })
|
|
233
|
+
await hydrationClient.prefetchQuery(['string'], () =>
|
|
234
|
+
fetchData('string-newer', 5),
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
hydrate(hydrationClient, parsed)
|
|
238
|
+
expect(hydrationCache.find(['string'])?.state.data).toBe('string-newer')
|
|
239
|
+
|
|
240
|
+
queryClient.clear()
|
|
241
|
+
hydrationClient.clear()
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
test('should overwrite query in cache if hydrated query is newer', async () => {
|
|
245
|
+
const hydrationCache = new QueryCache()
|
|
246
|
+
const hydrationClient = createQueryClient({ queryCache: hydrationCache })
|
|
247
|
+
await hydrationClient.prefetchQuery(['string'], () =>
|
|
248
|
+
fetchData('string-older', 5),
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
// ---
|
|
252
|
+
|
|
253
|
+
const queryCache = new QueryCache()
|
|
254
|
+
const queryClient = createQueryClient({ queryCache })
|
|
255
|
+
await queryClient.prefetchQuery(['string'], () =>
|
|
256
|
+
fetchData('string-newer', 5),
|
|
257
|
+
)
|
|
258
|
+
const dehydrated = dehydrate(queryClient)
|
|
259
|
+
const stringified = JSON.stringify(dehydrated)
|
|
260
|
+
|
|
261
|
+
// ---
|
|
262
|
+
|
|
263
|
+
const parsed = JSON.parse(stringified)
|
|
264
|
+
hydrate(hydrationClient, parsed)
|
|
265
|
+
expect(hydrationCache.find(['string'])?.state.data).toBe('string-newer')
|
|
266
|
+
|
|
267
|
+
queryClient.clear()
|
|
268
|
+
hydrationClient.clear()
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
test('should be able to dehydrate mutations and continue on hydration', async () => {
|
|
272
|
+
const consoleMock = jest.spyOn(console, 'error')
|
|
273
|
+
consoleMock.mockImplementation(() => undefined)
|
|
274
|
+
const onlineMock = mockNavigatorOnLine(false)
|
|
275
|
+
|
|
276
|
+
const serverAddTodo = jest
|
|
277
|
+
.fn()
|
|
278
|
+
.mockImplementation(() => Promise.reject('offline'))
|
|
279
|
+
const serverOnMutate = jest.fn().mockImplementation((variables) => {
|
|
280
|
+
const optimisticTodo = { id: 1, text: variables.text }
|
|
281
|
+
return { optimisticTodo }
|
|
282
|
+
})
|
|
283
|
+
const serverOnSuccess = jest.fn()
|
|
284
|
+
|
|
285
|
+
const serverClient = createQueryClient()
|
|
286
|
+
|
|
287
|
+
serverClient.setMutationDefaults(['addTodo'], {
|
|
288
|
+
mutationFn: serverAddTodo,
|
|
289
|
+
onMutate: serverOnMutate,
|
|
290
|
+
onSuccess: serverOnSuccess,
|
|
291
|
+
retry: 3,
|
|
292
|
+
retryDelay: 10,
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
executeMutation(serverClient, {
|
|
296
|
+
mutationKey: ['addTodo'],
|
|
297
|
+
variables: { text: 'text' },
|
|
298
|
+
}).catch(() => undefined)
|
|
299
|
+
|
|
300
|
+
await sleep(50)
|
|
301
|
+
|
|
302
|
+
const dehydrated = dehydrate(serverClient)
|
|
303
|
+
const stringified = JSON.stringify(dehydrated)
|
|
304
|
+
|
|
305
|
+
serverClient.clear()
|
|
306
|
+
|
|
307
|
+
// ---
|
|
308
|
+
|
|
309
|
+
onlineMock.mockReturnValue(true)
|
|
310
|
+
|
|
311
|
+
const parsed = JSON.parse(stringified)
|
|
312
|
+
const client = createQueryClient()
|
|
313
|
+
|
|
314
|
+
const clientAddTodo = jest.fn().mockImplementation((variables) => {
|
|
315
|
+
return { id: 2, text: variables.text }
|
|
316
|
+
})
|
|
317
|
+
const clientOnMutate = jest.fn().mockImplementation((variables) => {
|
|
318
|
+
const optimisticTodo = { id: 1, text: variables.text }
|
|
319
|
+
return { optimisticTodo }
|
|
320
|
+
})
|
|
321
|
+
const clientOnSuccess = jest.fn()
|
|
322
|
+
|
|
323
|
+
client.setMutationDefaults(['addTodo'], {
|
|
324
|
+
mutationFn: clientAddTodo,
|
|
325
|
+
onMutate: clientOnMutate,
|
|
326
|
+
onSuccess: clientOnSuccess,
|
|
327
|
+
retry: 3,
|
|
328
|
+
retryDelay: 10,
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
hydrate(client, parsed)
|
|
332
|
+
|
|
333
|
+
await client.resumePausedMutations()
|
|
334
|
+
|
|
335
|
+
expect(clientAddTodo).toHaveBeenCalledTimes(1)
|
|
336
|
+
expect(clientOnMutate).not.toHaveBeenCalled()
|
|
337
|
+
expect(clientOnSuccess).toHaveBeenCalledTimes(1)
|
|
338
|
+
expect(clientOnSuccess).toHaveBeenCalledWith(
|
|
339
|
+
{ id: 2, text: 'text' },
|
|
340
|
+
{ text: 'text' },
|
|
341
|
+
{ optimisticTodo: { id: 1, text: 'text' } },
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
client.clear()
|
|
345
|
+
consoleMock.mockRestore()
|
|
346
|
+
onlineMock.mockRestore()
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
test('should not dehydrate mutations if dehydrateMutations is set to false', async () => {
|
|
350
|
+
const consoleMock = jest.spyOn(console, 'error')
|
|
351
|
+
consoleMock.mockImplementation(() => undefined)
|
|
352
|
+
|
|
353
|
+
const serverAddTodo = jest
|
|
354
|
+
.fn()
|
|
355
|
+
.mockImplementation(() => Promise.reject('offline'))
|
|
356
|
+
|
|
357
|
+
const queryClient = createQueryClient()
|
|
358
|
+
|
|
359
|
+
queryClient.setMutationDefaults(['addTodo'], {
|
|
360
|
+
mutationFn: serverAddTodo,
|
|
361
|
+
retry: false,
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
executeMutation(queryClient, {
|
|
365
|
+
mutationKey: ['addTodo'],
|
|
366
|
+
variables: { text: 'text' },
|
|
367
|
+
}).catch(() => undefined)
|
|
368
|
+
|
|
369
|
+
await sleep(1)
|
|
370
|
+
const dehydrated = dehydrate(queryClient, { dehydrateMutations: false })
|
|
371
|
+
|
|
372
|
+
expect(dehydrated.mutations.length).toBe(0)
|
|
373
|
+
|
|
374
|
+
queryClient.clear()
|
|
375
|
+
consoleMock.mockRestore()
|
|
376
|
+
})
|
|
377
|
+
|
|
378
|
+
test('should not dehydrate mutation if mutation state is set to pause', async () => {
|
|
379
|
+
const consoleMock = jest.spyOn(console, 'error')
|
|
380
|
+
consoleMock.mockImplementation(() => undefined)
|
|
381
|
+
|
|
382
|
+
const serverAddTodo = jest
|
|
383
|
+
.fn()
|
|
384
|
+
.mockImplementation(() => Promise.reject('offline'))
|
|
385
|
+
|
|
386
|
+
const queryClient = createQueryClient()
|
|
387
|
+
|
|
388
|
+
queryClient.setMutationDefaults(['addTodo'], {
|
|
389
|
+
mutationFn: serverAddTodo,
|
|
390
|
+
retry: 1,
|
|
391
|
+
retryDelay: 20,
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
executeMutation(queryClient, {
|
|
395
|
+
mutationKey: ['addTodo'],
|
|
396
|
+
variables: { text: 'text' },
|
|
397
|
+
}).catch(() => undefined)
|
|
398
|
+
|
|
399
|
+
// Dehydrate mutation between retries
|
|
400
|
+
await sleep(1)
|
|
401
|
+
const dehydrated = dehydrate(queryClient)
|
|
402
|
+
|
|
403
|
+
expect(dehydrated.mutations.length).toBe(0)
|
|
404
|
+
|
|
405
|
+
await sleep(30)
|
|
406
|
+
queryClient.clear()
|
|
407
|
+
consoleMock.mockRestore()
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
test('should not hydrate if the hydratedState is null or is not an object', async () => {
|
|
411
|
+
const queryCache = new QueryCache()
|
|
412
|
+
const queryClient = createQueryClient({ queryCache })
|
|
413
|
+
|
|
414
|
+
expect(() => hydrate(queryClient, null)).not.toThrow()
|
|
415
|
+
expect(() => hydrate(queryClient, 'invalid')).not.toThrow()
|
|
416
|
+
|
|
417
|
+
queryClient.clear()
|
|
418
|
+
})
|
|
419
|
+
|
|
420
|
+
test('should support hydratedState with undefined queries and mutations', async () => {
|
|
421
|
+
const queryCache = new QueryCache()
|
|
422
|
+
const queryClient = createQueryClient({ queryCache })
|
|
423
|
+
|
|
424
|
+
expect(() => hydrate(queryClient, {})).not.toThrow()
|
|
425
|
+
expect(() => hydrate(queryClient, {})).not.toThrow()
|
|
426
|
+
|
|
427
|
+
queryClient.clear()
|
|
428
|
+
})
|
|
429
|
+
})
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { waitFor } from '@testing-library/react'
|
|
2
|
+
import {
|
|
3
|
+
QueryClient,
|
|
4
|
+
InfiniteQueryObserver,
|
|
5
|
+
InfiniteQueryObserverResult,
|
|
6
|
+
} from '@tanstack/query-core'
|
|
7
|
+
import { createQueryClient, queryKey } from '../../../../tests/utils'
|
|
8
|
+
|
|
9
|
+
describe('InfiniteQueryBehavior', () => {
|
|
10
|
+
let queryClient: QueryClient
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
queryClient = createQueryClient()
|
|
14
|
+
queryClient.mount()
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
queryClient.clear()
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
test('InfiniteQueryBehavior should throw an error if the queryFn is not defined', async () => {
|
|
22
|
+
const key = queryKey()
|
|
23
|
+
|
|
24
|
+
const observer = new InfiniteQueryObserver(queryClient, {
|
|
25
|
+
queryKey: key,
|
|
26
|
+
retry: false,
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
let observerResult:
|
|
30
|
+
| InfiniteQueryObserverResult<unknown, unknown>
|
|
31
|
+
| undefined
|
|
32
|
+
|
|
33
|
+
const unsubscribe = observer.subscribe((result) => {
|
|
34
|
+
observerResult = result
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
await waitFor(() => {
|
|
38
|
+
return expect(observerResult).toMatchObject({
|
|
39
|
+
isError: true,
|
|
40
|
+
error: 'Missing queryFn',
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
unsubscribe()
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('InfiniteQueryBehavior should not refetch the first page if another page refetched', async () => {
|
|
48
|
+
const key = queryKey()
|
|
49
|
+
let abortSignal: AbortSignal | null = null
|
|
50
|
+
|
|
51
|
+
const queryFnSpy = jest
|
|
52
|
+
.fn()
|
|
53
|
+
.mockImplementation(({ pageParam = 1, signal }) => {
|
|
54
|
+
abortSignal = signal
|
|
55
|
+
return pageParam
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
const observer = new InfiniteQueryObserver<number>(queryClient, {
|
|
59
|
+
queryKey: key,
|
|
60
|
+
queryFn: queryFnSpy,
|
|
61
|
+
getNextPageParam: (lastPage) => lastPage + 1,
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
let observerResult:
|
|
65
|
+
| InfiniteQueryObserverResult<unknown, unknown>
|
|
66
|
+
| undefined
|
|
67
|
+
|
|
68
|
+
const unsubscribe = observer.subscribe((result) => {
|
|
69
|
+
observerResult = result
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
// Wait for the first page to be fetched
|
|
73
|
+
await waitFor(() =>
|
|
74
|
+
expect(observerResult).toMatchObject({
|
|
75
|
+
isFetching: false,
|
|
76
|
+
data: { pages: [1] },
|
|
77
|
+
}),
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
expect(queryFnSpy).toHaveBeenNthCalledWith(1, {
|
|
81
|
+
queryKey: key,
|
|
82
|
+
pageParam: undefined,
|
|
83
|
+
meta: undefined,
|
|
84
|
+
signal: abortSignal,
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
queryFnSpy.mockClear()
|
|
88
|
+
|
|
89
|
+
// Fetch the second page
|
|
90
|
+
await observer.fetchNextPage()
|
|
91
|
+
|
|
92
|
+
expect(queryFnSpy).toHaveBeenNthCalledWith(1, {
|
|
93
|
+
queryKey: key,
|
|
94
|
+
pageParam: 2,
|
|
95
|
+
meta: undefined,
|
|
96
|
+
signal: abortSignal,
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
expect(observerResult).toMatchObject({
|
|
100
|
+
isFetching: false,
|
|
101
|
+
data: { pages: [1, 2] },
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
queryFnSpy.mockClear()
|
|
105
|
+
|
|
106
|
+
// Refetch the second page
|
|
107
|
+
await queryClient.refetchQueries({
|
|
108
|
+
refetchPage: (_page, index) => index === 1,
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
expect(queryFnSpy).toHaveBeenNthCalledWith(1, {
|
|
112
|
+
queryKey: key,
|
|
113
|
+
pageParam: 2,
|
|
114
|
+
meta: undefined,
|
|
115
|
+
signal: abortSignal,
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
expect(observerResult).toMatchObject({
|
|
119
|
+
data: { pages: [1, 2] },
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
unsubscribe()
|
|
123
|
+
})
|
|
124
|
+
})
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { createQueryClient, queryKey, sleep } from '../../../../tests/utils'
|
|
2
|
+
import { QueryClient, InfiniteQueryObserver } from '..'
|
|
3
|
+
|
|
4
|
+
describe('InfiniteQueryObserver', () => {
|
|
5
|
+
let queryClient: QueryClient
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
queryClient = createQueryClient()
|
|
9
|
+
queryClient.mount()
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
queryClient.clear()
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
test('InfiniteQueryObserver should be able to fetch an infinite query with selector', async () => {
|
|
17
|
+
const key = queryKey()
|
|
18
|
+
const observer = new InfiniteQueryObserver(queryClient, {
|
|
19
|
+
queryKey: key,
|
|
20
|
+
queryFn: () => 1,
|
|
21
|
+
select: (data) => ({
|
|
22
|
+
pages: data.pages.map((x) => `${x}`),
|
|
23
|
+
pageParams: data.pageParams,
|
|
24
|
+
}),
|
|
25
|
+
})
|
|
26
|
+
let observerResult
|
|
27
|
+
const unsubscribe = observer.subscribe((result) => {
|
|
28
|
+
observerResult = result
|
|
29
|
+
})
|
|
30
|
+
await sleep(1)
|
|
31
|
+
unsubscribe()
|
|
32
|
+
expect(observerResult).toMatchObject({
|
|
33
|
+
data: { pages: ['1'], pageParams: [undefined] },
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('InfiniteQueryObserver should pass the meta option to the queryFn', async () => {
|
|
38
|
+
const meta = {
|
|
39
|
+
it: 'works',
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const key = queryKey()
|
|
43
|
+
const queryFn = jest.fn(() => 1)
|
|
44
|
+
const observer = new InfiniteQueryObserver(queryClient, {
|
|
45
|
+
meta,
|
|
46
|
+
queryKey: key,
|
|
47
|
+
queryFn,
|
|
48
|
+
select: (data) => ({
|
|
49
|
+
pages: data.pages.map((x) => `${x}`),
|
|
50
|
+
pageParams: data.pageParams,
|
|
51
|
+
}),
|
|
52
|
+
})
|
|
53
|
+
let observerResult
|
|
54
|
+
const unsubscribe = observer.subscribe((result) => {
|
|
55
|
+
observerResult = result
|
|
56
|
+
})
|
|
57
|
+
await sleep(1)
|
|
58
|
+
unsubscribe()
|
|
59
|
+
expect(observerResult).toMatchObject({
|
|
60
|
+
data: { pages: ['1'], pageParams: [undefined] },
|
|
61
|
+
})
|
|
62
|
+
expect(queryFn).toBeCalledWith(expect.objectContaining({ meta }))
|
|
63
|
+
})
|
|
64
|
+
})
|