From 79f6ff8d15576d8d024a24b00ee93b49fe78a526 Mon Sep 17 00:00:00 2001 From: Mark Erikson Date: Sat, 14 Dec 2024 19:40:21 -0500 Subject: [PATCH] Implement infinite query status flags (#4771) * Extract InfiniteQueryDirection * Export page param functions * Fix useRefs with React 19 * Fix infinite query selector arg type * Implement infinite query status flags * Fix types and flags for infinite query hooks * Add new error messages --- errors.json | 4 +- packages/toolkit/src/query/core/apiState.ts | 17 +- .../toolkit/src/query/core/buildInitiate.ts | 3 +- .../toolkit/src/query/core/buildSelectors.ts | 82 ++++++++- packages/toolkit/src/query/core/buildSlice.ts | 34 ++-- .../toolkit/src/query/core/buildThunks.ts | 53 +++--- .../toolkit/src/query/react/buildHooks.ts | 30 ++- .../src/query/tests/buildHooks.test.tsx | 55 ++++++ .../src/query/tests/infiniteQueries.test.ts | 172 +++++++++++++----- 9 files changed, 319 insertions(+), 131 deletions(-) diff --git a/errors.json b/errors.json index 3c3d953125..082897b010 100644 --- a/errors.json +++ b/errors.json @@ -38,5 +38,7 @@ "36": "When using custom hooks for context, all hooks need to be provided: .\\nHook was either not provided or not a function.", "37": "Warning: Middleware for RTK-Query API at reducerPath \"\" has not been added to the store.\n You must add the middleware for RTK-Query to function correctly!", "38": "Cannot refetch a query that has not been started yet.", - "39": "called \\`injectEndpoints\\` to override already-existing endpointName without specifying \\`overrideExisting: true\\`" + "39": "called \\`injectEndpoints\\` to override already-existing endpointName without specifying \\`overrideExisting: true\\`", + "40": "maxPages for endpoint '' must be a number greater than 0", + "41": "getPreviousPageParam for endpoint '' must be a function if maxPages is used" } \ No newline at end of file diff --git a/packages/toolkit/src/query/core/apiState.ts b/packages/toolkit/src/query/core/apiState.ts index fa6b5e3832..e068a32589 100644 --- a/packages/toolkit/src/query/core/apiState.ts +++ b/packages/toolkit/src/query/core/apiState.ts @@ -201,13 +201,6 @@ type BaseQuerySubState< * Time that the latest query was fulfilled */ fulfilledTimeStamp?: number - /** - * Infinite Query Specific substate properties - */ - hasNextPage?: boolean - hasPreviousPage?: boolean - direction?: 'forward' | 'backward' - param?: QueryArgFrom } export type QuerySubState< @@ -238,18 +231,14 @@ export type QuerySubState< } > +export type InfiniteQueryDirection = 'forward' | 'backward' + export type InfiniteQuerySubState< D extends BaseEndpointDefinition, > = D extends InfiniteQueryDefinition ? QuerySubState, PageParamFrom>> & { - // TODO: These shouldn't be optional - hasNextPage?: boolean - hasPreviousPage?: boolean - isFetchingNextPage?: boolean - isFetchingPreviousPage?: boolean - param?: PageParamFrom - direction?: 'forward' | 'backward' + direction?: InfiniteQueryDirection } : never diff --git a/packages/toolkit/src/query/core/buildInitiate.ts b/packages/toolkit/src/query/core/buildInitiate.ts index 5132224bed..a3727b0112 100644 --- a/packages/toolkit/src/query/core/buildInitiate.ts +++ b/packages/toolkit/src/query/core/buildInitiate.ts @@ -24,6 +24,7 @@ import { countObjectKeys, getOrInsert, isNotNullish } from '../utils' import type { InfiniteData, InfiniteQueryConfigOptions, + InfiniteQueryDirection, SubscriptionOptions, } from './apiState' import type { @@ -73,7 +74,7 @@ export type StartInfiniteQueryActionCreatorOptions< subscribe?: boolean forceRefetch?: boolean | number subscriptionOptions?: SubscriptionOptions - direction?: 'forward' | 'backward' + direction?: InfiniteQueryDirection [forceQueryFnSymbol]?: () => QueryReturnValue param?: unknown previous?: boolean diff --git a/packages/toolkit/src/query/core/buildSelectors.ts b/packages/toolkit/src/query/core/buildSelectors.ts index d270c77493..65c42bdd7e 100644 --- a/packages/toolkit/src/query/core/buildSelectors.ts +++ b/packages/toolkit/src/query/core/buildSelectors.ts @@ -1,6 +1,7 @@ import type { InternalSerializeQueryArgs } from '../defaultSerializeQueryArgs' import type { EndpointDefinitions, + InfiniteQueryArgFrom, InfiniteQueryDefinition, MutationDefinition, QueryArgFrom, @@ -12,6 +13,8 @@ import type { import { expandTagDescription } from '../endpointDefinitions' import { flatten, isNotNullish } from '../utils' import type { + InfiniteData, + InfiniteQueryConfigOptions, InfiniteQuerySubState, MutationSubState, QueryCacheKey, @@ -25,6 +28,7 @@ import { QueryStatus, getRequestStatusFlags } from './apiState' import { getMutationCacheKey } from './buildSlice' import type { createSelector as _createSelector } from './rtkImports' import { createNextState } from './rtkImports' +import { getNextPageParam, getPreviousPageParam } from './buildThunks' export type SkipToken = typeof skipToken /** @@ -108,12 +112,23 @@ type InfiniteQueryResultSelectorFactory< Definition extends InfiniteQueryDefinition, RootState, > = ( - queryArg: QueryArgFrom | SkipToken, + queryArg: InfiniteQueryArgFrom | SkipToken, ) => (state: RootState) => InfiniteQueryResultSelectorResult +export type InfiniteQueryResultFlags = { + hasNextPage: boolean + hasPreviousPage: boolean + isFetchingNextPage: boolean + isFetchingPreviousPage: boolean + isFetchNextPageError: boolean + isFetchPreviousPageError: boolean +} + export type InfiniteQueryResultSelectorResult< Definition extends InfiniteQueryDefinition, -> = InfiniteQuerySubState & RequestStatusFlags +> = InfiniteQuerySubState & + RequestStatusFlags & + InfiniteQueryResultFlags type MutationResultSelectorFactory< Definition extends MutationDefinition, @@ -230,7 +245,52 @@ export function buildSelectors< const finalSelectQuerySubState = queryArgs === skipToken ? selectSkippedQuery : selectQuerySubstate - return createSelector(finalSelectQuerySubState, withRequestFlags) + const { infiniteQueryOptions } = endpointDefinition + + function withInfiniteQueryResultFlags( + substate: T, + ): T & RequestStatusFlags & InfiniteQueryResultFlags { + const infiniteSubstate = substate as InfiniteQuerySubState + const fetchDirection = infiniteSubstate.direction + const stateWithRequestFlags = { + ...infiniteSubstate, + ...getRequestStatusFlags(substate.status), + } + + const { isLoading, isError } = stateWithRequestFlags + + const isFetchNextPageError = isError && fetchDirection === 'forward' + const isFetchingNextPage = isLoading && fetchDirection === 'forward' + + const isFetchPreviousPageError = + isError && fetchDirection === 'backward' + const isFetchingPreviousPage = + isLoading && fetchDirection === 'backward' + + const hasNextPage = getHasNextPage( + infiniteQueryOptions, + stateWithRequestFlags.data, + ) + const hasPreviousPage = getHasPreviousPage( + infiniteQueryOptions, + stateWithRequestFlags.data, + ) + + return { + ...stateWithRequestFlags, + hasNextPage, + hasPreviousPage, + isFetchingNextPage, + isFetchingPreviousPage, + isFetchNextPageError, + isFetchPreviousPageError, + } + } + + return createSelector( + finalSelectQuerySubState, + withInfiniteQueryResultFlags, + ) }) as InfiniteQueryResultSelectorFactory } @@ -315,4 +375,20 @@ export function buildSelectors< ) .map((entry) => entry.originalArgs) } + + function getHasNextPage( + options: InfiniteQueryConfigOptions, + data?: InfiniteData, + ): boolean { + if (!data) return false + return getNextPageParam(options, data) != null + } + + function getHasPreviousPage( + options: InfiniteQueryConfigOptions, + data?: InfiniteData, + ): boolean { + if (!data || !options.getPreviousPageParam) return false + return getPreviousPageParam(options, data) != null + } } diff --git a/packages/toolkit/src/query/core/buildSlice.ts b/packages/toolkit/src/query/core/buildSlice.ts index 2b6ae7d2fd..ce089f0144 100644 --- a/packages/toolkit/src/query/core/buildSlice.ts +++ b/packages/toolkit/src/query/core/buildSlice.ts @@ -25,6 +25,7 @@ import type { ConfigState, QueryKeys, InfiniteQuerySubState, + InfiniteQueryDirection, } from './apiState' import { QueryStatus } from './apiState' import type { @@ -35,14 +36,15 @@ import type { RejectedAction, } from './buildThunks' import { calculateProvidedByThunk } from './buildThunks' -import type { - AssertTagTypes, - DefinitionType, - EndpointDefinitions, - FullTagDescription, - QueryArgFrom, - QueryDefinition, - ResultTypeFrom, +import { + isInfiniteQueryDefinition, + type AssertTagTypes, + type DefinitionType, + type EndpointDefinitions, + type FullTagDescription, + type QueryArgFrom, + type QueryDefinition, + type ResultTypeFrom, } from '../endpointDefinitions' import type { Patch } from 'immer' import { isDraft } from 'immer' @@ -205,15 +207,11 @@ export function buildSlice({ } substate.startedTimeStamp = meta.startedTimeStamp - // TODO: Awful - fix this most likely by just moving it to its own slice that only works on InfQuery's - if ( - 'param' in substate && - 'direction' in substate && - 'param' in arg && - 'direction' in arg - ) { - substate.param = arg.param - substate.direction = arg.direction as 'forward' | 'backward' | undefined + const endpointDefinition = definitions[meta.arg.endpointName] + + if (isInfiniteQueryDefinition(endpointDefinition) && 'direction' in arg) { + ;(substate as InfiniteQuerySubState).direction = + arg.direction as InfiniteQueryDirection } }) } @@ -223,11 +221,9 @@ export function buildSlice({ meta: { arg: QueryThunkArg requestId: string - // requestStatus: 'fulfilled' } & { fulfilledTimeStamp: number baseQueryMeta: unknown - // RTK_autoBatch: true }, payload: unknown, upserting: boolean, diff --git a/packages/toolkit/src/query/core/buildThunks.ts b/packages/toolkit/src/query/core/buildThunks.ts index 5c0e129757..c918516734 100644 --- a/packages/toolkit/src/query/core/buildThunks.ts +++ b/packages/toolkit/src/query/core/buildThunks.ts @@ -39,6 +39,7 @@ import type { InfiniteData, InfiniteQueryConfigOptions, QueryCacheKey, + InfiniteQueryDirection, } from './apiState' import { QueryStatus } from './apiState' import type { @@ -131,7 +132,7 @@ export type InfiniteQueryThunkArg< endpointName: string param: unknown previous?: boolean - direction?: 'forward' | 'backward' + direction?: InfiniteQueryDirection } type MutationThunkArg = { @@ -646,31 +647,6 @@ In the case of an unhandled error, no tags will be "provided" or "invalidated".` } } - function getNextPageParam( - options: InfiniteQueryConfigOptions, - { pages, pageParams }: InfiniteData, - ): unknown | undefined { - const lastIndex = pages.length - 1 - return options.getNextPageParam( - pages[lastIndex], - pages, - pageParams[lastIndex], - pageParams, - ) - } - - function getPreviousPageParam( - options: InfiniteQueryConfigOptions, - { pages, pageParams }: InfiniteData, - ): unknown | undefined { - return options.getPreviousPageParam?.( - pages[0], - pages, - pageParams[0], - pageParams, - ) - } - function isForcedQuery( arg: QueryThunkArg, state: RootState, @@ -892,6 +868,31 @@ In the case of an unhandled error, no tags will be "provided" or "invalidated".` } } +export function getNextPageParam( + options: InfiniteQueryConfigOptions, + { pages, pageParams }: InfiniteData, +): unknown | undefined { + const lastIndex = pages.length - 1 + return options.getNextPageParam( + pages[lastIndex], + pages, + pageParams[lastIndex], + pageParams, + ) +} + +export function getPreviousPageParam( + options: InfiniteQueryConfigOptions, + { pages, pageParams }: InfiniteData, +): unknown | undefined { + return options.getPreviousPageParam?.( + pages[0], + pages, + pageParams[0], + pageParams, + ) +} + export function calculateProvidedByThunk( action: UnwrapPromise< ReturnType> | ReturnType> diff --git a/packages/toolkit/src/query/react/buildHooks.ts b/packages/toolkit/src/query/react/buildHooks.ts index 08d0863fca..a0be4e9c49 100644 --- a/packages/toolkit/src/query/react/buildHooks.ts +++ b/packages/toolkit/src/query/react/buildHooks.ts @@ -63,6 +63,7 @@ import { UNINITIALIZED_VALUE } from './constants' import type { ReactHooksModuleOptions } from './module' import { useStableQueryArgs } from './useSerializedStableValue' import { useShallowStableValue } from './useShallowStableValue' +import { InfiniteQueryDirection } from '../core/apiState' // Copy-pasted from React-Redux const canUseDOM = () => @@ -786,7 +787,7 @@ export type LazyInfiniteQueryTrigger< */ ( arg: QueryArgFrom, - direction: 'forward' | 'backward', + direction: InfiniteQueryDirection, ): InfiniteQueryActionCreatorResult } @@ -889,7 +890,10 @@ export type UseInfiniteQuery< arg: InfiniteQueryArgFrom | SkipToken, options?: UseInfiniteQuerySubscriptionOptions & UseInfiniteQueryStateOptions, -) => UseInfiniteQueryHookResult +) => UseInfiniteQueryHookResult & { + fetchNextPage: () => InfiniteQueryActionCreatorResult + fetchPreviousPage: () => InfiniteQueryActionCreatorResult +} export type UseInfiniteQueryState< D extends InfiniteQueryDefinition, @@ -897,7 +901,6 @@ export type UseInfiniteQueryState< arg: QueryArgFrom | SkipToken, options?: UseInfiniteQueryStateOptions, ) => UseInfiniteQueryStateResult - export type TypedUseInfiniteQueryState< ResultType, QueryArg, @@ -1039,9 +1042,6 @@ type UseInfiniteQueryStateBaseResult< hasPreviousPage: false isFetchingNextPage: false isFetchingPreviousPage: false - - fetchNextPage: () => Promise> - fetchPreviousPage: () => Promise> } type UseInfiniteQueryStateDefaultResult< @@ -1346,12 +1346,6 @@ export function buildHooks({ // isSuccess = true when data is present const isSuccess = currentState.isSuccess || (isFetching && hasData) - const isFetchingNextPage = - isFetching && currentState.direction === 'forward' - - const isFetchingPreviousPage = - isFetching && currentState.direction === 'backward' - return { ...currentState, data, @@ -1359,8 +1353,6 @@ export function buildHooks({ isFetching, isLoading, isSuccess, - isFetchingNextPage, - isFetchingPreviousPage, } as UseInfiniteQueryStateDefaultResult } @@ -1767,7 +1759,9 @@ export function buildHooks({ Definitions > const dispatch = useDispatch>() - const subscriptionSelectorsRef = useRef() + const subscriptionSelectorsRef = useRef< + SubscriptionSelectors | undefined + >(undefined) if (!subscriptionSelectorsRef.current) { const returnedValue = dispatch( api.internalActions.internal_getRTKQSubscriptions(), @@ -1808,7 +1802,9 @@ export function buildHooks({ const lastRenderHadSubscription = useRef(false) - const promiseRef = useRef>() + const promiseRef = useRef< + InfiniteQueryActionCreatorResult | undefined + >(undefined) let { queryCacheKey, requestId } = promiseRef.current || {} @@ -1932,7 +1928,7 @@ export function buildHooks({ type ApiRootState = Parameters>[0] - const lastValue = useRef() + const lastValue = useRef(undefined) const selectDefaultResult: Selector = useMemo( () => diff --git a/packages/toolkit/src/query/tests/buildHooks.test.tsx b/packages/toolkit/src/query/tests/buildHooks.test.tsx index 46d4f70699..044d4c970a 100644 --- a/packages/toolkit/src/query/tests/buildHooks.test.tsx +++ b/packages/toolkit/src/query/tests/buildHooks.test.tsx @@ -36,6 +36,7 @@ import type { SyncScreen } from '@testing-library/react-render-stream/pure' import { createRenderStream } from '@testing-library/react-render-stream/pure' import { HttpResponse, http } from 'msw' import { useEffect, useState } from 'react' +import type { InfiniteQueryResultFlags } from '../core/buildSelectors' // Just setup a temporary in-memory counter for tests that `getIncrementedAmount`. // This can be used to test how many renders happen due to data changes or @@ -1798,6 +1799,26 @@ describe('hooks tests', () => { expect(queries).toBe(count) } + const checkEntryFlags = ( + arg: string, + expectedFlags: Partial, + ) => { + const selector = pokemonApi.endpoints.getInfinitePokemon.select(arg) + const entry = selector(storeRef.store.getState()) + + const actualFlags: InfiniteQueryResultFlags = { + hasNextPage: false, + hasPreviousPage: false, + isFetchingNextPage: false, + isFetchingPreviousPage: false, + isFetchNextPageError: false, + isFetchPreviousPageError: false, + ...expectedFlags, + } + + expect(entry).toMatchObject(actualFlags) + } + const checkPageRows = ( withinDOM: () => SyncScreen, type: string, @@ -1836,30 +1857,64 @@ describe('hooks tests', () => { const utils = render(, { wrapper: storeRef.wrapper }) checkNumQueries(1) + checkEntryFlags('fire', {}) await waitForFetch(true) checkNumQueries(1) checkPageRows(getCurrentRender().withinDOM, 'fire', [0]) + checkEntryFlags('fire', { + hasNextPage: true, + }) fireEvent.click(screen.getByTestId('nextPage'), {}) + checkEntryFlags('fire', { + hasNextPage: true, + isFetchingNextPage: true, + }) await waitForFetch() checkPageRows(getCurrentRender().withinDOM, 'fire', [0, 1]) + checkEntryFlags('fire', { + hasNextPage: true, + }) fireEvent.click(screen.getByTestId('nextPage')) await waitForFetch() checkPageRows(getCurrentRender().withinDOM, 'fire', [0, 1, 2]) utils.rerender() + checkEntryFlags('water', {}) await waitForFetch(true) checkNumQueries(2) checkPageRows(getCurrentRender().withinDOM, 'water', [3]) + checkEntryFlags('water', { + hasNextPage: true, + hasPreviousPage: true, + }) fireEvent.click(screen.getByTestId('nextPage')) + checkEntryFlags('water', { + hasNextPage: true, + hasPreviousPage: true, + isFetchingNextPage: true, + }) await waitForFetch() checkPageRows(getCurrentRender().withinDOM, 'water', [3, 4]) + checkEntryFlags('water', { + hasNextPage: true, + hasPreviousPage: true, + }) fireEvent.click(screen.getByTestId('prevPage')) + checkEntryFlags('water', { + hasNextPage: true, + hasPreviousPage: true, + isFetchingPreviousPage: true, + }) await waitForFetch() checkPageRows(getCurrentRender().withinDOM, 'water', [2, 3, 4]) + checkEntryFlags('water', { + hasNextPage: true, + hasPreviousPage: true, + }) }) }) diff --git a/packages/toolkit/src/query/tests/infiniteQueries.test.ts b/packages/toolkit/src/query/tests/infiniteQueries.test.ts index b317fd87a4..2fc891e870 100644 --- a/packages/toolkit/src/query/tests/infiniteQueries.test.ts +++ b/packages/toolkit/src/query/tests/infiniteQueries.test.ts @@ -10,6 +10,7 @@ import { import userEvent from '@testing-library/user-event' import { HttpResponse, http } from 'msw' import util from 'util' +import type { InfiniteQueryActionCreatorResult } from '@reduxjs/toolkit/query/react' import { QueryStatus, createApi, @@ -24,6 +25,7 @@ import { } from '../../tests/utils/helpers' import type { BaseQueryApi } from '../baseQueryTypes' import { server } from '@internal/query/tests/mocks/server' +import type { InfiniteQueryResultFlags } from '../core/buildSelectors' describe('Infinite queries', () => { type Pokemon = { @@ -139,92 +141,162 @@ describe('Infinite queries', () => { }) test('Basic infinite query behavior', async () => { + const checkFlags = ( + value: unknown, + expectedFlags: Partial, + ) => { + const actualFlags: InfiniteQueryResultFlags = { + hasNextPage: false, + hasPreviousPage: false, + isFetchingNextPage: false, + isFetchingPreviousPage: false, + isFetchNextPageError: false, + isFetchPreviousPageError: false, + ...expectedFlags, + } + + expect(value).toMatchObject(actualFlags) + } + + const checkEntryFlags = ( + arg: string, + expectedFlags: Partial, + ) => { + const selector = pokemonApi.endpoints.getInfinitePokemon.select(arg) + const entry = selector(storeRef.store.getState()) + + checkFlags(entry, expectedFlags) + } + + type InfiniteQueryResult = Awaited> + + const checkResultData = ( + result: InfiniteQueryResult, + expectedValues: Pokemon[][], + ) => { + expect(result.status).toBe(QueryStatus.fulfilled) + if (result.status === QueryStatus.fulfilled) { + expect(result.data.pages).toEqual(expectedValues) + } + } + const res1 = storeRef.store.dispatch( - // Should be `arg: string` pokemonApi.endpoints.getInfinitePokemon.initiate('fire', {}), ) + checkEntryFlags('fire', {}) + const entry1InitialLoad = await res1 - expect(entry1InitialLoad.status).toBe(QueryStatus.fulfilled) - // console.log('Value: ', util.inspect(entry1InitialLoad, { depth: Infinity })) - if (entry1InitialLoad.status === QueryStatus.fulfilled) { - expect(entry1InitialLoad.data.pages).toEqual([ - // one page, one entry - [{ id: '0', name: 'Pokemon 0' }], - ]) - } + checkResultData(entry1InitialLoad, [[{ id: '0', name: 'Pokemon 0' }]]) + checkFlags(entry1InitialLoad, { + hasNextPage: true, + }) - const entry1SecondPage = await storeRef.store.dispatch( + const res2 = storeRef.store.dispatch( pokemonApi.endpoints.getInfinitePokemon.initiate('fire', { direction: 'forward', }), ) - expect(entry1SecondPage.status).toBe(QueryStatus.fulfilled) - if (entry1SecondPage.status === QueryStatus.fulfilled) { - expect(entry1SecondPage.data.pages).toEqual([ - // two pages, one entry each - [{ id: '0', name: 'Pokemon 0' }], - [{ id: '1', name: 'Pokemon 1' }], - ]) - } + checkEntryFlags('fire', { + hasNextPage: true, + isFetchingNextPage: true, + }) + + const entry1SecondPage = await res2 + + checkResultData(entry1SecondPage, [ + [{ id: '0', name: 'Pokemon 0' }], + [{ id: '1', name: 'Pokemon 1' }], + ]) + checkFlags(entry1SecondPage, { + hasNextPage: true, + }) - const entry1PrevPageMissing = await storeRef.store.dispatch( + const res3 = storeRef.store.dispatch( pokemonApi.endpoints.getInfinitePokemon.initiate('fire', { direction: 'backward', }), ) - if (entry1PrevPageMissing.status === QueryStatus.fulfilled) { - expect(entry1PrevPageMissing.data.pages).toEqual([ - // two pages, one entry each - [{ id: '0', name: 'Pokemon 0' }], - [{ id: '1', name: 'Pokemon 1' }], - ]) - } + checkEntryFlags('fire', { + hasNextPage: true, + isFetchingPreviousPage: true, + }) + + const entry1PrevPageMissing = await res3 + + checkResultData(entry1PrevPageMissing, [ + [{ id: '0', name: 'Pokemon 0' }], + [{ id: '1', name: 'Pokemon 1' }], + ]) + checkFlags(entry1PrevPageMissing, { + hasNextPage: true, + }) - const entry2InitialLoad = await storeRef.store.dispatch( + const res4 = storeRef.store.dispatch( pokemonApi.endpoints.getInfinitePokemon.initiate('water', { initialPageParam: 3, }), ) - if (entry2InitialLoad.status === QueryStatus.fulfilled) { - expect(entry2InitialLoad.data.pages).toEqual([ - // one page, one entry - [{ id: '3', name: 'Pokemon 3' }], - ]) - } + checkEntryFlags('water', {}) + + const entry2InitialLoad = await res4 - const entry2NextPage = await storeRef.store.dispatch( + checkResultData(entry2InitialLoad, [[{ id: '3', name: 'Pokemon 3' }]]) + checkFlags(entry2InitialLoad, { + hasNextPage: true, + hasPreviousPage: true, + }) + + const res5 = storeRef.store.dispatch( pokemonApi.endpoints.getInfinitePokemon.initiate('water', { direction: 'forward', }), ) - if (entry2NextPage.status === QueryStatus.fulfilled) { - expect(entry2NextPage.data.pages).toEqual([ - // two pages, one entry each - [{ id: '3', name: 'Pokemon 3' }], - [{ id: '4', name: 'Pokemon 4' }], - ]) - } + checkEntryFlags('water', { + hasNextPage: true, + hasPreviousPage: true, + isFetchingNextPage: true, + }) + + const entry2NextPage = await res5 - const entry2PrevPage = await storeRef.store.dispatch( + checkResultData(entry2NextPage, [ + [{ id: '3', name: 'Pokemon 3' }], + [{ id: '4', name: 'Pokemon 4' }], + ]) + checkFlags(entry2NextPage, { + hasNextPage: true, + hasPreviousPage: true, + }) + + const res6 = storeRef.store.dispatch( pokemonApi.endpoints.getInfinitePokemon.initiate('water', { direction: 'backward', }), ) - if (entry2PrevPage.status === QueryStatus.fulfilled) { - expect(entry2PrevPage.data.pages).toEqual([ - // three pages, one entry each - [{ id: '2', name: 'Pokemon 2' }], - [{ id: '3', name: 'Pokemon 3' }], - [{ id: '4', name: 'Pokemon 4' }], - ]) - } + checkEntryFlags('water', { + hasNextPage: true, + hasPreviousPage: true, + isFetchingPreviousPage: true, + }) + + const entry2PrevPage = await res6 + + checkResultData(entry2PrevPage, [ + [{ id: '2', name: 'Pokemon 2' }], + [{ id: '3', name: 'Pokemon 3' }], + [{ id: '4', name: 'Pokemon 4' }], + ]) + checkFlags(entry2PrevPage, { + hasNextPage: true, + hasPreviousPage: true, + }) }) test.skip('does not break refetching query endpoints', async () => {