From: Eduardo San Martin Morote Date: Tue, 28 May 2019 17:27:24 +0000 (+0200) Subject: feat: add redirectedFrom in normalized location X-Git-Tag: v4.0.0-alpha.0~364 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=afd9730161695092f9c3ade72d07bfc7867eb8ef;p=thirdparty%2Fvuejs%2Frouter.git feat: add redirectedFrom in normalized location --- diff --git a/__tests__/router.spec.js b/__tests__/router.spec.js index 888c2872..6fec494d 100644 --- a/__tests__/router.spec.js +++ b/__tests__/router.spec.js @@ -13,8 +13,12 @@ function mockHistory() { /** @type {import('../src/types').RouteRecord[]} */ const routes = [ { path: '/', component: components.Home }, - { path: '/foo', component: components.Foo }, + { path: '/foo', component: components.Foo, name: 'Foo' }, { path: '/to-foo', redirect: '/foo' }, + { path: '/to-foo-named', redirect: { name: 'Foo' } }, + { path: '/to-foo2', redirect: '/to-foo' }, + { path: '/p/:p', component: components.Bar }, + { path: '/to-p/:p', redirect: to => `/p/${to.params.p}` }, ] describe('Router', () => { @@ -62,5 +66,40 @@ describe('Router', () => { }) }) + describe('matcher', () => { + it('handles one redirect from route record', async () => { + const history = mockHistory() + const router = new Router({ history, routes }) + const loc = await router.push('/to-foo') + expect(loc.name).toBe('Foo') + expect(loc.redirectedFrom).toMatchObject({ + path: '/to-foo', + }) + }) + + it('allows object in redirect', async () => { + const history = mockHistory() + const router = new Router({ history, routes }) + const loc = await router.push('/to-foo-named') + expect(loc.name).toBe('Foo') + expect(loc.redirectedFrom).toMatchObject({ + path: '/to-foo-named', + }) + }) + + it('handles multiple redirect fields in route record', async () => { + const history = mockHistory() + const router = new Router({ history, routes }) + const loc = await router.push('/to-foo2') + expect(loc.name).toBe('Foo') + expect(loc.redirectedFrom).toMatchObject({ + path: '/to-foo', + redirectedFrom: { + path: '/to-foo2', + }, + }) + }) + }) + // it('redirects with route record redirect') }) diff --git a/src/router.ts b/src/router.ts index b5a99937..0144f75f 100644 --- a/src/router.ts +++ b/src/router.ts @@ -16,6 +16,7 @@ import { PostNavigationGuard, Lazy, MatcherLocation, + RouteQueryAndHash, } from './types/index' import { guardToPromiseFn, extractComponentsGuards } from './utils' @@ -42,7 +43,7 @@ export class Router { this.history.listen(async (to, from, info) => { const matchedRoute = this.matchLocation(to, this.currentRoute) // console.log({ to, matchedRoute }) - // TODO: navigate & guards + const toLocation: RouteLocationNormalized = { ...to, ...matchedRoute } try { @@ -76,50 +77,74 @@ export class Router { }) } + // TODO: rename to resolveLocation? private matchLocation( - location: MatcherLocation, - currentLocation: MatcherLocationNormalized - ): MatcherLocationNormalized { + location: MatcherLocation & Required, + currentLocation: RouteLocationNormalized, + redirectedFrom?: RouteLocationNormalized + // ensure when returning that the redirectedFrom is a normalized location + ): MatcherLocationNormalized & { redirectedFrom?: RouteLocationNormalized } { const matchedRoute = this.matcher.resolve(location, currentLocation) + if ('redirect' in matchedRoute) { const { redirect, normalizedLocation } = matchedRoute - // TODO: add from to a redirect stack? + // target location normalized, used if we want to redirect again + // TODO: rename into normalizedLocation + const normalizedLocationForRedirect: RouteLocationNormalized = { + ...normalizedLocation, + fullPath: this.history.utils.stringifyURL(normalizedLocation), + query: this.history.utils.normalizeQuery(location.query || {}), + hash: location.hash, + redirectedFrom, + } + if (typeof redirect === 'string') { // match the redirect instead return this.matchLocation( this.history.utils.normalizeLocation(redirect), - currentLocation + currentLocation, + normalizedLocationForRedirect ) } else if (typeof redirect === 'function') { - const url = this.history.utils.normalizeLocation(normalizedLocation) - const newLocation = redirect({ - ...normalizedLocation, - ...url, - }) + const newLocation = redirect(normalizedLocationForRedirect) if (typeof newLocation === 'string') { return this.matchLocation( this.history.utils.normalizeLocation(newLocation), - currentLocation + currentLocation, + normalizedLocationForRedirect ) } - return this.matchLocation(newLocation, currentLocation) + return this.matchLocation( + { + ...newLocation, + query: this.history.utils.normalizeQuery(newLocation.query || {}), + hash: newLocation.hash || '', + }, + currentLocation, + normalizedLocationForRedirect + ) } else { - return this.matchLocation(redirect, currentLocation) + return this.matchLocation( + { + ...redirect, + query: this.history.utils.normalizeQuery(redirect.query || {}), + hash: redirect.hash || '', + }, + currentLocation, + normalizedLocationForRedirect + ) } } else { - return matchedRoute + // add the redirectedFrom field + return { + ...matchedRoute, + redirectedFrom, + } } } - async push(to: RouteLocation): Promise { - // match the location - const { url, location } = - let url: HistoryLocationNormalized - let location: MatcherLocationNormalized - } - /** * Trigger a navigation, adding an entry to the history stack. Also apply all navigation * guards first @@ -127,7 +152,9 @@ export class Router { */ async push(to: RouteLocation): Promise { let url: HistoryLocationNormalized - let location: MatcherLocationNormalized + let location: MatcherLocationNormalized & { + redirectedFrom?: RouteLocationNormalized + } // TODO: refactor into matchLocation to return location and url if (typeof to === 'string' || 'path' in to) { url = this.history.utils.normalizeLocation(to) @@ -135,17 +162,20 @@ export class Router { location = this.matchLocation(url, this.currentRoute) } else { // named or relative route + const query = to.query ? this.history.utils.normalizeQuery(to.query) : {} + const hash = to.hash || '' // we need to resolve first - location = this.matchLocation(to, this.currentRoute) + location = this.matchLocation({ ...to, query, hash }, this.currentRoute) // intentionally drop current query and hash url = this.history.utils.normalizeLocation({ - query: to.query ? this.history.utils.normalizeQuery(to.query) : {}, - hash: to.hash, + query, + hash, ...location, }) } // TODO: should we throw an error as the navigation was aborted + // TODO: needs a proper check because order could be different if (this.currentRoute.fullPath === url.fullPath) return this.currentRoute const toLocation: RouteLocationNormalized = { ...url, ...location } @@ -201,7 +231,7 @@ export class Router { ): Promise { let guards: Lazy[] - // TODO: is it okay to resolve all matched component or should we do it in order + // all components here have been resolved once because we are leaving guards = await extractComponentsGuards( from.matched.filter(record => to.matched.indexOf(record) < 0).reverse(), 'beforeRouteLeave', @@ -209,7 +239,7 @@ export class Router { from ) - // run the queue of per route beforeEnter guards + // run the queue of per route beforeRouteLeave guards await this.runGuardQueue(guards) // check global guards beforeEach diff --git a/src/types/index.ts b/src/types/index.ts index 1a569019..6badf683 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -48,6 +48,7 @@ export interface RouteLocationNormalized // TODO: do the same for params name: string | void matched: MatchedRouteRecord[] // non-enumerable + redirectedFrom?: RouteLocationNormalized } // interface PropsTransformer { @@ -150,6 +151,7 @@ export interface MatcherLocationNormalized { // record? params: RouteLocationNormalized['params'] matched: MatchedRouteRecord[] + redirectedFrom?: MatcherLocationNormalized } // used when the route records requires a redirection