From: Eduardo San Martin Morote Date: Wed, 17 Jul 2019 12:33:31 +0000 (+0200) Subject: feat(matcher): handle strict paths X-Git-Tag: v4.0.0-alpha.0~296 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=22aac2f089af073eff30d114ca784cda01006d53;p=thirdparty%2Fvuejs%2Frouter.git feat(matcher): handle strict paths --- diff --git a/__tests__/matcher-ranking.spec.js b/__tests__/matcher-ranking.spec.js index 7d0cb5a1..77483e2d 100644 --- a/__tests__/matcher-ranking.spec.js +++ b/__tests__/matcher-ranking.spec.js @@ -16,21 +16,31 @@ const component = null /** @typedef {import('../src/types').MatcherLocationNormalized} MatcherLocationNormalized */ /** @typedef {import('path-to-regexp').RegExpOptions} RegExpOptions */ +function stringifyOptions(options) { + return Object.keys(options).length ? ` (${JSON.stringify(options)})` : '' +} + describe('createRouteMatcher', () => { /** * - * @param {string[]} paths + * @param {Array} paths * @param {RegExpOptions} options */ function checkPathOrder(paths, options = {}) { - const matchers = paths + const normalizedPaths = paths.map(pathOrCombined => { + if (Array.isArray(pathOrCombined)) + return [pathOrCombined[0], { ...options, ...pathOrCombined[1] }] + return [pathOrCombined, options] + }) + const matchers = normalizedPaths .slice() // Because sorting order is conserved, allows to mismatch order on // routes with the same ranking .reverse() - .map(path => + .map(([path, options]) => createRouteMatcher( { + // @ts-ignore types are correct path, components: { default: component }, }, @@ -40,7 +50,9 @@ describe('createRouteMatcher', () => { ) .sort((a, b) => b.score - a.score) - expect(matchers.map(matcher => matcher.record.path)).toEqual(paths) + expect(matchers.map(matcher => matcher.record.path)).toEqual( + normalizedPaths.map(([path]) => path) + ) // Fail if two consecutive records have the same record for (let i = 1; i < matchers.length; i++) { @@ -50,7 +62,13 @@ describe('createRouteMatcher', () => { expect(a.score).not.toBe(b.score) } catch (e) { throw new Error( - `Record "${a.record.path}" and "${b.record.path}" have the same score: ${a.score}. Avoid putting routes with the same score on the same test` + `Record "${a.record.path}"${stringifyOptions( + normalizedPaths[i - 1][1] + )} and "${b.record.path}"${stringifyOptions( + normalizedPaths[i][1] + )} have the same score: ${ + a.score + }. Avoid putting routes with the same score on the same test` ) } } @@ -117,4 +135,25 @@ describe('createRouteMatcher', () => { '/:k/b/c/d/:j', ]) }) + + it('prioritizes ending slashes', () => { + checkPathOrder([ + // no strict + '/a/b/', + '/a/b', + '/a/', + '/a', + ]) + + checkPathOrder([ + ['/a/b/', { strict: true }], + '/a/b/', + ['/a/b', { strict: true }], + '/a/b', + ['/a/', { strict: true }], + '/a/', + ['/a', { strict: true }], + '/a', + ]) + }) }) diff --git a/src/matcher.ts b/src/matcher.ts index 442649a6..61734381 100644 --- a/src/matcher.ts +++ b/src/matcher.ts @@ -56,6 +56,7 @@ enum PathScore { Wildcard = -1, // /:namedWildcard(.*) 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 \/? Optional = -4, // /:w? or /:w* SubOptional = -0.1, // optional inside a subsegment /a-:w? or /a-:w* Root = 1, // just / @@ -77,7 +78,7 @@ export function createRouteMatcher( // to compute the score of routes const resolve = pathToRegexp.tokensToFunction([...tokens]) - let score = 0 + let score = options.strict ? PathScore.Strict : 0 // console.log(tokens) // console.log('--- GROUPING ---') @@ -266,7 +267,11 @@ export class RouterMatcher { record: Readonly, parent?: RouteMatcher ): void { - const options: pathToRegexp.RegExpOptions = {} + const options: pathToRegexp.RegExpOptions = { + // NOTE: should we make strict by default and redirect /users/ to /users + // so that it's the same from SEO perspective? + strict: false, + } const recordCopy = normalizeRecord(record)