diff --git a/packages/router-core/src/new-process-route-tree.ts b/packages/router-core/src/new-process-route-tree.ts index 6978b071ce3..a341d448d05 100644 --- a/packages/router-core/src/new-process-route-tree.ts +++ b/packages/router-core/src/new-process-route-tree.ts @@ -220,35 +220,23 @@ function parseSegments( switch (kind) { case SEGMENT_TYPE_PATHNAME: { const value = path.substring(segment[2], segment[3]) + let name = value + let staticChildren: Map> if (caseSensitive) { - const existingNode = node.static?.get(value) - if (existingNode) { - nextNode = existingNode - } else { - node.static ??= new Map() - const next = createStaticNode( - route.fullPath ?? route.from, - ) - next.parent = node - next.depth = depth - nextNode = next - node.static.set(value, next) - } + staticChildren = node.static ??= new Map() } else { - const name = value.toLowerCase() - const existingNode = node.staticInsensitive?.get(name) - if (existingNode) { - nextNode = existingNode - } else { - node.staticInsensitive ??= new Map() - const next = createStaticNode( - route.fullPath ?? route.from, - ) - next.parent = node - next.depth = depth - nextNode = next - node.staticInsensitive.set(name, next) - } + name = value.toLowerCase() + staticChildren = node.staticInsensitive ??= new Map() + } + const existingNode = staticChildren.get(name) + if (existingNode) { + nextNode = existingNode + } else { + const next = createStaticNode(path) + next.parent = node + next.depth = depth + nextNode = next + staticChildren.set(name, next) } break } diff --git a/packages/router-core/tests/new-process-route-tree.test.ts b/packages/router-core/tests/new-process-route-tree.test.ts index d93581355ea..0dc3d1091d7 100644 --- a/packages/router-core/tests/new-process-route-tree.test.ts +++ b/packages/router-core/tests/new-process-route-tree.test.ts @@ -390,6 +390,63 @@ describe('findRouteMatch', () => { }) describe('case sensitivity competition', () => { + it('reuses an insensitive static node for differently cased siblings', () => { + const tree = { + id: '__root__', + isRoot: true, + fullPath: '/', + path: '/', + children: [ + { + id: '/Docs/API', + fullPath: '/Docs/API', + path: 'Docs/API', + }, + { + id: '/docs/guide', + fullPath: '/docs/guide', + path: 'docs/guide', + }, + ], + } + const { processedTree } = processRouteTree(tree) + const docs = processedTree.segmentTree.staticInsensitive?.get('docs') + + expect(processedTree.segmentTree.staticInsensitive?.size).toBe(1) + expect(docs?.staticInsensitive?.size).toBe(2) + expect(findRouteMatch('/DOCS/api', processedTree)?.route.id).toBe( + '/Docs/API', + ) + expect(findRouteMatch('/Docs/GUIDE', processedTree)?.route.id).toBe( + '/docs/guide', + ) + }) + it('allows a route to override a sensitive tree default', () => { + const tree = { + id: '__root__', + isRoot: true, + fullPath: '/', + path: '/', + children: [ + { + id: '/Strict', + fullPath: '/Strict', + path: 'Strict', + }, + { + id: '/loose', + fullPath: '/loose', + path: 'loose', + options: { caseSensitive: false }, + }, + ], + } + const { processedTree } = processRouteTree(tree, true) + + expect(findRouteMatch('/Strict', processedTree)?.route.id).toBe('/Strict') + expect(findRouteMatch('/strict', processedTree)).toBeNull() + expect(findRouteMatch('/LOOSE', processedTree)?.route.id).toBe('/loose') + }) it('a case sensitive segment early on should not prevent a case insensitive match', () => { const tree = { id: '__root__', @@ -1645,6 +1702,7 @@ describe('processRouteMasks', { sequential: true }, () => { { from: '/a/$param/d', routeTree }, { from: '/a/{-$optional}/d', routeTree }, { from: '/a/b/{$}.txt', routeTree }, + { from: '/Admin/Panel', routeTree }, ] processRouteMasks(routeMasks, processedTree) const aBranch = processedTree.masksTree?.staticInsensitive?.get('a') @@ -1657,6 +1715,10 @@ describe('processRouteMasks', { sequential: true }, () => { const res = findFlatMatch('/a/b/c', processedTree) expect(res?.route.from).toBe('/a/b/c') }) + it('matches uppercase static route masks case-insensitively', () => { + const res = findFlatMatch('/admin/panel', processedTree) + expect(res?.route.from).toBe('/Admin/Panel') + }) it('can match dynamic route masks w/ `findFlatMatch`', () => { const res = findFlatMatch('/a/123/d', processedTree) expect(res?.route.from).toBe('/a/$param/d')