]> git.ipfire.org Git - thirdparty/vuejs/router.git/commitdiff
refactor: matched components always contain a `components` object
authorEduardo San Martin Morote <posva13@gmail.com>
Mon, 1 Jul 2019 12:26:27 +0000 (14:26 +0200)
committerEduardo San Martin Morote <posva13@gmail.com>
Mon, 1 Jul 2019 12:26:27 +0000 (14:26 +0200)
__tests__/extractComponentsGuards.spec.js
__tests__/matcher.spec.js
__tests__/router-view.spec.js
src/components/View.ts
src/matcher.ts
src/types/index.ts
src/utils/index.ts

index 0cc9863bfb437c060151e3483c46acbaf1ab1970..3b1663e6e244732863730039321faa5134514103 100644 (file)
@@ -1,6 +1,7 @@
 // @ts-check
 require('./helper')
 const expect = require('expect')
+const { normalizeRecord } = require('../src/matcher')
 const { extractComponentsGuards } = require('../src/utils')
 const { START_LOCATION_NORMALIZED } = require('../src/types')
 const { components } = require('./utils')
@@ -66,12 +67,14 @@ beforeEach(() => {
 
 /**
  *
- * @param {MatchedRouteRecord[]} components
+ * @param {Exclude<RouteRecord, RouteRecordRedirect>[]} components
  */
 async function checkGuards(components, n) {
   beforeRouteEnter.mockClear()
+  const ncomps = components.map(normalizeRecord)
   const guards = await extractComponentsGuards(
-    components,
+    // type is fine as we excluded RouteRecordRedirect in components argument
+    components.map(normalizeRecord),
     'beforeRouteEnter',
     to,
     from
index b0453cb63cd8a4ac5a28f9b4565a8206d809c39d..d33afbc3b3044b7fe712b7dadab89a66921caad7 100644 (file)
@@ -17,7 +17,7 @@ describe('Router Matcher', () => {
      *
      * @param {RouteRecord | RouteRecord[]} record Record or records we are testing the matcher against
      * @param {MatcherLocation} location location we want to reolve against
-     * @param {Partial<MatcherLocationNormalized>} resolved Expected resolved location given by the matcher
+     * @param {Partial<MatcherLocationNormalized & { component: any }>} resolved Expected resolved location given by the matcher
      * @param {MatcherLocationNormalized} [start] Optional currentLocation used when resolving
      */
     function assertRecordMatch(
@@ -49,6 +49,15 @@ describe('Router Matcher', () => {
         resolved.params = resolved.params || {}
       }
 
+      for (const matched of resolved.matched) {
+        if ('component' in matched) {
+          // @ts-ignore
+          matched.components = { default: matched.component }
+          // @ts-ignore
+          delete matched.component
+        }
+      }
+
       const result = matcher.resolve(
         {
           ...targetLocation,
@@ -646,9 +655,7 @@ describe('Router Matcher', () => {
               },
               {
                 ...NestedChildWithParam,
-                path: `${Foo.path}/${NestedWithParam.path}/${
-                  NestedChildWithParam.path
-                }`,
+                path: `${Foo.path}/${NestedWithParam.path}/${NestedChildWithParam.path}`,
               },
             ],
           }
@@ -677,9 +684,7 @@ describe('Router Matcher', () => {
               },
               {
                 ...NestedChildWithParam,
-                path: `${Foo.path}/${NestedWithParam.path}/${
-                  NestedChildWithParam.path
-                }`,
+                path: `${Foo.path}/${NestedWithParam.path}/${NestedChildWithParam.path}`,
               },
             ],
           }
index 075436b543fa7965e10a7bca54bc2f3409d23d68..3b28aed35760dbb316ca7d0f2eeb3cf37c634038 100644 (file)
@@ -3,32 +3,39 @@
  *
  */
 // @ts-check
-require("./helper");
-const expect = require("expect");
-const { default: RouterView } = require("../src/components/View");
-const { components, isMocha } = require("./utils");
+require('./helper')
+const expect = require('expect')
+const { default: RouterView } = require('../src/components/View')
+const { components, isMocha } = require('./utils')
 
-describe("RouterView", () => {
+/** @typedef {import('../src/types').RouteLocationNormalized} RouteLocationNormalized */
+
+/** @type {Record<string, RouteLocationNormalized>} */
+const routes = {
+  root: {
+    fullPath: '/',
+    name: undefined,
+    path: '/',
+    query: {},
+    params: {},
+    hash: '',
+    meta: {},
+    matched: [{ components: { default: components.Home }, path: '/' }],
+  },
+}
+
+describe('RouterView', () => {
   // skip these tests on mocha because @vue/test-utils
   // do not work correctly
-  if (isMocha()) return;
-  const { mount } = require("@vue/test-utils");
+  if (isMocha()) return
+  const { mount } = require('@vue/test-utils')
 
-  it("displays current route component", async () => {
+  it('displays current route component', async () => {
     const wrapper = await mount(RouterView, {
       mocks: {
-        $route: {
-          fullPath: "/",
-          name: undefined,
-          path: "/",
-          query: {},
-          params: {},
-          hash: "",
-          meta: {},
-          matched: [{ component: components.Home, path: "/", name: undefined }]
-        }
-      }
-    });
-    expect(wrapper.html()).toMatchInlineSnapshot(`"<div>Home</div>"`);
-  });
-});
+        $route: routes.root,
+      },
+    })
+    expect(wrapper.html()).toMatchInlineSnapshot(`"<div>Home</div>"`)
+  })
+})
index db04a5a7c9897e1f264bed2887329c631ef8758f..ee887ec81b4f997b7f23045c02ba0c122d8ef024 100644 (file)
@@ -6,7 +6,14 @@ const View: Component = {
   name: 'RouterView',
   functional: true,
 
-  render(_, { children, parent, data }) {
+  props: {
+    name: {
+      type: String,
+      default: 'default',
+    },
+  },
+
+  render(_, { children, parent, data, props }) {
     // used by devtools to display a router-view badge
     // @ts-ignore
     data.routerView = true
@@ -23,7 +30,7 @@ const View: Component = {
     // render empty node if no matched route
     if (!matched) return h()
 
-    const component = matched.component
+    const component = matched.components[props.name]
 
     return h(component, data, children)
   },
index e8944a3b33d3292e743b8631b939f16e8ec15676..8ce1a603c908153d7b0d9281495976c6b67aeae8 100644 (file)
@@ -5,19 +5,42 @@ import {
   MatcherLocation,
   MatcherLocationNormalized,
   MatcherLocationRedirect,
+  // TODO: add it to matched
+  // MatchedRouteRecord,
 } from './types/index'
 import { NoRouteMatchError, InvalidRouteMatch } from './errors'
 
+type NormalizedRouteRecord = Exclude<RouteRecord, { component: any }> // normalize component/components into components
+
 interface RouteMatcher {
   re: RegExp
   resolve: (params?: RouteParams) => string
-  record: RouteRecord // TODO: NormalizedRouteRecord?
+  record: NormalizedRouteRecord
   parent: RouteMatcher | void
   // TODO: children so they can be removed
   // children: RouteMatcher[]
   keys: string[]
 }
 
+/**
+ * Normalizes a RouteRecord into a MatchedRouteRecord. Creates a copy
+ * @param record
+ * @returns the normalized version
+ */
+export function normalizeRecord(
+  record: Readonly<RouteRecord>
+): NormalizedRouteRecord {
+  if ('component' in record) {
+    const { component, ...rest } = record
+    // @ts-ignore
+    rest.components = { default: component }
+    return rest as NormalizedRouteRecord
+  }
+
+  // otherwise just create a copy
+  return { ...record }
+}
+
 export class RouterMatcher {
   private matchers: RouteMatcher[] = []
 
@@ -34,7 +57,8 @@ export class RouterMatcher {
     const keys: pathToRegexp.Key[] = []
     const options: pathToRegexp.RegExpOptions = {}
 
-    const recordCopy = { ...record }
+    const recordCopy = normalizeRecord(record)
+
     if (parent) {
       // if the child isn't an absolute route
       if (record.path[0] !== '/') {
@@ -108,9 +132,7 @@ export class RouterMatcher {
         const value = result[i + 1]
         if (!value) {
           throw new Error(
-            `Error parsing path "${
-              location.path
-            }" when looking for param "${key}"`
+            `Error parsing path "${location.path}" when looking for param "${key}"`
           )
         }
         params[key] = value
index 2a00284808f9b3475231be2876d21e77be3fb2c0..fd36a445a632a512b07096751367147fcafc2488 100644 (file)
@@ -37,8 +37,12 @@ export type RouteLocation =
   | RouteQueryAndHash & LocationAsName & RouteLocationOptions
   | RouteQueryAndHash & LocationAsRelative & RouteLocationOptions
 
-// exposed to the user in a very consistant way
-export type MatchedRouteRecord = Exclude<RouteRecord, RouteRecordRedirect>
+// A matched record cannot be a redirection and must contain
+// a normalized version of components with { default: Component } instead of `component`
+export type MatchedRouteRecord = Exclude<
+  RouteRecord,
+  RouteRecordRedirect | RouteRecordSingleView
+>
 
 export interface RouteLocationNormalized
   extends Required<RouteQueryAndHash & LocationAsRelative & LocationAsPath> {
@@ -114,6 +118,8 @@ interface RouteRecordSingleView extends RouteRecordCommon {
 
 interface RouteRecordMultipleViews extends RouteRecordCommon {
   components: Record<string, RouteComponent | Lazy<RouteComponent>>
+  // TODO: add tests
+  children?: RouteRecord[]
 }
 
 export type RouteRecord =
index 48974b392eae256ff16c1271a30233b857088821..91aa3c50811e86e0b882635e573095b0fd7d9aa4 100644 (file)
@@ -14,8 +14,8 @@ export async function extractComponentsGuards(
   await Promise.all(
     matched.map(async record => {
       // TODO: cache async routes per record
-      if ('component' in record) {
-        const { component } = record
+      for (const name in record.components) {
+        const component = record.components[name]
         const resolvedComponent = await (typeof component === 'function'
           ? component()
           : component)
@@ -24,18 +24,6 @@ export async function extractComponentsGuards(
         if (guard) {
           guards.push(guardToPromiseFn(guard, to, from))
         }
-      } else {
-        for (const name in record.components) {
-          const component = record.components[name]
-          const resolvedComponent = await (typeof component === 'function'
-            ? component()
-            : component)
-
-          const guard = resolvedComponent[guardType]
-          if (guard) {
-            guards.push(guardToPromiseFn(guard, to, from))
-          }
-        }
       }
     })
   )