-
Notifications
You must be signed in to change notification settings - Fork 899
feat: add usePaginatedQuery hook #10803
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 1 commit
Commits
Show all changes
64 commits
Select commit
Hold shift + click to select a range
b4211f3
wip: commit current progress on usePaginatedQuery
Parkreiner 173e823
chore: add cacheTime to users query
Parkreiner d715d73
chore: update cache logic for UsersPage usersQuery
Parkreiner 8a199b7
wip: commit progress on Pagination
Parkreiner 98ae96d
chore: add function overloads to prepareQuery
Parkreiner 37ea50b
wip: commit progress on usePaginatedQuery
Parkreiner 2c1e9e3
docs: add clarifying comment about implementation
Parkreiner b3a9ab4
chore: remove optional prefetch property from query options
Parkreiner 39a2ced
chore: redefine queryKey
Parkreiner 4dcfd29
refactor: consolidate how queryKey/queryFn are called
Parkreiner 86f8437
refactor: clean up pagination code more
Parkreiner f612d8f
fix: remove redundant properties
Parkreiner 5878326
refactor: clean up code
Parkreiner 5cc1c2d
wip: commit progress on usePaginatedQuery
Parkreiner 0138f21
wip: commit current pagination progress
Parkreiner a38fd30
docs: clean up comments for clarity
Parkreiner 22d6c24
wip: get type signatures compatible (breaks runtime logic slightly)
Parkreiner e717da1
refactor: clean up type definitions
Parkreiner 7624d94
chore: add support for custom onInvalidPage functions
Parkreiner 2623131
refactor: clean up type definitions more for clarity reasons
Parkreiner 29242e9
chore: delete Pagination component (separate PR)
Parkreiner 5a9aa2d
chore: remove cacheTime fixes (to be resolved in future PR)
Parkreiner 3d67304
docs: add clarifying/intellisense comments for DX
Parkreiner d977060
refactor: link users queries to same queryKey implementation
Parkreiner 9224624
docs: remove misleading comment
Parkreiner 540c779
docs: more comments
Parkreiner 4b42b6f
chore: update onInvalidPage params for more flexibility
Parkreiner 5bc43c9
fix: remove explicit any
Parkreiner bd146a1
refactor: clean up type definitions
Parkreiner 983b83f
refactor: rename query params for consistency
Parkreiner a43e294
refactor: clean up input validation for page changes
Parkreiner db03f3e
refactor/fix: update hook to be aware of async data
Parkreiner 1b825f1
chore: add contravariance to dictionary
Parkreiner 9631a27
refactor: increase type-safety of usePaginatedQuery
Parkreiner 4ea0cba
docs: more comments
Parkreiner 3f63ec7
chore: move usePaginatedQuery file
Parkreiner 614e4ff
fix: add back cacheTime
Parkreiner b0c8d48
Merge branch 'main' into mes/pagination-2
Parkreiner e994532
chore: swap in usePaginatedQuery for users table
Parkreiner 9502044
chore: add goToFirstPage to usePaginatedQuery
Parkreiner 8dbbfd3
fix: make page redirects work properly
Parkreiner 88d0a5f
refactor: clean up clamp logic
Parkreiner 132f5b1
chore: swap in usePaginatedQuery for Audits table
Parkreiner 33bf4e8
refactor: move dependencies around
Parkreiner 218da68
fix: remove deprecated properties from hook
Parkreiner 54f01f1
refactor: clean up code more
Parkreiner ede2abc
docs: add todo comment
Parkreiner 9ecab16
chore: update testing fixtures
Parkreiner 0c81d1c
wip: commit current progress for tests
Parkreiner 3aa714c
fix: update useEffectEvent to sync via layout effects
Parkreiner 2893630
wip: commit more progress on tests
Parkreiner ef900d4
wip: stub out all expected test cases
Parkreiner 23dc583
wip: more test progress
Parkreiner 24361d1
wip: more test progress
Parkreiner 755f8c0
wip: commit more test progress
Parkreiner 8bff0d3
wip: AHHHHHHHH
Parkreiner 1fae773
chore: finish two more test cases
Parkreiner be82728
wip: add in all tests (still need to investigate prefetching
Parkreiner 848fa0f
refactor: clean up code slightly
Parkreiner c89e8e3
fix: remove math bugs when calculating pages
Parkreiner 3624a6d
fix: wrap up all testing and clean up cases
Parkreiner 13e2f30
docs: update comments for clarity
Parkreiner 8f83673
fix: update error-handling for invalid page handling
Parkreiner 5fb0643
fix: apply suggestions
Parkreiner File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
wip: commit current progress for tests
- Loading branch information
commit 0c81d1c965c15c48284ce40191df86638a7bc25d
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
import { renderHookWithAuth } from "testHelpers/renderHelpers"; | ||
import { | ||
type PaginatedData, | ||
type UsePaginatedQueryOptions, | ||
usePaginatedQuery, | ||
} from "./usePaginatedQuery"; | ||
import { waitFor } from "@testing-library/react"; | ||
|
||
beforeAll(() => { | ||
jest.useFakeTimers(); | ||
}); | ||
|
||
afterAll(() => { | ||
jest.useRealTimers(); | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
function render< | ||
TQueryFnData extends PaginatedData = PaginatedData, | ||
TQueryPayload = never, | ||
>( | ||
queryOptions: UsePaginatedQueryOptions<TQueryFnData, TQueryPayload>, | ||
route?: `/${string}`, | ||
) { | ||
type Props = { options: typeof queryOptions }; | ||
|
||
return renderHookWithAuth( | ||
({ options }: Props) => usePaginatedQuery(options), | ||
{ | ||
route, | ||
path: "/", | ||
initialProps: { | ||
options: queryOptions, | ||
}, | ||
}, | ||
); | ||
} | ||
|
||
describe(usePaginatedQuery.name, () => { | ||
describe("queryPayload method", () => { | ||
const mockQueryFn = jest.fn(() => { | ||
return { count: 0 }; | ||
}); | ||
|
||
it("Passes along an undefined payload if queryPayload is not used", async () => { | ||
const mockQueryKey = jest.fn(() => ["mockQuery"]); | ||
|
||
await render({ | ||
queryKey: mockQueryKey, | ||
queryFn: mockQueryFn, | ||
}); | ||
|
||
const payloadValueMock = expect.objectContaining({ | ||
payload: undefined, | ||
}); | ||
|
||
expect(mockQueryKey).toHaveBeenCalledWith(payloadValueMock); | ||
expect(mockQueryFn).toHaveBeenCalledWith(payloadValueMock); | ||
}); | ||
|
||
it("Passes along type-safe payload if queryPayload is provided", async () => { | ||
const mockQueryKey = jest.fn(({ payload }) => { | ||
return ["mockQuery", payload]; | ||
}); | ||
|
||
const testPayloadValues = [1, "Blah", { cool: true }]; | ||
for (const payload of testPayloadValues) { | ||
const { unmount } = await render({ | ||
queryPayload: () => payload, | ||
queryKey: mockQueryKey, | ||
queryFn: mockQueryFn, | ||
}); | ||
|
||
const matcher = expect.objectContaining({ payload }); | ||
expect(mockQueryKey).toHaveBeenCalledWith(matcher); | ||
expect(mockQueryFn).toHaveBeenCalledWith(matcher); | ||
unmount(); | ||
} | ||
}); | ||
}); | ||
|
||
describe("Querying for current page", () => { | ||
const mockQueryKey = jest.fn(() => ["mock"]); | ||
const mockQueryFn = jest.fn(() => Promise.resolve({ count: 50 })); | ||
|
||
it("Parses page number if it exists in URL params", async () => { | ||
const pageNumbers = [1, 2, 7, 39, 743]; | ||
|
||
for (const num of pageNumbers) { | ||
const { result, unmount } = await render( | ||
{ queryKey: mockQueryKey, queryFn: mockQueryFn }, | ||
`/?page=${num}`, | ||
); | ||
|
||
expect(result.current.currentPage).toBe(num); | ||
unmount(); | ||
} | ||
}); | ||
|
||
it("Defaults to page 1 if no page value can be parsed from params", async () => { | ||
const { result } = await render({ | ||
queryKey: mockQueryKey, | ||
queryFn: mockQueryFn, | ||
}); | ||
|
||
expect(result.current.currentPage).toBe(1); | ||
}); | ||
}); | ||
|
||
describe("Prefetching", () => { | ||
const noPrefetchTimeout = 1000; | ||
const mockQueryKey = jest.fn(({ pageNumber }) => ["query", pageNumber]); | ||
const mockQueryFn = jest.fn(({ pageNumber, limit }) => { | ||
return Promise.resolve({ | ||
data: new Array(limit).fill(pageNumber), | ||
count: 50, | ||
}); | ||
}); | ||
|
||
it("Prefetches the previous page if it exists", async () => { | ||
const startingPage = 2; | ||
await render( | ||
{ queryKey: mockQueryKey, queryFn: mockQueryFn }, | ||
`/?page=${startingPage}`, | ||
); | ||
|
||
const pageMatcher = expect.objectContaining({ | ||
pageNumber: 1, | ||
}); | ||
|
||
await waitFor(() => expect(mockQueryFn).toBeCalledWith(pageMatcher)); | ||
}); | ||
|
||
it("Prefetches the next page if it exists", async () => { | ||
const startingPage = 1; | ||
await render( | ||
{ queryKey: mockQueryKey, queryFn: mockQueryFn }, | ||
`/?page=${startingPage}`, | ||
); | ||
|
||
const pageMatcher = expect.objectContaining({ | ||
pageNumber: 2, | ||
}); | ||
|
||
await waitFor(() => expect(mockQueryFn).toBeCalledWith(pageMatcher)); | ||
}); | ||
|
||
it("Avoids prefetch for previous page if it doesn't exist", async () => { | ||
const startingPage = 1; | ||
await render( | ||
{ queryKey: mockQueryKey, queryFn: mockQueryFn }, | ||
`/?page=${startingPage}`, | ||
); | ||
|
||
const pageMatcher = expect.objectContaining({ | ||
pageNumber: 0, | ||
}); | ||
|
||
// Can't use waitFor to test this, because the expect call will | ||
// immediately succeed for the not case, even though queryFn needs to be | ||
// called async via React Query | ||
setTimeout(() => { | ||
expect(mockQueryFn).not.toBeCalledWith(pageMatcher); | ||
}, noPrefetchTimeout); | ||
|
||
jest.runAllTimers(); | ||
}); | ||
|
||
it("Avoids prefetch for next page if it doesn't exist", async () => { | ||
const startingPage = 2; | ||
await render( | ||
{ queryKey: mockQueryKey, queryFn: mockQueryFn }, | ||
`/?page=${startingPage}`, | ||
); | ||
|
||
const pageMatcher = expect.objectContaining({ | ||
pageNumber: 3, | ||
}); | ||
|
||
setTimeout(() => { | ||
expect(mockQueryFn).not.toBeCalledWith(pageMatcher); | ||
}, noPrefetchTimeout); | ||
|
||
jest.runAllTimers(); | ||
}); | ||
|
||
it("Reuses the same queryKey and queryFn methods for the current page and all prefetching", async () => { | ||
// | ||
}); | ||
}); | ||
|
||
describe("Invalid page safety nets/redirects", () => {}); | ||
|
||
describe("Returned properties", () => {}); | ||
|
||
describe("Passing outside value for URLSearchParams", () => {}); | ||
}); |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hooks don't really get "rendered" so to speak, so this name feels a bit weird. maybe it could be called
testPaginatedQuery
or something? not a huge deal tho