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
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`,
formatParams(
path: MatcherPathParams,
query: MatcherQueryParams,
- hash: string | null
+ hash: string
): MatcherParamsFormatted
}
extends PatternParamOptions_Base<string> {}
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 {
*/
match(hash: string): string
format(hash: string): MatcherParamsFormatted
+ unformat(params: MatcherParamsFormatted): string
}
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) ?? '',
+ ]
}
}
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: '',
+ })
})
})
})