--- /dev/null
+// @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<import('../src/router').RouterOptions> & { routes: import('../src/types').RouteRecord[]}} options
+ */
+function createRouter(options) {
+ return new Router({
+ history: new HTML5History(),
+ ...options,
+ })
+}
+
+const Home = { template: `<div>Home</div>` }
+const Foo = { template: `<div>Foo</div>` }
+
+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(
+ `<!DOCTYPE html><html><head></head><body></body></html>`,
+ {
+ 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')
+ })
+})
const Home = { template: `<div>Home</div>` }
const Foo = { template: `<div>Foo</div>` }
+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', () => {
})
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', () => {})
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()
})
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')
- })
})
import { Router, HTML5History } from '../src'
-const component = null
+const component = {
+ template: `<div>A component</div>`,
+}
const r = new Router({
history: new HTML5History(),
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()
}
// params: ReturnType<PT>
// }
+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