@dhis2/app-service-data 3.17.0 → 3.17.2
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/.gitignore +5 -0
- package/d2.config.js +9 -0
- package/jest.config.js +14 -0
- package/package.json +4 -4
- package/src/__tests__/integration.test.tsx +80 -0
- package/src/__tests__/mutations.test.tsx +71 -0
- package/src/index.ts +5 -0
- package/src/react/components/CustomDataProvider.tsx +33 -0
- package/src/react/components/DataMutation.tsx +24 -0
- package/src/react/components/DataProvider.test.tsx +22 -0
- package/src/react/components/DataProvider.tsx +58 -0
- package/src/react/components/DataQuery.tsx +26 -0
- package/src/react/components/index.ts +4 -0
- package/src/react/context/DataContext.tsx +5 -0
- package/src/react/context/defaultDataContext.test.ts +46 -0
- package/src/react/context/defaultDataContext.ts +9 -0
- package/src/react/hooks/index.ts +3 -0
- package/src/react/hooks/mergeAndCompareVariables.test.ts +65 -0
- package/src/react/hooks/mergeAndCompareVariables.ts +32 -0
- package/src/react/hooks/stableVariablesHash.test.ts +53 -0
- package/src/react/hooks/stableVariablesHash.ts +56 -0
- package/src/react/hooks/useDataEngine.ts +8 -0
- package/src/react/hooks/useDataMutation.test.tsx +296 -0
- package/src/react/hooks/useDataMutation.ts +42 -0
- package/src/react/hooks/useDataQuery.test.tsx +1029 -0
- package/src/react/hooks/useDataQuery.ts +170 -0
- package/src/react/hooks/useQueryExecutor.test.tsx +195 -0
- package/src/react/hooks/useQueryExecutor.ts +99 -0
- package/src/react/hooks/useStaticInput.test.ts +101 -0
- package/src/react/hooks/useStaticInput.ts +27 -0
- package/src/react/index.ts +2 -0
- package/src/setupRTL.ts +5 -0
- package/src/types.ts +72 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,1029 @@
|
|
|
1
|
+
import type { QueryVariables } from '@dhis2/data-engine'
|
|
2
|
+
import { renderHook, waitFor, act } from '@testing-library/react'
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { CustomDataProvider } from '../components/CustomDataProvider'
|
|
5
|
+
import { useDataQuery } from './useDataQuery'
|
|
6
|
+
|
|
7
|
+
describe('useDataQuery', () => {
|
|
8
|
+
describe('parameters: onComplete', () => {
|
|
9
|
+
it('Should call onComplete with the data after a successful fetch', async () => {
|
|
10
|
+
const query = { x: { resource: 'answer' } }
|
|
11
|
+
const data = { answer: 42 }
|
|
12
|
+
const wrapper = ({ children }: { children?: React.ReactNode }) => (
|
|
13
|
+
<CustomDataProvider data={data}>{children}</CustomDataProvider>
|
|
14
|
+
)
|
|
15
|
+
const onComplete = jest.fn()
|
|
16
|
+
const onError = jest.fn()
|
|
17
|
+
|
|
18
|
+
const { result } = renderHook(
|
|
19
|
+
() => useDataQuery(query, { lazy: false, onComplete, onError }),
|
|
20
|
+
{ wrapper }
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
expect(result.current).toMatchObject({
|
|
24
|
+
loading: true,
|
|
25
|
+
called: true,
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
await waitFor(() => {
|
|
29
|
+
expect(onComplete).toHaveBeenCalledWith({ x: 42 })
|
|
30
|
+
expect(onError).not.toHaveBeenCalled()
|
|
31
|
+
expect(result.current).toMatchObject({
|
|
32
|
+
data: { x: 42 },
|
|
33
|
+
})
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
describe('parameters: onError', () => {
|
|
39
|
+
it('Should call onError with the error after a fetch error', async () => {
|
|
40
|
+
const expectedError = new Error('Something went wrong')
|
|
41
|
+
const query = { x: { resource: 'answer' } }
|
|
42
|
+
const data = {
|
|
43
|
+
answer: () => {
|
|
44
|
+
throw expectedError
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
const wrapper = ({ children }: { children?: React.ReactNode }) => (
|
|
48
|
+
<CustomDataProvider data={data}>{children}</CustomDataProvider>
|
|
49
|
+
)
|
|
50
|
+
const onComplete = jest.fn()
|
|
51
|
+
const onError = jest.fn()
|
|
52
|
+
|
|
53
|
+
const { result } = renderHook(
|
|
54
|
+
() => useDataQuery(query, { onError, onComplete }),
|
|
55
|
+
{ wrapper }
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
expect(result.current).toMatchObject({
|
|
59
|
+
loading: true,
|
|
60
|
+
called: true,
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
await waitFor(() => {
|
|
64
|
+
expect(onError).toHaveBeenCalledWith(expectedError)
|
|
65
|
+
expect(onComplete).not.toHaveBeenCalled()
|
|
66
|
+
expect(result.current).toMatchObject({
|
|
67
|
+
error: expectedError,
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
describe('parameters: variables', () => {
|
|
74
|
+
it('Should ignore new variables from rerenders', async () => {
|
|
75
|
+
const one = 'one'
|
|
76
|
+
const two = 'two'
|
|
77
|
+
const resultOne = 1
|
|
78
|
+
const resultTwo = 2
|
|
79
|
+
const query = {
|
|
80
|
+
x: {
|
|
81
|
+
resource: 'answer',
|
|
82
|
+
id: ({ id }: QueryVariables) => id as string,
|
|
83
|
+
},
|
|
84
|
+
}
|
|
85
|
+
const mockSpy = jest.fn((_, { id }) => {
|
|
86
|
+
switch (id) {
|
|
87
|
+
case one:
|
|
88
|
+
return Promise.resolve(resultOne)
|
|
89
|
+
case two:
|
|
90
|
+
return Promise.resolve(resultTwo)
|
|
91
|
+
}
|
|
92
|
+
return Promise.resolve(undefined)
|
|
93
|
+
})
|
|
94
|
+
const data = {
|
|
95
|
+
answer: mockSpy,
|
|
96
|
+
}
|
|
97
|
+
const wrapper = ({ children }: { children?: any }) => (
|
|
98
|
+
<CustomDataProvider data={data}>{children}</CustomDataProvider>
|
|
99
|
+
)
|
|
100
|
+
const initialProps = {
|
|
101
|
+
query,
|
|
102
|
+
options: { variables: { id: one } },
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const { result, rerender } = renderHook(
|
|
106
|
+
(props) => useDataQuery(props.query, props.options),
|
|
107
|
+
{
|
|
108
|
+
wrapper,
|
|
109
|
+
initialProps,
|
|
110
|
+
}
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
expect(mockSpy).toHaveBeenCalledTimes(1)
|
|
114
|
+
expect(result.current).toMatchObject({
|
|
115
|
+
loading: true,
|
|
116
|
+
called: true,
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
await waitFor(() => {
|
|
120
|
+
expect(result.current).toMatchObject({
|
|
121
|
+
data: { x: resultOne },
|
|
122
|
+
})
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
act(() => {
|
|
126
|
+
const newProps = {
|
|
127
|
+
query,
|
|
128
|
+
options: {
|
|
129
|
+
variables: { id: two },
|
|
130
|
+
},
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
rerender(newProps)
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
expect(mockSpy).toHaveBeenCalledTimes(1)
|
|
137
|
+
expect(result.current).toMatchObject({
|
|
138
|
+
data: { x: resultOne },
|
|
139
|
+
})
|
|
140
|
+
})
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
describe('parameters: lazy', () => {
|
|
144
|
+
it('Should be false by default', async () => {
|
|
145
|
+
const query = { x: { resource: 'answer' } }
|
|
146
|
+
const mockSpy = jest.fn(() => Promise.resolve(42))
|
|
147
|
+
const data = { answer: mockSpy }
|
|
148
|
+
const wrapper = ({ children }: { children?: React.ReactNode }) => (
|
|
149
|
+
<CustomDataProvider data={data}>{children}</CustomDataProvider>
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
const { result } = renderHook(() => useDataQuery(query), {
|
|
153
|
+
wrapper,
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
expect(result.current).toMatchObject({
|
|
157
|
+
loading: true,
|
|
158
|
+
called: true,
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
await waitFor(() => {
|
|
162
|
+
expect(mockSpy).toHaveBeenCalledTimes(1)
|
|
163
|
+
expect(result.current).toMatchObject({
|
|
164
|
+
loading: false,
|
|
165
|
+
called: true,
|
|
166
|
+
data: { x: 42 },
|
|
167
|
+
})
|
|
168
|
+
})
|
|
169
|
+
})
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
describe('internal: caching', () => {
|
|
173
|
+
it('Should return data from the cache if it is not stale', async () => {
|
|
174
|
+
// Keep cached data forever, see: https://react-query.tanstack.com/reference/useQuery
|
|
175
|
+
const queryClientOptions = {
|
|
176
|
+
defaultOptions: {
|
|
177
|
+
queries: {
|
|
178
|
+
staleTime: Infinity,
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
}
|
|
182
|
+
const answers = [42, 43]
|
|
183
|
+
const mockSpy = jest.fn(() => Promise.resolve(answers.shift()))
|
|
184
|
+
const data = {
|
|
185
|
+
answer: mockSpy,
|
|
186
|
+
}
|
|
187
|
+
const query = {
|
|
188
|
+
x: { resource: 'answer' },
|
|
189
|
+
}
|
|
190
|
+
const wrapper = ({ children }: { children?: React.ReactNode }) => (
|
|
191
|
+
<CustomDataProvider
|
|
192
|
+
data={data}
|
|
193
|
+
queryClientOptions={queryClientOptions}
|
|
194
|
+
>
|
|
195
|
+
{children}
|
|
196
|
+
</CustomDataProvider>
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
const { result } = renderHook(() => useDataQuery(query), {
|
|
200
|
+
wrapper,
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
expect(mockSpy).toHaveBeenCalledTimes(1)
|
|
204
|
+
expect(result.current).toMatchObject({
|
|
205
|
+
loading: true,
|
|
206
|
+
called: true,
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
await waitFor(() => {
|
|
210
|
+
// Now the cache will contain a value for 'answer' without variables
|
|
211
|
+
expect(result.current).toMatchObject({
|
|
212
|
+
loading: false,
|
|
213
|
+
called: true,
|
|
214
|
+
data: { x: 42 },
|
|
215
|
+
})
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
act(() => {
|
|
219
|
+
// Add a variable to the request to ensure a different cache key
|
|
220
|
+
result.current.refetch({ one: 1 })
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
expect(mockSpy).toHaveBeenCalledTimes(2)
|
|
224
|
+
expect(result.current).toMatchObject({
|
|
225
|
+
loading: true,
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
await waitFor(() => {
|
|
229
|
+
// Now the cache will contain a value for 'answer' with and without variables
|
|
230
|
+
expect(result.current).toMatchObject({
|
|
231
|
+
loading: false,
|
|
232
|
+
data: { x: 43 },
|
|
233
|
+
})
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
act(() => {
|
|
237
|
+
// Request the resource without variables again
|
|
238
|
+
result.current.refetch({ one: undefined })
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
// This should return the resource from the cache without fetching
|
|
242
|
+
expect(mockSpy).toHaveBeenCalledTimes(2)
|
|
243
|
+
expect(result.current).toMatchObject({
|
|
244
|
+
loading: false,
|
|
245
|
+
called: true,
|
|
246
|
+
data: { x: 42 },
|
|
247
|
+
})
|
|
248
|
+
})
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
describe('internal: deduplication', () => {
|
|
252
|
+
it('Should deduplicate identical requests', async () => {
|
|
253
|
+
const mockSpy = jest.fn(() => Promise.resolve(42))
|
|
254
|
+
const data = {
|
|
255
|
+
answer: mockSpy,
|
|
256
|
+
}
|
|
257
|
+
const query = {
|
|
258
|
+
x: { resource: 'answer' },
|
|
259
|
+
}
|
|
260
|
+
const wrapper = ({ children }: { children?: React.ReactNode }) => (
|
|
261
|
+
<CustomDataProvider data={data}>{children}</CustomDataProvider>
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
const { result } = renderHook(
|
|
265
|
+
() => {
|
|
266
|
+
const one = useDataQuery(query)
|
|
267
|
+
const two = useDataQuery(query)
|
|
268
|
+
const three = useDataQuery(query)
|
|
269
|
+
|
|
270
|
+
return [one, two, three]
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
wrapper,
|
|
274
|
+
}
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
const loading = {
|
|
278
|
+
loading: true,
|
|
279
|
+
called: true,
|
|
280
|
+
}
|
|
281
|
+
expect(mockSpy).toHaveBeenCalledTimes(1)
|
|
282
|
+
expect(result.current).toMatchObject([loading, loading, loading])
|
|
283
|
+
|
|
284
|
+
await waitFor(() => {
|
|
285
|
+
const done = {
|
|
286
|
+
loading: false,
|
|
287
|
+
called: true,
|
|
288
|
+
data: { x: 42 },
|
|
289
|
+
}
|
|
290
|
+
expect(mockSpy).toHaveBeenCalledTimes(1)
|
|
291
|
+
expect(result.current).toMatchObject([done, done, done])
|
|
292
|
+
})
|
|
293
|
+
})
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
describe('return values: data', () => {
|
|
297
|
+
it('Should return the data for a single resource query on success', async () => {
|
|
298
|
+
const query = { x: { resource: 'answer' } }
|
|
299
|
+
const data = { answer: 42 }
|
|
300
|
+
const wrapper = ({ children }: { children?: React.ReactNode }) => (
|
|
301
|
+
<CustomDataProvider data={data}>{children}</CustomDataProvider>
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
const { result } = renderHook(() => useDataQuery(query), {
|
|
305
|
+
wrapper,
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
expect(result.current).toMatchObject({
|
|
309
|
+
loading: true,
|
|
310
|
+
called: true,
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
await waitFor(() => {
|
|
314
|
+
expect(result.current).toMatchObject({
|
|
315
|
+
loading: false,
|
|
316
|
+
called: true,
|
|
317
|
+
data: { x: 42 },
|
|
318
|
+
})
|
|
319
|
+
})
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
it('Should return the data for a multiple resource query on success', async () => {
|
|
323
|
+
const query = {
|
|
324
|
+
x: { resource: 'answer' },
|
|
325
|
+
y: { resource: 'opposite' },
|
|
326
|
+
}
|
|
327
|
+
const data = { answer: 42, opposite: 24 }
|
|
328
|
+
const wrapper = ({ children }: { children?: React.ReactNode }) => (
|
|
329
|
+
<CustomDataProvider data={data}>{children}</CustomDataProvider>
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
const { result } = renderHook(() => useDataQuery(query), {
|
|
333
|
+
wrapper,
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
expect(result.current).toMatchObject({
|
|
337
|
+
loading: true,
|
|
338
|
+
called: true,
|
|
339
|
+
})
|
|
340
|
+
|
|
341
|
+
await waitFor(() => {
|
|
342
|
+
expect(result.current).toMatchObject({
|
|
343
|
+
loading: false,
|
|
344
|
+
called: true,
|
|
345
|
+
data: { x: 42, y: 24 },
|
|
346
|
+
})
|
|
347
|
+
})
|
|
348
|
+
})
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
describe('return values: error', () => {
|
|
352
|
+
it('Should return errors it encounters for a single resource query', async () => {
|
|
353
|
+
const expectedError = new Error('Something went wrong')
|
|
354
|
+
const query = { x: { resource: 'answer' } }
|
|
355
|
+
const data = {
|
|
356
|
+
answer: () => {
|
|
357
|
+
throw expectedError
|
|
358
|
+
},
|
|
359
|
+
}
|
|
360
|
+
const wrapper = ({ children }: { children?: React.ReactNode }) => (
|
|
361
|
+
<CustomDataProvider data={data}>{children}</CustomDataProvider>
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
const { result } = renderHook(() => useDataQuery(query), {
|
|
365
|
+
wrapper,
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
expect(result.current).toMatchObject({
|
|
369
|
+
loading: true,
|
|
370
|
+
called: true,
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
await waitFor(() => {
|
|
374
|
+
expect(result.current).toMatchObject({
|
|
375
|
+
loading: false,
|
|
376
|
+
called: true,
|
|
377
|
+
error: expectedError,
|
|
378
|
+
})
|
|
379
|
+
})
|
|
380
|
+
})
|
|
381
|
+
|
|
382
|
+
it('Should return errors it encounters for multiple resource queries', async () => {
|
|
383
|
+
const expectedError = new Error('Something went wrong')
|
|
384
|
+
const query = {
|
|
385
|
+
x: { resource: 'answer' },
|
|
386
|
+
y: { resource: 'opposite' },
|
|
387
|
+
}
|
|
388
|
+
const data = {
|
|
389
|
+
answer: () => {
|
|
390
|
+
throw expectedError
|
|
391
|
+
},
|
|
392
|
+
opposite: 24,
|
|
393
|
+
}
|
|
394
|
+
const wrapper = ({ children }: { children?: React.ReactNode }) => (
|
|
395
|
+
<CustomDataProvider data={data}>{children}</CustomDataProvider>
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
const { result } = renderHook(() => useDataQuery(query), {
|
|
399
|
+
wrapper,
|
|
400
|
+
})
|
|
401
|
+
|
|
402
|
+
expect(result.current).toMatchObject({
|
|
403
|
+
loading: true,
|
|
404
|
+
called: true,
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
await waitFor(() => {
|
|
408
|
+
expect(result.current).toMatchObject({
|
|
409
|
+
loading: false,
|
|
410
|
+
called: true,
|
|
411
|
+
error: expectedError,
|
|
412
|
+
})
|
|
413
|
+
})
|
|
414
|
+
})
|
|
415
|
+
})
|
|
416
|
+
|
|
417
|
+
describe('return values: refetch', () => {
|
|
418
|
+
it('Should be stable if the query variables change', async () => {
|
|
419
|
+
let count = 0
|
|
420
|
+
const spy = jest.fn(() => {
|
|
421
|
+
count++
|
|
422
|
+
return Promise.resolve(count)
|
|
423
|
+
})
|
|
424
|
+
const data = {
|
|
425
|
+
answer: spy,
|
|
426
|
+
}
|
|
427
|
+
const query = {
|
|
428
|
+
x: { resource: 'answer' },
|
|
429
|
+
}
|
|
430
|
+
const wrapper = ({ children }: { children?: React.ReactNode }) => (
|
|
431
|
+
<CustomDataProvider data={data}>{children}</CustomDataProvider>
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
const { result } = renderHook(
|
|
435
|
+
() => useDataQuery(query, { lazy: true }),
|
|
436
|
+
{
|
|
437
|
+
wrapper,
|
|
438
|
+
}
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
expect(spy).not.toHaveBeenCalled()
|
|
442
|
+
|
|
443
|
+
const initialRefetch = result.current.refetch
|
|
444
|
+
act(() => {
|
|
445
|
+
initialRefetch()
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
await waitFor(() => {
|
|
449
|
+
expect(result.current).toMatchObject({
|
|
450
|
+
loading: false,
|
|
451
|
+
called: true,
|
|
452
|
+
data: { x: 1 },
|
|
453
|
+
})
|
|
454
|
+
})
|
|
455
|
+
|
|
456
|
+
expect(spy).toHaveBeenCalledTimes(1)
|
|
457
|
+
|
|
458
|
+
act(() => {
|
|
459
|
+
initialRefetch()
|
|
460
|
+
})
|
|
461
|
+
|
|
462
|
+
await waitFor(() => {
|
|
463
|
+
expect(result.current).toMatchObject({
|
|
464
|
+
loading: false,
|
|
465
|
+
called: true,
|
|
466
|
+
data: { x: 2 },
|
|
467
|
+
})
|
|
468
|
+
})
|
|
469
|
+
|
|
470
|
+
expect(spy).toHaveBeenCalledTimes(2)
|
|
471
|
+
expect(initialRefetch).toBe(result.current.refetch)
|
|
472
|
+
})
|
|
473
|
+
|
|
474
|
+
it('Should only trigger a single request when refetch is called on a lazy query with new variables', async () => {
|
|
475
|
+
const spy = jest.fn((type, query) => {
|
|
476
|
+
if (query.id === '1') {
|
|
477
|
+
return Promise.resolve(42)
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return Promise.resolve(0)
|
|
481
|
+
})
|
|
482
|
+
const data = {
|
|
483
|
+
answer: spy,
|
|
484
|
+
}
|
|
485
|
+
const query = {
|
|
486
|
+
x: {
|
|
487
|
+
resource: 'answer',
|
|
488
|
+
id: ({ id }: QueryVariables) => id as string,
|
|
489
|
+
},
|
|
490
|
+
}
|
|
491
|
+
const wrapper = ({ children }: { children?: React.ReactNode }) => (
|
|
492
|
+
<CustomDataProvider data={data}>{children}</CustomDataProvider>
|
|
493
|
+
)
|
|
494
|
+
|
|
495
|
+
const { result } = renderHook(
|
|
496
|
+
() => useDataQuery(query, { lazy: true }),
|
|
497
|
+
{
|
|
498
|
+
wrapper,
|
|
499
|
+
}
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
expect(spy).not.toHaveBeenCalled()
|
|
503
|
+
|
|
504
|
+
act(() => {
|
|
505
|
+
result.current.refetch({ id: '1' })
|
|
506
|
+
})
|
|
507
|
+
|
|
508
|
+
await waitFor(() => {
|
|
509
|
+
expect(result.current).toMatchObject({
|
|
510
|
+
loading: false,
|
|
511
|
+
called: true,
|
|
512
|
+
data: { x: 42 },
|
|
513
|
+
})
|
|
514
|
+
})
|
|
515
|
+
|
|
516
|
+
expect(spy).toHaveBeenCalledTimes(1)
|
|
517
|
+
})
|
|
518
|
+
|
|
519
|
+
it('Should only trigger a single request when refetch is called on a lazy query with identical variables', async () => {
|
|
520
|
+
const spy = jest.fn((type, query) => {
|
|
521
|
+
if (query.id === '1') {
|
|
522
|
+
return Promise.resolve(42)
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
return Promise.resolve(0)
|
|
526
|
+
})
|
|
527
|
+
const data = {
|
|
528
|
+
answer: spy,
|
|
529
|
+
}
|
|
530
|
+
const query = {
|
|
531
|
+
x: {
|
|
532
|
+
resource: 'answer',
|
|
533
|
+
id: ({ id }: QueryVariables) => id as string,
|
|
534
|
+
},
|
|
535
|
+
}
|
|
536
|
+
const wrapper = ({ children }: { children?: React.ReactNode }) => (
|
|
537
|
+
<CustomDataProvider data={data}>{children}</CustomDataProvider>
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
const { result } = renderHook(
|
|
541
|
+
() =>
|
|
542
|
+
useDataQuery(query, { lazy: true, variables: { id: '1' } }),
|
|
543
|
+
{
|
|
544
|
+
wrapper,
|
|
545
|
+
}
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
expect(spy).not.toHaveBeenCalled()
|
|
549
|
+
|
|
550
|
+
act(() => {
|
|
551
|
+
result.current.refetch({ id: '1' })
|
|
552
|
+
})
|
|
553
|
+
|
|
554
|
+
await waitFor(() => {
|
|
555
|
+
expect(result.current).toMatchObject({
|
|
556
|
+
loading: false,
|
|
557
|
+
called: true,
|
|
558
|
+
data: { x: 42 },
|
|
559
|
+
})
|
|
560
|
+
})
|
|
561
|
+
|
|
562
|
+
expect(spy).toHaveBeenCalledTimes(1)
|
|
563
|
+
})
|
|
564
|
+
|
|
565
|
+
it('Should have a stable identity if the variables have not changed', async () => {
|
|
566
|
+
const data = {
|
|
567
|
+
answer: () => Promise.resolve(42),
|
|
568
|
+
}
|
|
569
|
+
const query = { x: { resource: 'answer' } }
|
|
570
|
+
const wrapper = ({ children }: { children?: React.ReactNode }) => (
|
|
571
|
+
<CustomDataProvider data={data}>{children}</CustomDataProvider>
|
|
572
|
+
)
|
|
573
|
+
|
|
574
|
+
const { result, rerender } = renderHook(() => useDataQuery(query), {
|
|
575
|
+
wrapper,
|
|
576
|
+
})
|
|
577
|
+
|
|
578
|
+
const firstRefetch = result.current.refetch
|
|
579
|
+
|
|
580
|
+
// await waitFor(() => {})
|
|
581
|
+
|
|
582
|
+
act(() => {
|
|
583
|
+
result.current.refetch()
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* FIXME: https://github.com/tannerlinsley/react-query/issues/2481
|
|
587
|
+
* This forced rerender is not necessary in the app, just when testing.
|
|
588
|
+
* It is unclear why.
|
|
589
|
+
*/
|
|
590
|
+
rerender()
|
|
591
|
+
})
|
|
592
|
+
|
|
593
|
+
await waitFor(() => {
|
|
594
|
+
expect(result.current.refetch).toBe(firstRefetch)
|
|
595
|
+
})
|
|
596
|
+
})
|
|
597
|
+
|
|
598
|
+
it('Should return stale data and set loading to true on refetch', async () => {
|
|
599
|
+
const answers = [42, 43]
|
|
600
|
+
const mockSpy = jest.fn(() => Promise.resolve(answers.shift()))
|
|
601
|
+
const data = {
|
|
602
|
+
answer: mockSpy,
|
|
603
|
+
}
|
|
604
|
+
const query = { x: { resource: 'answer' } }
|
|
605
|
+
const wrapper = ({ children }: { children?: React.ReactNode }) => (
|
|
606
|
+
<CustomDataProvider data={data}>{children}</CustomDataProvider>
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
const { result, rerender } = renderHook(() => useDataQuery(query), {
|
|
610
|
+
wrapper,
|
|
611
|
+
})
|
|
612
|
+
|
|
613
|
+
expect(mockSpy).toHaveBeenCalledTimes(1)
|
|
614
|
+
expect(result.current).toMatchObject({
|
|
615
|
+
loading: true,
|
|
616
|
+
called: true,
|
|
617
|
+
})
|
|
618
|
+
|
|
619
|
+
await waitFor(() => {
|
|
620
|
+
expect(result.current).toMatchObject({
|
|
621
|
+
loading: false,
|
|
622
|
+
called: true,
|
|
623
|
+
data: { x: 42 },
|
|
624
|
+
})
|
|
625
|
+
})
|
|
626
|
+
|
|
627
|
+
act(() => {
|
|
628
|
+
result.current.refetch()
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* FIXME: https://github.com/tannerlinsley/react-query/issues/2481
|
|
632
|
+
* This forced rerender is not necessary in the app, just when testing.
|
|
633
|
+
* It is unclear why.
|
|
634
|
+
*/
|
|
635
|
+
rerender()
|
|
636
|
+
})
|
|
637
|
+
|
|
638
|
+
expect(mockSpy).toHaveBeenCalledTimes(2)
|
|
639
|
+
expect(result.current).toMatchObject({
|
|
640
|
+
loading: false,
|
|
641
|
+
fetching: true,
|
|
642
|
+
called: true,
|
|
643
|
+
data: { x: 42 },
|
|
644
|
+
})
|
|
645
|
+
|
|
646
|
+
await waitFor(() => {
|
|
647
|
+
expect(mockSpy).toHaveBeenCalledTimes(2)
|
|
648
|
+
expect(result.current).toMatchObject({
|
|
649
|
+
loading: false,
|
|
650
|
+
fetching: false,
|
|
651
|
+
called: true,
|
|
652
|
+
data: { x: 43 },
|
|
653
|
+
})
|
|
654
|
+
})
|
|
655
|
+
})
|
|
656
|
+
|
|
657
|
+
it('Should not fetch until refetch has been called if lazy', async () => {
|
|
658
|
+
const query = { x: { resource: 'answer' } }
|
|
659
|
+
const mockSpy = jest.fn(() => Promise.resolve(42))
|
|
660
|
+
const data = { answer: mockSpy }
|
|
661
|
+
const wrapper = ({ children }: { children?: React.ReactNode }) => (
|
|
662
|
+
<CustomDataProvider data={data}>{children}</CustomDataProvider>
|
|
663
|
+
)
|
|
664
|
+
|
|
665
|
+
const { result } = renderHook(
|
|
666
|
+
() => useDataQuery(query, { lazy: true }),
|
|
667
|
+
{ wrapper }
|
|
668
|
+
)
|
|
669
|
+
|
|
670
|
+
expect(mockSpy).toHaveBeenCalledTimes(0)
|
|
671
|
+
expect(result.current).toMatchObject({
|
|
672
|
+
loading: false,
|
|
673
|
+
called: false,
|
|
674
|
+
})
|
|
675
|
+
|
|
676
|
+
act(() => {
|
|
677
|
+
result.current.refetch()
|
|
678
|
+
})
|
|
679
|
+
|
|
680
|
+
expect(mockSpy).toHaveBeenCalledTimes(1)
|
|
681
|
+
expect(result.current).toMatchObject({
|
|
682
|
+
loading: true,
|
|
683
|
+
called: true,
|
|
684
|
+
})
|
|
685
|
+
|
|
686
|
+
await waitFor(() => {
|
|
687
|
+
expect(result.current).toMatchObject({
|
|
688
|
+
loading: false,
|
|
689
|
+
called: true,
|
|
690
|
+
data: { x: 42 },
|
|
691
|
+
})
|
|
692
|
+
})
|
|
693
|
+
})
|
|
694
|
+
|
|
695
|
+
it('Should call onComplete after a successful refetch', async () => {
|
|
696
|
+
const query = { x: { resource: 'answer' } }
|
|
697
|
+
const data = { answer: 42 }
|
|
698
|
+
const wrapper = ({ children }: { children?: React.ReactNode }) => (
|
|
699
|
+
<CustomDataProvider data={data}>{children}</CustomDataProvider>
|
|
700
|
+
)
|
|
701
|
+
const onComplete = jest.fn()
|
|
702
|
+
const onError = jest.fn()
|
|
703
|
+
|
|
704
|
+
const { result } = renderHook(
|
|
705
|
+
() => useDataQuery(query, { lazy: true, onComplete, onError }),
|
|
706
|
+
{ wrapper }
|
|
707
|
+
)
|
|
708
|
+
|
|
709
|
+
act(() => {
|
|
710
|
+
result.current.refetch()
|
|
711
|
+
})
|
|
712
|
+
|
|
713
|
+
await waitFor(() => {
|
|
714
|
+
expect(result.current).toMatchObject({
|
|
715
|
+
loading: false,
|
|
716
|
+
called: true,
|
|
717
|
+
data: { x: 42 },
|
|
718
|
+
})
|
|
719
|
+
expect(onComplete).toHaveBeenCalledWith({ x: 42 })
|
|
720
|
+
expect(onError).not.toHaveBeenCalled()
|
|
721
|
+
})
|
|
722
|
+
})
|
|
723
|
+
|
|
724
|
+
it('Should call onError after a failed refetch', async () => {
|
|
725
|
+
const expectedError = new Error('Something went wrong')
|
|
726
|
+
const query = { x: { resource: 'answer' } }
|
|
727
|
+
const data = {
|
|
728
|
+
answer: () => {
|
|
729
|
+
throw expectedError
|
|
730
|
+
},
|
|
731
|
+
}
|
|
732
|
+
const wrapper = ({ children }: { children?: React.ReactNode }) => (
|
|
733
|
+
<CustomDataProvider data={data}>{children}</CustomDataProvider>
|
|
734
|
+
)
|
|
735
|
+
const onComplete = jest.fn()
|
|
736
|
+
const onError = jest.fn()
|
|
737
|
+
|
|
738
|
+
const { result } = renderHook(
|
|
739
|
+
() => useDataQuery(query, { lazy: true, onComplete, onError }),
|
|
740
|
+
{ wrapper }
|
|
741
|
+
)
|
|
742
|
+
|
|
743
|
+
act(() => {
|
|
744
|
+
result.current.refetch()
|
|
745
|
+
})
|
|
746
|
+
|
|
747
|
+
await waitFor(() => {
|
|
748
|
+
expect(onError).toHaveBeenCalledWith(expectedError)
|
|
749
|
+
expect(onComplete).not.toHaveBeenCalled()
|
|
750
|
+
expect(result.current).toMatchObject({
|
|
751
|
+
error: expectedError,
|
|
752
|
+
})
|
|
753
|
+
})
|
|
754
|
+
})
|
|
755
|
+
|
|
756
|
+
it('Should refetch when refetch is called with variables that resolve to the same query key', async () => {
|
|
757
|
+
const variables = { one: 1, two: 2, three: 3 }
|
|
758
|
+
const query = {
|
|
759
|
+
x: {
|
|
760
|
+
resource: 'answer',
|
|
761
|
+
params: ({ one, two, three }: QueryVariables) => ({
|
|
762
|
+
one,
|
|
763
|
+
two,
|
|
764
|
+
three,
|
|
765
|
+
}),
|
|
766
|
+
},
|
|
767
|
+
}
|
|
768
|
+
const spy = jest.fn(() => Promise.resolve(42))
|
|
769
|
+
const data = { answer: spy }
|
|
770
|
+
const wrapper = ({ children }: { children?: React.ReactNode }) => (
|
|
771
|
+
<CustomDataProvider data={data}>{children}</CustomDataProvider>
|
|
772
|
+
)
|
|
773
|
+
|
|
774
|
+
const { result } = renderHook(
|
|
775
|
+
() => useDataQuery(query, { variables }),
|
|
776
|
+
{ wrapper }
|
|
777
|
+
)
|
|
778
|
+
|
|
779
|
+
await waitFor(() => {
|
|
780
|
+
expect(spy).toHaveBeenCalledTimes(1)
|
|
781
|
+
expect(result.current).toMatchObject({
|
|
782
|
+
loading: false,
|
|
783
|
+
called: true,
|
|
784
|
+
data: { x: 42 },
|
|
785
|
+
})
|
|
786
|
+
})
|
|
787
|
+
|
|
788
|
+
act(() => {
|
|
789
|
+
result.current.refetch(variables)
|
|
790
|
+
})
|
|
791
|
+
|
|
792
|
+
await waitFor(() => {
|
|
793
|
+
expect(spy).toHaveBeenCalledTimes(2)
|
|
794
|
+
expect(result.current).toMatchObject({
|
|
795
|
+
loading: false,
|
|
796
|
+
called: true,
|
|
797
|
+
data: { x: 42 },
|
|
798
|
+
})
|
|
799
|
+
})
|
|
800
|
+
})
|
|
801
|
+
|
|
802
|
+
it('Should return a promise that resolves with the data on success when refetching and lazy', async () => {
|
|
803
|
+
const query = { x: { resource: 'answer' } }
|
|
804
|
+
const data = { answer: 42 }
|
|
805
|
+
const wrapper = ({ children }: { children?: React.ReactNode }) => (
|
|
806
|
+
<CustomDataProvider data={data}>{children}</CustomDataProvider>
|
|
807
|
+
)
|
|
808
|
+
|
|
809
|
+
const { result } = renderHook(
|
|
810
|
+
() => useDataQuery(query, { lazy: true }),
|
|
811
|
+
{ wrapper }
|
|
812
|
+
)
|
|
813
|
+
|
|
814
|
+
let ourPromise!: Promise<unknown>
|
|
815
|
+
act(() => {
|
|
816
|
+
// This refetch will trigger our own refetch logic as the query is lazy
|
|
817
|
+
ourPromise = result.current.refetch()
|
|
818
|
+
})
|
|
819
|
+
|
|
820
|
+
await waitFor(() => {
|
|
821
|
+
expect(ourPromise).resolves.toEqual({ x: 42 })
|
|
822
|
+
expect(result.current).toMatchObject({
|
|
823
|
+
loading: false,
|
|
824
|
+
called: true,
|
|
825
|
+
data: { x: 42 },
|
|
826
|
+
})
|
|
827
|
+
})
|
|
828
|
+
})
|
|
829
|
+
|
|
830
|
+
it('Should return a promise that resolves with the data on success when refetching and not lazy', async () => {
|
|
831
|
+
const query = { x: { resource: 'answer' } }
|
|
832
|
+
const data = { answer: 42 }
|
|
833
|
+
const wrapper = ({ children }: { children?: React.ReactNode }) => (
|
|
834
|
+
<CustomDataProvider data={data}>{children}</CustomDataProvider>
|
|
835
|
+
)
|
|
836
|
+
|
|
837
|
+
const { result } = renderHook(
|
|
838
|
+
() => useDataQuery(query, { lazy: false }),
|
|
839
|
+
{ wrapper }
|
|
840
|
+
)
|
|
841
|
+
|
|
842
|
+
let reactQueryPromise!: Promise<unknown>
|
|
843
|
+
act(() => {
|
|
844
|
+
// This refetch will trigger react query's refetch logic as the query is not lazy
|
|
845
|
+
reactQueryPromise = result.current.refetch()
|
|
846
|
+
})
|
|
847
|
+
|
|
848
|
+
await waitFor(() => {
|
|
849
|
+
expect(reactQueryPromise).resolves.toEqual({ x: 42 })
|
|
850
|
+
expect(result.current).toMatchObject({
|
|
851
|
+
loading: false,
|
|
852
|
+
called: true,
|
|
853
|
+
data: { x: 42 },
|
|
854
|
+
})
|
|
855
|
+
})
|
|
856
|
+
})
|
|
857
|
+
|
|
858
|
+
it('Should return a promise that does not reject on errors when refetching and lazy', async () => {
|
|
859
|
+
const expectedError = new Error('Something went wrong')
|
|
860
|
+
const query = { x: { resource: 'answer' } }
|
|
861
|
+
const data = {
|
|
862
|
+
answer: () => {
|
|
863
|
+
throw expectedError
|
|
864
|
+
},
|
|
865
|
+
}
|
|
866
|
+
const wrapper = ({ children }: { children?: React.ReactNode }) => (
|
|
867
|
+
<CustomDataProvider data={data}>{children}</CustomDataProvider>
|
|
868
|
+
)
|
|
869
|
+
|
|
870
|
+
const { result } = renderHook(
|
|
871
|
+
() => useDataQuery(query, { lazy: true }),
|
|
872
|
+
{ wrapper }
|
|
873
|
+
)
|
|
874
|
+
|
|
875
|
+
let ourPromise!: Promise<unknown>
|
|
876
|
+
act(() => {
|
|
877
|
+
// This refetch will trigger our own refetch logic as the query is lazy
|
|
878
|
+
ourPromise = result.current.refetch()
|
|
879
|
+
})
|
|
880
|
+
|
|
881
|
+
await waitFor(() => {
|
|
882
|
+
expect(ourPromise).resolves.toBeUndefined()
|
|
883
|
+
expect(result.current).toMatchObject({
|
|
884
|
+
error: expectedError,
|
|
885
|
+
})
|
|
886
|
+
})
|
|
887
|
+
})
|
|
888
|
+
|
|
889
|
+
it('Should return a promise that does not reject on errors when refetching and not lazy', async () => {
|
|
890
|
+
const expectedError = new Error('Something went wrong')
|
|
891
|
+
const query = { x: { resource: 'answer' } }
|
|
892
|
+
const data = {
|
|
893
|
+
answer: () => {
|
|
894
|
+
throw expectedError
|
|
895
|
+
},
|
|
896
|
+
}
|
|
897
|
+
const wrapper = ({ children }: { children?: React.ReactNode }) => (
|
|
898
|
+
<CustomDataProvider data={data}>{children}</CustomDataProvider>
|
|
899
|
+
)
|
|
900
|
+
|
|
901
|
+
const { result } = renderHook(
|
|
902
|
+
() => useDataQuery(query, { lazy: false }),
|
|
903
|
+
{ wrapper }
|
|
904
|
+
)
|
|
905
|
+
|
|
906
|
+
let reactQueryPromise!: Promise<unknown>
|
|
907
|
+
act(() => {
|
|
908
|
+
// This refetch will trigger react query's refetch logic as the query is not lazy
|
|
909
|
+
reactQueryPromise = result.current.refetch()
|
|
910
|
+
})
|
|
911
|
+
|
|
912
|
+
await waitFor(() => {
|
|
913
|
+
expect(reactQueryPromise).resolves.toBeUndefined()
|
|
914
|
+
expect(result.current).toMatchObject({
|
|
915
|
+
error: expectedError,
|
|
916
|
+
})
|
|
917
|
+
})
|
|
918
|
+
})
|
|
919
|
+
|
|
920
|
+
it('Should accept new variables from refetch', async () => {
|
|
921
|
+
const one = 'one'
|
|
922
|
+
const two = 'two'
|
|
923
|
+
const resultOne = 1
|
|
924
|
+
const resultTwo = 2
|
|
925
|
+
const query = {
|
|
926
|
+
x: {
|
|
927
|
+
resource: 'answer',
|
|
928
|
+
id: ({ id }: QueryVariables) => id as string,
|
|
929
|
+
},
|
|
930
|
+
}
|
|
931
|
+
const mockSpy = jest.fn((_, { id }) => {
|
|
932
|
+
switch (id) {
|
|
933
|
+
case one:
|
|
934
|
+
return Promise.resolve(resultOne)
|
|
935
|
+
case two:
|
|
936
|
+
return Promise.resolve(resultTwo)
|
|
937
|
+
}
|
|
938
|
+
return Promise.resolve(undefined)
|
|
939
|
+
})
|
|
940
|
+
const data = {
|
|
941
|
+
answer: mockSpy,
|
|
942
|
+
}
|
|
943
|
+
const wrapper = ({ children }: { children?: any }) => (
|
|
944
|
+
<CustomDataProvider data={data}>{children}</CustomDataProvider>
|
|
945
|
+
)
|
|
946
|
+
const initialProps = {
|
|
947
|
+
query,
|
|
948
|
+
options: { variables: { id: one } },
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
const { result } = renderHook(
|
|
952
|
+
(props) => useDataQuery(props.query, props.options),
|
|
953
|
+
{
|
|
954
|
+
wrapper,
|
|
955
|
+
initialProps,
|
|
956
|
+
}
|
|
957
|
+
)
|
|
958
|
+
|
|
959
|
+
await waitFor(() => {
|
|
960
|
+
expect(result.current).toMatchObject({
|
|
961
|
+
data: { x: resultOne },
|
|
962
|
+
})
|
|
963
|
+
})
|
|
964
|
+
|
|
965
|
+
act(() => {
|
|
966
|
+
result.current.refetch({ id: two })
|
|
967
|
+
})
|
|
968
|
+
|
|
969
|
+
await waitFor(() => {
|
|
970
|
+
expect(mockSpy).toHaveBeenCalledTimes(2)
|
|
971
|
+
expect(result.current).toMatchObject({
|
|
972
|
+
data: { x: resultTwo },
|
|
973
|
+
})
|
|
974
|
+
})
|
|
975
|
+
})
|
|
976
|
+
|
|
977
|
+
it('Should merge new variables from refetch with the existing variables', async () => {
|
|
978
|
+
const initialVariables = { one: 1, two: 2, three: 3 }
|
|
979
|
+
const newVariables = { two: 1, three: 1 }
|
|
980
|
+
const query = {
|
|
981
|
+
x: {
|
|
982
|
+
resource: 'answer',
|
|
983
|
+
params: ({ one, two, three }: QueryVariables) => ({
|
|
984
|
+
one,
|
|
985
|
+
two,
|
|
986
|
+
three,
|
|
987
|
+
}),
|
|
988
|
+
},
|
|
989
|
+
}
|
|
990
|
+
const mockSpy = jest.fn(() => Promise.resolve(42))
|
|
991
|
+
const data = {
|
|
992
|
+
answer: mockSpy,
|
|
993
|
+
}
|
|
994
|
+
const wrapper = ({ children }: { children?: React.ReactNode }) => (
|
|
995
|
+
<CustomDataProvider data={data}>{children}</CustomDataProvider>
|
|
996
|
+
)
|
|
997
|
+
|
|
998
|
+
const { result } = renderHook(
|
|
999
|
+
() => useDataQuery(query, { variables: initialVariables }),
|
|
1000
|
+
{ wrapper }
|
|
1001
|
+
)
|
|
1002
|
+
|
|
1003
|
+
await waitFor(() => {
|
|
1004
|
+
expect(mockSpy).toHaveBeenLastCalledWith(
|
|
1005
|
+
expect.anything(),
|
|
1006
|
+
expect.objectContaining({ params: initialVariables }),
|
|
1007
|
+
expect.anything()
|
|
1008
|
+
)
|
|
1009
|
+
})
|
|
1010
|
+
|
|
1011
|
+
act(() => {
|
|
1012
|
+
result.current.refetch(newVariables)
|
|
1013
|
+
})
|
|
1014
|
+
|
|
1015
|
+
await waitFor(() => {
|
|
1016
|
+
expect(mockSpy).toHaveBeenLastCalledWith(
|
|
1017
|
+
expect.anything(),
|
|
1018
|
+
expect.objectContaining({
|
|
1019
|
+
params: {
|
|
1020
|
+
...initialVariables,
|
|
1021
|
+
...newVariables,
|
|
1022
|
+
},
|
|
1023
|
+
}),
|
|
1024
|
+
expect.anything()
|
|
1025
|
+
)
|
|
1026
|
+
})
|
|
1027
|
+
})
|
|
1028
|
+
})
|
|
1029
|
+
})
|