From: Eduardo San Martin Morote Date: Wed, 1 May 2019 13:23:59 +0000 (+0200) Subject: feat: add per component beforeRouteEnter X-Git-Tag: v4.0.0-alpha.0~424 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f9f5ca56cfac3b79589b320f1e94284b625e979a;p=thirdparty%2Fvuejs%2Frouter.git feat: add per component beforeRouteEnter --- diff --git a/__tests__/per-component-before-guards.spec.js b/__tests__/per-component-before-guards.spec.js new file mode 100644 index 00000000..2c215cdc --- /dev/null +++ b/__tests__/per-component-before-guards.spec.js @@ -0,0 +1,83 @@ +// @ts-check +require('./helper') +const expect = require('expect') +const { HTML5History } = require('../src/history/html5') +const { Router } = require('../src/router') +const { JSDOM } = require('jsdom') +const fakePromise = require('faked-promise') + +const tick = () => new Promise(resolve => process.nextTick(resolve)) + +/** + * @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 beforeRouteEnter = jest.fn() +/** @type {import('../src/types').RouteRecord[]} */ +const routes = [ + { path: '/', component: Home }, + { path: '/foo', component: Foo }, + { + path: '/guard/:n', + component: { + ...Foo, + beforeRouteEnter, + }, + }, +] + +beforeEach(() => { + beforeRouteEnter.mockReset() +}) + +describe('navigation guards', () => { + beforeAll(() => { + // TODO: move to utils for tests that need DOM + const dom = new JSDOM( + ``, + { + url: 'https://example.org/', + referrer: 'https://example.com/', + contentType: 'text/html', + } + ) + + // @ts-ignore + global.window = dom.window + }) + + it('calls beforeRouteEnter guards on push', async () => { + const router = createRouter({ routes }) + beforeRouteEnter.mockImplementationOnce((to, from, next) => { + if (to.params.n !== 'valid') return next(false) + next() + }) + await router.push('/guard/valid') + expect(beforeRouteEnter).toHaveBeenCalledTimes(1) + }) + + it.skip('calls beforeEnter guards on replace', () => {}) + + it.skip('waits before navigating', async () => { + const [promise, resolve] = fakePromise() + const router = createRouter({ routes }) + beforeRouteEnter.mockImplementationOnce(async (to, from, next) => { + await promise + next() + }) + const p = router.push('/foo') + expect(router.currentRoute.fullPath).toBe('/') + resolve() + await p + expect(router.currentRoute.fullPath).toBe('/foo') + }) +}) diff --git a/__tests__/per-router-before-guards.spec.js b/__tests__/per-route-before-guards.spec.js similarity index 60% rename from __tests__/per-router-before-guards.spec.js rename to __tests__/per-route-before-guards.spec.js index 36477914..1422c708 100644 --- a/__tests__/per-router-before-guards.spec.js +++ b/__tests__/per-route-before-guards.spec.js @@ -21,10 +21,16 @@ function createRouter(options) { const Home = { template: `
Home
` } const Foo = { template: `
Foo
` } +const beforeEnter = jest.fn() /** @type {import('../src/types').RouteRecord[]} */ const routes = [ { path: '/', component: Home }, { path: '/foo', component: Foo }, + { + path: '/guard/:n', + component: Foo, + beforeEnter, + }, ] describe('navigation guards', () => { @@ -44,23 +50,13 @@ describe('navigation guards', () => { }) it('calls beforeEnter guards on push', async () => { - const spy = jest.fn() - const router = createRouter({ - routes: [ - ...routes, - { - path: '/guard/:n', - component: Foo, - beforeEnter: spy, - }, - ], - }) - spy.mockImplementationOnce((to, from, next) => { + const router = createRouter({ routes }) + beforeEnter.mockImplementationOnce((to, from, next) => { if (to.params.n !== 'valid') return next(false) next() }) await router.push('/guard/valid') - expect(spy).toHaveBeenCalledTimes(1) + expect(beforeEnter).toHaveBeenCalledTimes(1) }) it.skip('calls beforeEnter guards on replace', () => {}) @@ -68,7 +64,7 @@ describe('navigation guards', () => { it('waits before navigating', async () => { const [promise, resolve] = fakePromise() const router = createRouter({ routes }) - router.beforeEach(async (to, from, next) => { + beforeEnter.mockImplementationOnce(async (to, from, next) => { await promise next() }) @@ -78,34 +74,4 @@ describe('navigation guards', () => { await p expect(router.currentRoute.fullPath).toBe('/foo') }) - - it('waits in the right order', async () => { - const [p1, r1] = fakePromise() - const [p2, r2] = fakePromise() - const router = createRouter({ routes }) - const guard1 = jest.fn(async (to, from, next) => { - await p1 - next() - }) - router.beforeEach(guard1) - const guard2 = jest.fn(async (to, from, next) => { - await p2 - next() - }) - router.beforeEach(guard2) - let navigation = router.push('/foo') - expect(router.currentRoute.fullPath).toBe('/') - expect(guard1).toHaveBeenCalled() - expect(guard2).not.toHaveBeenCalled() - r1() - // wait until the guard is called - await tick() - await tick() - expect(guard2).toHaveBeenCalled() - r2() - expect(router.currentRoute.fullPath).toBe('/') - await navigation - expect(guard2).toHaveBeenCalled() - expect(router.currentRoute.fullPath).toBe('/foo') - }) }) diff --git a/explorations/html5.ts b/explorations/html5.ts index 132c3c72..0028a17d 100644 --- a/explorations/html5.ts +++ b/explorations/html5.ts @@ -1,6 +1,8 @@ import { Router, HTML5History } from '../src' -const component = null +const component = { + template: `
A component
`, +} const r = new Router({ history: new HTML5History(), diff --git a/src/router.ts b/src/router.ts index cf560e65..466cb3c0 100644 --- a/src/router.ts +++ b/src/router.ts @@ -103,7 +103,24 @@ export class Router { guards.push(guardToPromiseFn(record.beforeEnter, to, from)) } - // run the queue of guards + // run the queue of per route beforeEnter guards + for (const guard of guards) { + await guard() + } + + // check in-component beforeRouteEnter + guards = [] + await Promise.all( + to.matched.map(async ({ component }) => { + // TODO: handle async routes + if (component.beforeRouteEnter) { + // TODO: handle the next callback + guards.push(guardToPromiseFn(component.beforeRouteEnter, to, from)) + } + }) + ) + + // run the queue of per route beforeEnter guards for (const guard of guards) { await guard() } diff --git a/src/types/index.ts b/src/types/index.ts index 52cf760e..d8c326fa 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -52,13 +52,22 @@ export interface RouteLocationNormalized // params: ReturnType // } +export interface RouteComponentInterface { + beforeRouteEnter?: NavigationGuard +} +// TODO: have a real type with augmented properties +// export type RouteComponent = TODO & RouteComponentInterface +export type RouteComponent = { + template?: string +} & RouteComponentInterface + // NOTE not sure the whole PropsTransformer thing can be usefull // since in callbacks we don't know where we are coming from // and I don't thin it's possible to filter out the route // by any means export interface RouteRecord { path: string // | RegExp - component: TODO + component: RouteComponent name?: string beforeEnter?: NavigationGuard // props: PT