From: Eduardo San Martin Morote Date: Thu, 2 May 2019 13:58:59 +0000 (+0200) Subject: feat: basic beforeRouteUpdate X-Git-Tag: v4.0.0-alpha.0~409 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fb9f8205378796c80caa151f5b46e46fc7b1231b;p=thirdparty%2Fvuejs%2Frouter.git feat: basic beforeRouteUpdate --- diff --git a/__tests__/guards/component-beforeRouteUpdate.spec.js b/__tests__/guards/component-beforeRouteUpdate.spec.js new file mode 100644 index 00000000..c3c15e37 --- /dev/null +++ b/__tests__/guards/component-beforeRouteUpdate.spec.js @@ -0,0 +1,93 @@ +// @ts-check +require('../helper') +const expect = require('expect') +const { HTML5History } = require('../../src/history/html5') +const { Router } = require('../../src/router') +const fakePromise = require('faked-promise') +const { NAVIGATION_TYPES, createDom } = require('../utils') + +/** + * @param {Partial & { routes: import('../../src/types').RouteRecord[]}} options + */ +function createRouter(options) { + return new Router({ + history: new HTML5History(), + ...options, + }) +} + +const Home = { template: `
Home
` } +const Foo = { template: `
Foo
` } + +const beforeRouteUpdate = jest.fn() +/** @type {import('../../src/types').RouteRecord[]} */ +const routes = [ + { path: '/', component: Home }, + { path: '/foo', component: Foo }, + { + path: '/guard/:go', + component: { + ...Foo, + beforeRouteUpdate, + }, + }, +] + +beforeEach(() => { + beforeRouteUpdate.mockReset() +}) + +describe('beforeRouteUpdate', () => { + beforeAll(() => { + createDom() + }) + + NAVIGATION_TYPES.forEach(navigationMethod => { + describe(navigationMethod, () => { + it('calls beforeRouteUpdate guards when changing params', async () => { + const router = createRouter({ routes }) + beforeRouteUpdate.mockImplementationOnce((to, from, next) => { + next() + }) + await router[navigationMethod]('/guard/valid') + // not called on initial navigation + expect(beforeRouteUpdate).not.toHaveBeenCalled() + await router[navigationMethod]('/guard/other') + expect(beforeRouteUpdate).toHaveBeenCalledTimes(1) + }) + + it('resolves async components before guarding', async () => { + const spy = jest.fn((to, from, next) => next()) + const component = { + template: `
`, + beforeRouteUpdate: spy, + } + const router = createRouter({ + routes: [ + ...routes, + { path: '/async/:a', component: () => Promise.resolve(component) }, + ], + }) + await router[navigationMethod]('/async/a') + expect(spy).not.toHaveBeenCalled() + await router[navigationMethod]('/async/b') + expect(spy).toHaveBeenCalledTimes(1) + }) + + it('waits before navigating', async () => { + const [promise, resolve] = fakePromise() + const router = createRouter({ routes }) + beforeRouteUpdate.mockImplementationOnce(async (to, from, next) => { + await promise + next() + }) + await router[navigationMethod]('/guard/one') + const p = router[navigationMethod]('/guard/foo') + expect(router.currentRoute.fullPath).toBe('/guard/one') + resolve() + await p + expect(router.currentRoute.fullPath).toBe('/guard/foo') + }) + }) + }) +}) diff --git a/src/router.ts b/src/router.ts index 0c4922ef..69043c2e 100644 --- a/src/router.ts +++ b/src/router.ts @@ -141,6 +141,37 @@ export class Router { } } + // check in components beforeRouteUpdate + guards = [] + await Promise.all( + to.matched.map(async record => { + // TODO: cache async routes per record + // TODO: handle components version. Probably repfactor in extractComponentGuards + if ('component' in record) { + const { component } = record + const resolvedComponent = await (typeof component === 'function' + ? component() + : component) + // trigger on reused views + // TODO: should we avoid triggering if no param changed on this route? + if ( + resolvedComponent.beforeRouteUpdate && + from.matched.indexOf(record) > -1 + ) { + // TODO: handle the next callback + guards.push( + guardToPromiseFn(resolvedComponent.beforeRouteUpdate, to, from) + ) + } + } + }) + ) + + // run the queue of per route beforeEnter guards + for (const guard of guards) { + await guard() + } + // check the route beforeEnter // TODO: check children. Should we also check reused routes guards guards = [] @@ -167,9 +198,10 @@ export class Router { const resolvedComponent = await (typeof component === 'function' ? component() : component) + // do not trigger beforeRouteEnter on reused views if ( resolvedComponent.beforeRouteEnter && - from.matched.indexOf(record) + from.matched.indexOf(record) < 0 ) { // TODO: handle the next callback guards.push( diff --git a/src/types/index.ts b/src/types/index.ts index c170af8f..cff6a64a 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -73,9 +73,13 @@ export interface RouteComponentInterface { * Guard called whenever the route that renders this component has changed but * it is reused for the new route. This allows you to guard for changes in params, * the query or the hash. + * @param to RouteLocation we are navigating to + * @param from RouteLocation we are navigating from + * @param next function to validate, cancel or modify (by redirectering) the navigation */ beforeRouteUpdate?: NavigationGuard } + // TODO: have a real type with augmented properties // export type RouteComponent = TODO & RouteComponentInterface export type RouteComponent = {