]> git.ipfire.org Git - thirdparty/vuejs/router.git/commitdiff
feat: allow empty path match
authorEduardo San Martin Morote <posva13@gmail.com>
Wed, 12 Feb 2020 14:18:07 +0000 (15:18 +0100)
committerEduardo San Martin Morote <posva13@gmail.com>
Wed, 12 Feb 2020 14:18:07 +0000 (15:18 +0100)
__tests__/matcher/__snapshots__/resolve.spec.ts.snap
__tests__/matcher/addingRemoving.spec.ts
__tests__/matcher/resolve.spec.ts
src/history/html5.ts
src/history/memory.ts
src/matcher/index.ts

index 187ce4327dc82723e7ca4c2ebf2478131569c360..1c56326491244c6275cbe876aea76494a0bb0259 100644 (file)
@@ -5,16 +5,6 @@ exports[`Router Matcher resolve LocationAsName throws if the named route does no
 {"name":"Home"}]
 `;
 
-exports[`Router Matcher resolve LocationAsPath disallows multiple trailing slashes 1`] = `
-[NoRouteMatchError: No match for
-{"path":"/home//"}]
-`;
-
-exports[`Router Matcher resolve LocationAsPath throws if the path does not exists 1`] = `
-[NoRouteMatchError: No match for
-{"path":"/foo"}]
-`;
-
 exports[`Router Matcher resolve LocationAsRelative redirects throws if relative location when redirecting 1`] = `
 [InvalidRouteMatch: Cannot redirect using a relative location:
 {
index 03000212e237e008424b5d5ae44b14d5c3b30f4d..474c5b641a124c69ce24a2bf3145c80948370e15 100644 (file)
@@ -19,9 +19,10 @@ describe('normalizeRouteRecord', () => {
       const matcher = createRouterMatcher([], {})
       const remove = matcher.addRoute({ path: '/', component, name: 'home' })
       remove()
-      expect(() => {
-        matcher.resolve({ path: '/' }, currentLocation)
-      }).toThrow()
+      expect(matcher.resolve({ path: '/' }, currentLocation)).toMatchObject({
+        name: undefined,
+        matched: [],
+      })
     })
 
     it.todo('remove aliases')
@@ -33,10 +34,10 @@ describe('normalizeRouteRecord', () => {
     const matcher = createRouterMatcher([], {})
     matcher.addRoute({ path: '/', component, name: 'home' })
     matcher.removeRoute('home')
-    // TODO: this sholud probably return an empty `matched` instead. It needs extra refactoring
-    expect(() => {
-      matcher.resolve({ path: '/' }, currentLocation)
-    }).toThrow()
+    expect(matcher.resolve({ path: '/' }, currentLocation)).toMatchObject({
+      name: undefined,
+      matched: [],
+    })
   })
 
   it('removes children', () => {
@@ -52,9 +53,10 @@ describe('normalizeRouteRecord', () => {
     })
 
     matcher.removeRoute('home')
-    expect(() => {
-      matcher.resolve({ path: '/about' }, currentLocation)
-    }).toThrow()
+    expect(matcher.resolve({ path: '/about' }, currentLocation)).toMatchObject({
+      name: undefined,
+      matched: [],
+    })
   })
 
   it.todo('removes alias by name')
index 8c2139f2f8f2e928b76814effd488b894bfa2569..befa9e50bad725e5a095a4babef1a73266d67d54 100644 (file)
@@ -202,16 +202,12 @@ describe('Router Matcher', () => {
         )
       })
 
-      it('throws if the path does not exists', () => {
-        expect(
-          assertErrorMatch({ path: '/', components }, { path: '/foo' })
-        ).toMatchSnapshot()
-      })
-
-      it('disallows multiple trailing slashes', () => {
-        expect(
-          assertErrorMatch({ path: '/home/', components }, { path: '/home//' })
-        ).toMatchSnapshot()
+      it('returns an empty match when the path does not exist', () => {
+        assertRecordMatch(
+          { path: '/', components },
+          { path: '/foo' },
+          { name: undefined, params: {}, path: '/foo', matched: [] }
+        )
       })
 
       it('allows an optional trailing slash', () => {
index b3a885620b63a1503f02c4fe2b4b42c9a0c8820e..6e6f09c5a9fe5fbca2d23ae7e3f7fd9f3a517846 100644 (file)
@@ -212,17 +212,20 @@ function useHistoryStateNavigation(base: string) {
     }
   }
 
-  // TODO: allow data as well
-  function replace(to: RawHistoryLocation) {
+  function replace(to: RawHistoryLocation, data?: HistoryState) {
     const normalized = normalizeHistoryLocation(to)
     // cs.info('replace', location, normalized)
 
-    const state: StateEntry = buildState(
-      historyState.value.back,
-      normalized,
-      historyState.value.forward,
-      true
-    )
+    const state: StateEntry = {
+      ...buildState(
+        historyState.value.back,
+        // keep back and forward entries but override current position
+        normalized,
+        historyState.value.forward,
+        true
+      ),
+      ...data,
+    }
     if (historyState) state.position = historyState.value.position
 
     changeLocation(normalized, state, true)
index 3deb11195961edf3816f52bd37a96f38d26bce46..9650419924281db47dfb4527f2f827c71b934cfd 100644 (file)
@@ -55,7 +55,6 @@ export default function createMemoryHistory(base: string = ''): RouterHistory {
   const routerHistory: RouterHistory = {
     // rewritten by Object.defineProperty
     location: START,
-    // TODO: acutally use it
     base,
 
     replace(to) {
index 6ade595d8f432b6b25fcfce7ec5ea8d5ea9b5a22..3b8ea8a63f53e12d3a1e3a0ceeea413567bb25d4 100644 (file)
@@ -31,11 +31,6 @@ interface RouterMatcher {
   ) => MatcherLocationNormalized
 }
 
-const TRAILING_SLASH_RE = /(.)\/+$/
-function removeTrailingSlash(path: string): string {
-  return path.replace(TRAILING_SLASH_RE, '$1')
-}
-
 export function createRouterMatcher(
   routes: RouteRecord[],
   globalOptions: PathParserOptions
@@ -50,15 +45,12 @@ export function createRouterMatcher(
   ) {
     const mainNormalizedRecord = normalizeRouteRecord(record)
     const options: PathParserOptions = { ...globalOptions, ...record.options }
-    // TODO: can probably be removed now that we have our own parser and we handle this correctly
-    if (!options.strict)
-      mainNormalizedRecord.path = removeTrailingSlash(mainNormalizedRecord.path)
     // generate an array of records to correctly handle aliases
     const normalizedRecords: RouteRecordNormalized[] = [mainNormalizedRecord]
     // TODO: remember aliases in records to allow active in router-link
-    if ('alias' in record && record.alias) {
+    if ('alias' in record) {
       const aliases =
-        typeof record.alias === 'string' ? [record.alias] : record.alias
+        typeof record.alias === 'string' ? [record.alias] : record.alias!
       for (const alias of aliases) {
         normalizedRecords.push({
           ...mainNormalizedRecord,
@@ -67,7 +59,7 @@ export function createRouterMatcher(
       }
     }
 
-    let addedMatchers: RouteRecordMatcher[] = []
+    let matcher: RouteRecordMatcher
 
     for (const normalizedRecord of normalizedRecords) {
       let { path } = normalizedRecord
@@ -78,11 +70,7 @@ export function createRouterMatcher(
       }
 
       // create the object before hand so it can be passed to children
-      const matcher = createRouteRecordMatcher(
-        normalizedRecord,
-        parent,
-        options
-      )
+      matcher = createRouteRecordMatcher(normalizedRecord, parent, options)
 
       if ('children' in record) {
         for (const childRecord of record.children!)
@@ -90,16 +78,16 @@ export function createRouterMatcher(
       }
 
       insertMatcher(matcher)
-      addedMatchers.push(matcher)
     }
 
     return () => {
-      // TODO: not the good method because it should work when passing a string too
-      addedMatchers.forEach(removeRoute)
+      // since other matchers are aliases, they should should be removed by any of the matchers
+      removeRoute(matcher)
     }
   }
 
   function removeRoute(matcherRef: string | RouteRecordMatcher) {
+    // TODO: remove aliases (needs to keep them in the RouteRecordMatcher first)
     if (typeof matcherRef === 'string') {
       const matcher = matcherMap.get(matcherRef)
       if (matcher) {
@@ -152,26 +140,22 @@ export function createRouterMatcher(
 
       name = matcher.record.name
       // TODO: merge params with current location. Should this be done by name. I think there should be some kind of relationship between the records like children of a parent should keep parent props but not the rest
+      // needs an RFC if breaking change
       params = location.params || currentLocation.params
-      // params are automatically encoded
-      // TODO: try catch to provide better error messages
+      // throws if cannot be stringified
       path = matcher.stringify(params)
     } else if ('path' in location) {
       matcher = matchers.find(m => m.re.test(location.path))
       // matcher should have a value after the loop
 
-      // TODO: if no matcher, return the location with an empty matched array
-      // to allow non existent matches
-      // TODO: warning of unused params if provided
-      if (!matcher) throw new NoRouteMatchError(location)
-
-      params = matcher.parse(location.path)!
       // no need to resolve the path with the matcher as it was provided
       // this also allows the user to control the encoding
-      // TODO: check if the note above regarding encoding is still true
       path = location.path
-      name = matcher.record.name
-
+      if (matcher) {
+        // TODO: dev warning of unused params if provided
+        params = matcher.parse(location.path)!
+        name = matcher.record.name
+      }
       // location is a relative path
     } else {
       // match by name or path of current route
@@ -197,7 +181,8 @@ export function createRouterMatcher(
       path,
       params,
       matched,
-      meta: matcher.record.meta || {},
+      // TODO: merge all meta properties from parent to child
+      meta: (matcher && matcher.record.meta) || {},
     }
   }