-
Notifications
You must be signed in to change notification settings - Fork 974
fix: fix workspaces pagination #19448
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
Conversation
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.
the actual guts of the change are straight forward and good, but the test seems a bit wonky. can we pls clean it up a little more before merging?
const secondCallArgs = getWorkspacesSpy.mock.calls[1][0]; | ||
expect(firstCallArgs.offset).toBe(0); | ||
expect(secondCallArgs.offset).toBe(25); | ||
expect(firstCallArgs.offset).not.toBe(secondCallArgs.offset); |
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.
redundant, we just asserted that it’s 0
|
||
// Verify the key difference: the offset changed between calls | ||
const firstCallArgs = getWorkspacesSpy.mock.calls[0][0]; | ||
const secondCallArgs = getWorkspacesSpy.mock.calls[1][0]; |
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.
we just asserted on this with toHaveBeenNthCalledWith
const nextPageButton = screen.getByRole("button", { name: /next page/i }); | ||
await user.click(nextPageButton); | ||
|
||
// Wait for second page to load |
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.
this comment doesn’t add anything imo
const user = userEvent.setup(); | ||
renderWithAuth(<WorkspacesPage />); | ||
|
||
// Wait for first page to load |
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.
same here. waitFor
is already the name of the function being called.
getWorkspacesSpy | ||
.mockResolvedValueOnce({ | ||
workspaces: workspacesPage1, | ||
count: totalWorkspaces, | ||
}) | ||
.mockResolvedValueOnce({ | ||
workspaces: workspacesPage2, | ||
count: totalWorkspaces, | ||
}); |
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.
I feel like this setup is a little fragile. I've never stacked multiple calls to mockResolvedValueOnce
onto the same spy before, but if I'm reading it right, it means that, no matter what we actually call the function with, we'll always get page 1 for the first call, and page 2 for the second call. But then:
- Any future calls immediately go back to the actual non-mocked version
- We never check the inputs for the functions to decide what to return out. If we accidentally pass page 3 in, we might get page 1 or we might get page 2
- If we try to get page 1 multiple times, we can't
I don't know all the details, but I know that MSW has tools to mock out the API response. Maybe that's not needed and we could keep the Jest Spys, but I think I'd rather make it so that we have a mock that stays locked in for the duration of the test, and then checks the page input:
- A page value of 1 always gives back page 1 content
- A page value of 2 always gives back page 2 content
- Any other page values just give back an empty array
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.
I know you have the checks for the exact inputs a little bit below, but if the React testing tools ever add double-rendering to help check for correctness (like what you get with StrictMode
and dev mode), that would immediately cause this test to break
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.
Not sure if I got it. Let me share what I understood.
The issue with this test is it is using stacked mockResolvedValueOnce
to mock the responses for the first and second calls so, if react renders twice in the same page, the test would break. Is that right?
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.
Yeah, if React ever double-mounts the same hook for the data fetching, this will happen if we start at page 1:
- The page component gets mounted, and we trigger the data fetch, using a page value of 1 as input. The function is mocked, so we always get the page 1 data back no matter what
- The render completes
- React unmounts and re-mounts the component from scratch, which potentially causes a second data fetch, still using a page value of 1 as input
- But because we've already popped off the first mock, calling the function with a page value of 1 actually gets us page 2's data, because the mock doesn't actually check what number gets passed in
- We get back page 2's data, and render that out. React considers the render "stabilized"
- We go back to the testing/assertion logic, and the tests fail because the page 1 content is no longer on screen
- If we were to keep going, and try switching the page value to 2 by clicking the UI, we have no more mocks left, and the call goes to the actual API implementation
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.
Updated the spyOn
to use the mockImplementation
, wdyt?
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.
thanks bruno! one last suggestion, but this test is looking much better.
expect(getWorkspacesSpy).toHaveBeenNthCalledWith( | ||
1, |
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.
expect(getWorkspacesSpy).toHaveBeenNthCalledWith( | |
1, | |
expect(getWorkspacesSpy).toHaveBeenLastCalledWith( |
one last thing, I think toHaveBeenLastCalledWith
would be better here. to michael's point: how many times this function actually gets called could be considered an implementation detail of react. but we do know we want it to have been called, and that at least the most recent call should match this assertion.
https://jestjs.io/docs/expect#tohavebeenlastcalledwitharg1-arg2-
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.
Makes sense! 👍
expect(getWorkspacesSpy).toHaveBeenNthCalledWith( | ||
2, |
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.
expect(getWorkspacesSpy).toHaveBeenNthCalledWith( | |
2, | |
expect(getWorkspacesSpy).toHaveBeenLastCalledWith( |
same thought here
Fixes #18707
Before:
Screen.Recording.2025-08-20.at.10.44.36.mov
After:
Screen.Recording.2025-08-20.at.10.44.52.mov