From: Eduardo San Martin Morote Date: Tue, 25 Jun 2024 15:22:01 +0000 (+0200) Subject: chore: build location X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b908aa75af7b08e3076f41bccc243fc280a37935;p=thirdparty%2Fvuejs%2Frouter.git chore: build location --- diff --git a/packages/router/src/new-matcher/matcher-pattern.ts b/packages/router/src/new-matcher/matcher-pattern.ts index 021b975c..bb993658 100644 --- a/packages/router/src/new-matcher/matcher-pattern.ts +++ b/packages/router/src/new-matcher/matcher-pattern.ts @@ -13,12 +13,12 @@ export interface MatcherPattern { name: MatcherName /** - * Extracts from a formatted, unencoded params object the ones belonging to the path, query, and hash. If any of them is missing, returns `null`. TODO: throw instead? + * Extracts from a formatted, unencoded params object the ones belonging to the path, query, and hash. * @param params - Params to extract from. */ unformatParams( params: MatcherParamsFormatted - ): [path: MatcherPathParams, query: MatcherQueryParams, hash: string | null] + ): [path: MatcherPathParams, query: MatcherQueryParams, hash: string] /** * Extracts the defined params from an encoded path, query, and hash parsed from a URL. Does not apply formatting or @@ -44,7 +44,7 @@ export interface MatcherPattern { path: string query: MatcherQueryParams hash: string - }): [path: MatcherPathParams, query: MatcherQueryParams, hash: string | null] + }): [path: MatcherPathParams, query: MatcherQueryParams, hash: string] /** * Takes encoded params object to form the `path`, @@ -59,7 +59,7 @@ export interface MatcherPattern { formatParams( path: MatcherPathParams, query: MatcherQueryParams, - hash: string | null + hash: string ): MatcherParamsFormatted } @@ -82,13 +82,16 @@ export interface PatternHashParamOptions extends PatternParamOptions_Base {} export interface MatcherPatternPath { + build(path: MatcherPathParams): string match(path: string): MatcherPathParams format(params: MatcherPathParams): MatcherParamsFormatted + unformat(params: MatcherParamsFormatted): MatcherPathParams } export interface MatcherPatternQuery { match(query: MatcherQueryParams): MatcherQueryParams format(params: MatcherQueryParams): MatcherParamsFormatted + unformat(params: MatcherParamsFormatted): MatcherQueryParams } export interface MatcherPatternHash { @@ -98,6 +101,7 @@ export interface MatcherPatternHash { */ match(hash: string): string format(hash: string): MatcherParamsFormatted + unformat(params: MatcherParamsFormatted): string } export class MatcherPatternImpl implements MatcherPattern { @@ -133,12 +137,16 @@ export class MatcherPatternImpl implements MatcherPattern { } buildPath(path: MatcherPathParams): string { - return '' + return this.path.build(path) } unformatParams( params: MatcherParamsFormatted - ): [path: MatcherPathParams, query: MatcherQueryParams, hash: string | null] { - throw new Error('Method not implemented.') + ): [path: MatcherPathParams, query: MatcherQueryParams, hash: string] { + return [ + this.path.unformat(params), + this.query?.unformat(params) ?? {}, + this.hash?.unformat(params) ?? '', + ] } } diff --git a/packages/router/src/new-matcher/matcher.spec.ts b/packages/router/src/new-matcher/matcher.spec.ts index 29f6a40a..9c6ccb2f 100644 --- a/packages/router/src/new-matcher/matcher.spec.ts +++ b/packages/router/src/new-matcher/matcher.spec.ts @@ -11,93 +11,212 @@ function createMatcherPattern( const EMPTY_PATH_PATTERN_MATCHER = { match: (path: string) => ({}), format: (params: {}) => ({}), + unformat: (params: {}) => ({}), + build: () => '/', } satisfies MatcherPatternPath describe('Matcher', () => { describe('resolve()', () => { - it('resolves string locations with no params', () => { - const matcher = createCompiledMatcher() - matcher.addRoute( - createMatcherPattern(Symbol('foo'), EMPTY_PATH_PATTERN_MATCHER) - ) - - expect(matcher.resolve('/foo?a=a&b=b#h')).toMatchObject({ - path: '/foo', - params: {}, - query: { a: 'a', b: 'b' }, - hash: '#h', + describe('absolute locationss as strings', () => { + it('resolves string locations with no params', () => { + const matcher = createCompiledMatcher() + matcher.addRoute( + createMatcherPattern(Symbol('foo'), EMPTY_PATH_PATTERN_MATCHER) + ) + + expect(matcher.resolve('/foo?a=a&b=b#h')).toMatchObject({ + path: '/foo', + params: {}, + query: { a: 'a', b: 'b' }, + hash: '#h', + }) }) - }) - it('resolves string locations with params', () => { - const matcher = createCompiledMatcher() - matcher.addRoute( - // /users/:id - createMatcherPattern(Symbol('foo'), { - match: (path: string) => { - const match = path.match(/^\/foo\/([^/]+?)$/) - if (!match) throw new Error('no match') - return { id: match[1] } + it('resolves string locations with params', () => { + const matcher = createCompiledMatcher() + matcher.addRoute( + // /users/:id + createMatcherPattern(Symbol('foo'), { + match: (path: string) => { + const match = path.match(/^\/foo\/([^/]+?)$/) + if (!match) throw new Error('no match') + return { id: match[1] } + }, + format: (params: { id: string }) => ({ id: Number(params.id) }), + unformat: (params: { id: number }) => ({ id: String(params.id) }), + build: params => `/foo/${params.id}`, + }) + ) + + expect(matcher.resolve('/foo/1?a=a&b=b#h')).toMatchObject({ + path: '/foo/1', + params: { id: 1 }, + query: { a: 'a', b: 'b' }, + hash: '#h', + }) + expect(matcher.resolve('/foo/54?a=a&b=b#h')).toMatchObject({ + path: '/foo/54', + params: { id: 54 }, + query: { a: 'a', b: 'b' }, + hash: '#h', + }) + }) + + it('resolve string locations with query', () => { + const matcher = createCompiledMatcher() + matcher.addRoute( + createMatcherPattern(Symbol('foo'), EMPTY_PATH_PATTERN_MATCHER, { + match: query => ({ + id: Array.isArray(query.id) ? query.id[0] : query.id, + }), + format: (params: { id: string }) => ({ id: Number(params.id) }), + unformat: (params: { id: number }) => ({ id: String(params.id) }), + }) + ) + + expect(matcher.resolve('/foo?id=100&b=b#h')).toMatchObject({ + params: { id: 100 }, + path: '/foo', + query: { + id: '100', + b: 'b', }, - format: (params: { id: string }) => ({ id: Number(params.id) }), + hash: '#h', + }) + }) + + it('resolves string locations with hash', () => { + const matcher = createCompiledMatcher() + matcher.addRoute( + createMatcherPattern( + Symbol('foo'), + EMPTY_PATH_PATTERN_MATCHER, + undefined, + { + match: hash => hash, + format: hash => ({ a: hash.slice(1) }), + unformat: ({ a }) => '#a', + } + ) + ) + + expect(matcher.resolve('/foo?a=a&b=b#bar')).toMatchObject({ + hash: '#bar', + params: { a: 'bar' }, + path: '/foo', + query: { a: 'a', b: 'b' }, }) - ) + }) - expect(matcher.resolve('/foo/1?a=a&b=b#h')).toMatchObject({ - path: '/foo/1', - params: { id: 1 }, - query: { a: 'a', b: 'b' }, - hash: '#h', + it('returns a valid location with an empty `matched` array if no match', () => { + const matcher = createCompiledMatcher() + expect(matcher.resolve('/bar')).toMatchInlineSnapshot( + { + hash: '', + matched: [], + params: {}, + path: '/bar', + query: {}, + }, + ` + { + "fullPath": "/bar", + "hash": "", + "matched": [], + "name": Symbol(no-match), + "params": {}, + "path": "/bar", + "query": {}, + } + ` + ) }) - expect(matcher.resolve('/foo/54?a=a&b=b#h')).toMatchObject({ - path: '/foo/54', - params: { id: 54 }, - query: { a: 'a', b: 'b' }, - hash: '#h', + + it('resolves string locations with all', () => { + const matcher = createCompiledMatcher() + matcher.addRoute( + createMatcherPattern( + Symbol('foo'), + { + build: params => `/foo/${params.id}`, + match: path => { + const match = path.match(/^\/foo\/([^/]+?)$/) + if (!match) throw new Error('no match') + return { id: match[1] } + }, + format: params => ({ id: Number(params.id) }), + unformat: params => ({ id: String(params.id) }), + }, + { + match: query => ({ + id: Array.isArray(query.id) ? query.id[0] : query.id, + }), + format: params => ({ q: Number(params.id) }), + unformat: params => ({ id: String(params.q) }), + }, + { + match: hash => hash, + format: hash => ({ a: hash.slice(1) }), + unformat: ({ a }) => '#a', + } + ) + ) + + expect(matcher.resolve('/foo/1?id=100#bar')).toMatchObject({ + hash: '#bar', + params: { id: 1, q: 100, a: 'bar' }, + }) }) }) - it('resolve string locations with query', () => { - const matcher = createCompiledMatcher() - matcher.addRoute( - createMatcherPattern(Symbol('foo'), EMPTY_PATH_PATTERN_MATCHER, { - match: query => ({ - id: Array.isArray(query.id) ? query.id[0] : query.id, - }), - format: (params: { id: string }) => ({ id: Number(params.id) }), + describe('relative locations as strings', () => { + it('resolves a simple relative location', () => { + const matcher = createCompiledMatcher() + matcher.addRoute( + createMatcherPattern(Symbol('foo'), EMPTY_PATH_PATTERN_MATCHER) + ) + + expect( + matcher.resolve('foo', matcher.resolve('/nested/')) + ).toMatchObject({ + params: {}, + path: '/nested/foo', + query: {}, + hash: '', + }) + expect( + matcher.resolve('../foo', matcher.resolve('/nested/')) + ).toMatchObject({ + params: {}, + path: '/foo', + query: {}, + hash: '', + }) + expect( + matcher.resolve('./foo', matcher.resolve('/nested/')) + ).toMatchObject({ + params: {}, + path: '/nested/foo', + query: {}, + hash: '', }) - ) - - expect(matcher.resolve('/foo?id=100&b=b#h')).toMatchObject({ - params: { id: 100 }, - path: '/foo', - query: { - id: '100', - b: 'b', - }, - hash: '#h', }) }) - it('resolves string locations with hash', () => { - const matcher = createCompiledMatcher() - matcher.addRoute( - createMatcherPattern( - Symbol('foo'), - EMPTY_PATH_PATTERN_MATCHER, - undefined, - { - match: hash => hash, - format: hash => ({ a: hash.slice(1) }), - } + describe('named locations', () => { + it('resolves named locations with no params', () => { + const matcher = createCompiledMatcher() + matcher.addRoute( + createMatcherPattern('home', EMPTY_PATH_PATTERN_MATCHER) ) - ) - expect(matcher.resolve('/foo?a=a&b=b#bar')).toMatchObject({ - hash: '#bar', - params: { a: 'bar' }, - path: '/foo', - query: { a: 'a', b: 'b' }, + expect(matcher.resolve({ name: 'home', params: {} })).toMatchObject({ + name: 'home', + path: '/', + params: {}, + query: {}, + hash: '', + }) }) }) }) diff --git a/packages/router/src/new-matcher/matcher.ts b/packages/router/src/new-matcher/matcher.ts index bd48a124..5d204f7b 100644 --- a/packages/router/src/new-matcher/matcher.ts +++ b/packages/router/src/new-matcher/matcher.ts @@ -187,6 +187,12 @@ function transformObject( return encoded } +export const NO_MATCH_LOCATION = { + name: Symbol('no-match'), + params: {}, + matched: [], +} satisfies Omit + export function createCompiledMatcher(): NEW_Matcher_Resolve { const matchers = new Map() @@ -220,13 +226,21 @@ export function createCompiledMatcher(): NEW_Matcher_Resolve { if (parsedParams) break } } + + // No match location if (!parsedParams || !matcher) { - throw new Error(`No matcher found for location "${location}"`) + return { + ...url, + ...NO_MATCH_LOCATION, + query: transformObject(decode, decode, url.query), + hash: decode(url.hash), + } } + // TODO: build fullPath return { + ...url, name: matcher.name, - path: url.path, params: parsedParams, query: transformObject(decode, decode, url.query), hash: decode(url.hash), @@ -244,11 +258,6 @@ export function createCompiledMatcher(): NEW_Matcher_Resolve { const params = location.params ?? currentLocation!.params const mixedUnencodedParams = matcher.unformatParams(params) - // TODO: they could just throw? - if (!mixedUnencodedParams) { - throw new Error(`Missing params for matcher "${String(name)}"`) - } - const path = matcher.buildPath( // encode the values before building the path transformObject(String, encodeParam, mixedUnencodedParams[0])