From: Eduardo San Martin Morote Date: Fri, 31 Jul 2020 14:26:18 +0000 (+0200) Subject: feat(warn): warn if guard returns without calling next X-Git-Tag: v4.0.0-beta.5~13 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6e16bdd6338ea3b7da1f8a0b3000ec880be840d6;p=thirdparty%2Fvuejs%2Frouter.git feat(warn): warn if guard returns without calling next --- diff --git a/__tests__/guards/guardToPromiseFn.spec.ts b/__tests__/guards/guardToPromiseFn.spec.ts index c593c4d6..471502d8 100644 --- a/__tests__/guards/guardToPromiseFn.spec.ts +++ b/__tests__/guards/guardToPromiseFn.spec.ts @@ -1,6 +1,7 @@ import { guardToPromiseFn } from '../../src/navigationGuards' import { START_LOCATION_NORMALIZED } from '../../src/types' import { ErrorTypes } from '../../src/errors' +import { mockWarn } from 'jest-mock-warn' // stub those two const to = START_LOCATION_NORMALIZED @@ -11,6 +12,7 @@ const from = { } describe('guardToPromiseFn', () => { + mockWarn() 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.toEqual(undefined) @@ -232,4 +234,19 @@ describe('guardToPromiseFn', () => { } }) }) + + it('warns if guard resolves without calling next', async () => { + expect.assertions(2) + await expect( + guardToPromiseFn((to, from, next) => false, to, from)() + ).rejects.toEqual(expect.any(Error)) + + // try { + // await guardToPromiseFn((to, from, next) => false, to, from)() + // } catch (error) { + // expect(error).toEqual(expect.any(Error)) + // } + + expect('callback was never called').toHaveBeenWarned() + }) }) diff --git a/src/navigationGuards.ts b/src/navigationGuards.ts index d618f34c..e2e35c6d 100644 --- a/src/navigationGuards.ts +++ b/src/navigationGuards.ts @@ -165,6 +165,17 @@ export function guardToPromiseFn( ) if (guard.length < 3) guardCall.then(next) + if (__DEV__ && guard.length > 2) + guardCall.then(() => { + // @ts-ignore: _called is added at canOnlyBeCalledOnce + if (!next._called) + warn( + `The "next" callback was never called inside of ${ + guard.name ? '"' + guard.name + '"' : '' + }:\n${guard.toString()}\n. If you are returning a value instead of calling "next", make sure to remove the "next" parameter from your function.` + ) + reject(new Error('Invalid navigation guard')) + }) guardCall.catch(err => reject(err)) }) } @@ -180,6 +191,8 @@ function canOnlyBeCalledOnce( warn( `The "next" callback was called more than once in one navigation guard when going from "${from.fullPath}" to "${to.fullPath}". It should be called exactly one time in each navigation guard. This will fail in production.` ) + // @ts-ignore: we put it in the original one because it's easier to check + next._called = true if (called === 1) next.apply(null, arguments as any) } } diff --git a/src/types/index.ts b/src/types/index.ts index 629219e8..fd9e14a2 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -302,6 +302,13 @@ export interface NavigationGuardNext { (location: RouteLocationRaw): void (valid: boolean): void (cb: NavigationGuardNextCallback): void + /** + * Allows to detect if `next` isn't called in a resolved guard. Used + * internally in DEV mode to emit a warning. Commented out to simplify + * typings. + * @internal + */ + // _called: boolean } export type NavigationGuardNextCallback = (vm: ComponentPublicInstance) => any