-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
test(router, start): query navigation transitions #5802
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,37 +1,95 @@ | ||
| import { expect, test } from '@playwright/test' | ||
|
|
||
| test('react-query transitions keep previous data during navigation', async ({ | ||
| test('transitions/count/query should keep old values visible during navigation', async ({ | ||
| page, | ||
| }) => { | ||
| await page.goto('/transition/count/query') | ||
|
|
||
| await expect(page.getByTestId('n-value')).toContainText('n: 1') | ||
| await expect(page.getByTestId('double-value')).toContainText('double: 2') | ||
|
|
||
| const bodySnapshots: Array<string> = [] | ||
| const bodyTexts: Array<string> = [] | ||
|
|
||
| const interval = setInterval(async () => { | ||
| const pollInterval = setInterval(async () => { | ||
| const text = await page | ||
| .locator('body') | ||
| .textContent() | ||
| .catch(() => '') | ||
| if (text) bodySnapshots.push(text) | ||
| if (text) bodyTexts.push(text) | ||
| }, 50) | ||
|
|
||
| await page.getByTestId('increase-button').click() | ||
| // 1 click | ||
|
|
||
| page.getByTestId('increase-button').click() | ||
|
|
||
| await expect(page.getByTestId('n-value')).toContainText('n: 1', { | ||
| timeout: 2_000, | ||
| }) | ||
| await expect(page.getByTestId('double-value')).toContainText('double: 2', { | ||
| timeout: 2_000, | ||
| }) | ||
|
|
||
| await page.waitForTimeout(200) | ||
|
|
||
| clearInterval(interval) | ||
| await expect(page.getByTestId('n-value')).toContainText('n: 2', { | ||
| timeout: 2000, | ||
| }) | ||
| await expect(page.getByTestId('double-value')).toContainText('double: 4', { | ||
| timeout: 2000, | ||
| }) | ||
|
|
||
| // 2 clicks | ||
|
|
||
| page.getByTestId('increase-button').click() | ||
| page.getByTestId('increase-button').click() | ||
|
|
||
| await expect(page.getByTestId('n-value')).toContainText('n: 2', { | ||
| timeout: 2_000, | ||
| timeout: 2000, | ||
| }) | ||
| await expect(page.getByTestId('double-value')).toContainText('double: 4', { | ||
| timeout: 2_000, | ||
| timeout: 2000, | ||
| }) | ||
|
|
||
| await page.waitForTimeout(200) | ||
|
|
||
| await expect(page.getByTestId('n-value')).toContainText('n: 4', { | ||
| timeout: 2000, | ||
| }) | ||
| await expect(page.getByTestId('double-value')).toContainText('double: 8', { | ||
| timeout: 2000, | ||
| }) | ||
|
|
||
| // 3 clicks | ||
|
|
||
| page.getByTestId('increase-button').click() | ||
| page.getByTestId('increase-button').click() | ||
| page.getByTestId('increase-button').click() | ||
|
|
||
| await expect(page.getByTestId('n-value')).toContainText('n: 4', { | ||
| timeout: 2000, | ||
| }) | ||
| await expect(page.getByTestId('double-value')).toContainText('double: 8', { | ||
| timeout: 2000, | ||
| }) | ||
|
|
||
| await page.waitForTimeout(200) | ||
|
|
||
| await expect(page.getByTestId('n-value')).toContainText('n: 7', { | ||
| timeout: 2000, | ||
| }) | ||
| await expect(page.getByTestId('double-value')).toContainText('double: 14', { | ||
| timeout: 2000, | ||
| }) | ||
|
|
||
| clearInterval(pollInterval) | ||
|
|
||
| const sawLoading = bodySnapshots.some((text) => text.includes('Loading...')) | ||
| // With proper transitions, old values should remain visible until new ones arrive | ||
| const hasLoadingText = bodyTexts.some((text) => text.includes('Loading...')) | ||
|
|
||
| expect(sawLoading).toBeFalsy() | ||
| if (hasLoadingText) { | ||
| throw new Error( | ||
| 'FAILED: "Loading..." appeared during navigation. ' + | ||
| 'Solid Router should use transitions to keep old values visible.', | ||
| ) | ||
| } | ||
| }) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| import { Link, createFileRoute } from '@tanstack/react-router' | ||
| import { Suspense } from 'react' | ||
| import { queryOptions, useQuery } from '@tanstack/react-query' | ||
| import { z } from 'zod' | ||
|
|
||
| const searchSchema = z.object({ | ||
| n: z.number().default(1), | ||
| }) | ||
|
|
||
| const doubleQueryOptions = (n: number) => | ||
| queryOptions({ | ||
| queryKey: ['transition-double', n], | ||
| queryFn: async () => { | ||
| await new Promise((resolve) => setTimeout(resolve, 1000)) | ||
| return { n, double: n * 2 } | ||
| }, | ||
| placeholderData: (oldData) => oldData, | ||
| }) | ||
|
|
||
| export const Route = createFileRoute('/transition/count/query')({ | ||
| validateSearch: searchSchema, | ||
| loader: ({ context: { queryClient }, location }) => { | ||
| const { n } = searchSchema.parse(location.search) | ||
| return queryClient.ensureQueryData(doubleQueryOptions(n)) | ||
| }, | ||
| component: TransitionPage, | ||
| }) | ||
|
|
||
| function TransitionPage() { | ||
| const search = Route.useSearch() | ||
|
|
||
| const doubleQuery = useQuery(doubleQueryOptions(search.n)) | ||
|
|
||
| return ( | ||
| <Suspense fallback="Loading..."> | ||
| <div className="p-2"> | ||
| <Link | ||
| data-testid="increase-button" | ||
| className="border bg-gray-50 px-3 py-1" | ||
| from="/transition/count/query" | ||
| search={(s) => ({ n: s.n + 1 })} | ||
| > | ||
| Increase | ||
| </Link> | ||
|
|
||
| <div className="mt-2"> | ||
| <div data-testid="n-value">n: {doubleQuery.data?.n}</div> | ||
| <div data-testid="double-value"> | ||
| double: {doubleQuery.data?.double} | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </Suspense> | ||
| ) | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,95 @@ | ||||||||||||||||||||||||||
| import { expect, test } from '@playwright/test' | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| test('transitions/count/query should keep old values visible during navigation', async ({ | ||||||||||||||||||||||||||
| page, | ||||||||||||||||||||||||||
| }) => { | ||||||||||||||||||||||||||
| await page.goto('/transition/count/query') | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| await expect(page.getByTestId('n-value')).toContainText('n: 1') | ||||||||||||||||||||||||||
| await expect(page.getByTestId('double-value')).toContainText('double: 2') | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const bodyTexts: Array<string> = [] | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const pollInterval = setInterval(async () => { | ||||||||||||||||||||||||||
| const text = await page | ||||||||||||||||||||||||||
| .locator('body') | ||||||||||||||||||||||||||
| .textContent() | ||||||||||||||||||||||||||
| .catch(() => '') | ||||||||||||||||||||||||||
| if (text) bodyTexts.push(text) | ||||||||||||||||||||||||||
| }, 50) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // 1 click | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| page.getByTestId('increase-button').click() | ||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Await the click action to avoid race conditions.
Apply this diff: - page.getByTestId('increase-button').click()
+ await page.getByTestId('increase-button').click()📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| await expect(page.getByTestId('n-value')).toContainText('n: 1', { | ||||||||||||||||||||||||||
| timeout: 2_000, | ||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||
| await expect(page.getByTestId('double-value')).toContainText('double: 2', { | ||||||||||||||||||||||||||
| timeout: 2_000, | ||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| await page.waitForTimeout(200) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| await expect(page.getByTestId('n-value')).toContainText('n: 2', { | ||||||||||||||||||||||||||
| timeout: 2000, | ||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||
| await expect(page.getByTestId('double-value')).toContainText('double: 4', { | ||||||||||||||||||||||||||
| timeout: 2000, | ||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // 2 clicks | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // page.getByTestId('increase-button').click() | ||||||||||||||||||||||||||
| // page.getByTestId('increase-button').click() | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // await expect(page.getByTestId('n-value')).toContainText('n: 2', { | ||||||||||||||||||||||||||
| // timeout: 2000, | ||||||||||||||||||||||||||
| // }) | ||||||||||||||||||||||||||
| // await expect(page.getByTestId('double-value')).toContainText('double: 4', { | ||||||||||||||||||||||||||
| // timeout: 2000, | ||||||||||||||||||||||||||
| // }) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // await page.waitForTimeout(200) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // await expect(page.getByTestId('n-value')).toContainText('n: 4', { | ||||||||||||||||||||||||||
| // timeout: 2000, | ||||||||||||||||||||||||||
| // }) | ||||||||||||||||||||||||||
| // await expect(page.getByTestId('double-value')).toContainText('double: 8', { | ||||||||||||||||||||||||||
| // timeout: 2000, | ||||||||||||||||||||||||||
| // }) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // // 3 clicks | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // page.getByTestId('increase-button').click() | ||||||||||||||||||||||||||
| // page.getByTestId('increase-button').click() | ||||||||||||||||||||||||||
| // page.getByTestId('increase-button').click() | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // await expect(page.getByTestId('n-value')).toContainText('n: 4', { | ||||||||||||||||||||||||||
| // timeout: 2000, | ||||||||||||||||||||||||||
| // }) | ||||||||||||||||||||||||||
| // await expect(page.getByTestId('double-value')).toContainText('double: 8', { | ||||||||||||||||||||||||||
| // timeout: 2000, | ||||||||||||||||||||||||||
| // }) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // await page.waitForTimeout(200) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // await expect(page.getByTestId('n-value')).toContainText('n: 7', { | ||||||||||||||||||||||||||
| // timeout: 2000, | ||||||||||||||||||||||||||
| // }) | ||||||||||||||||||||||||||
| // await expect(page.getByTestId('double-value')).toContainText('double: 14', { | ||||||||||||||||||||||||||
| // timeout: 2000, | ||||||||||||||||||||||||||
| // }) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| clearInterval(pollInterval) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // With proper transitions, old values should remain visible until new ones arrive | ||||||||||||||||||||||||||
| const hasLoadingText = bodyTexts.some((text) => text.includes('Loading...')) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| if (hasLoadingText) { | ||||||||||||||||||||||||||
| throw new Error( | ||||||||||||||||||||||||||
| 'FAILED: "Loading..." appeared during navigation. ' + | ||||||||||||||||||||||||||
| 'Solid Router should use transitions to keep old values visible.', | ||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
Comment on lines
+89
to
+94
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix incorrect framework name in error message. The error message references "Solid Router" but this test is for React Router/React Start. This is a copy-paste error that would confuse developers if the test fails. Apply this diff: if (hasLoadingText) {
throw new Error(
'FAILED: "Loading..." appeared during navigation. ' +
- 'Solid Router should use transitions to keep old values visible.',
+ 'React Router should use transitions to keep old values visible.',
)
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||
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.
Critical: Error message references wrong framework.
The error message mentions "Solid Router" but this is a React Router test. This appears to be a copy-paste error from the solid-router equivalent test.
Apply this diff:
if (hasLoadingText) { throw new Error( 'FAILED: "Loading..." appeared during navigation. ' + - 'Solid Router should use transitions to keep old values visible.', + 'React Router should use transitions to keep old values visible.', ) }📝 Committable suggestion
🤖 Prompt for AI Agents