]> git.ipfire.org Git - thirdparty/vuejs/router.git/commitdiff
fix(link): non active repeatable params
authorEduardo San Martin Morote <posva13@gmail.com>
Fri, 28 Feb 2020 15:32:36 +0000 (16:32 +0100)
committerEduardo San Martin Morote <posva13@gmail.com>
Fri, 28 Feb 2020 15:32:36 +0000 (16:32 +0100)
__tests__/RouterLink.spec.ts
__tests__/router.spec.ts
playground/App.vue
src/components/Link.ts
src/router.ts
src/utils/index.ts
src/utils/query.ts

index 52d88ac32bdc24957d694b1a6e23b082a5fa9d1e..24c5f230416fa98bec01d48913569d7014be37e6 100644 (file)
@@ -71,6 +71,34 @@ const locations: Record<
       name: undefined,
     },
   },
+  repeatedParams2: {
+    string: '/p/1/2',
+    normalized: {
+      fullPath: '/p/1/2',
+      path: '/p/1/2',
+      params: { p: ['1', '2'] },
+      meta: {},
+      query: {},
+      hash: '',
+      matched: [records.home],
+      redirectedFrom: undefined,
+      name: undefined,
+    },
+  },
+  repeatedParams3: {
+    string: '/p/1/2/3',
+    normalized: {
+      fullPath: '/p/1/2/3',
+      path: '/p/1/2/3',
+      params: { p: ['1', '2', '3'] },
+      meta: {},
+      query: {},
+      hash: '',
+      matched: [records.home],
+      redirectedFrom: undefined,
+      name: undefined,
+    },
+  },
 }
 
 describe('RouterLink', () => {
@@ -143,6 +171,24 @@ describe('RouterLink', () => {
     expect(el.querySelector('a')!.className).toContain('router-link-active')
   })
 
+  it('is not active with more repeated params', () => {
+    const { el } = factory(
+      locations.repeatedParams2.normalized,
+      { to: locations.repeatedParams3.string },
+      locations.repeatedParams3.normalized
+    )
+    expect(el.querySelector('a')!.className).toBe('')
+  })
+
+  it('is not active with partial repeated params', () => {
+    const { el } = factory(
+      locations.repeatedParams3.normalized,
+      { to: locations.repeatedParams2.string },
+      locations.repeatedParams2.normalized
+    )
+    expect(el.querySelector('a')!.className).toBe('')
+  })
+
   it.todo('can be active as an alias')
   it.todo('can be exact-active as an alias')
   it.todo('is active when a child is active')
index 06424f1f2e9eda092ea5ca019b042cf8b482d947..1f03a15ac3a8ef0160814ce72f2311a35f248fd3 100644 (file)
@@ -382,6 +382,52 @@ describe('Router', () => {
       })
     })
 
