--- /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>` }
+
+/** @type {import('../src/types').RouteRecord[]} */
+const routes = [
+ { path: '/', component: Home },
+ { path: '/foo', component: Foo },
+]
+
+describe('router.afterEach', () => {
+ 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 afterEach guards on push', async () => {
+ const spy = jest.fn()
+ const router = createRouter({ routes })
+ router.afterEach(spy)
+ await router.push('/foo')
+ expect(spy).toHaveBeenCalledTimes(1)
+ expect(spy).toHaveBeenCalledWith(
+ expect.objectContaining({ fullPath: '/foo' }),
+ expect.objectContaining({ fullPath: '/' })
+ )
+ })
+
+ it.skip('calls afterEach guards on replace', async () => {
+ const spy = jest.fn()
+ const router = createRouter({ routes })
+ router.afterEach(spy)
+ // await router.replace('/foo')
+ expect(spy).toHaveBeenCalledTimes(1)
+ })
+})
NavigationGuard,
TODO,
NavigationGuardCallback,
+ PostNavigationGuard,
} from './types/index'
export interface RouterOptions {
protected history: BaseHistory
private matcher: RouterMatcher
private beforeGuards: NavigationGuard[] = []
+ private afterGuards: PostNavigationGuard[] = []
currentRoute: Readonly<RouteLocationNormalized> = START_LOCATION_NORMALIZED
constructor(options: RouterOptions) {
this.history.listen((to, from, info) => {
// TODO: check navigation guards
const matchedRoute = this.matcher.resolve(to, this.currentRoute)
- console.log({ to, matchedRoute })
+ // console.log({ to, matchedRoute })
// TODO: navigate
this.currentRoute = {
const toLocation: RouteLocationNormalized = { ...url, ...location }
await this.navigate(toLocation, this.currentRoute)
this.history.push(url)
+ const from = this.currentRoute
this.currentRoute = toLocation
+
+ // navigation is confirmed, call afterGuards
+ for (const guard of this.afterGuards) guard(toLocation, from)
}
private async navigate(
)
}
- console.log('Guarding against', guards.length, 'guards')
+ // console.log('Guarding against', guards.length, 'guards')
for (const guard of guards) {
await guard()
}
getRouteRecord(location: RouteLocation) {}
+ /**
+ * Add a global beforeGuard that can confirm, abort or modify a navigation
+ * @param guard
+ */
beforeEach(guard: NavigationGuard): ListenerRemover {
this.beforeGuards.push(guard)
return () => {
this.beforeGuards.splice(this.beforeGuards.indexOf(guard), 1)
}
}
+
+ /**
+ * Add a global after guard that is called once the navigation is confirmed
+ * @param guard
+ */
+ afterEach(guard: PostNavigationGuard): ListenerRemover {
+ this.afterGuards.push(guard)
+ return () => {
+ this.afterGuards.splice(this.afterGuards.indexOf(guard), 1)
+ }
+ }
}
next: NavigationGuardCallback
): any
}
+
+export interface PostNavigationGuard {
+ (to: RouteLocationNormalized, from: RouteLocationNormalized): any
+}