diff --git a/__tests__/RouterLink.spec.ts b/__tests__/RouterLink.spec.ts index 5eab0e97b..fb627e0d7 100644 --- a/__tests__/RouterLink.spec.ts +++ b/__tests__/RouterLink.spec.ts @@ -19,6 +19,7 @@ const records = { homeAlias: {} as RouteRecordNormalized, foo: {} as RouteRecordNormalized, parent: {} as RouteRecordNormalized, + childEmpty: {} as RouteRecordNormalized, child: {} as RouteRecordNormalized, parentAlias: {} as RouteRecordNormalized, childAlias: {} as RouteRecordNormalized, @@ -33,14 +34,20 @@ records.childAlias = { aliasOf: records.child } as RouteRecordNormalized type RouteLocationResolved = RouteLocationNormalized & { href: string } -const locations: Record< - string, - { - string: string - normalized: RouteLocationResolved - toResolve?: MatcherLocationRaw & Required - } -> = { +function createLocations< + T extends Record< + string, + { + string: string + normalized: RouteLocationResolved + toResolve?: MatcherLocationRaw & Required + } + > +>(locs: T) { + return locs +} + +const locations = createLocations({ basic: { string: '/home', // toResolve: { path: '/home', fullPath: '/home', undefined, query: {}, hash: '' }, @@ -167,6 +174,21 @@ const locations: Record< }, }, + childEmpty: { + string: '/parent', + normalized: { + fullPath: '/parent', + href: '/parent', + path: '/parent', + params: {}, + meta: {}, + query: {}, + hash: '', + matched: [records.parent, records.childEmpty], + redirectedFrom: undefined, + name: undefined, + }, + }, child: { string: '/parent/child', normalized: { @@ -257,6 +279,14 @@ const locations: Record< name: undefined, }, }, +}) + +// add paths to records because they are used to check isActive +for (let record in records) { + let location = locations[record as keyof typeof locations] + if (location) { + records[record as keyof typeof records].path = location.normalized.path + } } async function factory( @@ -461,6 +491,18 @@ describe('RouterLink', () => { ) }) + it('empty path child is active as if it was the parent when on adjacent child', async () => { + const { wrapper } = await factory( + locations.child.normalized, + { to: locations.childEmpty.string }, + locations.childEmpty.normalized + ) + expect(wrapper.find('a')!.className).toContain('router-link-active') + expect(wrapper.find('a')!.className).not.toContain( + 'router-link-exact-active' + ) + }) + it('alias parent is active if the child is an absolute path', async () => { const { wrapper } = await factory( locations.childAsAbsolute.normalized, diff --git a/playground/App.vue b/playground/App.vue index 02212248b..95bdf45ca 100644 --- a/playground/App.vue +++ b/playground/App.vue @@ -69,6 +69,25 @@
  • /always-redirect
  • +
  • + /children +
  • +
  • + /children (child named) +
  • +
  • + /children (parent named) +
  • +
  • + /children/a +
  • +
  • + /children/b +
  • /nested
  • diff --git a/playground/router.ts b/playground/router.ts index a25363568..d1ba49e31 100644 --- a/playground/router.ts +++ b/playground/router.ts @@ -59,6 +59,7 @@ export const router = createRouter({ { path: '/cant-leave', component: GuardedWithLeave }, { path: '/children', + name: 'WithChildren', component, children: [ { path: '', name: 'default-child', component }, diff --git a/src/RouterLink.ts b/src/RouterLink.ts index 6c9eda60c..21829e28a 100644 --- a/src/RouterLink.ts +++ b/src/RouterLink.ts @@ -30,13 +30,29 @@ export function useLink(props: UseLinkOptions) { const route = computed(() => router.resolve(unref(props.to))) const activeRecordIndex = computed(() => { - // TODO: handle children with empty path: they should relate to their parent - const currentMatched: RouteRecord | undefined = - route.value.matched[route.value.matched.length - 1] - if (!currentMatched) return -1 - return currentRoute.matched.findIndex( - isSameRouteRecord.bind(null, currentMatched) + let { matched } = route.value + let { length } = matched + const routeMatched: RouteRecord | undefined = matched[length - 1] + let currentMatched = currentRoute.matched + if (!routeMatched || !currentMatched.length) return -1 + let index = currentMatched.findIndex( + isSameRouteRecord.bind(null, routeMatched) ) + if (index > -1) return index + // possible parent record + let parentRecord = matched[length - 2] + if ( + length > 1 && + // if the have the same path, this link is referring to the empty child + // are we currently are on a different child of the same parent + routeMatched.path === parentRecord.path && + // avoid comparing the child with its parent + currentMatched[currentMatched.length - 1].path !== parentRecord.path + ) + return currentMatched.findIndex( + isSameRouteRecord.bind(null, matched[length - 2]) + ) + return index }) const isActive = computed(