]> git.ipfire.org Git - thirdparty/vuejs/router.git/commitdiff
feat(matcher): link aliases to their original record
authorEduardo San Martin Morote <posva13@gmail.com>
Fri, 13 Mar 2020 15:15:47 +0000 (16:15 +0100)
committerEduardo San Martin Morote <posva13@gmail.com>
Fri, 13 Mar 2020 15:15:47 +0000 (16:15 +0100)
__tests__/matcher/resolve.spec.ts
src/matcher/index.ts
src/matcher/path-matcher.ts
src/matcher/types.ts
src/utils/index.ts

index 70be36142c72f909d7439c01437e44fb0de46c3f..51fb50a541d9a5e3bec29977bac996db6f0b10aa 100644 (file)
@@ -177,12 +177,324 @@ describe('Router Matcher', () => {
                 components,
                 aliasOf: expect.objectContaining({ path: '/parent' }),
               },
-              { path: '/p/one', name: 'nested', components },
+              {
+                path: '/p/one',
+                name: 'nested',
+                components,
+                aliasOf: expect.objectContaining({ path: '/parent/one' }),
+              },
             ],
           }
         )
       })
 
+      describe('nested aliases', () => {
+        const children = [
+          {
+            path: 'one',
+            component,
+            name: 'nested',
+            alias: 'o',
+            children: [
+              { path: 'two', alias: 't', name: 'nestednested', component },
+            ],
+          },
+          {
+            path: 'other',
+            alias: 'otherAlias',
+            component,
+            name: 'other',
+          },
+        ]
+        const record = {
+          path: '/parent',
+          name: 'parent',
+          alias: '/p',
+          component,
+          children,
+        }
+
+        it('resolves the parent as an alias', () => {
+          assertRecordMatch(
+            record,
+            { path: '/p' },
+            expect.objectContaining({
+              path: '/p',
+              name: 'parent',
+              matched: [
+                expect.objectContaining({
+                  path: '/p',
+                  aliasOf: expect.objectContaining({ path: '/parent' }),
+                }),
+              ],
+            })
+          )
+        })
+
+        describe('multiple children', () => {
+          // tests concerning the /parent/other path and its aliases
+
+          it('resolves the alias parent', () => {
+            assertRecordMatch(
+              record,
+              { path: '/p/other' },
+              expect.objectContaining({
+                path: '/p/other',
+                name: 'other',
+                matched: [
+                  expect.objectContaining({
+                    path: '/p',
+                    aliasOf: expect.objectContaining({ path: '/parent' }),
+                  }),
+                  expect.objectContaining({
+                    path: '/p/other',
+                    aliasOf: expect.objectContaining({ path: '/parent/other' }),
+                  }),
+                ],
+              })
+            )
+          })
+
+          it('resolves the alias child', () => {
+            assertRecordMatch(
+              record,
+              { path: '/parent/otherAlias' },
+              expect.objectContaining({
+                path: '/parent/otherAlias',
+                name: 'other',
+                matched: [
+                  expect.objectContaining({
+                    path: '/parent',
+                    aliasOf: undefined,
+                  }),
+                  expect.objectContaining({
+                    path: '/parent/otherAlias',
+                    aliasOf: expect.objectContaining({ path: '/parent/other' }),
+                  }),
+                ],
+              })
+            )
+          })
+
+          it('resolves the alias parent and child', () => {
+            assertRecordMatch(
+              record,
+              { path: '/p/otherAlias' },
+              expect.objectContaining({
+                path: '/p/otherAlias',
+                name: 'other',
+                matched: [
+                  expect.objectContaining({
+                    path: '/p',
+                    aliasOf: expect.objectContaining({ path: '/parent' }),
+                  }),
+                  expect.objectContaining({
+                    path: '/p/otherAlias',
+                    aliasOf: expect.objectContaining({ path: '/parent/other' }),
+                  }),
+                ],
+              })
+            )
+          })
+        })
+
+        it('resolves the original one with no aliases', () => {
+          assertRecordMatch(
+            record,
+            { path: '/parent/one/two' },
+            expect.objectContaining({
+              path: '/parent/one/two',
+              name: 'nestednested',
+              matched: [
+                expect.objectContaining({
+                  path: '/parent',
+                  aliasOf: undefined,
+                }),
+                expect.objectContaining({
+                  path: '/parent/one',
+                  aliasOf: undefined,
+                }),
+                expect.objectContaining({
+                  path: '/parent/one/two',
+                  aliasOf: undefined,
+                }),
+              ],
+            })
+          )
+        })
+
+        it('resolves when parent is an alias', () => {
+          assertRecordMatch(
+            record,
+            { path: '/p/one/two' },
+            expect.objectContaining({
+              path: '/p/one/two',
+              name: 'nestednested',
+              matched: [
+                expect.objectContaining({
+                  path: '/p',
+                  aliasOf: expect.objectContaining({ path: '/parent' }),
+                }),
+                expect.objectContaining({
+                  path: '/p/one',
+                  aliasOf: expect.objectContaining({ path: '/parent/one' }),
+                }),
+                expect.objectContaining({
+                  path: '/p/one/two',
+                  aliasOf: expect.objectContaining({ path: '/parent/one/two' }),
+                }),
+              ],
+            })
+          )
+        })
+
+        it('resolves a different child when parent is an alias', () => {
+          assertRecordMatch(
+            record,
+            { path: '/p/other' },
+            expect.objectContaining({
+              path: '/p/other',
+              name: 'other',
+              matched: [
+                expect.objectContaining({
+                  path: '/p',
+                  aliasOf: expect.objectContaining({ path: '/parent' }),
+                }),
+                expect.objectContaining({
+                  path: '/p/other',
+                  aliasOf: expect.objectContaining({ path: '/parent/other' }),
+                }),
+              ],
+            })
+          )
+        })
+
+        it('resolves when the first child is an alias', () => {
+          assertRecordMatch(
+            record,
+            { path: '/parent/o/two' },
+            expect.objectContaining({
+              path: '/parent/o/two',
+              name: 'nestednested',
+              matched: [
+                expect.objectContaining({
+                  path: '/parent',
+                  aliasOf: undefined,
+                }),
+                expect.objectContaining({
+                  path: '/parent/o',
+                  aliasOf: expect.objectContaining({ path: '/parent/one' }),
+                }),
+                expect.objectContaining({
+                  path: '/parent/o/two',
+                  aliasOf: expect.objectContaining({ path: '/parent/one/two' }),
+                }),
+              ],
+            })
+          )
+        })
+
+        it('resolves when the second child is an alias', () => {
+          assertRecordMatch(
+            record,
+            { path: '/parent/one/t' },
+            expect.objectContaining({
+              path: '/parent/one/t',
+              name: 'nestednested',
+              matched: [
+                expect.objectContaining({
+                  path: '/parent',
+                  aliasOf: undefined,
+                }),
+                expect.objectContaining({
+                  path: '/parent/one',
+                  aliasOf: undefined,
+                }),
+                expect.objectContaining({
+                  path: '/parent/one/t',
+                  aliasOf: expect.objectContaining({ path: '/parent/one/two' }),
+                }),
+              ],
+            })
+          )
+        })
+
+        it('resolves when the two last children are aliases', () => {
+          assertRecordMatch(
+            record,
+            { path: '/parent/o/t' },
+            expect.objectContaining({
+              path: '/parent/o/t',
+              name: 'nestednested',
+              matched: [
+                expect.objectContaining({
+                  path: '/parent',
+                  aliasOf: undefined,
+                }),
+                expect.objectContaining({
+                  path: '/parent/o',
+                  aliasOf: expect.objectContaining({ path: '/parent/one' }),
+                }),
+                expect.objectContaining({
+                  path: '/parent/o/t',
+                  aliasOf: expect.objectContaining({ path: '/parent/one/two' }),
+                }),
+              ],
+            })
+          )
+        })
+
+        it('resolves when all are aliases', () => {
+          assertRecordMatch(
+            record,
+            { path: '/p/o/t' },
+            expect.objectContaining({
+              path: '/p/o/t',
+              name: 'nestednested',
+              matched: [
+                expect.objectContaining({
+                  path: '/p',
+                  aliasOf: expect.objectContaining({ path: '/parent' }),
+                }),
+                expect.objectContaining({
+                  path: '/p/o',
+                  aliasOf: expect.objectContaining({ path: '/parent/one' }),
+                }),
+                expect.objectContaining({
+                  path: '/p/o/t',
+                  aliasOf: expect.objectContaining({ path: '/parent/one/two' }),
+                }),
+              ],
+            })
+          )
+        })
+
+        it('resolves when first and last are aliases', () => {
+          assertRecordMatch(
+            record,
+            { path: '/p/one/t' },
+            expect.objectContaining({
+              path: '/p/one/t',
+              name: 'nestednested',
+              matched: [
+                expect.objectContaining({
+                  path: '/p',
+                  aliasOf: expect.objectContaining({ path: '/parent' }),
+                }),
+                expect.objectContaining({
+                  path: '/p/one',
+                  aliasOf: expect.objectContaining({ path: '/parent/one' }),
+                }),
+                expect.objectContaining({
+                  path: '/p/one/t',
+                  aliasOf: expect.objectContaining({ path: '/parent/one/two' }),
+                }),
+              ],
+            })
+          )
+        })
+      })
+
       it('resolves the original path of the named children of a route with an alias', () => {
         const children = [{ path: 'one', component, name: 'nested' }]
         assertRecordMatch(
index 97802d99e2653fbe504fe2e1232abc9964a9824b..621eb226c84a7626634eb2bdbb5096382d1c8aab 100644 (file)
@@ -47,9 +47,12 @@ export function createRouterMatcher(
   // TODO: add routes to children of parent
   function addRoute(
     record: Readonly<RouteRecord>,
-    parent?: RouteRecordMatcher
+    parent?: RouteRecordMatcher,
+    originalRecord?: RouteRecordMatcher
   ) {
-    const mainNormalizedRecord = normalizeRouteRecord(record)
+    let mainNormalizedRecord = normalizeRouteRecord(record)
+    // we might be the child of an alias
+    mainNormalizedRecord.aliasOf = originalRecord && originalRecord.record
     const options: PathParserOptions = { ...globalOptions, ...record.options }
     // generate an array of records to correctly handle aliases
     const normalizedRecords: RouteRecordNormalized[] = [mainNormalizedRecord]
@@ -60,7 +63,10 @@ export function createRouterMatcher(
         normalizedRecords.push({
           ...mainNormalizedRecord,
           path: alias,
-          aliasOf: mainNormalizedRecord,
+          // we might be the child of an alias
+          aliasOf: originalRecord
+            ? originalRecord.record
+            : mainNormalizedRecord,
         })
       }
     }
@@ -83,11 +89,19 @@ export function createRouterMatcher(
       // create the object before hand so it can be passed to children
       matcher = createRouteRecordMatcher(normalizedRecord, parent, options)
 
-      if ('children' in record) {
-        for (const childRecord of record.children!)
-          addRoute(childRecord, matcher)
+      let children = mainNormalizedRecord.children
+      for (let i = 0; i < children.length; i++) {
+        addRoute(
+          children[i],
+          matcher,
+          originalRecord && originalRecord.children[i]
+        )
       }
 
+      // if there was no original record, then the first one was not an alias and all
+      // other alias (if any) need to reference this record when adding children
+      originalRecord = originalRecord || matcher
+
       insertMatcher(matcher)
     }
 
@@ -235,8 +249,8 @@ export function normalizeRouteRecord(
   return {
     path: record.path,
     components,
-    // fallback to empty array for monomorphic objects
-    children: (record as any).children,
+    // record is an object and if it has a children property, it's an array
+    children: (record as any).children || [],
     name: record.name,
     beforeEnter,
     meta: record.meta || {},
index beb933398621e8a8e972c99d3f105c043e9c8376..0d315e289da3f00822d83f2374a238faf2af22d6 100644 (file)
@@ -23,9 +23,18 @@ export function createRouteRecordMatcher(
     ...parser,
     record,
     parent,
+    // these needs to be populated by the parent
     children: [],
   }
 
-  if (parent) parent.children.push(matcher)
+  if (parent) {
+    // both are aliases or both are not aliases
+    // we don't want to mix them because the order is used when
+    // passing originalRecord in Matcher.addRoute
+    if (!matcher.record.aliasOf === !parent.record.aliasOf)
+      parent.children.push(matcher)
+    // else TODO: save alias children to be able to remove them
+  }
+
   return matcher
 }
index 58e2603901b5b57595347e0f5ca4cc4e08337557..d29c8423e89b9440ad194df407916e4370a4cb34 100644 (file)
@@ -5,7 +5,7 @@ export interface RouteRecordNormalized {
   path: RouteRecordMultipleViews['path']
   name: RouteRecordMultipleViews['name']
   components: RouteRecordMultipleViews['components']
-  children: RouteRecordMultipleViews['children']
+  children: Exclude<RouteRecordMultipleViews['children'], void>
   meta: Exclude<RouteRecordMultipleViews['meta'], void>
   beforeEnter: RouteRecordMultipleViews['beforeEnter']
   leaveGuards: NavigationGuard[]
index 755bc85cb64035e5659bc0c761289e3cb27bea32..e401b24b482bb2e57c9511d76929f4735843c928 100644 (file)
@@ -55,11 +55,10 @@ export function isSameRouteRecord(
   a: Immutable<RouteRecordNormalized>,
   b: Immutable<RouteRecordNormalized>
 ): boolean {
-  return (
-    a === b || a.aliasOf === b || b.aliasOf === a
-    // TODO: doesn't work if not named and parent is an alias but child is not
-    // || (!!a.name && a.name === b.name)
-  )
+  // since the original record has an undefined value for aliasOf
+  // but all aliases point to the original record, this will always compare
+  // the original record
+  return (a.aliasOf || a) === (b.aliasOf || b)
 }
 
 export function isSameLocationObject(