From 23ed08d983f308b7b118f2a235e58d29bf1994ec Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Wed, 8 Apr 2020 17:41:10 +0200 Subject: [PATCH] feat(guards): support errors in navigation guards --- __tests__/guards/navigationGuards.spec.ts | 111 ++++++++++++++++++++++ src/navigationGuards.ts | 10 +- src/types/index.ts | 1 + 3 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 __tests__/guards/navigationGuards.spec.ts diff --git a/__tests__/guards/navigationGuards.spec.ts b/__tests__/guards/navigationGuards.spec.ts new file mode 100644 index 00000000..2ade465a --- /dev/null +++ b/__tests__/guards/navigationGuards.spec.ts @@ -0,0 +1,111 @@ +import { guardToPromiseFn } from '../../src/navigationGuards' +import { START_LOCATION_NORMALIZED } from '../../src/types' +import { ErrorTypes } from '../../src/errors' + +// stub those two +const to = START_LOCATION_NORMALIZED +const from = { + ...START_LOCATION_NORMALIZED, + path: '/other', + fullPath: '/other', +} + +describe('guardToPromiseFn', () => { + it('calls the guard with to, from and, next', async () => { + const spy = jest.fn((to, from, next) => next()) + await expect(guardToPromiseFn(spy, to, from)()).resolves + expect(spy).toHaveBeenCalledWith(to, from, expect.any(Function)) + }) + + it('resolves if next is called with no arguments', async () => { + await expect(guardToPromiseFn((to, from, next) => next(), to, from)()) + .resolves + }) + + it('resolves if next is called with true', async () => { + await expect(guardToPromiseFn((to, from, next) => next(true), to, from)()) + .resolves + }) + + it('rejects if next is called with false', async () => { + expect.assertions(1) + try { + await guardToPromiseFn((to, from, next) => next(false), to, from)() + } catch (err) { + expect(err).toMatchObject({ + from, + to, + type: ErrorTypes.NAVIGATION_ABORTED, + }) + } + }) + + it('rejects if next is called with a string location', async () => { + expect.assertions(1) + try { + await guardToPromiseFn((to, from, next) => next('/new'), to, from)() + } catch (err) { + expect(err).toMatchObject({ + from: to, + to: '/new', + type: ErrorTypes.NAVIGATION_GUARD_REDIRECT, + }) + } + }) + + it('rejects if next is called with an object location', async () => { + expect.assertions(1) + let redirectTo = { path: '/new' } + try { + await guardToPromiseFn((to, from, next) => next(redirectTo), to, from)() + } catch (err) { + expect(err).toMatchObject({ + from: to, + to: redirectTo, + type: ErrorTypes.NAVIGATION_GUARD_REDIRECT, + }) + } + }) + + it('rejects if next is called with an error', async () => { + expect.assertions(1) + let error = new Error('nope') + try { + await guardToPromiseFn((to, from, next) => next(error), to, from)() + } catch (err) { + expect(err).toBe(error) + } + }) + + it('rejects if guard rejects a Promise', async () => { + expect.assertions(1) + let error = new Error('nope') + try { + await guardToPromiseFn( + async (to, from, next) => { + throw error + }, + to, + from + )() + } catch (err) { + expect(err).toBe(error) + } + }) + + it('rejects if guard throws an error', async () => { + expect.assertions(1) + let error = new Error('nope') + try { + await guardToPromiseFn( + (to, from, next) => { + throw error + }, + to, + from + )() + } catch (err) { + expect(err).toBe(error) + } + }) +}) diff --git a/src/navigationGuards.ts b/src/navigationGuards.ts index e647c79d..119a0685 100644 --- a/src/navigationGuards.ts +++ b/src/navigationGuards.ts @@ -59,7 +59,7 @@ export function guardToPromiseFn< return () => new Promise((resolve, reject) => { const next: NavigationGuardCallback = ( - valid?: boolean | RouteLocationRaw | NavigationGuardNextCallback + valid?: boolean | RouteLocationRaw | NavigationGuardNextCallback | Error ) => { if (valid === false) reject( @@ -68,7 +68,9 @@ export function guardToPromiseFn< to, }) ) - else if (isRouteLocation(valid)) { + else if (valid instanceof Error) { + reject(valid) + } else if (isRouteLocation(valid)) { reject( createRouterError( ErrorTypes.NAVIGATION_GUARD_REDIRECT, @@ -78,8 +80,6 @@ export function guardToPromiseFn< } ) ) - } else if (valid instanceof Error) { - // TODO } else { // TODO: call the in component enter callbacks. Maybe somewhere else // record && record.enterCallbacks.push(valid) @@ -87,7 +87,7 @@ export function guardToPromiseFn< } } - // TODO: write tests + // wrapping with Promise.resolve allows it to work with both async and sync guards Promise.resolve(guard.call(instance, to, from, next)).catch(err => reject(err) ) diff --git a/src/types/index.ts b/src/types/index.ts index e1ce660c..2d089b4d 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -274,6 +274,7 @@ export interface MatcherLocation // where the navigation guard callback is defined export interface NavigationGuardCallback { (): void + (error: Error): void (location: RouteLocationRaw): void (valid: boolean): void (cb: (vm: any) => void): void -- 2.39.5