Skip to content

Commit f768adf

Browse files
committed
parse non-nested path params correctly
1 parent fb4c6a1 commit f768adf

File tree

6 files changed

+79
-2
lines changed

6 files changed

+79
-2
lines changed

e2e/react-router/basic-file-based/src/routes/params-ps/non-nested/route.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ function RouteComponent() {
1818
>
1919
/params-ps/non-nested/$foo/$bar
2020
</Link>
21+
<Link
22+
from={Route.fullPath}
23+
data-testid="l-to-non-nested-foo2-bar2"
24+
to="./$foo/$bar"
25+
params={{ foo: 'foo2', bar: 'bar2' }}
26+
>
27+
/params-ps/non-nested/$foo/$bar
28+
</Link>
2129
</li>
2230
</ul>
2331
<Outlet />

e2e/react-router/basic-file-based/tests/params.spec.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ test.describe('params operations + non-nested routes', () => {
6969
'href',
7070
'/params-ps/non-nested/foo/bar',
7171
)
72+
7273
await fooBarLink.click()
7374
await page.waitForLoadState('networkidle')
7475
const pagePathname = new URL(page.url()).pathname
@@ -83,6 +84,27 @@ test.describe('params operations + non-nested routes', () => {
8384
const paramsText = await paramsValue.innerText()
8485
const paramsObj = JSON.parse(paramsText)
8586
expect(paramsObj).toEqual({ foo: 'foo', bar: 'bar' })
87+
88+
const foo2Bar2Link = page.getByTestId('l-to-non-nested-foo2-bar2')
89+
90+
await expect(foo2Bar2Link).toHaveAttribute(
91+
'href',
92+
'/params-ps/non-nested/foo2/bar2',
93+
)
94+
await foo2Bar2Link.click()
95+
const pagePathname2 = new URL(page.url()).pathname
96+
await page.waitForLoadState('networkidle')
97+
expect(pagePathname2).toBe('/params-ps/non-nested/foo2/bar2')
98+
99+
const foo2ParamsValue = page.getByTestId('foo-params-value')
100+
const foo2ParamsText = await foo2ParamsValue.innerText()
101+
const foo2ParamsObj = JSON.parse(foo2ParamsText)
102+
expect(foo2ParamsObj).toEqual({ foo: 'foo2' })
103+
104+
const params2Value = page.getByTestId('foo-bar-params-value')
105+
const params2Text = await params2Value.innerText()
106+
const params2Obj = JSON.parse(params2Text)
107+
expect(params2Obj).toEqual({ foo: 'foo2', bar: 'bar2' })
86108
})
87109
})
88110

e2e/solid-router/basic-file-based/src/routes/params-ps/non-nested/route.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,15 @@ function RouteComponent() {
1616
to="./$foo/$bar"
1717
params={{ foo: 'foo', bar: 'bar' }}
1818
>
19-
/params-ps/non-nested/$foo/$bar
19+
/params-ps/non-nested/foo/bar
20+
</Link>
21+
<Link
22+
from={Route.fullPath}
23+
data-testid="l-to-non-nested-foo2-bar2"
24+
to="./$foo/$bar"
25+
params={{ foo: 'foo2', bar: 'bar2' }}
26+
>
27+
/params-ps/non-nested/foo2/bar2
2028
</Link>
2129
</li>
2230
</ul>

e2e/solid-router/basic-file-based/tests/params.spec.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,5 +158,26 @@ test.describe('params operations + non-nested routes', () => {
158158
const paramsText = await paramsValue.innerText()
159159
const paramsObj = JSON.parse(paramsText)
160160
expect(paramsObj).toEqual({ foo: 'foo', bar: 'bar' })
161+
162+
const foo2Bar2Link = page.getByTestId('l-to-non-nested-foo2-bar2')
163+
164+
await expect(foo2Bar2Link).toHaveAttribute(
165+
'href',
166+
'/params-ps/non-nested/foo2/bar2',
167+
)
168+
await foo2Bar2Link.click()
169+
const pagePathname2 = new URL(page.url()).pathname
170+
await page.waitForLoadState('networkidle')
171+
expect(pagePathname2).toBe('/params-ps/non-nested/foo2/bar2')
172+
173+
const foo2ParamsValue = page.getByTestId('foo-params-value')
174+
const foo2ParamsText = await foo2ParamsValue.innerText()
175+
const foo2ParamsObj = JSON.parse(foo2ParamsText)
176+
expect(foo2ParamsObj).toEqual({ foo: 'foo2' })
177+
178+
const params2Value = page.getByTestId('foo-bar-params-value')
179+
const params2Text = await params2Value.innerText()
180+
const params2Obj = JSON.parse(params2Text)
181+
expect(params2Obj).toEqual({ foo: 'foo2', bar: 'bar2' })
161182
})
162183
})

packages/router-core/src/path.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,8 @@ export const parsePathname = (
218218
return parsed
219219
}
220220

221-
const PARAM_RE = /^\$.{1,}$/ // $paramName
221+
const PARAM_RE = /^\$.{1,}(?<!_)$/ // $paramName
222+
const PARAM_NON_NESTED_RE = /^\$.{1,}_$/ // $paramName_
222223
const PARAM_W_CURLY_BRACES_RE = /^(.*?)\{(\$[a-zA-Z_$][a-zA-Z0-9_$]*)\}(.*)$/ // prefix{$paramName}suffix
223224
const OPTIONAL_PARAM_W_CURLY_BRACES_RE =
224225
/^(.*?)\{-(\$[a-zA-Z_$][a-zA-Z0-9_$]*)\}(.*)$/ // prefix{-$paramName}suffix
@@ -319,6 +320,17 @@ function baseParsePathname(pathname: string): ReadonlyArray<Segment> {
319320
}
320321
}
321322

323+
// Check for non-nested bare parameter format: $paramName_ (without curly braces)
324+
if (PARAM_NON_NESTED_RE.test(part)) {
325+
const paramName = part.slice(1, -1)
326+
return {
327+
type: SEGMENT_TYPE_PARAM,
328+
value: '$' + paramName,
329+
prefixSegment: undefined,
330+
suffixSegment: undefined,
331+
}
332+
}
333+
322334
// Check for bare wildcard: $ (without curly braces)
323335
if (WILDCARD_RE.test(part)) {
324336
return {

packages/router-core/tests/path.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,12 @@ describe('interpolatePath', () => {
432432
params: { _splat: 'sean/cassiere' },
433433
result: '/users/sean/cassiere',
434434
},
435+
{
436+
name: 'should interpolate the non-nested path',
437+
path: '/users/$id_',
438+
params: { id: '123' },
439+
result: '/users/123',
440+
},
435441
])('$name', ({ path, params, decodeCharMap, result }) => {
436442
expect(
437443
interpolatePath({

0 commit comments

Comments
 (0)