From: Eduardo San Martin Morote Date: Thu, 2 May 2019 10:05:50 +0000 (+0200) Subject: feat: call beforeRouteLeave X-Git-Tag: v4.0.0-alpha.0~415 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f3423150b537dc09d6ece6cf0f75c7c58925831c;p=thirdparty%2Fvuejs%2Frouter.git feat: call beforeRouteLeave --- diff --git a/__tests__/guards/component-beforeRouteLeave.spec.js b/__tests__/guards/component-beforeRouteLeave.spec.js new file mode 100644 index 00000000..ad25fdeb --- /dev/null +++ b/__tests__/guards/component-beforeRouteLeave.spec.js @@ -0,0 +1,73 @@ +// @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 beforeRouteLeave = jest.fn() +/** @type {import('../../src/types').RouteRecord[]} */ +const routes = [ + { path: '/', component: Home }, + { path: '/foo', component: Foo }, + { + path: '/guard', + component: { + ...Foo, + beforeRouteLeave, + }, + }, +] + +beforeEach(() => { + beforeRouteLeave.mockReset() +}) + +describe('beforeRouteLeave', () => { + beforeAll(() => { + createDom() + }) + + NAVIGATION_TYPES.forEach(navigationMethod => { + describe(navigationMethod, () => { + it('calls beforeRouteLeave guard on navigation', async () => { + const router = createRouter({ routes }) + beforeRouteLeave.mockImplementationOnce((to, from, next) => { + if (to.path === 'foo') next(false) + else next() + }) + await router.push('/guard') + expect(beforeRouteLeave).not.toHaveBeenCalled() + + await router[navigationMethod]('/foo') + expect(beforeRouteLeave).toHaveBeenCalledTimes(1) + }) + + it('can cancel navigation', async () => { + const router = createRouter({ routes }) + beforeRouteLeave.mockImplementationOnce(async (to, from, next) => { + next(false) + }) + await router.push('/guard') + const p = router[navigationMethod]('/') + expect(router.currentRoute.fullPath).toBe('/guard') + await p.catch(err => {}) // catch the navigation abortion + expect(router.currentRoute.fullPath).toBe('/guard') + }) + }) + }) +}) diff --git a/explorations/html5.ts b/explorations/html5.ts index 0028a17d..c4080080 100644 --- a/explorations/html5.ts +++ b/explorations/html5.ts @@ -1,9 +1,20 @@ import { Router, HTML5History } from '../src' +import { RouteComponent } from '../src/types' -const component = { +const component: RouteComponent = { template: `
A component
`, } +const GuardedWithLeave: RouteComponent = { + template: `
+

try to leave

+
`, + beforeRouteLeave(to, from, next) { + if (window.confirm()) next() + else next(false) + }, +} + const r = new Router({ history: new HTML5History(), routes: [ @@ -14,11 +25,12 @@ const r = new Router({ path: '/with-guard/:n', name: 'guarded', component, - beforeEnter: (to, from, next) => { + beforeEnter(to, from, next) { if (to.params.n !== 'valid') next(false) next() }, }, + { path: '/cant-leave', component: GuardedWithLeave }, // { path: /^\/about\/?$/, component }, ], }) diff --git a/src/router.ts b/src/router.ts index 4ba45d1d..10909139 100644 --- a/src/router.ts +++ b/src/router.ts @@ -100,7 +100,30 @@ export class Router { // elements and other stuff let guards: Array<() => Promise> = [] + // TODO: ensure we are leaving since we could just be changing params or not changing anything + // TODO: is it okay to resolve all matched component or should we do it in order + await Promise.all( + from.matched.map(async ({ component }) => { + // TODO: cache async routes per record + const resolvedComponent = await (typeof component === 'function' + ? component() + : component) + if (resolvedComponent.beforeRouteLeave) { + // TODO: handle the next callback + guards.push( + guardToPromiseFn(resolvedComponent.beforeRouteLeave, to, from) + ) + } + }) + ) + + // run the queue of per route beforeEnter guards + for (const guard of guards) { + await guard() + } + // check global guards first + guards = [] for (const guard of this.beforeGuards) { guards.push(guardToPromiseFn(guard, to, from)) } diff --git a/src/types/index.ts b/src/types/index.ts index 536743d9..078bb9c5 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -58,8 +58,10 @@ export interface RouteLocationNormalized // params: ReturnType // } +// TODO: type this for beforeRouteUpdate and beforeRouteLeave export interface RouteComponentInterface { beforeRouteEnter?: NavigationGuard + beforeRouteLeave?: NavigationGuard } // TODO: have a real type with augmented properties // export type RouteComponent = TODO & RouteComponentInterface @@ -120,8 +122,10 @@ export interface NavigationGuardCallback { (): void (valid: false): void } -export interface NavigationGuard { + +export interface NavigationGuard { ( + this: V, to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardCallback diff --git a/tsconfig.json b/tsconfig.json index 02bf6e57..6331e3a7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,5 @@ { - "include": ["src/**/*.ts"], + "include": ["src/**/*.ts", "explorations/**/*.ts"], "compilerOptions": { /* Basic Options */ "target": "esnext" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */,