refactor(router): make v2RouteName the single authority for the v2 name rule
Moves the `v2-` de-dup (needed because getPascalCaseRouteName folds the v2/ URL segment into the base) into the unit-tested v2RouteName helper and simplifies the vite.config.ts call site to v2RouteName(raw, nodePath). Removes the duplicated isV2 detection. No behavioural change: /v2/dashboard still resolves to route name v2-dashboard; v1 names unchanged. Addresses the Task 3 code-review Important finding. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -12,11 +12,23 @@ describe('v2RouteName', () => {
|
||||
expect(v2RouteName('index', 'v2/')).toBe('v2-index')
|
||||
})
|
||||
|
||||
it('de-dups when the raw name already folded in the v2 segment', () => {
|
||||
// getPascalCaseRouteName folds the /v2/ URL prefix into the base, so
|
||||
// the raw name arrives as `v2-dashboard`; the result must still be a
|
||||
// single `v2-dashboard`, never `v2-v2-dashboard`.
|
||||
expect(v2RouteName('v2-dashboard', '/v2/dashboard')).toBe('v2-dashboard')
|
||||
expect(v2RouteName('v2-events-id', '/v2/events/:id')).toBe('v2-events-id')
|
||||
})
|
||||
|
||||
it('leaves v1 route names untouched', () => {
|
||||
expect(v2RouteName('dashboard', '/dashboard')).toBe('dashboard')
|
||||
expect(v2RouteName('events', '/events')).toBe('events')
|
||||
})
|
||||
|
||||
it('does not strip a v1 name that starts with v2- when the path is not under /v2', () => {
|
||||
expect(v2RouteName('v2-legacy', '/v2-legacy')).toBe('v2-legacy')
|
||||
})
|
||||
|
||||
it('does not match a v1 path that merely starts with the letters v2', () => {
|
||||
expect(v2RouteName('v2x-thing', '/v2x-thing')).toBe('v2x-thing')
|
||||
})
|
||||
|
||||
@@ -1,20 +1,30 @@
|
||||
/**
|
||||
* Route-NAME collision guard for the parallel /v2/* tree.
|
||||
*
|
||||
* unplugin-vue-router derives the route name from the file path relative
|
||||
* to its routesFolder, so `src/pages/events/index.vue` and
|
||||
* `src/pages-v2/events/index.vue` would BOTH yield name `events` — a
|
||||
* silent runtime collision (router.push({ name: 'events' }) becomes
|
||||
* ambiguous). The pages-v2 routesFolder carries `path: 'v2/'`, so every
|
||||
* v2 route's URL path is under `/v2`. Prefix the NAME with `v2-` for
|
||||
* those, leaving v1 names untouched. Stripped at final cutover.
|
||||
* unplugin-vue-router derives the route name from the file path. With a
|
||||
* second routesFolder mounting `src/pages-v2` under URL prefix `/v2/`,
|
||||
* `getPascalCaseRouteName` folds that `v2` segment into the base name
|
||||
* (e.g. `/v2/dashboard` → `v2-dashboard`), while a same-named v1 page
|
||||
* under `src/pages` yields a bare `dashboard` — a silent runtime name
|
||||
* collision. This helper is the SINGLE authority for the v2 name rule:
|
||||
* for any route whose path is under `/v2`, normalise to exactly one
|
||||
* canonical `v2-` prefix (stripping the prefix the pascal name already
|
||||
* folded in, then re-adding it); v1 names pass through untouched.
|
||||
* Reverted at final cutover.
|
||||
*
|
||||
* @param baseName the kebab name already computed by getRouteName
|
||||
* @param rawName the kebab name computed by getRouteName
|
||||
* @param routePath the node's URL path (leading slash optional)
|
||||
*/
|
||||
export function v2RouteName(baseName: string, routePath: string): string {
|
||||
export function v2RouteName(rawName: string, routePath: string): string {
|
||||
const normalized = routePath.replace(/^\//, '')
|
||||
const isV2 = normalized === 'v2' || normalized.startsWith('v2/')
|
||||
|
||||
return isV2 ? `v2-${baseName}` : baseName
|
||||
if (!isV2)
|
||||
return rawName
|
||||
|
||||
const base = rawName.startsWith('v2-')
|
||||
? rawName.slice('v2-'.length)
|
||||
: rawName
|
||||
|
||||
return `v2-${base}`
|
||||
}
|
||||
|
||||
@@ -38,26 +38,16 @@ export default defineConfig({
|
||||
|
||||
// Defensive path read: unplugin-vue-router 0.8.8 TreeNode exposes
|
||||
// `.fullPath`; fall back to `.value.path` then '' so a future
|
||||
// plugin bump can't silently drop the v2- prefix. Step 5 below
|
||||
// empirically verifies the emitted name.
|
||||
// plugin bump can't silently drop the v2- prefix.
|
||||
const nodePath
|
||||
= (routeNode.fullPath
|
||||
?? routeNode.value?.path
|
||||
?? '') as string
|
||||
|
||||
// getPascalCaseRouteName includes the 'v2' segment from the URL
|
||||
// path prefix set by routesFolder (e.g. 'v2/dashboard' → 'v2-dashboard').
|
||||
// Strip that prefix before v2RouteName re-adds the canonical `v2-`
|
||||
// so we get 'v2-dashboard' not 'v2-v2-dashboard'.
|
||||
const isV2
|
||||
= nodePath === '/v2'
|
||||
|| nodePath === 'v2'
|
||||
|| nodePath.startsWith('/v2/')
|
||||
|| nodePath.startsWith('v2/')
|
||||
|
||||
const base = isV2 && raw.startsWith('v2-') ? raw.slice(3) : raw
|
||||
|
||||
return v2RouteName(base, nodePath)
|
||||
// v2RouteName is the single authority for the /v2 name rule,
|
||||
// including de-duping the `v2-` that getPascalCaseRouteName
|
||||
// already folds in from the routesFolder URL prefix.
|
||||
return v2RouteName(raw, nodePath)
|
||||
},
|
||||
}),
|
||||
vue(),
|
||||
|
||||
Reference in New Issue
Block a user