Skip to content
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

Add test cases for route id consistency #2401

Merged
merged 14 commits into from
Oct 15, 2024
9 changes: 9 additions & 0 deletions packages/react-router/src/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ export function removeTrailingSlash(value: string, basepath: string): string {
return value
}

export function getLastPathSegment(path: string) {
if (typeof path !== 'string') {
return undefined
}

const match = path.match(/(\/[^/]+)\/?$/)
return match?.[1]
}

// intended to only compare path name
// see the usage in the isActive under useLinkProps
// /sample/path1 = /sample/path1/
Expand Down
6 changes: 3 additions & 3 deletions packages/react-router/src/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import invariant from 'tiny-invariant'
import { useMatch } from './useMatch'
import { useLoaderDeps } from './useLoaderDeps'
import { useLoaderData } from './useLoaderData'
import { joinPaths, trimPathLeft } from './path'
import { getLastPathSegment, joinPaths, trimPathLeft } from './path'
import { useParams } from './useParams'
import { useSearch } from './useSearch'
import { notFound } from './not-found'
Expand All @@ -16,7 +16,7 @@ import type { NavigateOptions, ParsePathParams, ToMaskOptions } from './link'
import type { ParsedLocation } from './location'
import type { RouteById, RouteIds, RoutePaths } from './routeInfo'
import type { AnyRouter, RegisteredRouter, Router } from './router'
import type { Assign, Constrain, Expand, NoInfer, PickRequired } from './utils'
import type { Assign, Constrain, Expand, NoInfer } from './utils'
import type { BuildLocationFn, NavigateFn } from './RouterProvider'
import type { NotFoundError } from './not-found'
import type { LazyRoute } from './fileRoute'
Expand Down Expand Up @@ -1029,7 +1029,7 @@ export class Route<
path = trimPathLeft(path)
}

const customId = options?.id || path
const customId = path || getLastPathSegment(options?.id)

// Strip the parentId prefix from the first level of children
let id = isRoot
Expand Down
86 changes: 86 additions & 0 deletions packages/react-router/tests/path.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { describe, expect, it } from 'vitest'
import {
exactPathTest,
getLastPathSegment,
interpolatePath,
removeBasepath,
removeTrailingSlash,
Expand Down Expand Up @@ -276,3 +277,88 @@ describe('interpolatePath', () => {
})
})
})

describe('getLastPathSegment', () => {
;[
{
name: 'should return the last segment with leading slash when path ends with a slash',
path: '/_protected/test/',
expected: '/test',
},
{
name: 'should return the last segment with leading slash when path does not end with a slash',
path: '/_protected/test',
expected: '/test',
},
{
name: 'should return the last segment in a longer path with trailing slash',
path: '/another/path/example/',
expected: '/example',
},
{
name: 'should return the last segment in a longer path without trailing slash',
path: '/another/path/example',
expected: '/example',
},
{
name: 'should return the only segment with leading slash',
path: '/single',
expected: '/single',
},
{
name: 'should return the last segment with leading slash in a nested path',
path: '/trailing/slash/',
expected: '/slash',
},
{
name: 'should return undefined for root path',
path: '/',
expected: undefined,
},
{
name: 'should return undefined for empty string',
path: '',
expected: undefined,
},
{
name: 'should handle paths with numbers correctly',
path: '/users/123/',
expected: '/123',
},
{
name: 'should handle paths with special characters',
path: '/path/with-special_chars!',
expected: '/with-special_chars!',
},
{
name: 'should handle paths with multiple special characters and trailing slash',
path: '/path/with/$pecial-Chars123/',
expected: '/$pecial-Chars123',
},
{
name: 'should return undefined when there is no segment after the last slash',
path: '/path/to/directory//',
expected: undefined,
},
{
name: 'should handle paths without leading slash',
path: 'no/leading/slash',
expected: '/slash',
},
{
name: 'should handle single slash',
path: '/',
expected: undefined,
},
{
name: 'should handle path with multiple trailing slashes',
path: '/multiple/trailing/slashes///',
expected: undefined,
},
].forEach(({ name, path, expected }) => {
it(name, () => {
stevenlyd marked this conversation as resolved.
Show resolved Hide resolved
const result = getLastPathSegment(path)
expect(result).toBe(expected)
})
})
})