Skip to content

Commit 85d9820

Browse files
committed
Add test for initial page param references
1 parent f8f0b00 commit 85d9820

File tree

1 file changed

+246
-6
lines changed

1 file changed

+246
-6
lines changed

packages/toolkit/src/query/tests/buildHooks.test.tsx

Lines changed: 246 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import {
1818
createSlice,
1919
} from '@reduxjs/toolkit'
2020
import {
21-
QueryStatus,
2221
createApi,
2322
fetchBaseQuery,
2423
skipToken,
@@ -34,8 +33,8 @@ import {
3433
import { userEvent } from '@testing-library/user-event'
3534
import type { SyncScreen } from '@testing-library/react-render-stream/pure'
3635
import { createRenderStream } from '@testing-library/react-render-stream/pure'
37-
import { HttpResponse, http } from 'msw'
38-
import { useEffect, useState } from 'react'
36+
import { HttpResponse, http, delay } from 'msw'
37+
import { useEffect, useMemo, useState } from 'react'
3938
import type { InfiniteQueryResultFlags } from '../core/buildSelectors'
4039

4140
// Just setup a temporary in-memory counter for tests that `getIncrementedAmount`.
@@ -929,9 +928,6 @@ describe('hooks tests', () => {
929928
// See https://github.com/reduxjs/redux-toolkit/issues/4267 - Memory leak in useQuery rapid query arg changes
930929
test('Hook subscriptions are properly cleaned up when query is fulfilled/rejected', async () => {
931930
// This is imported already, but it seems to be causing issues with the test on certain matrixes
932-
function delay(ms: number) {
933-
return new Promise((resolve) => setTimeout(resolve, ms))
934-
}
935931

936932
const pokemonApi = createApi({
937933
baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }),
@@ -1974,6 +1970,250 @@ describe('hooks tests', () => {
19741970
hasPreviousPage: true,
19751971
})
19761972
})
1973+
1974+
test('Object page params does not keep forcing refetching', async () => {
1975+
type Project = {
1976+
id: number
1977+
createdAt: string
1978+
}
1979+
1980+
type ProjectsResponse = {
1981+
projects: Project[]
1982+
numFound: number
1983+
serverTime: string
1984+
}
1985+
1986+
interface ProjectsInitialPageParam {
1987+
offset: number
1988+
limit: number
1989+
}
1990+
1991+
const apiWithInfiniteScroll = createApi({
1992+
baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com/' }),
1993+
endpoints: (builder) => ({
1994+
projectsLimitOffset: builder.infiniteQuery<
1995+
ProjectsResponse,
1996+
void,
1997+
ProjectsInitialPageParam
1998+
>({
1999+
infiniteQueryOptions: {
2000+
initialPageParam: {
2001+
offset: 0,
2002+
limit: 20,
2003+
},
2004+
getNextPageParam: (
2005+
lastPage,
2006+
allPages,
2007+
lastPageParam,
2008+
allPageParams,
2009+
) => {
2010+
const nextOffset = lastPageParam.offset + lastPageParam.limit
2011+
const remainingItems = lastPage?.numFound - nextOffset
2012+
2013+
if (remainingItems <= 0) {
2014+
return undefined
2015+
}
2016+
2017+
return {
2018+
...lastPageParam,
2019+
offset: nextOffset,
2020+
}
2021+
},
2022+
getPreviousPageParam: (
2023+
firstPage,
2024+
allPages,
2025+
firstPageParam,
2026+
allPageParams,
2027+
) => {
2028+
const prevOffset = firstPageParam.offset - firstPageParam.limit
2029+
if (prevOffset < 0) return undefined
2030+
2031+
return {
2032+
...firstPageParam,
2033+
offset: firstPageParam.offset - firstPageParam.limit,
2034+
}
2035+
},
2036+
},
2037+
query: ({ offset, limit }) => {
2038+
return {
2039+
url: `https://example.com/api/projectsLimitOffset?offset=${offset}&limit=${limit}`,
2040+
method: 'GET',
2041+
}
2042+
},
2043+
}),
2044+
}),
2045+
})
2046+
2047+
const projects = Array.from({ length: 50 }, (_, i) => {
2048+
return {
2049+
id: i,
2050+
createdAt: Date.now() + i * 1000,
2051+
}
2052+
})
2053+
2054+
let numRequests = 0
2055+
2056+
server.use(
2057+
http.get(
2058+
'https://example.com/api/projectsLimitOffset',
2059+
async ({ request }) => {
2060+
const url = new URL(request.url)
2061+
const limit = parseInt(url.searchParams.get('limit') ?? '5', 10)
2062+
let offset = parseInt(url.searchParams.get('offset') ?? '0', 10)
2063+
2064+
numRequests++
2065+
2066+
if (isNaN(offset) || offset < 0) {
2067+
offset = 0
2068+
}
2069+
if (isNaN(limit) || limit <= 0) {
2070+
return HttpResponse.json(
2071+
{
2072+
message:
2073+
"Invalid 'limit' parameter. It must be a positive integer.",
2074+
} as any,
2075+
{ status: 400 },
2076+
)
2077+
}
2078+
2079+
const result = projects.slice(offset, offset + limit)
2080+
2081+
await delay(10)
2082+
return HttpResponse.json({
2083+
projects: result,
2084+
serverTime: Date.now(),
2085+
numFound: projects.length,
2086+
})
2087+
},
2088+
),
2089+
)
2090+
2091+
function LimitOffsetExample() {
2092+
const {
2093+
data,
2094+
hasPreviousPage,
2095+
hasNextPage,
2096+
error,
2097+
isFetching,
2098+
isLoading,
2099+
isError,
2100+
fetchNextPage,
2101+
fetchPreviousPage,
2102+
isFetchingNextPage,
2103+
isFetchingPreviousPage,
2104+
status,
2105+
} = apiWithInfiniteScroll.useProjectsLimitOffsetInfiniteQuery(
2106+
undefined,
2107+
{
2108+
initialPageParam: {
2109+
offset: 10,
2110+
limit: 10,
2111+
},
2112+
},
2113+
)
2114+
2115+
const [counter, setCounter] = useState(0)
2116+
2117+
const combinedData = useMemo(() => {
2118+
return data?.pages?.map((item) => item?.projects)?.flat()
2119+
}, [data])
2120+
2121+
return (
2122+
<div>
2123+
<h2>Limit and Offset Infinite Scroll</h2>
2124+
<button onClick={() => setCounter((c) => c + 1)}>Increment</button>
2125+
<div>Counter: {counter}</div>
2126+
{isLoading ? (
2127+
<p>Loading...</p>
2128+
) : isError ? (
2129+
<span>Error: {error.message}</span>
2130+
) : null}
2131+
2132+
<>
2133+
<div>
2134+
<button
2135+
onClick={() => fetchPreviousPage()}
2136+
disabled={!hasPreviousPage || isFetchingPreviousPage}
2137+
>
2138+
{isFetchingPreviousPage
2139+
? 'Loading more...'
2140+
: hasPreviousPage
2141+
? 'Load Older'
2142+
: 'Nothing more to load'}
2143+
</button>
2144+
</div>
2145+
<div data-testid="projects">
2146+
{combinedData?.map((project, index, arr) => {
2147+
return (
2148+
<div key={project.id}>
2149+
<div data-testid="project">
2150+
<div>{`Project ${project.id} (created at: ${project.createdAt})`}</div>
2151+
</div>
2152+
</div>
2153+
)
2154+
})}
2155+
</div>
2156+
<div>
2157+
<button
2158+
onClick={() => fetchNextPage()}
2159+
disabled={!hasNextPage || isFetchingNextPage}
2160+
>
2161+
{isFetchingNextPage
2162+
? 'Loading more...'
2163+
: hasNextPage
2164+
? 'Load Newer'
2165+
: 'Nothing more to load'}
2166+
</button>
2167+
</div>
2168+
<div>
2169+
{isFetching && !isFetchingPreviousPage && !isFetchingNextPage
2170+
? 'Background Updating...'
2171+
: null}
2172+
</div>
2173+
</>
2174+
</div>
2175+
)
2176+
}
2177+
2178+
const storeRef = setupApiStore(
2179+
apiWithInfiniteScroll,
2180+
{ ...actionsReducer },
2181+
{
2182+
withoutTestLifecycles: true,
2183+
},
2184+
)
2185+
2186+
const { takeRender, render, totalRenderCount } = createRenderStream({
2187+
snapshotDOM: true,
2188+
})
2189+
2190+
render(<LimitOffsetExample />, {
2191+
wrapper: storeRef.wrapper,
2192+
})
2193+
2194+
{
2195+
const { withinDOM } = await takeRender()
2196+
withinDOM().getByText('Counter: 0')
2197+
withinDOM().getByText('Loading...')
2198+
}
2199+
2200+
{
2201+
const { withinDOM } = await takeRender()
2202+
withinDOM().getByText('Counter: 0')
2203+
withinDOM().getByText('Loading...')
2204+
}
2205+
2206+
{
2207+
const { withinDOM } = await takeRender()
2208+
withinDOM().getByText('Counter: 0')
2209+
2210+
expect(withinDOM().getAllByTestId('project').length).toBe(10)
2211+
expect(withinDOM().queryByTestId('Loading...')).toBeNull()
2212+
}
2213+
2214+
expect(totalRenderCount()).toBe(3)
2215+
expect(numRequests).toBe(1)
2216+
})
19772217
})
19782218

19792219
describe('useMutation', () => {

0 commit comments

Comments
 (0)