diff --git a/e2e/react-router/basic/src/main.tsx b/e2e/react-router/basic/src/main.tsx index 16eda46d5c..756686d634 100644 --- a/e2e/react-router/basic/src/main.tsx +++ b/e2e/react-router/basic/src/main.tsx @@ -7,6 +7,7 @@ import { createRootRoute, createRoute, createRouter, + useNavigate, } from '@tanstack/react-router' import { TanStackRouterDevtools } from '@tanstack/router-devtools' import { NotFoundError, fetchPost, fetchPosts } from './posts' @@ -59,6 +60,15 @@ function RootComponent() { > Layout {' '} + + Search Param Binding + {' '} I'm layout B! } +const searchParamBindingRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/search-param-binding', + component: SearchParamBindingComponent, + validateSearch: (input): { filter?: string } => { + return { + filter: typeof input.filter === 'string' ? input.filter : undefined, + } + }, +}) + +function SearchParamBindingComponent() { + const navigate = useNavigate() + const { filter } = searchParamBindingRoute.useSearch() + + return ( +
+ + navigate({ + to: '.', + search: { filter: e.target.value }, + }) + } + /> +
+ ) +} + const routeTree = rootRoute.addChildren([ postsRoute.addChildren([postRoute, postsIndexRoute]), layoutRoute.addChildren([ layout2Route.addChildren([layoutARoute, layoutBRoute]), ]), + searchParamBindingRoute, indexRoute, ]) diff --git a/e2e/react-router/basic/tests/app.spec.ts b/e2e/react-router/basic/tests/app.spec.ts index 5ccdafa513..6c6a069a8b 100644 --- a/e2e/react-router/basic/tests/app.spec.ts +++ b/e2e/react-router/basic/tests/app.spec.ts @@ -45,3 +45,33 @@ test('Navigating to a post page with viewTransition types', async ({ await page.getByRole('link', { name: 'sunt aut facere repe' }).click() await expect(page.getByRole('heading')).toContainText('sunt aut facere') }) + +test('#3162 - Binding an input to search params with stable cursor position', async ({ + page, +}) => { + await page + .getByRole('link', { name: 'Search Param Binding', exact: true }) + .click() + expect(page).toHaveURL(/.*\/search-param-binding/) + + await page.getByTestId('filter').fill('Hello World') + expect(page.getByTestId('filter')).toHaveValue('Hello World') + expect(page).toHaveURL(/.*\/search-param-binding\?filter=Hello%20World/) + + await page.getByTestId('filter').click() + for (let i = 0; i < 5; i++) { + await page.keyboard.press('ArrowLeft') + } + await page.keyboard.press('Space') + await page.keyboard.press('H') + await page.keyboard.press('A') + await page.keyboard.press('P') + await page.keyboard.press('P') + await page.keyboard.press('Y') + await page.getByTestId('filter').blur() + + expect(page.getByTestId('filter')).toHaveValue('Hello Happy World') + expect(page).toHaveURL( + /.*\/search-param-binding\?filter=Hello%20Happy%20World/, + ) +})