]> git.ipfire.org Git - thirdparty/vuejs/router.git/commitdiff
feat: add redirectedFrom
authorEduardo San Martin Morote <posva13@gmail.com>
Wed, 12 Feb 2020 17:35:01 +0000 (18:35 +0100)
committerEduardo San Martin Morote <posva13@gmail.com>
Wed, 12 Feb 2020 17:35:01 +0000 (18:35 +0100)
__tests__/RouterLink.spec.ts
__tests__/router.spec.ts
src/matcher/path-parser-ranker.ts
src/router.ts
src/types/index.ts

index c9c9bd51f5be48b6c84dd6c749efc58fd471ea57..0e3aacbcf2c29bd48d448b594be558aea1bfe1fc 100644 (file)
@@ -31,6 +31,7 @@ const locations: Record<
       query: {},
       hash: '',
       matched: [],
+      redirectedFrom: undefined,
       name: undefined,
     },
   },
@@ -45,6 +46,7 @@ const locations: Record<
       query: {},
       hash: '',
       matched: [],
+      redirectedFrom: undefined,
       name: undefined,
     },
   },
@@ -59,6 +61,7 @@ const locations: Record<
       query: { foo: 'a', bar: 'b' },
       hash: '',
       matched: [],
+      redirectedFrom: undefined,
       name: undefined,
     },
   },
index b745aa94cd5ed580ade12dd2a6c7d508771e1162..c8a896c48b5ad5be096befca82a02e4bf8ea7438 100644 (file)
@@ -10,7 +10,15 @@ import {
 import { RouterHistory } from '../src/history/common'
 
 const routes: RouteRecord[] = [
-  { path: '/', component: components.Home },
+  { path: '/', component: components.Home, name: 'home' },
+  { path: '/home', redirect: '/' },
+  {
+    path: '/home-before',
+    component: components.Home,
+    beforeEnter: (to, from, next) => {
+      next('/')
+    },
+  },
   { path: '/search', component: components.Home },
   { path: '/foo', component: components.Foo, name: 'Foo' },
   { path: '/to-foo', redirect: '/foo' },
@@ -197,6 +205,32 @@ describe('Router', () => {
     })
   })
 
