]> git.ipfire.org Git - thirdparty/vuejs/router.git/commitdiff
feat(matcher): allow case sensitive routes
authorEduardo San Martin Morote <posva13@gmail.com>
Wed, 17 Jul 2019 14:43:53 +0000 (16:43 +0200)
committerEduardo San Martin Morote <posva13@gmail.com>
Wed, 17 Jul 2019 14:43:53 +0000 (16:43 +0200)
__tests__/matcher-ranking.spec.js
src/matcher.ts

index 77483e2dee7ac841548ef8d1db004283d2ff776a..6b94bf4f749790a8ef8ac8a9cca1793bad597d7f 100644 (file)
@@ -2,8 +2,6 @@
 require('./helper')
 const expect = require('expect')
 const { createRouteMatcher } = require('../src/matcher')
-const { START_LOCATION_NORMALIZED } = require('../src/types')
-const { normalizeRouteRecord } = require('./utils')
 
 /** @type {RouteComponent} */
 const component = null
@@ -14,6 +12,7 @@ const component = null
 /** @typedef {import('../src/types').MatcherLocation} MatcherLocation */
 /** @typedef {import('../src/types').MatcherLocationRedirect} MatcherLocationRedirect */
 /** @typedef {import('../src/types').MatcherLocationNormalized} MatcherLocationNormalized */
+/** @typedef {import('../src/matcher').RouteMatcher} RouteMatcher */
 /** @typedef {import('path-to-regexp').RegExpOptions} RegExpOptions */
 
 function stringifyOptions(options) {
@@ -32,13 +31,15 @@ describe('createRouteMatcher', () => {
         return [pathOrCombined[0], { ...options, ...pathOrCombined[1] }]
       return [pathOrCombined, options]
     })
+
+    /** @type {Array<RouteMatcher & { _options: RegExpOptions }>} */
     const matchers = normalizedPaths
       .slice()
       // Because sorting order is conserved, allows to mismatch order on
       // routes with the same ranking
       .reverse()
-      .map(([path, options]) =>
-        createRouteMatcher(
+      .map(([path, options]) => ({
+        ...createRouteMatcher(
           {
             // @ts-ignore types are correct
             path,
@@ -46,8 +47,10 @@ describe('createRouteMatcher', () => {
           },
           null,
           options
-        )
-      )
+        ),
+        // add original options
+        _options: options,
+      }))
       .sort((a, b) => b.score - a.score)
 
     expect(matchers.map(matcher => matcher.record.path)).toEqual(
@@ -63,9 +66,9 @@ describe('createRouteMatcher', () => {
       } catch (e) {
         throw new Error(
           `Record "${a.record.path}"${stringifyOptions(
-            normalizedPaths[i - 1][1]
+            matchers[i - 1]._options
           )} and "${b.record.path}"${stringifyOptions(
-            normalizedPaths[i][1]
+            matchers[i]._options
           )} have the same score: ${
             a.score
           }. Avoid putting routes with the same score on the same test`
@@ -136,6 +139,14 @@ describe('createRouteMatcher', () => {
     ])
   })
 
+  it('ending slashes less than params', () => {
+    checkPathOrder([
+      ['/a/:b/', { strict: true }],
+      ['/a/b', { strict: false }],
+      ['/a/:b', { strict: true }],
+    ])
+  })
+
   it('prioritizes ending slashes', () => {
     checkPathOrder([
       // no strict
@@ -156,4 +167,13 @@ describe('createRouteMatcher', () => {
       '/a',
     ])
   })
+
+  it('prioritizes case sensitive', () => {
+    checkPathOrder([
+      ['/a/', { sensitive: true }],
+      '/a/', // explicit ending slash
+      ['/a', { sensitive: true }],
+      '/a', // also matches /A
+    ])
+  })
 })
index 617343818d3cc662d1c3d76e42e50eadd441ef03..f6d34fd3008c4bce922c11a1ac6201f1768371d4 100644 (file)
@@ -12,7 +12,7 @@ import { NoRouteMatchError, InvalidRouteMatch } from './errors'
 
 type NormalizedRouteRecord = Exclude<RouteRecord, { component: any }> // normalize component/components into components
 
-interface RouteMatcher {
+export interface RouteMatcher {
   re: RegExp
   resolve: (params?: RouteParams) => string
   record: NormalizedRouteRecord
@@ -57,6 +57,7 @@ enum PathScore {
   SubWildcard = 1, // Wildcard as a subsegment
   Repeatable = -0.5, // /:w+ or /:w*
   Strict = 0.5, // when options strict: true is passed, as the regex omits \/?
+  CaseSensitive = 0.25, // when options strict: true is passed, as the regex omits \/?
   Optional = -4, // /:w? or /:w*
   SubOptional = -0.1, // optional inside a subsegment /a-:w? or /a-:w*
   Root = 1, // just /
@@ -78,14 +79,16 @@ export function createRouteMatcher(
   // to compute the score of routes
   const resolve = pathToRegexp.tokensToFunction([...tokens])
 
-  let score = options.strict ? PathScore.Strict : 0
+  let score =
+    (options.strict ? PathScore.Strict : 0) +
+    (options.sensitive ? PathScore.CaseSensitive : 0)
 
   // console.log(tokens)
   // console.log('--- GROUPING ---')
 
   // special case for root path
   if (tokens.length === 1 && tokens[0] === '/') {
-    score = 5
+    score = PathScore.Segment + PathScore.Root
   } else {
     // allows us to group tokens into one single segment
     // it will point to the first token of the current group
@@ -293,6 +296,10 @@ export class RouterMatcher {
       for (const childRecord of record.children) {
         this.addRouteRecord(childRecord, matcher)
       }
+      // TODO: the parent is special, we should match their children. They
+      // reference to the parent so we can render the parent
+      //
+      // matcher.score = -10
     }
 
     this.insertMatcher(matcher)