Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions e2e/react-router/basic-file-based/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { Route as StructuralSharingEnabledRouteImport } from './routes/structura
import { Route as SearchParamsDefaultRouteImport } from './routes/search-params/default'
import { Route as RedirectTargetRouteImport } from './routes/redirect/$target'
import { Route as PostsPostIdRouteImport } from './routes/posts.$postId'
import { Route as PipeReferenceRouteImport } from './routes/pipe.$reference'
import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2'
import { Route as groupLazyinsideRouteImport } from './routes/(group)/lazyinside'
import { Route as groupInsideRouteImport } from './routes/(group)/inside'
Expand Down Expand Up @@ -239,6 +240,11 @@ const PostsPostIdRoute = PostsPostIdRouteImport.update({
path: '/$postId',
getParentRoute: () => PostsRoute,
} as any)
const PipeReferenceRoute = PipeReferenceRouteImport.update({
id: '/pipe/$reference',
path: '/pipe/$reference',
getParentRoute: () => rootRouteImport,
} as any)
const LayoutLayout2Route = LayoutLayout2RouteImport.update({
id: '/_layout-2',
getParentRoute: () => LayoutRoute,
Expand Down Expand Up @@ -726,6 +732,7 @@ export interface FileRoutesByFullPath {
'/onlyrouteinside': typeof anotherGroupOnlyrouteinsideRoute
'/inside': typeof groupInsideRoute
'/lazyinside': typeof groupLazyinsideRoute
'/pipe/$reference': typeof PipeReferenceRoute
'/posts/$postId': typeof PostsPostIdRoute
'/redirect/$target': typeof RedirectTargetRouteWithChildren
'/search-params/default': typeof SearchParamsDefaultRoute
Expand Down Expand Up @@ -830,6 +837,7 @@ export interface FileRoutesByTo {
'/onlyrouteinside': typeof anotherGroupOnlyrouteinsideRoute
'/inside': typeof groupInsideRoute
'/lazyinside': typeof groupLazyinsideRoute
'/pipe/$reference': typeof PipeReferenceRoute
'/posts/$postId': typeof PostsPostIdRoute
'/search-params/default': typeof SearchParamsDefaultRoute
'/structural-sharing/$enabled': typeof StructuralSharingEnabledRoute
Expand Down Expand Up @@ -932,6 +940,7 @@ export interface FileRoutesById {
'/(group)/inside': typeof groupInsideRoute
'/(group)/lazyinside': typeof groupLazyinsideRoute
'/_layout/_layout-2': typeof LayoutLayout2RouteWithChildren
'/pipe/$reference': typeof PipeReferenceRoute
'/posts/$postId': typeof PostsPostIdRoute
'/redirect/$target': typeof RedirectTargetRouteWithChildren
'/search-params/default': typeof SearchParamsDefaultRoute
Expand Down Expand Up @@ -1040,6 +1049,7 @@ export interface FileRouteTypes {
| '/onlyrouteinside'
| '/inside'
| '/lazyinside'
| '/pipe/$reference'
| '/posts/$postId'
| '/redirect/$target'
| '/search-params/default'
Expand Down Expand Up @@ -1144,6 +1154,7 @@ export interface FileRouteTypes {
| '/onlyrouteinside'
| '/inside'
| '/lazyinside'
| '/pipe/$reference'
| '/posts/$postId'
| '/search-params/default'
| '/structural-sharing/$enabled'
Expand Down Expand Up @@ -1245,6 +1256,7 @@ export interface FileRouteTypes {
| '/(group)/inside'
| '/(group)/lazyinside'
| '/_layout/_layout-2'
| '/pipe/$reference'
| '/posts/$postId'
| '/redirect/$target'
| '/search-params/default'
Expand Down Expand Up @@ -1349,6 +1361,7 @@ export interface RootRouteChildren {
groupLayoutRoute: typeof groupLayoutRouteWithChildren
groupInsideRoute: typeof groupInsideRoute
groupLazyinsideRoute: typeof groupLazyinsideRoute
PipeReferenceRoute: typeof PipeReferenceRoute
RedirectTargetRoute: typeof RedirectTargetRouteWithChildren
StructuralSharingEnabledRoute: typeof StructuralSharingEnabledRoute
ParamsPsIndexRoute: typeof ParamsPsIndexRoute
Expand Down Expand Up @@ -1542,6 +1555,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof PostsPostIdRouteImport
parentRoute: typeof PostsRoute
}
'/pipe/$reference': {
id: '/pipe/$reference'
path: '/pipe/$reference'
fullPath: '/pipe/$reference'
preLoaderRoute: typeof PipeReferenceRouteImport
parentRoute: typeof rootRouteImport
}
'/_layout/_layout-2': {
id: '/_layout/_layout-2'
path: ''
Expand Down Expand Up @@ -2605,6 +2625,7 @@ const rootRouteChildren: RootRouteChildren = {
groupLayoutRoute: groupLayoutRouteWithChildren,
groupInsideRoute: groupInsideRoute,
groupLazyinsideRoute: groupLazyinsideRoute,
PipeReferenceRoute: PipeReferenceRoute,
RedirectTargetRoute: RedirectTargetRouteWithChildren,
StructuralSharingEnabledRoute: StructuralSharingEnabledRoute,
ParamsPsIndexRoute: ParamsPsIndexRoute,
Expand Down
9 changes: 9 additions & 0 deletions e2e/react-router/basic-file-based/src/routes/__root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,15 @@ function RootComponent() {
>
Masks
</Link>{' '}
<Link
to="/pipe/$reference"
params={{ reference: 'hello|world' }}
activeProps={{
className: 'font-bold',
}}
>
Dynamic Pipe Character
</Link>{' '}
<Link
to="/pathless-layout"
data-testid="link-to-pathless-layout"
Expand Down
10 changes: 10 additions & 0 deletions e2e/react-router/basic-file-based/src/routes/pipe.$reference.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/pipe/$reference')({
component: RouteComponent,
})

function RouteComponent() {
const params = Route.useParams()
return <h4>Hello {params.reference}!</h4>
}
13 changes: 13 additions & 0 deletions e2e/react-router/basic-file-based/tests/app.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -394,3 +394,16 @@ test.describe('Pathless layout routes', () => {
await expect(page.locator('body')).toContainText('Not Found')
})
})

test.describe('Special characters in route paths', () => {
test('should render route with pipe character in path', async ({
page,
baseURL,
}) => {
await page.goto('/pipe/hello|world')

await expect(page.locator('body')).toContainText('Hello hello|world!')

expect(page.url()).toBe(`${baseURL}/pipe/hello%7Cworld`)
})
})
21 changes: 21 additions & 0 deletions e2e/react-start/basic/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import { Route as RawStreamSsrMixedRouteImport } from './routes/raw-stream/ssr-m
import { Route as RawStreamSsrBinaryHintRouteImport } from './routes/raw-stream/ssr-binary-hint'
import { Route as RawStreamClientCallRouteImport } from './routes/raw-stream/client-call'
import { Route as PostsPostIdRouteImport } from './routes/posts.$postId'
import { Route as PipeReferenceRouteImport } from './routes/pipe.$reference'
import { Route as NotFoundViaLoaderRouteImport } from './routes/not-found/via-loader'
import { Route as NotFoundViaBeforeLoadRouteImport } from './routes/not-found/via-beforeLoad'
import { Route as MultiCookieRedirectTargetRouteImport } from './routes/multi-cookie-redirect/target'
Expand Down Expand Up @@ -228,6 +229,11 @@ const PostsPostIdRoute = PostsPostIdRouteImport.update({
path: '/$postId',
getParentRoute: () => PostsRoute,
} as any)
const PipeReferenceRoute = PipeReferenceRouteImport.update({
id: '/pipe/$reference',
path: '/pipe/$reference',
getParentRoute: () => rootRouteImport,
} as any)
const NotFoundViaLoaderRoute = NotFoundViaLoaderRouteImport.update({
id: '/via-loader',
path: '/via-loader',
Expand Down Expand Up @@ -343,6 +349,7 @@ export interface FileRoutesByFullPath {
'/multi-cookie-redirect/target': typeof MultiCookieRedirectTargetRoute
'/not-found/via-beforeLoad': typeof NotFoundViaBeforeLoadRoute
'/not-found/via-loader': typeof NotFoundViaLoaderRoute
'/pipe/$reference': typeof PipeReferenceRoute
'/posts/$postId': typeof PostsPostIdRoute
'/raw-stream/client-call': typeof RawStreamClientCallRoute
'/raw-stream/ssr-binary-hint': typeof RawStreamSsrBinaryHintRoute
Expand Down Expand Up @@ -389,6 +396,7 @@ export interface FileRoutesByTo {
'/multi-cookie-redirect/target': typeof MultiCookieRedirectTargetRoute
'/not-found/via-beforeLoad': typeof NotFoundViaBeforeLoadRoute
'/not-found/via-loader': typeof NotFoundViaLoaderRoute
'/pipe/$reference': typeof PipeReferenceRoute
'/posts/$postId': typeof PostsPostIdRoute
'/raw-stream/client-call': typeof RawStreamClientCallRoute
'/raw-stream/ssr-binary-hint': typeof RawStreamSsrBinaryHintRoute
Expand Down Expand Up @@ -441,6 +449,7 @@ export interface FileRoutesById {
'/multi-cookie-redirect/target': typeof MultiCookieRedirectTargetRoute
'/not-found/via-beforeLoad': typeof NotFoundViaBeforeLoadRoute
'/not-found/via-loader': typeof NotFoundViaLoaderRoute
'/pipe/$reference': typeof PipeReferenceRoute
'/posts/$postId': typeof PostsPostIdRoute
'/raw-stream/client-call': typeof RawStreamClientCallRoute
'/raw-stream/ssr-binary-hint': typeof RawStreamSsrBinaryHintRoute
Expand Down Expand Up @@ -494,6 +503,7 @@ export interface FileRouteTypes {
| '/multi-cookie-redirect/target'
| '/not-found/via-beforeLoad'
| '/not-found/via-loader'
| '/pipe/$reference'
| '/posts/$postId'
| '/raw-stream/client-call'
| '/raw-stream/ssr-binary-hint'
Expand Down Expand Up @@ -540,6 +550,7 @@ export interface FileRouteTypes {
| '/multi-cookie-redirect/target'
| '/not-found/via-beforeLoad'
| '/not-found/via-loader'
| '/pipe/$reference'
| '/posts/$postId'
| '/raw-stream/client-call'
| '/raw-stream/ssr-binary-hint'
Expand Down Expand Up @@ -591,6 +602,7 @@ export interface FileRouteTypes {
| '/multi-cookie-redirect/target'
| '/not-found/via-beforeLoad'
| '/not-found/via-loader'
| '/pipe/$reference'
| '/posts/$postId'
| '/raw-stream/client-call'
| '/raw-stream/ssr-binary-hint'
Expand Down Expand Up @@ -642,6 +654,7 @@ export interface RootRouteChildren {
Char45824Char54620Char48124Char44397Route: typeof Char45824Char54620Char48124Char44397Route
ApiUsersRoute: typeof ApiUsersRouteWithChildren
MultiCookieRedirectTargetRoute: typeof MultiCookieRedirectTargetRoute
PipeReferenceRoute: typeof PipeReferenceRoute
RedirectTargetRoute: typeof RedirectTargetRouteWithChildren
MultiCookieRedirectIndexRoute: typeof MultiCookieRedirectIndexRoute
RedirectIndexRoute: typeof RedirectIndexRoute
Expand Down Expand Up @@ -882,6 +895,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof PostsPostIdRouteImport
parentRoute: typeof PostsRoute
}
'/pipe/$reference': {
id: '/pipe/$reference'
path: '/pipe/$reference'
fullPath: '/pipe/$reference'
preLoaderRoute: typeof PipeReferenceRouteImport
parentRoute: typeof rootRouteImport
}
'/not-found/via-loader': {
id: '/not-found/via-loader'
path: '/via-loader'
Expand Down Expand Up @@ -1184,6 +1204,7 @@ const rootRouteChildren: RootRouteChildren = {
Char45824Char54620Char48124Char44397Route,
ApiUsersRoute: ApiUsersRouteWithChildren,
MultiCookieRedirectTargetRoute: MultiCookieRedirectTargetRoute,
PipeReferenceRoute: PipeReferenceRoute,
RedirectTargetRoute: RedirectTargetRouteWithChildren,
MultiCookieRedirectIndexRoute: MultiCookieRedirectIndexRoute,
RedirectIndexRoute: RedirectIndexRoute,
Expand Down
9 changes: 9 additions & 0 deletions e2e/react-start/basic/src/routes/__root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,15 @@ function RootDocument({ children }: { children: React.ReactNode }) {
>
Raw Stream
</Link>{' '}
<Link
to="/pipe/$reference"
params={{ reference: 'hello|world' }}
activeProps={{
className: 'font-bold',
}}
>
Dynamic Pipe Character
</Link>{' '}
<Link
// @ts-expect-error
to="/this-route-does-not-exist"
Expand Down
10 changes: 10 additions & 0 deletions e2e/react-start/basic/src/routes/pipe.$reference.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/pipe/$reference')({
component: RouteComponent,
})

function RouteComponent() {
const params = Route.useParams()
return <h4>Hello {params.reference}!</h4>
}
24 changes: 24 additions & 0 deletions e2e/react-start/basic/tests/pipe-character-in-path.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { expect } from '@playwright/test'

import { test } from '@tanstack/router-e2e-utils'

test('encodes pipe character in href for param link', async ({ page }) => {
await page.goto('/pipe/hello|world')

await expect(page.locator('body')).toContainText('Hello hello|world!')
})

test('direct navigation keeps encoded url after reload', async ({
page,
baseURL,
}) => {
await page.goto('/pipe/hello|world')

await expect(page.locator('body')).toContainText('Hello hello|world!')
expect(page.url()).toBe(`${baseURL}/pipe/hello%7Cworld`)

await page.reload()

await expect(page.locator('body')).toContainText('Hello hello|world!')
expect(page.url()).toBe(`${baseURL}/pipe/hello%7Cworld`)
})
2 changes: 1 addition & 1 deletion packages/router-core/src/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export function joinPaths(paths: Array<string | undefined>) {
/** Remove repeated slashes from a path string. */
export function cleanPath(path: string) {
// remove double slashes
return path.replace(/\/{2,}/g, '/')
return path.replace(/\/{2,}/g, '/').replace(/\|/g, '%7C')
}

/** Trim leading slashes (except preserving root '/'). */
Expand Down
Loading