]> git.ipfire.org Git - thirdparty/vuejs/router.git/commitdiff
feat(warn): improve warning for invalid components
authorEduardo San Martin Morote <posva13@gmail.com>
Tue, 6 Oct 2020 12:55:42 +0000 (14:55 +0200)
committerEduardo San Martin Morote <posva13@gmail.com>
Tue, 6 Oct 2020 13:03:46 +0000 (15:03 +0200)
Close #517

__tests__/guards/extractComponentsGuards.spec.ts
src/navigationGuards.ts

index 19de60626ae9b10ca6d7eff8e13832da4fe43b57..7301ea47da30f10087c389cac9f72220c08cd42a 100644 (file)
@@ -3,6 +3,7 @@ import { START_LOCATION_NORMALIZED, RouteRecordRaw } from '../../src/types'
 import { components } from '../utils'
 import { normalizeRouteRecord } from '../../src/matcher'
 import { RouteRecordNormalized } from 'src/matcher/types'
+import { mockWarn } from 'jest-mock-warn'
 
 const beforeRouteEnter = jest.fn()
 
@@ -11,6 +12,16 @@ const to = START_LOCATION_NORMALIZED
 const from = START_LOCATION_NORMALIZED
 
 const NoGuard: RouteRecordRaw = { path: '/', component: components.Home }
+const InvalidRoute: RouteRecordRaw = {
+  path: '/',
+  // @ts-ignore: intended error
+  component: null,
+}
+const WrongLazyRoute: RouteRecordRaw = {
+  path: '/',
+  // @ts-ignore: intended error
+  component: Promise.resolve(components.Home),
+}
 const SingleGuard: RouteRecordRaw = {
   path: '/',
   component: { ...components.Home, beforeRouteEnter },
@@ -52,6 +63,8 @@ async function checkGuards(
 }
 
 describe('extractComponentsGuards', () => {
+  mockWarn()
+
   it('extracts guards from one single component', async () => {
     await checkGuards([SingleGuard], 1)
   })
@@ -69,4 +82,18 @@ describe('extractComponentsGuards', () => {
     await checkGuards([SingleGuard, SingleGuard], 2)
     await checkGuards([SingleGuardNamed, SingleGuardNamed], 4)
   })
+
+  it('throws if component is null', async () => {
+    // @ts-ignore
+    await expect(checkGuards([InvalidRoute], 2)).rejects.toHaveProperty(
+      'message',
+      expect.stringMatching('Invalid route component')
+    )
+    expect('is not a valid component').toHaveBeenWarned()
+  })
+
+  it('warns wrong lazy component', async () => {
+    await checkGuards([WrongLazyRoute], 0, 1)
+    expect('Promise instead of a function').toHaveBeenWarned()
+  })
 })
index 0ffb9e088004c902dac87a85477e85bf57b34661..410462823c9b016eb9c08b0dca139783ba34f3ef 100644 (file)
@@ -18,10 +18,11 @@ import {
   NavigationRedirectError,
 } from './errors'
 import { ComponentOptions, onUnmounted, onActivated, onDeactivated } from 'vue'
-import { inject, getCurrentInstance, warn } from 'vue'
+import { inject, getCurrentInstance } from 'vue'
 import { matchedRouteKey } from './injectionSymbols'
 import { RouteRecordNormalized } from './matcher/types'
 import { isESModule } from './utils'
+import { warn } from './warning'
 
 function registerGuard(list: NavigationGuard[], guard: NavigationGuard) {
   const removeFromList = () => {
@@ -225,13 +226,32 @@ export function extractComponentsGuards(
   for (const record of matched) {
     for (const name in record.components) {
       let rawComponent = record.components[name]
-      // warn if user wrote import('/component.vue') instead of () => import('./component.vue')
-      if (__DEV__ && 'then' in rawComponent) {
-        warn(
-          `Component "${name}" in record with path "${record.path}" is a Promise instead of a function that returns a Promise. Did you write "import('./MyPage.vue')" instead of "() => import('./MyPage.vue')"? This will break in production if not fixed.`
-        )
-        let promise = rawComponent
-        rawComponent = () => promise
+      if (__DEV__) {
+        if (
+          !rawComponent ||
+          (typeof rawComponent !== 'object' &&
+            typeof rawComponent !== 'function')
+        ) {
+          warn(
+            `Component "${name}" in record with path "${record.path}" is not` +
+              ` a valid component. Received "${String(rawComponent)}".`
+          )
+          // throw to ensure we stop here but warn to ensure the message isn't
+          // missed by the user
+          throw new Error('Invalid route component')
+        } else if ('then' in rawComponent) {
+          // warn if user wrote import('/component.vue') instead of () =>
+          // import('./component.vue')
+          warn(
+            `Component "${name}" in record with path "${record.path}" is a ` +
+              `Promise instead of a function that returns a Promise. Did you ` +
+              `write "import('./MyPage.vue')" instead of ` +
+              `"() => import('./MyPage.vue')" ? This will break in ` +
+              `production if not fixed.`
+          )
+          let promise = rawComponent
+          rawComponent = () => promise
+        }
       }
 
       // skip update and leave guards if the route component is not mounted