Skip to content

Commit 249e0ec

Browse files
committed
Add test for initial page param references
1 parent f8f0b00 commit 249e0ec

File tree

1 file changed

+246
-5
lines changed

1 file changed

+246
-5
lines changed

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

Lines changed: 246 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ import {
3434
import { userEvent } from '@testing-library/user-event'
3535
import type { SyncScreen } from '@testing-library/react-render-stream/pure'
3636
import { createRenderStream } from '@testing-library/react-render-stream/pure'
37-
import { HttpResponse, http } from 'msw'
38-
import { useEffect, useState } from 'react'
37+
import { HttpResponse, http, delay } from 'msw'
38+
import { useEffect, useMemo, useState } from 'react'
3939
import type { InfiniteQueryResultFlags } from '../core/buildSelectors'
4040

4141
// Just setup a temporary in-memory counter for tests that `getIncrementedAmount`.
@@ -929,9 +929,6 @@ describe('hooks tests', () => {
929929
// See https://github.com/reduxjs/redux-toolkit/issues/4267 - Memory leak in useQuery rapid query arg changes
930930
test('Hook subscriptions are properly cleaned up when query is fulfilled/rejected', async () => {
931931
// 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-
}
935932

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

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

0 commit comments

Comments
 (0)