+  describe('redirectedFrom', () => {
+    it('adds a redirectedFrom property with a redirect in record', async () => {
+      const { router } = await newRouter({ history: createMemoryHistory() })
+      // go to a different route first
+      await router.push('/foo')
+      await router.push('/home')
+      expect(router.currentRoute.value).toMatchObject({
+        path: '/',
+        name: 'home',
+        redirectedFrom: { path: '/home' },
+      })
+    })
+
+    it('adds a redirectedFrom property with beforeEnter', async () => {
+      const { router } = await newRouter({ history: createMemoryHistory() })
+      // go to a different route first
+      await router.push('/foo')
+      await router.push('/home-before')
+      expect(router.currentRoute.value).toMatchObject({
+        path: '/',
+        name: 'home',
+        redirectedFrom: { path: '/home-before' },
+      })
+    })
+  })
+
   describe('matcher', () => {
     // TODO: rewrite after redirect refactor
     it.skip('handles one redirect from route record', async () => {
index 847348d774a5cc8a521e0f6d58c7ba17ce29b56f..93df0ff020e972666eb70ec603b2a02ba568eb2c 100644 (file)
@@ -184,7 +184,7 @@ export function tokensToParser(
     score[i][score[i].length - 1] += PathScore.BonusStrict
   }
 
-  // TODO: warn double trailing slash
+  // TODO: dev only warn double trailing slash
   if (!options.strict) pattern += '/?'
 
   if (options.end) pattern += '$'
index 2b17b0dbd13c639f46392f6240c4a2f64c553fab..54cbf6a46e055c19cc27114235f951c863c619ca 100644 (file)
@@ -101,20 +101,11 @@ export function createRouter({
   const decodeParams = applyToParams.bind(null, decode)
 
   function resolve(
-    to: RouteLocation,
-    from?: RouteLocationNormalized
-  ): RouteLocationNormalized {
-    return resolveLocation(to, from || currentRoute.value)
-  }
-
-  function resolveLocation(
     location: RouteLocation,
-    currentLocation: RouteLocationNormalized,
-    // TODO: we should benefit from this with navigation guards
-    // https://github.com/vuejs/vue-router/issues/1822
-    redirectedFrom?: RouteLocationNormalized
+    currentLocation?: RouteLocationNormalized
   ): RouteLocationNormalized {
     // const objectLocation = routerLocationAsObject(location)
+    currentLocation = currentLocation || currentRoute.value
     if (typeof location === 'string') {
       let locationNormalized = parseURL(parseQuery, location)
       let matchedRoute = matcher.resolve(
@@ -126,7 +117,7 @@ export function createRouter({
         ...locationNormalized,
         ...matchedRoute,
         params: decodeParams(matchedRoute.params),
-        redirectedFrom,
+        redirectedFrom: undefined,
       }
     }
 
@@ -156,44 +147,52 @@ export function createRouter({
       hash: location.hash || '',
       query: normalizeQuery(location.query || {}),
       ...matchedRoute,
-      redirectedFrom,
+      redirectedFrom: undefined,
     }
   }
 
-  async function push(to: RouteLocation): Promise<RouteLocationNormalized> {
+  function push(to: RouteLocation): Promise<RouteLocationNormalized> {
+    return pushWithRedirect(to, undefined)
+  }
+
+  async function pushWithRedirect(
+    to: RouteLocation,
+    redirectedFrom: RouteLocationNormalized | undefined
+  ): Promise<RouteLocationNormalized> {
     const toLocation: RouteLocationNormalized = (pendingLocation = resolve(to))
+    const from: RouteLocationNormalized = currentRoute.value
 
     // TODO: should we throw an error as the navigation was aborted
     // TODO: needs a proper check because order in query could be different
     if (
-      currentRoute.value !== START_LOCATION_NORMALIZED &&
-      currentRoute.value.fullPath === toLocation.fullPath
+      from !== START_LOCATION_NORMALIZED &&
+      from.fullPath === toLocation.fullPath
     )
-      return currentRoute.value
+      return from
+
+    toLocation.redirectedFrom = redirectedFrom
 
     // trigger all guards, throw if navigation is rejected
     try {
-      await navigate(toLocation, currentRoute.value)
+      await navigate(toLocation, from)
     } catch (error) {
       if (NavigationGuardRedirect.is(error)) {
         // push was called while waiting in guards
         if (pendingLocation !== toLocation) {
-          triggerError(new NavigationCancelled(toLocation, currentRoute.value))
+          triggerError(new NavigationCancelled(toLocation, from))
         }
-        // TODO: setup redirect stack
-        // TODO: shouldn't we trigger the error as well
-        return push(error.to)
+        // preserve the original redirectedFrom if any
+        return pushWithRedirect(error.to, redirectedFrom || toLocation)
       } else {
         // TODO: write tests
         if (pendingLocation !== toLocation) {
-          // TODO: trigger onError as well
-          triggerError(new NavigationCancelled(toLocation, currentRoute.value))
+          triggerError(new NavigationCancelled(toLocation, from))
         }
       }
       triggerError(error)
     }
 
-    finalizeNavigation(toLocation, true, to.replace === true)
+    finalizeNavigation(toLocation, from, true, to.replace === true)
 
     return currentRoute.value
   }
@@ -290,10 +289,10 @@ export function createRouter({
    */
   function finalizeNavigation(
     toLocation: RouteLocationNormalized,
+    from: RouteLocationNormalized,
     isPush: boolean,
     replace?: boolean
   ) {
-    const from = currentRoute.value
     // a more recent navigation took place
     if (pendingLocation !== toLocation) {
       return triggerError(new NavigationCancelled(toLocation, from), isPush)
@@ -331,31 +330,29 @@ export function createRouter({
   }
 
   // attach listener to history to trigger navigations
-  history.listen(async (to, from, info) => {
+  history.listen(async (to, _from, info) => {
     const toLocation = resolve(to.fullPath)
     // console.log({ to, matchedRoute })
 
     pendingLocation = toLocation
+    const from = currentRoute.value
 
     try {
-      await navigate(toLocation, currentRoute.value)
-      finalizeNavigation(toLocation, false)
+      await navigate(toLocation, from)
+      finalizeNavigation(toLocation, from, false)
     } catch (error) {
       if (NavigationGuardRedirect.is(error)) {
         // TODO: refactor the duplication of new NavigationCancelled by
         // checking instanceof NavigationError (it's another TODO)
         // a more recent navigation took place
         if (pendingLocation !== toLocation) {
-          return triggerError(
-            new NavigationCancelled(toLocation, currentRoute.value),
-            false
-          )
+          return triggerError(new NavigationCancelled(toLocation, from), false)
         }
         triggerError(error, false)
 
         // the error is already handled by router.push
         // we just want to avoid logging the error
-        push(error.to).catch(() => {})
+        pushWithRedirect(error.to, toLocation).catch(() => {})
       } else if (NavigationAborted.is(error)) {
         console.log('Cancelled, going to', -info.distance)
         // TODO: test on different browsers ensure consistent behavior
@@ -369,7 +366,6 @@ export function createRouter({
   // Initialization and Errors
 
   let readyHandlers = useCallbacks<OnReadyCallback>()
-  // TODO: should these be triggered before or after route.push().catch()
   let errorHandlers = useCallbacks<ErrorHandler>()
   let ready: boolean
 
index 77448fb4ea757002f8844727977691f7db2da8cd..488f2271461d42110ad80cac571c8ce298255e15 100644 (file)
@@ -64,8 +64,8 @@ export interface RouteLocationNormalized {
   name: string | null | undefined
   params: RouteParams
   matched: RouteRecordNormalized[] // non-enumerable
-  // TODO: make non optional
-  redirectedFrom?: RouteLocationNormalized
+  // TODO: make it an array?
+  redirectedFrom: RouteLocationNormalized | undefined
   meta: Record<string | number | symbol, any>
 }
 
@@ -154,6 +154,7 @@ export const START_LOCATION_NORMALIZED: RouteLocationNormalized = markNonReactiv
     fullPath: '/',
     matched: [],
     meta: {},
+    redirectedFrom: undefined,
   }
 )
 
@@ -173,7 +174,7 @@ export type MatcherLocation =
 export interface MatcherLocationNormalized
   extends Pick<
     RouteLocationNormalized,
-    'name' | 'path' | 'params' | 'matched' | 'redirectedFrom' | 'meta'
+    'name' | 'path' | 'params' | 'matched' | 'meta'
   > {}
 
 // used when the route records requires a redirection