source: node_modules/@reduxjs/toolkit/src/query/tests/infiniteQueries.test.ts

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

Added visualizations

  • Property mode set to 100644
File size: 49.6 KB
Line 
1import { server } from '@internal/query/tests/mocks/server'
2import type { InfiniteQueryActionCreatorResult } from '@reduxjs/toolkit/query/react'
3import {
4 QueryStatus,
5 createApi,
6 fakeBaseQuery,
7 fetchBaseQuery,
8} from '@reduxjs/toolkit/query/react'
9import { HttpResponse, delay, http } from 'msw'
10import { actionsReducer, setupApiStore } from '../../tests/utils/helpers'
11import type { InfiniteQueryResultFlags } from '../core/buildSelectors'
12
13describe('Infinite queries', () => {
14 type Pokemon = {
15 id: string
16 name: string
17 }
18
19 type HitCounter = { page: number; hitCounter: number }
20 let counters: Record<string, number> = {}
21 let queryCounter = 0
22
23 const pokemonApi = createApi({
24 baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }),
25 endpoints: (build) => ({
26 getInfinitePokemon: build.infiniteQuery<Pokemon[], string, number>({
27 infiniteQueryOptions: {
28 initialPageParam: 0,
29 getNextPageParam: (
30 lastPage,
31 allPages,
32 lastPageParam,
33 allPageParams,
34 queryArg,
35 ) => {
36 expect(typeof queryArg).toBe('string')
37 return lastPageParam + 1
38 },
39 getPreviousPageParam: (
40 firstPage,
41 allPages,
42 firstPageParam,
43 allPageParams,
44 queryArg,
45 ) => {
46 expect(typeof queryArg).toBe('string')
47 return firstPageParam > 0 ? firstPageParam - 1 : undefined
48 },
49 },
50 query({ pageParam }) {
51 return `https://example.com/listItems?page=${pageParam}`
52 },
53 }),
54 getInfinitePokemonWithMax: build.infiniteQuery<Pokemon[], string, number>(
55 {
56 infiniteQueryOptions: {
57 initialPageParam: 0,
58 maxPages: 3,
59 getNextPageParam: (
60 lastPage,
61 allPages,
62 lastPageParam,
63 allPageParams,
64 ) => lastPageParam + 1,
65 getPreviousPageParam: (
66 firstPage,
67 allPages,
68 firstPageParam,
69 allPageParams,
70 ) => {
71 return firstPageParam > 0 ? firstPageParam - 1 : undefined
72 },
73 },
74 query({ pageParam }) {
75 return `https://example.com/listItems?page=${pageParam}`
76 },
77 },
78 ),
79 counters: build.query<{ id: string; counter: number }, string>({
80 queryFn: async (arg) => {
81 if (!(arg in counters)) {
82 counters[arg] = 0
83 }
84 counters[arg]++
85
86 return { data: { id: arg, counter: counters[arg] } }
87 },
88 }),
89 }),
90 })
91
92 function createCountersApi() {
93 let hitCounter = 0
94
95 const countersApi = createApi({
96 baseQuery: fakeBaseQuery(),
97 tagTypes: ['Counter'],
98 endpoints: (build) => ({
99 counters: build.infiniteQuery<HitCounter, string, number>({
100 queryFn({ pageParam }) {
101 hitCounter++
102
103 return { data: { page: pageParam, hitCounter } }
104 },
105 infiniteQueryOptions: {
106 initialPageParam: 0,
107 getNextPageParam: (
108 lastPage,
109 allPages,
110 lastPageParam,
111 allPageParams,
112 ) => lastPageParam + 1,
113 },
114 providesTags: ['Counter'],
115 }),
116 mutation: build.mutation<null, void>({
117 queryFn: async () => {
118 return { data: null }
119 },
120 invalidatesTags: ['Counter'],
121 }),
122 }),
123 })
124
125 return countersApi
126 }
127
128 let storeRef = setupApiStore(
129 pokemonApi,
130 { ...actionsReducer },
131 {
132 withoutTestLifecycles: true,
133 },
134 )
135
136 beforeEach(() => {
137 server.use(
138 http.get('https://example.com/listItems', ({ request }) => {
139 const url = new URL(request.url)
140 const pageString = url.searchParams.get('page')
141 const pageNum = parseInt(pageString || '0')
142 queryCounter++
143
144 const results: Pokemon[] = [
145 { id: `${pageNum}`, name: `Pokemon ${pageNum}` },
146 ]
147 return HttpResponse.json(results)
148 }),
149 )
150
151 storeRef = setupApiStore(
152 pokemonApi,
153 { ...actionsReducer },
154 {
155 withoutTestLifecycles: true,
156 },
157 )
158
159 counters = {}
160
161 queryCounter = 0
162 })
163
164 type InfiniteQueryResult = Awaited<InfiniteQueryActionCreatorResult<any>>
165
166 const checkResultData = (
167 result: InfiniteQueryResult,
168 expectedValues: Pokemon[][],
169 ) => {
170 expect(result.status).toBe(QueryStatus.fulfilled)
171 if (result.status === QueryStatus.fulfilled) {
172 expect(result.data.pages).toEqual(expectedValues)
173 }
174 }
175
176 const checkResultLength = (
177 result: InfiniteQueryResult,
178 expectedLength: number,
179 ) => {
180 expect(result.status).toBe(QueryStatus.fulfilled)
181 if (result.status === QueryStatus.fulfilled) {
182 expect(result.data.pages).toHaveLength(expectedLength)
183 }
184 }
185
186 test('Basic infinite query behavior', async () => {
187 const checkFlags = (
188 value: unknown,
189 expectedFlags: Partial<InfiniteQueryResultFlags>,
190 ) => {
191 const actualFlags: InfiniteQueryResultFlags = {
192 hasNextPage: false,
193 hasPreviousPage: false,
194 isFetchingNextPage: false,
195 isFetchingPreviousPage: false,
196 isFetchNextPageError: false,
197 isFetchPreviousPageError: false,
198 ...expectedFlags,
199 }
200
201 expect(value).toMatchObject(actualFlags)
202 }
203
204 const checkEntryFlags = (
205 arg: string,
206 expectedFlags: Partial<InfiniteQueryResultFlags>,
207 ) => {
208 const selector = pokemonApi.endpoints.getInfinitePokemon.select(arg)
209 const entry = selector(storeRef.store.getState())
210
211 checkFlags(entry, expectedFlags)
212 }
213
214 const res1 = storeRef.store.dispatch(
215 pokemonApi.endpoints.getInfinitePokemon.initiate('fire', {}),
216 )
217
218 checkEntryFlags('fire', {})
219
220 const entry1InitialLoad = await res1
221
222 checkResultData(entry1InitialLoad, [[{ id: '0', name: 'Pokemon 0' }]])
223 checkFlags(entry1InitialLoad, {
224 hasNextPage: true,
225 })
226
227 const res2 = storeRef.store.dispatch(
228 pokemonApi.endpoints.getInfinitePokemon.initiate('fire', {
229 direction: 'forward',
230 }),
231 )
232
233 checkEntryFlags('fire', {
234 hasNextPage: true,
235 isFetchingNextPage: true,
236 })
237
238 const entry1SecondPage = await res2
239
240 checkResultData(entry1SecondPage, [
241 [{ id: '0', name: 'Pokemon 0' }],
242 [{ id: '1', name: 'Pokemon 1' }],
243 ])
244 checkFlags(entry1SecondPage, {
245 hasNextPage: true,
246 })
247
248 const res3 = storeRef.store.dispatch(
249 pokemonApi.endpoints.getInfinitePokemon.initiate('fire', {
250 direction: 'backward',
251 }),
252 )
253
254 checkEntryFlags('fire', {
255 hasNextPage: true,
256 isFetchingPreviousPage: true,
257 })
258
259 const entry1PrevPageMissing = await res3
260
261 checkResultData(entry1PrevPageMissing, [
262 [{ id: '0', name: 'Pokemon 0' }],
263 [{ id: '1', name: 'Pokemon 1' }],
264 ])
265 checkFlags(entry1PrevPageMissing, {
266 hasNextPage: true,
267 })
268
269 const res4 = storeRef.store.dispatch(
270 pokemonApi.endpoints.getInfinitePokemon.initiate('water', {
271 initialPageParam: 3,
272 }),
273 )
274
275 checkEntryFlags('water', {})
276
277 const entry2InitialLoad = await res4
278
279 checkResultData(entry2InitialLoad, [[{ id: '3', name: 'Pokemon 3' }]])
280 checkFlags(entry2InitialLoad, {
281 hasNextPage: true,
282 hasPreviousPage: true,
283 })
284
285 const res5 = storeRef.store.dispatch(
286 pokemonApi.endpoints.getInfinitePokemon.initiate('water', {
287 direction: 'forward',
288 }),
289 )
290
291 checkEntryFlags('water', {
292 hasNextPage: true,
293 hasPreviousPage: true,
294 isFetchingNextPage: true,
295 })
296
297 const entry2NextPage = await res5
298
299 checkResultData(entry2NextPage, [
300 [{ id: '3', name: 'Pokemon 3' }],
301 [{ id: '4', name: 'Pokemon 4' }],
302 ])
303 checkFlags(entry2NextPage, {
304 hasNextPage: true,
305 hasPreviousPage: true,
306 })
307
308 const res6 = storeRef.store.dispatch(
309 pokemonApi.endpoints.getInfinitePokemon.initiate('water', {
310 direction: 'backward',
311 }),
312 )
313
314 checkEntryFlags('water', {
315 hasNextPage: true,
316 hasPreviousPage: true,
317 isFetchingPreviousPage: true,
318 })
319
320 const entry2PrevPage = await res6
321
322 checkResultData(entry2PrevPage, [
323 [{ id: '2', name: 'Pokemon 2' }],
324 [{ id: '3', name: 'Pokemon 3' }],
325 [{ id: '4', name: 'Pokemon 4' }],
326 ])
327 checkFlags(entry2PrevPage, {
328 hasNextPage: true,
329 hasPreviousPage: true,
330 })
331 })
332
333 test('does not have a page limit without maxPages', async () => {
334 for (let i = 1; i <= 10; i++) {
335 const res = await storeRef.store.dispatch(
336 pokemonApi.endpoints.getInfinitePokemon.initiate('fire', {
337 direction: 'forward',
338 }),
339 )
340
341 checkResultLength(res, i)
342 }
343 })
344
345 test('applies a page limit with maxPages', async () => {
346 for (let i = 1; i <= 10; i++) {
347 const res = await storeRef.store.dispatch(
348 pokemonApi.endpoints.getInfinitePokemonWithMax.initiate('fire', {
349 direction: 'forward',
350 }),
351 )
352
353 checkResultLength(res, Math.min(i, 3))
354 }
355
356 // Should now have entries 7, 8, 9 after the loop
357
358 const res = await storeRef.store.dispatch(
359 pokemonApi.endpoints.getInfinitePokemonWithMax.initiate('fire', {
360 direction: 'backward',
361 }),
362 )
363
364 checkResultData(res, [
365 [{ id: '6', name: 'Pokemon 6' }],
366 [{ id: '7', name: 'Pokemon 7' }],
367 [{ id: '8', name: 'Pokemon 8' }],
368 ])
369 })
370
371 test('validates maxPages during createApi call', async () => {
372 vi.stubEnv('NODE_ENV', 'development')
373
374 const createApiWithMaxPages = (
375 maxPages: number,
376 getPreviousPageParam: (() => number) | undefined,
377 ) => {
378 createApi({
379 baseQuery: fakeBaseQuery(),
380 endpoints: (build) => ({
381 getInfinitePokemon: build.infiniteQuery<Pokemon[], string, number>({
382 query(pageParam) {
383 return `https://example.com/listItems?page=${pageParam}`
384 },
385 infiniteQueryOptions: {
386 initialPageParam: 0,
387 maxPages,
388 getNextPageParam: () => 1,
389 getPreviousPageParam,
390 },
391 }),
392 }),
393 })
394 }
395
396 expect(() => createApiWithMaxPages(0, () => 0)).toThrowError(
397 `maxPages for endpoint 'getInfinitePokemon' must be a number greater than 0`,
398 )
399
400 expect(() => createApiWithMaxPages(1, undefined)).toThrowError(
401 `getPreviousPageParam for endpoint 'getInfinitePokemon' must be a function if maxPages is used`,
402 )
403 })
404
405 test('refetches all existing pages', async () => {
406 const checkResultData = (
407 result: InfiniteQueryResult,
408 expectedValues: HitCounter[],
409 ) => {
410 expect(result.status).toBe(QueryStatus.fulfilled)
411 if (result.status === QueryStatus.fulfilled) {
412 expect(result.data.pages).toEqual(expectedValues)
413 }
414 }
415
416 const countersApi = createCountersApi()
417
418 const storeRef = setupApiStore(
419 countersApi,
420 { ...actionsReducer },
421 {
422 withoutTestLifecycles: true,
423 },
424 )
425
426 await storeRef.store.dispatch(
427 countersApi.endpoints.counters.initiate('item', {
428 initialPageParam: 3,
429 }),
430 )
431
432 await storeRef.store.dispatch(
433 countersApi.endpoints.counters.initiate('item', {
434 direction: 'forward',
435 }),
436 )
437
438 const thirdPromise = storeRef.store.dispatch(
439 countersApi.endpoints.counters.initiate('item', {
440 direction: 'forward',
441 }),
442 )
443
444 const thirdRes = await thirdPromise
445
446 checkResultData(thirdRes, [
447 { page: 3, hitCounter: 1 },
448 { page: 4, hitCounter: 2 },
449 { page: 5, hitCounter: 3 },
450 ])
451
452 const fourthRes = await thirdPromise.refetch()
453
454 checkResultData(fourthRes, [
455 { page: 3, hitCounter: 4 },
456 { page: 4, hitCounter: 5 },
457 { page: 5, hitCounter: 6 },
458 ])
459 })
460
461 test('Refetches on invalidation', async () => {
462 const checkResultData = (
463 result: InfiniteQueryResult,
464 expectedValues: HitCounter[],
465 ) => {
466 expect(result.status).toBe(QueryStatus.fulfilled)
467 if (result.status === QueryStatus.fulfilled) {
468 expect(result.data.pages).toEqual(expectedValues)
469 }
470 }
471
472 const countersApi = createCountersApi()
473
474 const storeRef = setupApiStore(
475 countersApi,
476 { ...actionsReducer },
477 {
478 withoutTestLifecycles: true,
479 },
480 )
481
482 await storeRef.store.dispatch(
483 countersApi.endpoints.counters.initiate('item', {
484 initialPageParam: 3,
485 }),
486 )
487
488 await storeRef.store.dispatch(
489 countersApi.endpoints.counters.initiate('item', {
490 direction: 'forward',
491 }),
492 )
493
494 const thirdPromise = storeRef.store.dispatch(
495 countersApi.endpoints.counters.initiate('item', {
496 direction: 'forward',
497 }),
498 )
499
500 const thirdRes = await thirdPromise
501
502 checkResultData(thirdRes, [
503 { page: 3, hitCounter: 1 },
504 { page: 4, hitCounter: 2 },
505 { page: 5, hitCounter: 3 },
506 ])
507
508 await storeRef.store.dispatch(countersApi.endpoints.mutation.initiate())
509
510 let entry = countersApi.endpoints.counters.select('item')(
511 storeRef.store.getState(),
512 )
513 const promise = storeRef.store.dispatch(
514 countersApi.util.getRunningQueryThunk('counters', 'item'),
515 )
516 const promises = storeRef.store.dispatch(
517 countersApi.util.getRunningQueriesThunk(),
518 )
519 expect(entry).toMatchObject({
520 status: 'pending',
521 })
522
523 expect(promise).toBeInstanceOf(Promise)
524
525 expect(promises).toEqual([promise])
526
527 const finalRes = await promise
528
529 checkResultData(finalRes as any, [
530 { page: 3, hitCounter: 4 },
531 { page: 4, hitCounter: 5 },
532 { page: 5, hitCounter: 6 },
533 ])
534 })
535
536 test('Refetches on polling', async () => {
537 const countersApi = createCountersApi()
538 const checkResultData = (
539 result: InfiniteQueryResult,
540 expectedValues: HitCounter[],
541 ) => {
542 expect(result.status).toBe(QueryStatus.fulfilled)
543 if (result.status === QueryStatus.fulfilled) {
544 expect(result.data.pages).toEqual(expectedValues)
545 }
546 }
547
548 const storeRef = setupApiStore(
549 countersApi,
550 { ...actionsReducer },
551 {
552 withoutTestLifecycles: true,
553 },
554 )
555
556 await storeRef.store.dispatch(
557 countersApi.endpoints.counters.initiate('item', {
558 initialPageParam: 3,
559 }),
560 )
561
562 await storeRef.store.dispatch(
563 countersApi.endpoints.counters.initiate('item', {
564 direction: 'forward',
565 }),
566 )
567
568 const thirdPromise = storeRef.store.dispatch(
569 countersApi.endpoints.counters.initiate('item', {
570 direction: 'forward',
571 }),
572 )
573
574 const thirdRes = await thirdPromise
575
576 checkResultData(thirdRes, [
577 { page: 3, hitCounter: 1 },
578 { page: 4, hitCounter: 2 },
579 { page: 5, hitCounter: 3 },
580 ])
581
582 thirdPromise.updateSubscriptionOptions({
583 pollingInterval: 50,
584 })
585
586 await delay(25)
587
588 let entry = countersApi.endpoints.counters.select('item')(
589 storeRef.store.getState(),
590 )
591
592 checkResultData(thirdRes, [
593 { page: 3, hitCounter: 1 },
594 { page: 4, hitCounter: 2 },
595 { page: 5, hitCounter: 3 },
596 ])
597
598 await delay(50)
599
600 entry = countersApi.endpoints.counters.select('item')(
601 storeRef.store.getState(),
602 )
603
604 checkResultData(entry as any, [
605 { page: 3, hitCounter: 4 },
606 { page: 4, hitCounter: 5 },
607 { page: 5, hitCounter: 6 },
608 ])
609 })
610
611 test('Handles multiple next page fetches at once', async () => {
612 const initialEntry = await storeRef.store.dispatch(
613 pokemonApi.endpoints.getInfinitePokemon.initiate('fire', {}),
614 )
615
616 checkResultData(initialEntry, [[{ id: '0', name: 'Pokemon 0' }]])
617
618 expect(queryCounter).toBe(1)
619
620 const promise1 = storeRef.store.dispatch(
621 pokemonApi.endpoints.getInfinitePokemon.initiate('fire', {
622 direction: 'forward',
623 }),
624 )
625
626 expect(queryCounter).toBe(1)
627
628 const promise2 = storeRef.store.dispatch(
629 pokemonApi.endpoints.getInfinitePokemon.initiate('fire', {
630 direction: 'forward',
631 }),
632 )
633
634 const entry1 = await promise1
635 const entry2 = await promise2
636
637 // The second thunk should have bailed out because the entry was now
638 // pending, so we should only have sent one request.
639 expect(queryCounter).toBe(2)
640
641 expect(entry1).toEqual(entry2)
642
643 checkResultData(entry1, [
644 [{ id: '0', name: 'Pokemon 0' }],
645 [{ id: '1', name: 'Pokemon 1' }],
646 ])
647
648 expect(queryCounter).toBe(2)
649
650 const promise3 = storeRef.store.dispatch(
651 pokemonApi.endpoints.getInfinitePokemon.initiate('fire', {
652 direction: 'forward',
653 }),
654 )
655
656 expect(queryCounter).toBe(2)
657
658 // We can abort an existing promise, but due to timing issues,
659 // we have to await the promise first before triggering the next request.
660 promise3.abort()
661 const entry3 = await promise3
662
663 const promise4 = storeRef.store.dispatch(
664 pokemonApi.endpoints.getInfinitePokemon.initiate('fire', {
665 direction: 'forward',
666 }),
667 )
668
669 const entry4 = await promise4
670
671 expect(queryCounter).toBe(4)
672
673 checkResultData(entry4, [
674 [{ id: '0', name: 'Pokemon 0' }],
675 [{ id: '1', name: 'Pokemon 1' }],
676 [{ id: '2', name: 'Pokemon 2' }],
677 ])
678 })
679
680 test('can fetch pages with refetchOnMountOrArgChange active', async () => {
681 const pokemonApiWithRefetch = createApi({
682 baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }),
683 endpoints: (build) => ({
684 getInfinitePokemon: build.infiniteQuery<Pokemon[], string, number>({
685 infiniteQueryOptions: {
686 initialPageParam: 0,
687 getNextPageParam: (
688 lastPage,
689 allPages,
690 // Page param type should be `number`
691 lastPageParam,
692 allPageParams,
693 ) => lastPageParam + 1,
694 getPreviousPageParam: (
695 firstPage,
696 allPages,
697 firstPageParam,
698 allPageParams,
699 ) => {
700 return firstPageParam > 0 ? firstPageParam - 1 : undefined
701 },
702 },
703 query({ pageParam }) {
704 return `https://example.com/listItems?page=${pageParam}`
705 },
706 }),
707 }),
708 refetchOnMountOrArgChange: true,
709 })
710
711 const storeRef = setupApiStore(
712 pokemonApiWithRefetch,
713 { ...actionsReducer },
714 {
715 withoutTestLifecycles: true,
716 },
717 )
718
719 const res1 = storeRef.store.dispatch(
720 pokemonApiWithRefetch.endpoints.getInfinitePokemon.initiate('fire', {}),
721 )
722
723 const entry1InitialLoad = await res1
724 checkResultData(entry1InitialLoad, [[{ id: '0', name: 'Pokemon 0' }]])
725
726 const res2 = storeRef.store.dispatch(
727 pokemonApiWithRefetch.endpoints.getInfinitePokemon.initiate('fire', {
728 direction: 'forward',
729 }),
730 )
731
732 const entry1SecondPage = await res2
733 checkResultData(entry1SecondPage, [
734 [{ id: '0', name: 'Pokemon 0' }],
735 [{ id: '1', name: 'Pokemon 1' }],
736 ])
737
738 expect(queryCounter).toBe(2)
739
740 const entry2InitialLoad = await storeRef.store.dispatch(
741 pokemonApiWithRefetch.endpoints.getInfinitePokemon.initiate('water', {}),
742 )
743
744 checkResultData(entry2InitialLoad, [[{ id: '0', name: 'Pokemon 0' }]])
745
746 expect(queryCounter).toBe(3)
747
748 const entry2SecondPage = await storeRef.store.dispatch(
749 pokemonApiWithRefetch.endpoints.getInfinitePokemon.initiate('water', {
750 direction: 'forward',
751 }),
752 )
753 checkResultData(entry2SecondPage, [
754 [{ id: '0', name: 'Pokemon 0' }],
755 [{ id: '1', name: 'Pokemon 1' }],
756 ])
757
758 expect(queryCounter).toBe(4)
759
760 // Should now be able to switch back to the first query.
761 // The hooks dispatch on arg change without a direction.
762 // That should trigger a refetch of the first query, meaning two requests.
763 // It should also _replace_ the existing results, rather than appending
764 // duplicate entries ([0, 1, 0, 1])
765 const entry1Refetched = await storeRef.store.dispatch(
766 pokemonApiWithRefetch.endpoints.getInfinitePokemon.initiate('fire', {}),
767 )
768
769 checkResultData(entry1Refetched, [
770 [{ id: '0', name: 'Pokemon 0' }],
771 [{ id: '1', name: 'Pokemon 1' }],
772 ])
773
774 expect(queryCounter).toBe(6)
775 })
776
777 test('Works with cache manipulation utils', async () => {
778 const res1 = storeRef.store.dispatch(
779 pokemonApi.endpoints.getInfinitePokemon.initiate('fire', {}),
780 )
781
782 const entry1InitialLoad = await res1
783 checkResultData(entry1InitialLoad, [[{ id: '0', name: 'Pokemon 0' }]])
784
785 storeRef.store.dispatch(
786 pokemonApi.util.updateQueryData('getInfinitePokemon', 'fire', (draft) => {
787 draft.pages.push([{ id: '1', name: 'Pokemon 1' }])
788 draft.pageParams.push(1)
789 }),
790 )
791
792 const selectFire = pokemonApi.endpoints.getInfinitePokemon.select('fire')
793 const entry1Updated = selectFire(storeRef.store.getState())
794
795 expect(entry1Updated.data).toEqual({
796 pages: [
797 [{ id: '0', name: 'Pokemon 0' }],
798 [{ id: '1', name: 'Pokemon 1' }],
799 ],
800 pageParams: [0, 1],
801 })
802
803 const res2 = storeRef.store.dispatch(
804 pokemonApi.util.upsertQueryData('getInfinitePokemon', 'water', {
805 pages: [[{ id: '2', name: 'Pokemon 2' }]],
806 pageParams: [2],
807 }),
808 )
809
810 const entry2InitialLoad = await res2
811 const selectWater = pokemonApi.endpoints.getInfinitePokemon.select('water')
812 const entry2Updated = selectWater(storeRef.store.getState())
813
814 expect(entry2Updated.data).toEqual({
815 pages: [[{ id: '2', name: 'Pokemon 2' }]],
816 pageParams: [2],
817 })
818
819 storeRef.store.dispatch(
820 pokemonApi.util.upsertQueryEntries([
821 {
822 endpointName: 'getInfinitePokemon',
823 arg: 'air',
824 value: {
825 pages: [[{ id: '3', name: 'Pokemon 3' }]],
826 pageParams: [3],
827 },
828 },
829 ]),
830 )
831
832 const selectAir = pokemonApi.endpoints.getInfinitePokemon.select('air')
833 const entry3Initial = selectAir(storeRef.store.getState())
834
835 expect(entry3Initial.data).toEqual({
836 pages: [[{ id: '3', name: 'Pokemon 3' }]],
837 pageParams: [3],
838 })
839
840 await storeRef.store.dispatch(
841 pokemonApi.endpoints.getInfinitePokemon.initiate('air', {
842 direction: 'forward',
843 }),
844 )
845
846 const entry3Updated = selectAir(storeRef.store.getState())
847
848 expect(entry3Updated.data).toEqual({
849 pages: [
850 [{ id: '3', name: 'Pokemon 3' }],
851 [{ id: '4', name: 'Pokemon 4' }],
852 ],
853 pageParams: [3, 4],
854 })
855 })
856
857 test('Cache lifecycle methods are called', async () => {
858 const cacheEntryAddedCallback = vi.fn()
859 const queryStartedCallback = vi.fn()
860
861 const pokemonApi = createApi({
862 baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }),
863 endpoints: (build) => ({
864 getInfinitePokemonWithLifecycles: build.infiniteQuery<
865 Pokemon[],
866 string,
867 number
868 >({
869 infiniteQueryOptions: {
870 initialPageParam: 0,
871 getNextPageParam: (
872 lastPage,
873 allPages,
874 // Page param type should be `number`
875 lastPageParam,
876 allPageParams,
877 ) => lastPageParam + 1,
878 getPreviousPageParam: (
879 firstPage,
880 allPages,
881 firstPageParam,
882 allPageParams,
883 ) => {
884 return firstPageParam > 0 ? firstPageParam - 1 : undefined
885 },
886 },
887 query({ pageParam }) {
888 return `https://example.com/listItems?page=${pageParam}`
889 },
890 async onCacheEntryAdded(arg, api) {
891 const data = await api.cacheDataLoaded
892 cacheEntryAddedCallback(arg, data)
893 },
894 async onQueryStarted(arg, api) {
895 const data = await api.queryFulfilled
896 queryStartedCallback(arg, data)
897 },
898 }),
899 }),
900 })
901
902 const storeRef = setupApiStore(
903 pokemonApi,
904 { ...actionsReducer },
905 {
906 withoutTestLifecycles: true,
907 },
908 )
909
910 const res1 = storeRef.store.dispatch(
911 pokemonApi.endpoints.getInfinitePokemonWithLifecycles.initiate(
912 'fire',
913 {},
914 ),
915 )
916
917 const entry1InitialLoad = await res1
918 checkResultData(entry1InitialLoad, [[{ id: '0', name: 'Pokemon 0' }]])
919
920 expect(cacheEntryAddedCallback).toHaveBeenCalledWith('fire', {
921 data: {
922 pages: [[{ id: '0', name: 'Pokemon 0' }]],
923 pageParams: [0],
924 },
925 meta: expect.objectContaining({
926 request: expect.anything(),
927 response: expect.anything(),
928 }),
929 })
930
931 expect(queryStartedCallback).toHaveBeenCalledWith('fire', {
932 data: {
933 pages: [[{ id: '0', name: 'Pokemon 0' }]],
934 pageParams: [0],
935 },
936 meta: expect.objectContaining({
937 request: expect.anything(),
938 response: expect.anything(),
939 }),
940 })
941 })
942
943 test('Can use transformResponse', async () => {
944 type PokemonPage = { items: Pokemon[]; page: number }
945 const pokemonApi = createApi({
946 baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }),
947 endpoints: (build) => ({
948 getInfinitePokemonWithTransform: build.infiniteQuery<
949 PokemonPage,
950 string,
951 number
952 >({
953 infiniteQueryOptions: {
954 initialPageParam: 0,
955 getNextPageParam: (
956 lastPage,
957 allPages,
958 // Page param type should be `number`
959 lastPageParam,
960 allPageParams,
961 ) => lastPageParam + 1,
962 },
963 query({ pageParam }) {
964 return `https://example.com/listItems?page=${pageParam}`
965 },
966 transformResponse(baseQueryReturnValue: Pokemon[], meta, arg) {
967 expect(Array.isArray(baseQueryReturnValue)).toBe(true)
968 return {
969 items: baseQueryReturnValue,
970 page: arg.pageParam,
971 }
972 },
973 }),
974 }),
975 })
976
977 const storeRef = setupApiStore(
978 pokemonApi,
979 { ...actionsReducer },
980 {
981 withoutTestLifecycles: true,
982 },
983 )
984
985 const checkResultData = (
986 result: InfiniteQueryResult,
987 expectedValues: PokemonPage[],
988 ) => {
989 expect(result.status).toBe(QueryStatus.fulfilled)
990 if (result.status === QueryStatus.fulfilled) {
991 expect(result.data.pages).toEqual(expectedValues)
992 }
993 }
994
995 const res1 = storeRef.store.dispatch(
996 pokemonApi.endpoints.getInfinitePokemonWithTransform.initiate('fire', {}),
997 )
998
999 const entry1InitialLoad = await res1
1000 checkResultData(entry1InitialLoad, [
1001 { items: [{ id: '0', name: 'Pokemon 0' }], page: 0 },
1002 ])
1003
1004 const entry1Updated = await storeRef.store.dispatch(
1005 pokemonApi.endpoints.getInfinitePokemonWithTransform.initiate('fire', {
1006 direction: 'forward',
1007 }),
1008 )
1009
1010 checkResultData(entry1Updated, [
1011 { items: [{ id: '0', name: 'Pokemon 0' }], page: 0 },
1012 { items: [{ id: '1', name: 'Pokemon 1' }], page: 1 },
1013 ])
1014 })
1015
1016 describe('refetchCachedPages option', () => {
1017 test('refetches all pages by default (refetchCachedPages: true)', async () => {
1018 let hitCounter = 0
1019
1020 const countersApi = createApi({
1021 baseQuery: fakeBaseQuery(),
1022 tagTypes: ['Counter'],
1023 endpoints: (build) => ({
1024 counters: build.infiniteQuery<HitCounter, string, number>({
1025 queryFn({ pageParam }) {
1026 hitCounter++
1027 return { data: { page: pageParam, hitCounter } }
1028 },
1029 infiniteQueryOptions: {
1030 initialPageParam: 0,
1031 getNextPageParam: (
1032 lastPage,
1033 allPages,
1034 lastPageParam,
1035 allPageParams,
1036 ) => lastPageParam + 1,
1037 },
1038 providesTags: ['Counter'],
1039 }),
1040 }),
1041 })
1042
1043 const storeRef = setupApiStore(
1044 countersApi,
1045 { ...actionsReducer },
1046 {
1047 withoutTestLifecycles: true,
1048 },
1049 )
1050
1051 // Load 3 pages
1052 await storeRef.store.dispatch(
1053 countersApi.endpoints.counters.initiate('item', {
1054 initialPageParam: 0,
1055 }),
1056 )
1057
1058 await storeRef.store.dispatch(
1059 countersApi.endpoints.counters.initiate('item', {
1060 direction: 'forward',
1061 }),
1062 )
1063
1064 const thirdPromise = storeRef.store.dispatch(
1065 countersApi.endpoints.counters.initiate('item', {
1066 direction: 'forward',
1067 }),
1068 )
1069
1070 const thirdRes = await thirdPromise
1071
1072 // Should have 3 pages with hitCounters 1, 2, 3
1073 expect(thirdRes.data!.pages).toEqual([
1074 { page: 0, hitCounter: 1 },
1075 { page: 1, hitCounter: 2 },
1076 { page: 2, hitCounter: 3 },
1077 ])
1078
1079 // Refetch without specifying refetchCachedPages
1080 const refetchRes = await thirdPromise.refetch()
1081
1082 // All 3 pages should be refetched (hitCounters 4, 5, 6)
1083 expect(refetchRes.data!.pages).toEqual([
1084 { page: 0, hitCounter: 4 },
1085 { page: 1, hitCounter: 5 },
1086 { page: 2, hitCounter: 6 },
1087 ])
1088 expect(refetchRes.data!.pages).toHaveLength(3)
1089 })
1090
1091 test('refetches all pages when refetchCachedPages is explicitly true', async () => {
1092 let hitCounter = 0
1093
1094 const countersApi = createApi({
1095 baseQuery: fakeBaseQuery(),
1096 tagTypes: ['Counter'],
1097 endpoints: (build) => ({
1098 counters: build.infiniteQuery<HitCounter, string, number>({
1099 queryFn({ pageParam }) {
1100 hitCounter++
1101 return { data: { page: pageParam, hitCounter } }
1102 },
1103 infiniteQueryOptions: {
1104 initialPageParam: 0,
1105 getNextPageParam: (
1106 lastPage,
1107 allPages,
1108 lastPageParam,
1109 allPageParams,
1110 ) => lastPageParam + 1,
1111 refetchCachedPages: true, // Explicit true
1112 },
1113 providesTags: ['Counter'],
1114 }),
1115 }),
1116 })
1117
1118 const storeRef = setupApiStore(
1119 countersApi,
1120 { ...actionsReducer },
1121 {
1122 withoutTestLifecycles: true,
1123 },
1124 )
1125
1126 // Load 3 pages
1127 await storeRef.store.dispatch(
1128 countersApi.endpoints.counters.initiate('item', {
1129 initialPageParam: 0,
1130 }),
1131 )
1132
1133 await storeRef.store.dispatch(
1134 countersApi.endpoints.counters.initiate('item', {
1135 direction: 'forward',
1136 }),
1137 )
1138
1139 const thirdPromise = storeRef.store.dispatch(
1140 countersApi.endpoints.counters.initiate('item', {
1141 direction: 'forward',
1142 }),
1143 )
1144
1145 const thirdRes = await thirdPromise
1146
1147 expect(thirdRes.data!.pages).toHaveLength(3)
1148
1149 // Refetch
1150 const refetchRes = await thirdPromise.refetch()
1151
1152 // All 3 pages should be refetched
1153 expect(refetchRes.data!.pages).toHaveLength(3)
1154 expect(refetchRes.data!.pages[0].hitCounter).toBeGreaterThan(
1155 thirdRes.data!.pages[0].hitCounter,
1156 )
1157 })
1158
1159 test('refetches only first page when refetchCachedPages is false', async () => {
1160 let hitCounter = 0
1161
1162 const countersApi = createApi({
1163 baseQuery: fakeBaseQuery(),
1164 tagTypes: ['Counter'],
1165 endpoints: (build) => ({
1166 counters: build.infiniteQuery<HitCounter, string, number>({
1167 queryFn({ pageParam }) {
1168 hitCounter++
1169 return { data: { page: pageParam, hitCounter } }
1170 },
1171 infiniteQueryOptions: {
1172 initialPageParam: 0,
1173 getNextPageParam: (
1174 lastPage,
1175 allPages,
1176 lastPageParam,
1177 allPageParams,
1178 ) => lastPageParam + 1,
1179 refetchCachedPages: false, // Only refetch first page
1180 },
1181 providesTags: ['Counter'],
1182 }),
1183 }),
1184 })
1185
1186 const storeRef = setupApiStore(
1187 countersApi,
1188 { ...actionsReducer },
1189 {
1190 withoutTestLifecycles: true,
1191 },
1192 )
1193
1194 // Load 3 pages
1195 await storeRef.store.dispatch(
1196 countersApi.endpoints.counters.initiate('item', {
1197 initialPageParam: 0,
1198 }),
1199 )
1200
1201 await storeRef.store.dispatch(
1202 countersApi.endpoints.counters.initiate('item', {
1203 direction: 'forward',
1204 }),
1205 )
1206
1207 const thirdPromise = storeRef.store.dispatch(
1208 countersApi.endpoints.counters.initiate('item', {
1209 direction: 'forward',
1210 }),
1211 )
1212
1213 const thirdRes = await thirdPromise
1214
1215 // Should have 3 pages with hitCounters 1, 2, 3
1216 expect(thirdRes.data!.pages).toEqual([
1217 { page: 0, hitCounter: 1 },
1218 { page: 1, hitCounter: 2 },
1219 { page: 2, hitCounter: 3 },
1220 ])
1221
1222 // Refetch with refetchCachedPages: false
1223 const refetchRes = await thirdPromise.refetch()
1224
1225 // Only first page should be refetched, cache reset to 1 page
1226 expect(refetchRes.data!.pages).toEqual([{ page: 0, hitCounter: 4 }])
1227 expect(refetchRes.data!.pageParams).toEqual([0])
1228 })
1229
1230 test('refetches only first page on tag invalidation when refetchCachedPages is false', async () => {
1231 let hitCounter = 0
1232
1233 const countersApi = createApi({
1234 baseQuery: fakeBaseQuery(),
1235 tagTypes: ['Counter'],
1236 endpoints: (build) => ({
1237 counters: build.infiniteQuery<HitCounter, string, number>({
1238 queryFn({ pageParam }) {
1239 hitCounter++
1240 return { data: { page: pageParam, hitCounter } }
1241 },
1242 infiniteQueryOptions: {
1243 initialPageParam: 0,
1244 getNextPageParam: (
1245 lastPage,
1246 allPages,
1247 lastPageParam,
1248 allPageParams,
1249 ) => lastPageParam + 1,
1250 refetchCachedPages: false,
1251 },
1252 providesTags: ['Counter'],
1253 }),
1254 mutation: build.mutation<null, void>({
1255 queryFn: async () => ({ data: null }),
1256 invalidatesTags: ['Counter'],
1257 }),
1258 }),
1259 })
1260
1261 const storeRef = setupApiStore(
1262 countersApi,
1263 { ...actionsReducer },
1264 {
1265 withoutTestLifecycles: true,
1266 },
1267 )
1268
1269 // Load 3 pages
1270 await storeRef.store.dispatch(
1271 countersApi.endpoints.counters.initiate('item', {
1272 initialPageParam: 0,
1273 }),
1274 )
1275
1276 await storeRef.store.dispatch(
1277 countersApi.endpoints.counters.initiate('item', {
1278 direction: 'forward',
1279 }),
1280 )
1281
1282 await storeRef.store.dispatch(
1283 countersApi.endpoints.counters.initiate('item', {
1284 direction: 'forward',
1285 }),
1286 )
1287
1288 // Verify we have 3 pages
1289 let entry = countersApi.endpoints.counters.select('item')(
1290 storeRef.store.getState(),
1291 )
1292 expect(entry.data?.pages).toHaveLength(3)
1293
1294 // Trigger mutation to invalidate tags
1295 await storeRef.store.dispatch(countersApi.endpoints.mutation.initiate())
1296
1297 // Wait for refetch to complete
1298 const promise = storeRef.store.dispatch(
1299 countersApi.util.getRunningQueryThunk('counters', 'item'),
1300 )
1301 const finalRes = await promise
1302
1303 // Only first page should be refetched
1304 expect((finalRes as any).data.pages).toEqual([{ page: 0, hitCounter: 4 }])
1305 })
1306
1307 test('refetches only first page during polling when refetchCachedPages is false', async () => {
1308 let hitCounter = 0
1309
1310 const countersApi = createApi({
1311 baseQuery: fakeBaseQuery(),
1312 endpoints: (build) => ({
1313 counters: build.infiniteQuery<HitCounter, string, number>({
1314 queryFn({ pageParam }) {
1315 hitCounter++
1316 return { data: { page: pageParam, hitCounter } }
1317 },
1318 infiniteQueryOptions: {
1319 initialPageParam: 0,
1320 getNextPageParam: (
1321 lastPage,
1322 allPages,
1323 lastPageParam,
1324 allPageParams,
1325 ) => lastPageParam + 1,
1326 refetchCachedPages: false,
1327 },
1328 }),
1329 }),
1330 })
1331
1332 const storeRef = setupApiStore(
1333 countersApi,
1334 { ...actionsReducer },
1335 {
1336 withoutTestLifecycles: true,
1337 },
1338 )
1339
1340 // Load 3 pages
1341 await storeRef.store.dispatch(
1342 countersApi.endpoints.counters.initiate('item', {
1343 initialPageParam: 0,
1344 }),
1345 )
1346
1347 await storeRef.store.dispatch(
1348 countersApi.endpoints.counters.initiate('item', {
1349 direction: 'forward',
1350 }),
1351 )
1352
1353 const thirdPromise = storeRef.store.dispatch(
1354 countersApi.endpoints.counters.initiate('item', {
1355 direction: 'forward',
1356 }),
1357 )
1358
1359 await thirdPromise
1360
1361 // Enable polling
1362 thirdPromise.updateSubscriptionOptions({
1363 pollingInterval: 50,
1364 })
1365
1366 // Wait for first poll
1367 await delay(75)
1368
1369 const entry = countersApi.endpoints.counters.select('item')(
1370 storeRef.store.getState(),
1371 )
1372
1373 // Should only have 1 page after poll
1374 expect(entry.data?.pages).toEqual([{ page: 0, hitCounter: 4 }])
1375 })
1376
1377 test('refetchCachedPages: false works with maxPages', async () => {
1378 let hitCounter = 0
1379
1380 const countersApi = createApi({
1381 baseQuery: fakeBaseQuery(),
1382 endpoints: (build) => ({
1383 counters: build.infiniteQuery<HitCounter, string, number>({
1384 queryFn({ pageParam }) {
1385 hitCounter++
1386 return { data: { page: pageParam, hitCounter } }
1387 },
1388 infiniteQueryOptions: {
1389 initialPageParam: 0,
1390 maxPages: 3,
1391 getNextPageParam: (
1392 lastPage,
1393 allPages,
1394 lastPageParam,
1395 allPageParams,
1396 ) => lastPageParam + 1,
1397 getPreviousPageParam: (
1398 firstPage,
1399 allPages,
1400 firstPageParam,
1401 allPageParams,
1402 ) => (firstPageParam > 0 ? firstPageParam - 1 : undefined),
1403 refetchCachedPages: false,
1404 },
1405 }),
1406 }),
1407 })
1408
1409 const storeRef = setupApiStore(
1410 countersApi,
1411 { ...actionsReducer },
1412 {
1413 withoutTestLifecycles: true,
1414 },
1415 )
1416
1417 // Load 5 pages (but maxPages will limit to 3)
1418 await storeRef.store.dispatch(
1419 countersApi.endpoints.counters.initiate('item', {
1420 initialPageParam: 0,
1421 }),
1422 )
1423
1424 for (let i = 0; i < 4; i++) {
1425 await storeRef.store.dispatch(
1426 countersApi.endpoints.counters.initiate('item', {
1427 direction: 'forward',
1428 }),
1429 )
1430 }
1431
1432 let entry = countersApi.endpoints.counters.select('item')(
1433 storeRef.store.getState(),
1434 )
1435
1436 // Should have 3 pages due to maxPages
1437 expect(entry.data?.pages).toHaveLength(3)
1438
1439 // Refetch
1440 const refetchPromise = storeRef.store.dispatch(
1441 countersApi.endpoints.counters.initiate('item', {
1442 forceRefetch: true,
1443 }),
1444 )
1445
1446 const refetchRes = await refetchPromise
1447
1448 // Should only have 1 page after refetch (refetchCachedPages: false)
1449 // Note: With maxPages: 3, the cache kept pages 2, 3, 4
1450 // So refetch starts from the first cached page param, which is 2
1451 expect(refetchRes.data!.pages).toHaveLength(1)
1452 expect(refetchRes.data!.pages[0].page).toBe(2)
1453 })
1454
1455 test('can fetch next page after refetch with refetchCachedPages: false', async () => {
1456 let hitCounter = 0
1457
1458 const countersApi = createApi({
1459 baseQuery: fakeBaseQuery(),
1460 endpoints: (build) => ({
1461 counters: build.infiniteQuery<HitCounter, string, number>({
1462 queryFn({ pageParam }) {
1463 hitCounter++
1464 return { data: { page: pageParam, hitCounter } }
1465 },
1466 infiniteQueryOptions: {
1467 initialPageParam: 0,
1468 getNextPageParam: (
1469 lastPage,
1470 allPages,
1471 lastPageParam,
1472 allPageParams,
1473 ) => lastPageParam + 1,
1474 refetchCachedPages: false,
1475 },
1476 }),
1477 }),
1478 })
1479
1480 const storeRef = setupApiStore(
1481 countersApi,
1482 { ...actionsReducer },
1483 {
1484 withoutTestLifecycles: true,
1485 },
1486 )
1487
1488 // Load 3 pages
1489 await storeRef.store.dispatch(
1490 countersApi.endpoints.counters.initiate('item', {
1491 initialPageParam: 0,
1492 }),
1493 )
1494
1495 await storeRef.store.dispatch(
1496 countersApi.endpoints.counters.initiate('item', {
1497 direction: 'forward',
1498 }),
1499 )
1500
1501 const thirdPromise = storeRef.store.dispatch(
1502 countersApi.endpoints.counters.initiate('item', {
1503 direction: 'forward',
1504 }),
1505 )
1506
1507 await thirdPromise
1508
1509 // Refetch (resets to 1 page)
1510 await thirdPromise.refetch()
1511
1512 let entry = countersApi.endpoints.counters.select('item')(
1513 storeRef.store.getState(),
1514 )
1515 expect(entry.data?.pages).toHaveLength(1)
1516
1517 // Fetch next page
1518 const nextPageRes = await storeRef.store.dispatch(
1519 countersApi.endpoints.counters.initiate('item', {
1520 direction: 'forward',
1521 }),
1522 )
1523
1524 // Should now have 2 pages
1525 expect(nextPageRes.data!.pages).toEqual([
1526 { page: 0, hitCounter: 4 },
1527 { page: 1, hitCounter: 5 },
1528 ])
1529 })
1530
1531 describe('per-call refetchCachedPages override', () => {
1532 test('per-call false overrides endpoint true', async () => {
1533 let hitCounter = 0
1534
1535 const countersApi = createApi({
1536 baseQuery: fakeBaseQuery(),
1537 endpoints: (build) => ({
1538 counters: build.infiniteQuery<HitCounter, string, number>({
1539 queryFn({ pageParam }) {
1540 hitCounter++
1541 return { data: { page: pageParam, hitCounter } }
1542 },
1543 infiniteQueryOptions: {
1544 initialPageParam: 0,
1545 getNextPageParam: (
1546 lastPage,
1547 allPages,
1548 lastPageParam,
1549 allPageParams,
1550 ) => lastPageParam + 1,
1551 refetchCachedPages: true, // Endpoint default: refetch all
1552 },
1553 }),
1554 }),
1555 })
1556
1557 const storeRef = setupApiStore(
1558 countersApi,
1559 { ...actionsReducer },
1560 {
1561 withoutTestLifecycles: true,
1562 },
1563 )
1564
1565 // Load 3 pages
1566 await storeRef.store.dispatch(
1567 countersApi.endpoints.counters.initiate('item', {
1568 initialPageParam: 0,
1569 }),
1570 )
1571
1572 await storeRef.store.dispatch(
1573 countersApi.endpoints.counters.initiate('item', {
1574 direction: 'forward',
1575 }),
1576 )
1577
1578 await storeRef.store.dispatch(
1579 countersApi.endpoints.counters.initiate('item', {
1580 direction: 'forward',
1581 }),
1582 )
1583
1584 // Should have 3 pages with hitCounters 1, 2, 3
1585 let entry = countersApi.endpoints.counters.select('item')(
1586 storeRef.store.getState(),
1587 )
1588 expect(entry.data?.pages).toEqual([
1589 { page: 0, hitCounter: 1 },
1590 { page: 1, hitCounter: 2 },
1591 { page: 2, hitCounter: 3 },
1592 ])
1593
1594 // Refetch with per-call override: false
1595 const refetchRes = await storeRef.store.dispatch(
1596 countersApi.endpoints.counters.initiate('item', {
1597 forceRefetch: true,
1598 refetchCachedPages: false, // Override to false
1599 }),
1600 )
1601
1602 // Only first page should be refetched (hitCounter 4)
1603 expect(refetchRes.data!.pages).toEqual([{ page: 0, hitCounter: 4 }])
1604 expect(refetchRes.data!.pageParams).toEqual([0])
1605 })
1606
1607 test('per-call true overrides endpoint false', async () => {
1608 let hitCounter = 0
1609
1610 const countersApi = createApi({
1611 baseQuery: fakeBaseQuery(),
1612 endpoints: (build) => ({
1613 counters: build.infiniteQuery<HitCounter, string, number>({
1614 queryFn({ pageParam }) {
1615 hitCounter++
1616 return { data: { page: pageParam, hitCounter } }
1617 },
1618 infiniteQueryOptions: {
1619 initialPageParam: 0,
1620 getNextPageParam: (
1621 lastPage,
1622 allPages,
1623 lastPageParam,
1624 allPageParams,
1625 ) => lastPageParam + 1,
1626 refetchCachedPages: false, // Endpoint default: only first page
1627 },
1628 }),
1629 }),
1630 })
1631
1632 const storeRef = setupApiStore(
1633 countersApi,
1634 { ...actionsReducer },
1635 {
1636 withoutTestLifecycles: true,
1637 },
1638 )
1639
1640 // Load 3 pages
1641 await storeRef.store.dispatch(
1642 countersApi.endpoints.counters.initiate('item', {
1643 initialPageParam: 0,
1644 }),
1645 )
1646
1647 await storeRef.store.dispatch(
1648 countersApi.endpoints.counters.initiate('item', {
1649 direction: 'forward',
1650 }),
1651 )
1652
1653 await storeRef.store.dispatch(
1654 countersApi.endpoints.counters.initiate('item', {
1655 direction: 'forward',
1656 }),
1657 )
1658
1659 // Should have 3 pages
1660 let entry = countersApi.endpoints.counters.select('item')(
1661 storeRef.store.getState(),
1662 )
1663 expect(entry.data?.pages).toHaveLength(3)
1664
1665 // Refetch with per-call override: true
1666 const refetchRes = await storeRef.store.dispatch(
1667 countersApi.endpoints.counters.initiate('item', {
1668 forceRefetch: true,
1669 refetchCachedPages: true, // Override to true
1670 }),
1671 )
1672
1673 // All 3 pages should be refetched
1674 expect(refetchRes.data!.pages).toEqual([
1675 { page: 0, hitCounter: 4 },
1676 { page: 1, hitCounter: 5 },
1677 { page: 2, hitCounter: 6 },
1678 ])
1679 expect(refetchRes.data!.pages).toHaveLength(3)
1680 })
1681
1682 test('uses endpoint config when no per-call override', async () => {
1683 let hitCounter = 0
1684
1685 const countersApi = createApi({
1686 baseQuery: fakeBaseQuery(),
1687 endpoints: (build) => ({
1688 counters: build.infiniteQuery<HitCounter, string, number>({
1689 queryFn({ pageParam }) {
1690 hitCounter++
1691 return { data: { page: pageParam, hitCounter } }
1692 },
1693 infiniteQueryOptions: {
1694 initialPageParam: 0,
1695 getNextPageParam: (
1696 lastPage,
1697 allPages,
1698 lastPageParam,
1699 allPageParams,
1700 ) => lastPageParam + 1,
1701 refetchCachedPages: false, // Endpoint config
1702 },
1703 }),
1704 }),
1705 })
1706
1707 const storeRef = setupApiStore(
1708 countersApi,
1709 { ...actionsReducer },
1710 {
1711 withoutTestLifecycles: true,
1712 },
1713 )
1714
1715 // Load 3 pages
1716 await storeRef.store.dispatch(
1717 countersApi.endpoints.counters.initiate('item', {
1718 initialPageParam: 0,
1719 }),
1720 )
1721
1722 await storeRef.store.dispatch(
1723 countersApi.endpoints.counters.initiate('item', {
1724 direction: 'forward',
1725 }),
1726 )
1727
1728 await storeRef.store.dispatch(
1729 countersApi.endpoints.counters.initiate('item', {
1730 direction: 'forward',
1731 }),
1732 )
1733
1734 // Refetch without per-call override
1735 const refetchRes = await storeRef.store.dispatch(
1736 countersApi.endpoints.counters.initiate('item', {
1737 forceRefetch: true,
1738 // No refetchCachedPages specified
1739 }),
1740 )
1741
1742 // Should use endpoint config (false) - only first page
1743 expect(refetchRes.data!.pages).toEqual([{ page: 0, hitCounter: 4 }])
1744 })
1745
1746 test('defaults to true when no config at any level', async () => {
1747 let hitCounter = 0
1748
1749 const countersApi = createApi({
1750 baseQuery: fakeBaseQuery(),
1751 endpoints: (build) => ({
1752 counters: build.infiniteQuery<HitCounter, string, number>({
1753 queryFn({ pageParam }) {
1754 hitCounter++
1755 return { data: { page: pageParam, hitCounter } }
1756 },
1757 infiniteQueryOptions: {
1758 initialPageParam: 0,
1759 getNextPageParam: (
1760 lastPage,
1761 allPages,
1762 lastPageParam,
1763 allPageParams,
1764 ) => lastPageParam + 1,
1765 // No refetchCachedPages specified
1766 },
1767 }),
1768 }),
1769 })
1770
1771 const storeRef = setupApiStore(
1772 countersApi,
1773 { ...actionsReducer },
1774 {
1775 withoutTestLifecycles: true,
1776 },
1777 )
1778
1779 // Load 3 pages
1780 await storeRef.store.dispatch(
1781 countersApi.endpoints.counters.initiate('item', {
1782 initialPageParam: 0,
1783 }),
1784 )
1785
1786 await storeRef.store.dispatch(
1787 countersApi.endpoints.counters.initiate('item', {
1788 direction: 'forward',
1789 }),
1790 )
1791
1792 await storeRef.store.dispatch(
1793 countersApi.endpoints.counters.initiate('item', {
1794 direction: 'forward',
1795 }),
1796 )
1797
1798 // Refetch without any config
1799 const refetchRes = await storeRef.store.dispatch(
1800 countersApi.endpoints.counters.initiate('item', {
1801 forceRefetch: true,
1802 }),
1803 )
1804
1805 // Should default to true - refetch all pages
1806 expect(refetchRes.data!.pages).toEqual([
1807 { page: 0, hitCounter: 4 },
1808 { page: 1, hitCounter: 5 },
1809 { page: 2, hitCounter: 6 },
1810 ])
1811 })
1812 })
1813 })
1814})
Note: See TracBrowser for help on using the repository browser.