]> git.ipfire.org Git - thirdparty/vuejs/router.git/commitdiff
feat(link): make empty child active with adjacent children
authorEduardo San Martin Morote <posva13@gmail.com>
Wed, 29 Apr 2020 14:55:19 +0000 (16:55 +0200)
committerEduardo San Martin Morote <posva13@gmail.com>
Wed, 29 Apr 2020 14:55:19 +0000 (16:55 +0200)
__tests__/RouterLink.spec.ts
playground/App.vue
playground/router.ts
src/RouterLink.ts

index 5eab0e97b5b406c0a1320b2a3c0aeededb90d974..fb627e0d78771cc86833ddf202f3863413e2f543 100644 (file)
@@ -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<RouteQueryAndHash>
-  }
-> = {
+function createLocations<
+  T extends Record<
+    string,
+    {
+      string: string
+      normalized: RouteLocationResolved
+      toResolve?: MatcherLocationRaw & Required<RouteQueryAndHash>
+    }
+  >
+>(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,
index 02212248ba55e83f35c25c1784b056414f3c7623..95bdf45cae5cb16579ebec7ce5d8f4709f115329 100644 (file)
       <li>
         <router-link to="/always-redirect">/always-redirect</router-link>
       </li>
+      <li>
+        <router-link to="/children">/children</router-link>
+      </li>
+      <li>
+        <router-link :to="{ name: 'default-child' }"
+          >/children (child named)</router-link
+        >
+      </li>
+      <li>
+        <router-link :to="{ name: 'WithChildren' }"
+          >/children (parent named)</router-link
+        >
+      </li>
+      <li>
+        <router-link to="/children/a">/children/a</router-link>
+      </li>
+      <li>
+        <router-link to="/children/b">/children/b</router-link>
+      </li>
       <li>
         <router-link to="/nested">/nested</router-link>
       </li>
index a253635687cafcc2b4d532cf59857aac2f2fe13d..d1ba49e31563249ae8f3c46969478015b10a337f 100644 (file)
@@ -59,6 +59,7 @@ export const router = createRouter({
     { path: '/cant-leave', component: GuardedWithLeave },
     {
       path: '/children',
+      name: 'WithChildren',
       component,
       children: [
         { path: '', name: 'default-child', component },
index 6c9eda60cfdca7c9362652fc1f4ed1d3f970cd8e..21829e28afe8c0923404c62cd6e245a77ff142e0 100644 (file)
@@ -30,13 +30,29 @@ export function useLink(props: UseLinkOptions) {
   const route = computed(() => router.resolve(unref(props.to)))
 
   const activeRecordIndex = computed<number>(() => {
-    // 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<boolean>(