]> git.ipfire.org Git - thirdparty/vuejs/router.git/commitdiff
feat(guards): support errors in navigation guards
authorEduardo San Martin Morote <posva13@gmail.com>
Wed, 8 Apr 2020 15:41:10 +0000 (17:41 +0200)
committerEduardo San Martin Morote <posva13@gmail.com>
Wed, 8 Apr 2020 15:41:10 +0000 (17:41 +0200)
__tests__/guards/navigationGuards.spec.ts [new file with mode: 0644]
src/navigationGuards.ts
src/types/index.ts

diff --git a/__tests__/guards/navigationGuards.spec.ts b/__tests__/guards/navigationGuards.spec.ts
new file mode 100644 (file)
index 0000000..2ade465
--- /dev/null
@@ -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)
+    }
+  })
+})
index e647c79deef3a6fab016a691c3e6f77d3c2f4559..119a0685aa7d409e4ed8592b81be149119df2477 100644 (file)
@@ -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<NavigationRedirectError>(
               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)
       )
index e1ce660c740bf83925cf1a9f145f9249e12c5f9a..2d089b4d22165b8177df0dbc99a301922892911d 100644 (file)
@@ -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