Skip to content

Commit c35c88c

Browse files
committed
Add support for handling any kind of empty _splat consistently
1 parent 2f2a009 commit c35c88c

File tree

3 files changed

+111
-2
lines changed

3 files changed

+111
-2
lines changed

packages/react-router/tests/link.test.tsx

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5549,6 +5549,99 @@ describe.each([{ basepath: '' }, { basepath: '/basepath' }])(
55495549
},
55505550
)
55515551

5552+
describe("splat routes with empty splat", () => {
5553+
test.each([{ trailingSlash: true }, { trailingSlash: false }])(
5554+
'should handle empty _splat parameter with trailingSlash: $trailingSlash',
5555+
async ({ trailingSlash }) => {
5556+
const tail = trailingSlash ? '/' : ''
5557+
5558+
const rootRoute = createRootRoute()
5559+
const indexRoute = createRoute({
5560+
getParentRoute: () => rootRoute,
5561+
path: '/',
5562+
component: () => {
5563+
return (
5564+
<>
5565+
<h1>Index Route</h1>
5566+
<Link
5567+
data-testid="splat-link-with-empty-splat"
5568+
to="/splat/$"
5569+
params={{ _splat: "" }}
5570+
activeProps={{ className: "active" }}
5571+
>
5572+
Link to splat with _splat value
5573+
</Link>
5574+
<Link
5575+
data-testid="splat-link-with-undefined-splat"
5576+
to="/splat/$"
5577+
params={{ _splat: undefined }}
5578+
activeProps={{ className: "active" }}
5579+
>
5580+
5581+
Link to splat with undefined _splat
5582+
</Link>
5583+
<Link
5584+
data-testid="splat-link-with-no-splat"
5585+
to="/splat/$"
5586+
params={{ }}
5587+
activeProps={{ className: "active" }}
5588+
>
5589+
5590+
Link to splat with no _splat at all
5591+
</Link>
5592+
</>
5593+
)
5594+
},
5595+
})
5596+
5597+
const splatRoute = createRoute({
5598+
getParentRoute: () => rootRoute,
5599+
path: 'splat/$',
5600+
component: () => {
5601+
return <h1>Splat Route</h1>
5602+
},
5603+
})
5604+
5605+
const router = createRouter({
5606+
routeTree: rootRoute.addChildren([indexRoute, splatRoute]),
5607+
history,
5608+
trailingSlash: trailingSlash ? 'always' : 'never',
5609+
})
5610+
5611+
render(<RouterProvider router={router} />)
5612+
5613+
const splatLinkWithEmptySplat = await screen.findByTestId(
5614+
'splat-link-with-empty-splat',
5615+
)
5616+
const splatLinkWithUndefinedSplat = await screen.findByTestId(
5617+
'splat-link-with-undefined-splat',
5618+
)
5619+
const splatLinkWithNoSplat = await screen.findByTestId(
5620+
'splat-link-with-no-splat',
5621+
)
5622+
5623+
// When _splat has a value, it should follow the trailingSlash setting
5624+
expect(splatLinkWithEmptySplat.getAttribute('href')).toBe(
5625+
`/splat${tail}`,
5626+
)
5627+
5628+
expect(splatLinkWithUndefinedSplat.getAttribute('href')).toBe(`/splat${tail}`)
5629+
expect(splatLinkWithNoSplat.getAttribute('href')).toBe(`/splat${tail}`)
5630+
5631+
// Click the link with empty _splat and ensure the route matches
5632+
await act(async () => {
5633+
fireEvent.click(splatLinkWithEmptySplat)
5634+
})
5635+
5636+
expect(splatLinkWithEmptySplat).toHaveClass('active')
5637+
expect(splatLinkWithUndefinedSplat).toHaveClass('active')
5638+
expect(splatLinkWithNoSplat).toHaveClass('active')
5639+
expect(window.location.pathname).toBe(`/splat${tail}`)
5640+
expect(await screen.findByText('Splat Route')).toBeInTheDocument()
5641+
},
5642+
)
5643+
})
5644+
55525645
describe('relative links to current route', () => {
55535646
test.each([true, false])(
55545647
'should navigate to current route when using "." in nested route structure from Index Route with trailingSlash: %s',

packages/router-core/src/path.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -426,8 +426,8 @@ export function interpolatePath({
426426
const segmentPrefix = segment.prefixSegment || ''
427427
const segmentSuffix = segment.suffixSegment || ''
428428

429-
// Check if _splat parameter is missing
430-
if (!('_splat' in params)) {
429+
// Check if _splat parameter is missing. _splat could be missing if undefined or an empty string or some other falsy value.
430+
if (!params._splat) {
431431
isMissingParams = true
432432
// For missing splat parameters, just return the prefix and suffix without the wildcard
433433
if (leaveWildcards) {

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,22 @@ describe('interpolatePath', () => {
472472
params: {},
473473
expectedResult: '/hello/prefixsuffix',
474474
},
475+
{
476+
name: 'splat route with empty splat',
477+
path: '/hello/$',
478+
params: {
479+
_splat: "",
480+
},
481+
expectedResult: '/hello',
482+
},
483+
{
484+
name: 'splat route with undefined splat',
485+
path: '/hello/$',
486+
params: {
487+
_splat: undefined
488+
},
489+
expectedResult: '/hello',
490+
}
475491
])('$name', ({ path, params, expectedResult }) => {
476492
const result = interpolatePath({
477493
path,

0 commit comments

Comments
 (0)