+    it('can reroute to a replaced route with the same component', async () => {
+      const { router } = await newRouter()
+      router.addRoute({
+        path: '/new/foo',
+        component: components.Foo,
+        name: 'new',
+      })
+      // navigate to the route we just added
+      await router.replace({ name: 'new' })
+      // replace it
+      router.addRoute({
+        path: '/new/bar',
+        component: components.Foo,
+        name: 'new',
+      })
+      // navigate again
+      await router.replace({ name: 'new' })
+      expect(router.currentRoute.value).toMatchObject({
+        path: '/new/bar',
+        name: 'new',
+      })
+    })
+
+    it('can reroute to child', async () => {
+      const { router } = await newRouter()
+      router.addRoute({
+        path: '/new',
+        component: components.Foo,
+        children: [],
+        name: 'new',
+      })
+      // navigate to the route we just added
+      await router.replace('/new/child')
+      // replace it
+      router.addRoute('new', {
+        path: 'child',
+        component: components.Bar,
+        name: 'new-child',
+      })
+      // navigate again
+      await router.replace('/new/child')
+      expect(router.currentRoute.value).toMatchObject({
+        name: 'new-child',
+      })
+    })
+
     it('can reroute when adding a new route', async () => {
       const { router } = await newRouter()
       await router.push('/p/p')
index a09d59907fcfe00eef033f5d3e4679cbfcaa2bc4..fe0866d1785046548cbf64b0f752ddd62a310430 100644 (file)
           >/docs/é</router-link
         >
       </li>
+      <li>
+        <router-link to="/rep">/rep</router-link>
+      </li>
+      <li>
+        <router-link to="/rep/a">/rep/a</router-link>
+      </li>
+      <li>
+        <router-link to="/rep/a/b">/rep/a/b</router-link>
+      </li>
     </ul>
     <!-- <transition
       name="fade"
index beead366a6d7158d31329be9b7ccacf7cdf8b32e..d95871a404406371c0d454980d46218d463cc140 100644 (file)
@@ -9,9 +9,8 @@ import {
   unref,
 } from 'vue'
 import { RouteLocation, RouteLocationNormalized, Immutable } from '../types'
-import { isSameLocationObject } from '../utils'
+import { isSameLocationObject, isSameRouteRecord } from '../utils'
 import { routerKey } from '../injectKeys'
-import { RouteRecordNormalized } from '../matcher/types'
 
 type VueUseOptions<T> = {
   [k in keyof T]: Ref<T[k]> | T[k]
@@ -25,35 +24,6 @@ interface LinkProps {
 
 type UseLinkOptions = VueUseOptions<LinkProps>
 
-function isSameRouteRecord(
-  a: Immutable<RouteRecordNormalized>,
-  b: Immutable<RouteRecordNormalized>
-): boolean {
-  // TODO: handle aliases
-  return a === b
-}
-
-function includesParams(
-  outter: Immutable<RouteLocationNormalized['params']>,
-  inner: Immutable<RouteLocationNormalized['params']>
-): boolean {
-  for (let key in inner) {
-    let innerValue = inner[key]
-    let outterValue = outter[key]
-    if (typeof innerValue === 'string') {
-      if (innerValue !== outterValue) return false
-    } else {
-      if (
-        !Array.isArray(outterValue) ||
-        innerValue.some((value, i) => value !== outterValue[i])
-      )
-        return false
-    }
-  }
-
-  return true
-}
-
 export function useLink(props: UseLinkOptions) {
   const router = inject(routerKey)!
 
@@ -147,3 +117,25 @@ function guardEvent(e: MouseEvent) {
 
   return true
 }
+
+function includesParams(
+  outter: Immutable<RouteLocationNormalized['params']>,
+  inner: Immutable<RouteLocationNormalized['params']>
+): boolean {
+  for (let key in inner) {
+    let innerValue = inner[key]
+    let outterValue = outter[key]
+    if (typeof innerValue === 'string') {
+      if (innerValue !== outterValue) return false
+    } else {
+      if (
+        !Array.isArray(outterValue) ||
+        outterValue.length !== innerValue.length ||
+        innerValue.some((value, i) => value !== outterValue[i])
+      )
+        return false
+    }
+  }
+
+  return true
+}
index b41a46c5e70d538ef8591a63d59ef0ff57c8c16b..e2de1a9953a9d4f84986710d366e2f66d1a38a54 100644 (file)
@@ -28,6 +28,7 @@ import {
   guardToPromiseFn,
   isSameLocationObject,
   applyToParams,
+  isSameRouteRecord,
 } from './utils'
 import { useCallbacks } from './utils/callbacks'
 import { encodeParam, decode } from './utils/encoding'
@@ -177,7 +178,7 @@ export function createRouter({
         path: matchedRoute.path,
       }),
       hash: location.hash || '',
-      query: normalizeQuery(location.query || {}),
+      query: normalizeQuery(location.query),
       ...matchedRoute,
       redirectedFrom: undefined,
     }
@@ -197,7 +198,6 @@ export function createRouter({
     const force: boolean | undefined = to.force
 
     // TODO: should we throw an error as the navigation was aborted
-    // TODO: needs a proper check because order in query could be different
     if (!force && isSameLocation(from, toLocation)) return from
 
     toLocation.redirectedFrom = redirectedFrom
@@ -543,6 +543,8 @@ function isSameLocation(
     a.name === b.name &&
     a.path === b.path &&
     a.hash === b.hash &&
-    isSameLocationObject(a.query, b.query)
+    isSameLocationObject(a.query, b.query) &&
+    a.matched.length === b.matched.length &&
+    a.matched.every((record, i) => isSameRouteRecord(record, b.matched[i]))
   )
 }
index 342cd69248b4f391689d1fb5a1e992dd0552f0aa..e070765bff41f8750d9bb1fa1c7f31c5444c0095 100644 (file)
@@ -51,6 +51,14 @@ export function applyToParams(
   return newParams
 }
 
+export function isSameRouteRecord(
+  a: Immutable<RouteRecordNormalized>,
+  b: Immutable<RouteRecordNormalized>
+): boolean {
+  // TODO: handle aliases
+  return a === b
+}
+
 export function isSameLocationObject(
   a: Immutable<RouteLocationNormalized['query']>,
   b: Immutable<RouteLocationNormalized['query']>
@@ -97,6 +105,9 @@ function isSameLocationObjectValue(
   if (typeof a !== typeof b) return false
   // both a and b are arrays
   if (Array.isArray(a))
-    return a.every((value, i) => value === (b as LocationQueryValue[])[i])
+    return (
+      a.length === (b as any[]).length &&
+      a.every((value, i) => value === (b as LocationQueryValue[])[i])
+    )
   return a === b
 }
index 498ea7d0e6ca72d32b78c02cb54bd918d6b450ae..7ef1bc3f9b9a8fd42fbafc9888d4b1c88ea3d575 100644 (file)
@@ -82,7 +82,9 @@ export function stringifyQuery(query: LocationQueryRaw): string {
  * null in arrays
  * @param query
  */
-export function normalizeQuery(query: LocationQueryRaw): LocationQuery {
+export function normalizeQuery(
+  query: LocationQueryRaw | undefined
+): LocationQuery {
   const normalizedQuery: LocationQuery = {}
 
   for (let key in query) {