diff --git a/packages/toolkit/src/query/core/module.ts b/packages/toolkit/src/query/core/module.ts index 004a6e8f80..7ccf49ade6 100644 --- a/packages/toolkit/src/query/core/module.ts +++ b/packages/toolkit/src/query/core/module.ts @@ -58,10 +58,12 @@ import { buildSelectors } from './buildSelectors' import type { SliceActions, UpsertEntries } from './buildSlice' import { buildSlice } from './buildSlice' import type { + AllQueryKeys, BuildThunksApiEndpointInfiniteQuery, BuildThunksApiEndpointMutation, BuildThunksApiEndpointQuery, PatchQueryDataThunk, + QueryArgFromAnyQueryDefinition, UpdateQueryDataThunk, UpsertQueryDataThunk, } from './buildThunks' @@ -167,9 +169,9 @@ export interface ApiModules< * * See https://redux-toolkit.js.org/rtk-query/usage/server-side-rendering for details. */ - getRunningQueryThunk>( + getRunningQueryThunk>( endpointName: EndpointName, - arg: QueryArgFrom, + arg: QueryArgFromAnyQueryDefinition, ): ThunkWithReturnValue< | QueryActionCreatorResult< Definitions[EndpointName] & { type: 'query' } diff --git a/packages/toolkit/src/query/endpointDefinitions.ts b/packages/toolkit/src/query/endpointDefinitions.ts index a80c11f4ca..fe9f1fde05 100644 --- a/packages/toolkit/src/query/endpointDefinitions.ts +++ b/packages/toolkit/src/query/endpointDefinitions.ts @@ -594,7 +594,13 @@ export interface InfiniteQueryExtraOptions< CacheCollectionQueryExtraOptions { type: DefinitionType.infinitequery - providesTags?: never + providesTags?: ResultDescription< + TagTypes, + ResultType, + QueryArg, + BaseQueryError, + BaseQueryMeta + > /** * Not to be used. A query should not invalidate tags in the cache. */ diff --git a/packages/toolkit/src/query/tests/infiniteQueries.test.ts b/packages/toolkit/src/query/tests/infiniteQueries.test.ts index 91ef76b653..dd6b24a921 100644 --- a/packages/toolkit/src/query/tests/infiniteQueries.test.ts +++ b/packages/toolkit/src/query/tests/infiniteQueries.test.ts @@ -8,9 +8,12 @@ import { waitFor, } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { HttpResponse, http } from 'msw' +import { HttpResponse, delay, http } from 'msw' import util from 'util' -import type { InfiniteQueryActionCreatorResult } from '@reduxjs/toolkit/query/react' +import type { + InfiniteQueryActionCreatorResult, + QueryCacheKey, +} from '@reduxjs/toolkit/query/react' import { QueryStatus, createApi, @@ -101,6 +104,40 @@ describe('Infinite queries', () => { }), }) + let hitCounter = 0 + + type HitCounter = { page: number; hitCounter: number } + + const countersApi = createApi({ + baseQuery: fakeBaseQuery(), + tagTypes: ['Counter'], + endpoints: (build) => ({ + counters: build.infiniteQuery({ + queryFn(page) { + hitCounter++ + + return { data: { page, hitCounter } } + }, + infiniteQueryOptions: { + initialPageParam: 0, + getNextPageParam: ( + lastPage, + allPages, + lastPageParam, + allPageParams, + ) => lastPageParam + 1, + }, + providesTags: ['Counter'], + }), + mutation: build.mutation({ + queryFn: async () => { + return { data: null } + }, + invalidatesTags: ['Counter'], + }), + }), + }) + let storeRef = setupApiStore( pokemonApi, { ...actionsReducer }, @@ -133,6 +170,8 @@ describe('Infinite queries', () => { counters = {} + hitCounter = 0 + process.env.NODE_ENV = 'development' }) @@ -404,32 +443,133 @@ describe('Infinite queries', () => { }) test('refetches all existing pages', async () => { - let hitCounter = 0 + const checkResultData = ( + result: InfiniteQueryResult, + expectedValues: HitCounter[], + ) => { + expect(result.status).toBe(QueryStatus.fulfilled) + if (result.status === QueryStatus.fulfilled) { + expect(result.data.pages).toEqual(expectedValues) + } + } - type HitCounter = { page: number; hitCounter: number } + const storeRef = setupApiStore( + countersApi, + { ...actionsReducer }, + { + withoutTestLifecycles: true, + }, + ) - const countersApi = createApi({ - baseQuery: fakeBaseQuery(), - endpoints: (build) => ({ - counters: build.infiniteQuery({ - queryFn(page) { - hitCounter++ + await storeRef.store.dispatch( + countersApi.endpoints.counters.initiate('item', { + initialPageParam: 3, + }), + ) - return { data: { page, hitCounter } } - }, - infiniteQueryOptions: { - initialPageParam: 0, - getNextPageParam: ( - lastPage, - allPages, - lastPageParam, - allPageParams, - ) => lastPageParam + 1, - }, - }), + await storeRef.store.dispatch( + countersApi.endpoints.counters.initiate('item', { + direction: 'forward', + }), + ) + + const thirdPromise = storeRef.store.dispatch( + countersApi.endpoints.counters.initiate('item', { + direction: 'forward', }), + ) + + const thirdRes = await thirdPromise + + checkResultData(thirdRes, [ + { page: 3, hitCounter: 1 }, + { page: 4, hitCounter: 2 }, + { page: 5, hitCounter: 3 }, + ]) + + const fourthRes = await thirdPromise.refetch() + + checkResultData(fourthRes, [ + { page: 3, hitCounter: 4 }, + { page: 4, hitCounter: 5 }, + { page: 5, hitCounter: 6 }, + ]) + }) + + test('Refetches on invalidation', async () => { + const checkResultData = ( + result: InfiniteQueryResult, + expectedValues: HitCounter[], + ) => { + expect(result.status).toBe(QueryStatus.fulfilled) + if (result.status === QueryStatus.fulfilled) { + expect(result.data.pages).toEqual(expectedValues) + } + } + + const storeRef = setupApiStore( + countersApi, + { ...actionsReducer }, + { + withoutTestLifecycles: true, + }, + ) + + await storeRef.store.dispatch( + countersApi.endpoints.counters.initiate('item', { + initialPageParam: 3, + }), + ) + + await storeRef.store.dispatch( + countersApi.endpoints.counters.initiate('item', { + direction: 'forward', + }), + ) + + const thirdPromise = storeRef.store.dispatch( + countersApi.endpoints.counters.initiate('item', { + direction: 'forward', + }), + ) + + const thirdRes = await thirdPromise + + checkResultData(thirdRes, [ + { page: 3, hitCounter: 1 }, + { page: 4, hitCounter: 2 }, + { page: 5, hitCounter: 3 }, + ]) + + await storeRef.store.dispatch(countersApi.endpoints.mutation.initiate()) + + let entry = countersApi.endpoints.counters.select('item')( + storeRef.store.getState(), + ) + const promise = storeRef.store.dispatch( + countersApi.util.getRunningQueryThunk('counters', 'item'), + ) + const promises = storeRef.store.dispatch( + countersApi.util.getRunningQueriesThunk(), + ) + expect(entry).toMatchObject({ + status: 'pending', }) + expect(promise).toBeInstanceOf(Promise) + + expect(promises).toEqual([promise]) + + const finalRes = await promise + + checkResultData(finalRes as any, [ + { page: 3, hitCounter: 4 }, + { page: 4, hitCounter: 5 }, + { page: 5, hitCounter: 6 }, + ]) + }) + + test('Refetches on polling', async () => { const checkResultData = ( result: InfiniteQueryResult, expectedValues: HitCounter[], @@ -474,9 +614,29 @@ describe('Infinite queries', () => { { page: 5, hitCounter: 3 }, ]) - const fourthRes = await thirdPromise.refetch() + thirdPromise.updateSubscriptionOptions({ + pollingInterval: 10, + }) - checkResultData(fourthRes, [ + await delay(5) + + let entry = countersApi.endpoints.counters.select('item')( + storeRef.store.getState(), + ) + + checkResultData(thirdRes, [ + { page: 3, hitCounter: 1 }, + { page: 4, hitCounter: 2 }, + { page: 5, hitCounter: 3 }, + ]) + + await delay(10) + + entry = countersApi.endpoints.counters.select('item')( + storeRef.store.getState(), + ) + + checkResultData(entry as any, [ { page: 3, hitCounter: 4 }, { page: 4, hitCounter: 5 }, { page: 5, hitCounter: 6 },