source: node_modules/@reduxjs/toolkit/src/query/tests/refetchingBehaviors.test.tsx

Last change on this file was a762898, checked in by istevanoska <ilinastevanoska@…>, 5 months ago

Added visualizations

  • Property mode set to 100644
File size: 12.8 KB
RevLine 
[a762898]1import { createApi, setupListeners } from '@reduxjs/toolkit/query/react'
2import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
3import { delay } from 'msw'
4import { setupApiStore } from '../../tests/utils/helpers'
5
6// Just setup a temporary in-memory counter for tests that `getIncrementedAmount`.
7// This can be used to test how many renders happen due to data changes or
8// the refetching behavior of components.
9let amount = 0
10
11const defaultApi = createApi({
12 baseQuery: async (arg: any) => {
13 await delay(150)
14 if ('amount' in arg?.body) {
15 amount += 1
16 }
17 return {
18 data: arg?.body
19 ? { ...arg.body, ...(amount ? { amount } : {}) }
20 : undefined,
21 }
22 },
23 endpoints: (build) => ({
24 getIncrementedAmount: build.query<any, void>({
25 query: () => ({
26 url: '',
27 body: {
28 amount,
29 },
30 }),
31 }),
32 }),
33 refetchOnFocus: true,
34 refetchOnReconnect: true,
35})
36
37const storeRef = setupApiStore(defaultApi)
38
39const getIncrementedAmountState = () =>
40 storeRef.store.getState().api.queries['getIncrementedAmount(undefined)']
41
42afterEach(() => {
43 amount = 0
44})
45
46describe('refetchOnFocus tests', () => {
47 test('useQuery hook respects refetchOnFocus: true when set in createApi options', async () => {
48 let data, isLoading, isFetching
49
50 function User() {
51 ;({ data, isFetching, isLoading } =
52 defaultApi.endpoints.getIncrementedAmount.useQuery())
53 return (
54 <div>
55 <div data-testid="isLoading">{String(isLoading)}</div>
56 <div data-testid="isFetching">{String(isFetching)}</div>
57 <div data-testid="amount">{String(data?.amount)}</div>
58 </div>
59 )
60 }
61
62 render(<User />, { wrapper: storeRef.wrapper })
63
64 await waitFor(() =>
65 expect(screen.getByTestId('isLoading').textContent).toBe('true'),
66 )
67 await waitFor(() =>
68 expect(screen.getByTestId('isLoading').textContent).toBe('false'),
69 )
70 await waitFor(() =>
71 expect(screen.getByTestId('amount').textContent).toBe('1'),
72 )
73
74 fireEvent.focus(window)
75
76 await waitFor(() =>
77 expect(screen.getByTestId('amount').textContent).toBe('2'),
78 )
79 })
80
81 test('useQuery hook respects refetchOnFocus: false from a hook and overrides createApi defaults', async () => {
82 let data, isLoading, isFetching
83
84 function User() {
85 ;({ data, isFetching, isLoading } =
86 defaultApi.endpoints.getIncrementedAmount.useQuery(undefined, {
87 refetchOnFocus: false,
88 }))
89 return (
90 <div>
91 <div data-testid="isLoading">{String(isLoading)}</div>
92 <div data-testid="isFetching">{String(isFetching)}</div>
93 <div data-testid="amount">{String(data?.amount)}</div>
94 </div>
95 )
96 }
97
98 render(<User />, { wrapper: storeRef.wrapper })
99
100 await waitFor(() =>
101 expect(screen.getByTestId('isLoading').textContent).toBe('true'),
102 )
103 await waitFor(() =>
104 expect(screen.getByTestId('isLoading').textContent).toBe('false'),
105 )
106 await waitFor(() =>
107 expect(screen.getByTestId('amount').textContent).toBe('1'),
108 )
109
110 fireEvent.focus(window)
111
112 await waitFor(() =>
113 expect(screen.getByTestId('amount').textContent).toBe('1'),
114 )
115 })
116
117 test('useQuery hook prefers refetchOnFocus: true when multiple components have different configurations', async () => {
118 let data, isLoading, isFetching
119
120 function User() {
121 ;({ data, isFetching, isLoading } =
122 defaultApi.endpoints.getIncrementedAmount.useQuery(undefined, {
123 refetchOnFocus: false,
124 }))
125 return (
126 <div>
127 <div data-testid="isLoading">{String(isLoading)}</div>
128 <div data-testid="isFetching">{String(isFetching)}</div>
129 <div data-testid="amount">{String(data?.amount)}</div>
130 </div>
131 )
132 }
133
134 function UserWithRefetchTrue() {
135 ;({ data, isFetching, isLoading } =
136 defaultApi.endpoints.getIncrementedAmount.useQuery(undefined, {
137 refetchOnFocus: true,
138 }))
139 return <div />
140 }
141
142 render(
143 <div>
144 <User />
145 <UserWithRefetchTrue />
146 </div>,
147 { wrapper: storeRef.wrapper },
148 )
149
150 await waitFor(() =>
151 expect(screen.getByTestId('isLoading').textContent).toBe('true'),
152 )
153 await waitFor(() =>
154 expect(screen.getByTestId('isLoading').textContent).toBe('false'),
155 )
156 await waitFor(() =>
157 expect(screen.getByTestId('amount').textContent).toBe('1'),
158 )
159
160 fireEvent.focus(window)
161 expect(screen.getByTestId('isLoading').textContent).toBe('false')
162 await waitFor(() =>
163 expect(screen.getByTestId('isFetching').textContent).toBe('true'),
164 )
165 await waitFor(() =>
166 expect(screen.getByTestId('isFetching').textContent).toBe('false'),
167 )
168 await waitFor(() =>
169 expect(screen.getByTestId('amount').textContent).toBe('2'),
170 )
171 })
172
173 test('useQuery hook cleans data if refetch without active subscribers', async () => {
174 let data, isLoading, isFetching
175
176 function User() {
177 ;({ data, isFetching, isLoading } =
178 defaultApi.endpoints.getIncrementedAmount.useQuery(undefined, {
179 refetchOnFocus: true,
180 }))
181 return (
182 <div>
183 <div data-testid="isLoading">{String(isLoading)}</div>
184 <div data-testid="isFetching">{String(isFetching)}</div>
185 <div data-testid="amount">{String(data?.amount)}</div>
186 </div>
187 )
188 }
189
190 const { unmount } = render(<User />, { wrapper: storeRef.wrapper })
191
192 await waitFor(() =>
193 expect(screen.getByTestId('isLoading').textContent).toBe('true'),
194 )
195 await waitFor(() =>
196 expect(screen.getByTestId('isLoading').textContent).toBe('false'),
197 )
198 await waitFor(() =>
199 expect(screen.getByTestId('amount').textContent).toBe('1'),
200 )
201
202 unmount()
203
204 expect(getIncrementedAmountState()).not.toBeUndefined()
205
206 fireEvent.focus(window)
207
208 await delay(1)
209 expect(getIncrementedAmountState()).toBeUndefined()
210 })
211})
212
213describe('refetchOnReconnect tests', () => {
214 test('useQuery hook respects refetchOnReconnect: true when set in createApi options', async () => {
215 let data, isLoading, isFetching
216
217 function User() {
218 ;({ data, isFetching, isLoading } =
219 defaultApi.endpoints.getIncrementedAmount.useQuery())
220 return (
221 <div>
222 <div data-testid="isLoading">{String(isLoading)}</div>
223 <div data-testid="isFetching">{String(isFetching)}</div>
224 <div data-testid="amount">{String(data?.amount)}</div>
225 </div>
226 )
227 }
228
229 render(<User />, { wrapper: storeRef.wrapper })
230
231 await waitFor(() =>
232 expect(screen.getByTestId('isLoading').textContent).toBe('true'),
233 )
234 await waitFor(() =>
235 expect(screen.getByTestId('isLoading').textContent).toBe('false'),
236 )
237 await waitFor(() =>
238 expect(screen.getByTestId('amount').textContent).toBe('1'),
239 )
240
241 act(() => {
242 window.dispatchEvent(new Event('offline'))
243 window.dispatchEvent(new Event('online'))
244 })
245
246 await waitFor(() =>
247 expect(screen.getByTestId('isFetching').textContent).toBe('true'),
248 )
249 await waitFor(() =>
250 expect(screen.getByTestId('isFetching').textContent).toBe('false'),
251 )
252 await waitFor(() =>
253 expect(screen.getByTestId('amount').textContent).toBe('2'),
254 )
255 })
256
257 test('useQuery hook should not refetch when refetchOnReconnect: false from a hook and overrides createApi defaults', async () => {
258 let data, isLoading, isFetching
259
260 function User() {
261 ;({ data, isFetching, isLoading } =
262 defaultApi.endpoints.getIncrementedAmount.useQuery(undefined, {
263 refetchOnReconnect: false,
264 }))
265 return (
266 <div>
267 <div data-testid="isLoading">{String(isLoading)}</div>
268 <div data-testid="isFetching">{String(isFetching)}</div>
269 <div data-testid="amount">{String(data?.amount)}</div>
270 </div>
271 )
272 }
273
274 render(<User />, { wrapper: storeRef.wrapper })
275
276 await waitFor(() =>
277 expect(screen.getByTestId('isLoading').textContent).toBe('true'),
278 )
279 await waitFor(() =>
280 expect(screen.getByTestId('isLoading').textContent).toBe('false'),
281 )
282 await waitFor(() =>
283 expect(screen.getByTestId('amount').textContent).toBe('1'),
284 )
285
286 act(() => {
287 window.dispatchEvent(new Event('offline'))
288 window.dispatchEvent(new Event('online'))
289 })
290 expect(screen.getByTestId('isFetching').textContent).toBe('false')
291 await waitFor(() =>
292 expect(screen.getByTestId('amount').textContent).toBe('1'),
293 )
294 })
295
296 test('useQuery hook prefers refetchOnReconnect: true when multiple components have different configurations', async () => {
297 let data, isLoading, isFetching
298
299 function User() {
300 ;({ data, isFetching, isLoading } =
301 defaultApi.endpoints.getIncrementedAmount.useQuery(undefined, {
302 refetchOnReconnect: false,
303 }))
304 return (
305 <div>
306 <div data-testid="isLoading">{String(isLoading)}</div>
307 <div data-testid="isFetching">{String(isFetching)}</div>
308 <div data-testid="amount">{String(data?.amount)}</div>
309 </div>
310 )
311 }
312
313 function UserWithRefetchTrue() {
314 ;({ data, isFetching, isLoading } =
315 defaultApi.endpoints.getIncrementedAmount.useQuery(undefined, {
316 refetchOnReconnect: true,
317 }))
318 return <div />
319 }
320
321 render(
322 <div>
323 <User />
324 <UserWithRefetchTrue />
325 </div>,
326 { wrapper: storeRef.wrapper },
327 )
328
329 await waitFor(() =>
330 expect(screen.getByTestId('isLoading').textContent).toBe('true'),
331 )
332 await waitFor(() =>
333 expect(screen.getByTestId('isLoading').textContent).toBe('false'),
334 )
335 await waitFor(() =>
336 expect(screen.getByTestId('amount').textContent).toBe('1'),
337 )
338
339 act(() => {
340 window.dispatchEvent(new Event('offline'))
341 window.dispatchEvent(new Event('online'))
342 })
343
344 await waitFor(() =>
345 expect(screen.getByTestId('isFetching').textContent).toBe('true'),
346 )
347 await waitFor(() =>
348 expect(screen.getByTestId('isFetching').textContent).toBe('false'),
349 )
350 await waitFor(() =>
351 expect(screen.getByTestId('amount').textContent).toBe('2'),
352 )
353 })
354})
355
356describe('customListenersHandler', () => {
357 const storeRef = setupApiStore(defaultApi, undefined, {
358 withoutListeners: true,
359 })
360
361 test('setupListeners accepts a custom callback and executes it', async () => {
362 const consoleSpy = vi.spyOn(console, 'log')
363 consoleSpy.mockImplementation((...args: any[]) => {
364 // console.info(...args)
365 })
366 const dispatchSpy = vi.spyOn(storeRef.store, 'dispatch')
367
368 let unsubscribe = () => {}
369 unsubscribe = setupListeners(
370 storeRef.store.dispatch,
371 (dispatch, actions) => {
372 const handleOnline = () =>
373 dispatch(defaultApi.internalActions.onOnline())
374 window.addEventListener('online', handleOnline, false)
375 console.log('setup!')
376 return () => {
377 window.removeEventListener('online', handleOnline)
378 console.log('cleanup!')
379 }
380 },
381 )
382
383 await delay(150)
384
385 let data, isLoading, isFetching
386
387 function User() {
388 ;({ data, isFetching, isLoading } =
389 defaultApi.endpoints.getIncrementedAmount.useQuery(undefined, {
390 refetchOnReconnect: true,
391 }))
392 return (
393 <div>
394 <div data-testid="isLoading">{String(isLoading)}</div>
395 <div data-testid="isFetching">{String(isFetching)}</div>
396 <div data-testid="amount">{String(data?.amount)}</div>
397 </div>
398 )
399 }
400
401 render(<User />, { wrapper: storeRef.wrapper })
402
403 expect(consoleSpy).toHaveBeenCalledWith('setup!')
404
405 await waitFor(() =>
406 expect(screen.getByTestId('isLoading').textContent).toBe('true'),
407 )
408 await waitFor(() =>
409 expect(screen.getByTestId('isLoading').textContent).toBe('false'),
410 )
411 await waitFor(() =>
412 expect(screen.getByTestId('amount').textContent).toBe('1'),
413 )
414
415 act(() => {
416 window.dispatchEvent(new Event('offline'))
417 window.dispatchEvent(new Event('online'))
418 })
419 expect(dispatchSpy).toHaveBeenCalled()
420
421 // Ignore RTKQ middleware internal data calls
422 const mockCallsWithoutInternals = dispatchSpy.mock.calls.filter((call) => {
423 const type = (call[0] as any)?.type ?? ''
424 const reIsInternal = /internal/i
425 return !reIsInternal.test(type)
426 })
427
428 expect(
429 defaultApi.internalActions.onOnline.match(
430 mockCallsWithoutInternals[1][0] as any,
431 ),
432 ).toBe(true)
433
434 await waitFor(() =>
435 expect(screen.getByTestId('isFetching').textContent).toBe('true'),
436 )
437 await waitFor(() =>
438 expect(screen.getByTestId('isFetching').textContent).toBe('false'),
439 )
440 await waitFor(() =>
441 expect(screen.getByTestId('amount').textContent).toBe('2'),
442 )
443
444 unsubscribe()
445 expect(consoleSpy).toHaveBeenCalledWith('cleanup!')
446 })
447})
Note: See TracBrowser for help on using the repository browser.