Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

simplified approach to ensuring Url type is returned from urlAssembly #308

Merged
merged 8 commits into from
Nov 16, 2024
2 changes: 1 addition & 1 deletion docs/api/types/RouterResolve.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,4 @@ If source is `Url`, expected type for params is `never`. Else when source is `TS

## Returns

`string`
`Url`
2 changes: 1 addition & 1 deletion src/components/routerLink.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import { RouterPushOptions } from '@/types/routerPush'
import { Url, isUrl } from '@/types/url'

type ToCallback = (resolve: RegisteredRouter['resolve']) => string
type ToCallback = (resolve: RegisteredRouter['resolve']) => Url

type RouterLinkProps = {
/**
Expand Down
4 changes: 2 additions & 2 deletions src/services/combinePath.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ test('given 2 paths, returns new Path joined together', () => {

const response = combinePath(aPath, bPath)

expect(response.toString()).toBe('/foo/bar')
expect(response.value).toBe('/foo/bar')
})

test('given 2 paths with params, returns new Path joined together with params', () => {
Expand All @@ -18,7 +18,7 @@ test('given 2 paths with params, returns new Path joined together with params',

const response = combinePath(aPath, bPath)

expect(response.toString()).toBe('/[foz]/[?baz]')
expect(response.value).toBe('/[foz]/[?baz]')
expect(Object.keys(response.params)).toMatchObject(['foz', '?baz'])
})

Expand Down
1 change: 0 additions & 1 deletion src/services/combinePath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,5 @@ export function combinePath(parentPath: Path, childPath: Path): Path {
return {
value: newPathString,
params: { ...parentPath.params, ...childPath.params },
toString: () => newPathString,
}
}
4 changes: 2 additions & 2 deletions src/services/combineQuery.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ test('given 2 queries, returns new Query joined together', () => {

const response = combineQuery(aQuery, bQuery)

expect(response.toString()).toBe('foo=ABC&bar=123')
expect(response.value).toBe('foo=ABC&bar=123')
})

test('given 2 queries with params, returns new Query joined together with params', () => {
Expand All @@ -18,7 +18,7 @@ test('given 2 queries with params, returns new Query joined together with params

const response = combineQuery(aQuery, bQuery)

expect(response.toString()).toBe('foo=[foz]&bar=[?baz]')
expect(response.value).toBe('foo=[foz]&bar=[?baz]')
expect(Object.keys(response.params)).toMatchObject(['foz', '?baz'])
})

Expand Down
1 change: 0 additions & 1 deletion src/services/combineQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,5 @@ export function combineQuery(parentQuery: Query, childQuery: Query): Query {
return {
value: newQueryString,
params: { ...parentQuery.params, ...childQuery.params },
toString: () => newQueryString,
}
}
4 changes: 3 additions & 1 deletion src/services/createRouterResolve.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,5 +88,7 @@ test('when given an external route with params in host, interpolates param value
test('given a route with hash, interpolates hash value', () => {
const resolve = createRouterResolve(routes)

expect(resolve('parentA', { paramA: 'bar' }, { hash: 'foo' })).toBe('/parentA/bar#foo')
const url = resolve('parentA', { paramA: 'bar' }, { hash: 'foo' })

expect(url).toBe('/parentA/bar#foo')
})
6 changes: 3 additions & 3 deletions src/services/createRouterResolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,16 @@ type RouterResolveArgs<
export type RouterResolve<
TRoutes extends Routes
> = {
<TSource extends RoutesName<TRoutes>>(name: TSource, ...args: RouterResolveArgs<TRoutes, TSource>): string,
(url: Url, options?: RouterResolveOptions): string,
<TSource extends RoutesName<TRoutes>>(name: TSource, ...args: RouterResolveArgs<TRoutes, TSource>): Url,
(url: Url, options?: RouterResolveOptions): Url,
}

export function createRouterResolve<const TRoutes extends Routes>(routes: TRoutes): RouterResolve<TRoutes> {
return (
source: Url | RoutesName<TRoutes>,
paramsOrOptions?: Record<string, unknown>,
maybeOptions?: RouterResolveOptions,
): string => {
): Url => {
if (isUrl(source)) {
const options: RouterPushOptions = paramsOrOptions ?? {}

Expand Down
16 changes: 1 addition & 15 deletions src/services/hash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,9 @@ import { stringHasValue } from '@/utilities/guards'

export function hash<THash extends string>(hash?: THash): Hash<THash>
export function hash(hash?: string): Hash {
const value = !stringHasValue(hash) ? undefined : hash.replace(/^#/, '')

function hasValue(): boolean {
return value !== undefined
}

function toString(): string {
if (hasValue()) {
return `#${hash}`
}

return ''
}
const value = !stringHasValue(hash) ? undefined : hash.replace(/^#*/, '')

return {
value,
hasValue,
toString,
}
}
1 change: 0 additions & 1 deletion src/services/host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,5 @@ export function host(value: string, params: Record<string, Param | undefined>):
return {
value,
params: getParamsForString(value, params),
toString: () => value,
}
}
2 changes: 1 addition & 1 deletion src/services/insertBaseRoute.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,5 @@ test('given value for base, returns routes with base prefixed', () => {

const response = insertBaseRoute(routes, base)

expect(response.every((route) => route.path.toString().startsWith('/kitbag'))).toBe(true)
expect(response.every((route) => route.path.value.startsWith('/kitbag'))).toBe(true)
})
2 changes: 1 addition & 1 deletion src/services/paramValidation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ function getPathParams(path: Path, url: string): Record<string, unknown> {
for (const [key, param] of Object.entries(path.params)) {
const isOptional = key.startsWith('?')
const paramName = isOptional ? key.slice(1) : key
const stringValue = getParamValueFromUrl(decodedValueFromUrl, path.toString(), key)
const stringValue = getParamValueFromUrl(decodedValueFromUrl, path.value, key)
const paramValue = getParamValue(stringValue, param, isOptional)

values[paramName] = paramValue
Expand Down
1 change: 0 additions & 1 deletion src/services/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,5 @@ export function path(value: string, params: Record<string, Param | undefined>):
return {
value,
params: getParamsForString(value, params),
toString: () => value,
}
}
1 change: 0 additions & 1 deletion src/services/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,5 @@ export function query(value: string, params: Record<string, Param | undefined>):
return {
value,
params: getParamsForString(value, params),
toString: () => value,
}
}
4 changes: 2 additions & 2 deletions src/services/routeMatchRules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const routeQueryMatches: RouteMatchRule = (route, url) => {

export const routeHashMatches: RouteMatchRule = (route, url) => {
const { hash } = createMaybeRelativeUrl(url)
const value = route.hash.toString()
const { value } = route.hash

return !route.hash.hasValue() || value.toLowerCase() === hash.toLowerCase()
return value === undefined || `#${value.toLowerCase()}` === hash.toLowerCase()
}
10 changes: 5 additions & 5 deletions src/services/routeMatchScore.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createMaybeRelativeUrl } from '@/services/createMaybeRelativeUrl'
import { getParamValueFromUrl } from '@/services/paramsFinder'
import { Route } from '@/types/route'
import { routeHashMatches } from './routeMatchRules'

type RouteSortMethod = (aRoute: Route, bRoute: Route) => number

Expand Down Expand Up @@ -29,11 +30,10 @@ export function getRouteScoreSortMethod(url: string): RouteSortMethod {
return sortAfter
}

const { hash } = createMaybeRelativeUrl(url)
if (aRoute.hash.toString() === hash) {
if (routeHashMatches(aRoute, url)) {
return sortBefore
}
if (bRoute.hash.toString() === hash) {
if (routeHashMatches(bRoute, url)) {
return sortAfter
}

Expand All @@ -46,13 +46,13 @@ export function countExpectedPathParams(route: Route, actualPath: string): numbe
.filter((key) => key.startsWith('?'))
.map((key) => key)

const missing = optionalParams.filter((expected) => getParamValueFromUrl(actualPath, route.path.toString(), expected) === undefined)
const missing = optionalParams.filter((expected) => getParamValueFromUrl(actualPath, route.path.value, expected) === undefined)

return optionalParams.length - missing.length
}

export function countExpectedQueryParams(route: Route, actualQuery: URLSearchParams): number {
const expectedQuery = new URLSearchParams(route.query.toString())
const expectedQuery = new URLSearchParams(route.query.value)
const expectedQueryKeys = Array.from(expectedQuery.keys())

const missing = expectedQueryKeys.filter((expected) => !actualQuery.has(expected))
Expand Down
2 changes: 1 addition & 1 deletion src/services/routeRegex.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ describe('generateRoutePathRegexPattern', () => {

const result = generateRoutePathRegexPattern(route)

const expected = new RegExp(`^${route.path}$`, 'i')
const expected = new RegExp(`^${route.path.value}$`, 'i')
expect(result.toString()).toBe(expected.toString())
})

Expand Down
4 changes: 2 additions & 2 deletions src/services/routeRegex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,13 @@ export function splitByMatches(string: string, regexp: RegExp): string[] {
}

export function generateRoutePathRegexPattern(route: Route): RegExp {
const pathRegex = replaceParamSyntaxWithCatchAllsAndEscapeRest(route.path.toString())
const pathRegex = replaceParamSyntaxWithCatchAllsAndEscapeRest(route.path.value)

return new RegExp(`^${pathRegex}$`, 'i')
}

export function generateRouteQueryRegexPatterns(route: Route): RegExp[] {
const queryParams = new URLSearchParams(route.query.toString())
const queryParams = new URLSearchParams(route.query.value)

return Array
.from(queryParams.entries())
Expand Down
87 changes: 67 additions & 20 deletions src/services/urlAssembly.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ describe('path params', () => {
test.each([
['/simple/[simple]'],
[path('/simple/[simple]', { simple: String })],
])('given route with required string param NOT provided, throws error', (path) => {
])('given route with required string param NOT provided, throws InvalidRouteParamValueError', (path) => {
const route = createRoute({
name: 'simple',
path,
Expand Down Expand Up @@ -202,7 +202,7 @@ describe('query params', () => {
test.each([
['simple=[simple]'],
[query('simple=[simple]', { simple: String })],
])('given route with required string param NOT provided, throws error', (query) => {
])('given route with required string param NOT provided, throws InvalidRouteParamValueError', (query) => {
const route = createRoute({
name: 'simple',
path: '/',
Expand Down Expand Up @@ -265,8 +265,8 @@ describe('static query', () => {

describe('host params', () => {
test.each([
['kitbag.dev'],
[host('kitbag.dev', {})],
['https://kitbag.dev'],
[host('https://kitbag.dev', {})],
])('given simple route with string host and without params, returns route host', (host) => {
const route = createExternalRoute({
name: 'simple',
Expand All @@ -276,13 +276,13 @@ describe('host params', () => {

const url = assembleUrl(route)

expect(url).toBe('kitbag.dev/')
expect(url).toBe('https://kitbag.dev/')
})

test.each([
['[?subdomain]kitbag.dev'],
[host('[?subdomain]kitbag.dev', { subdomain: String })],
[host('[?subdomain]kitbag.dev', { subdomain: withDefault(String, 'abc') })],
['https://[?subdomain]kitbag.dev'],
[host('https://[?subdomain]kitbag.dev', { subdomain: String })],
[host('https://[?subdomain]kitbag.dev', { subdomain: withDefault(String, 'abc') })],
])('given route with optional param NOT provided, leaves entire key off', (host) => {
const route = createExternalRoute({
name: 'simple',
Expand All @@ -292,12 +292,12 @@ describe('host params', () => {

const url = assembleUrl(route)

expect(url).toBe('kitbag.dev/')
expect(url).toBe('https://kitbag.dev/')
})

test.each([
['[?subdomain]kitbag.dev'],
[host('[?subdomain]kitbag.dev', { subdomain: String })],
['https://[?subdomain]kitbag.dev'],
[host('https://[?subdomain]kitbag.dev', { subdomain: String })],
])('given route with optional string param provided, returns route Host with string with values interpolated', (host) => {
const route = createExternalRoute({
name: 'simple',
Expand All @@ -309,27 +309,27 @@ describe('host params', () => {
params: { subdomain: 'ABC.' },
})

expect(url).toBe('ABC.kitbag.dev/')
expect(url).toBe('https://ABC.kitbag.dev/')
})

test('given route with default string param provided, returns route Host with string with values interpolated', () => {
const route = createExternalRoute({
name: 'simple',
path: '/',
host: host('[?subdomain]kitbag.dev', { subdomain: withDefault(String, 'abc.') }),
host: host('https://[?subdomain]kitbag.dev', { subdomain: withDefault(String, 'abc.') }),
})

const url = assembleUrl(route, {
params: { subdomain: 'DEF.' },
})

expect(url).toBe('DEF.kitbag.dev/')
expect(url).toBe('https://DEF.kitbag.dev/')
})

test.each([
['[subdomain]kitbag.dev'],
[host('[subdomain]kitbag.dev', { subdomain: String })],
])('given route with required string param NOT provided, throws error', (host) => {
['https://[subdomain]kitbag.dev'],
[host('https://[subdomain]kitbag.dev', { subdomain: String })],
])('given route with required string param NOT provided, throws InvalidRouteParamValueError', (host) => {
const route = createExternalRoute({
name: 'simple',
path: '/',
Expand All @@ -340,8 +340,8 @@ describe('host params', () => {
})

test.each([
['[subdomain]kitbag.dev'],
[host('[subdomain]kitbag.dev', { subdomain: String })],
['https://[subdomain]kitbag.dev'],
[host('https://[subdomain]kitbag.dev', { subdomain: String })],
])('given route with required string param provided, returns route Host with string with values interpolated', (host) => {
const route = createExternalRoute({
name: 'simple',
Expand All @@ -353,7 +353,7 @@ describe('host params', () => {
params: { subdomain: 'ABC.' },
})

expect(url).toBe('ABC.kitbag.dev/')
expect(url).toBe('https://ABC.kitbag.dev/')
})
})

Expand All @@ -367,3 +367,50 @@ test('given route with hash, returns url with hash value interpolated', () => {

expect(url).toBe('/#foo')
})

test('given route without host that does not start with a forward slash, returns url with forward slash', () => {
const route = createRoute({
name: 'invalid-relative-path',
path: 'foo',
})

const url = assembleUrl(route)

expect(url).toBe('/foo')
})

test('given route with host that does not start with a protocol, returns url with protocol', () => {
const route = createExternalRoute({
name: 'invalid-host-protocol',
path: '/foo',
host: 'kitbag.dev',
})

const url = assembleUrl(route)

expect(url).toBe('https://kitbag.dev/foo')
})

test('given route with host and path without forward slash, returns forward slash after host', () => {
const route = createExternalRoute({
name: 'missing-delimiter-after-host',
path: 'foo',
host: 'https://kitbag.dev',
})

const url = assembleUrl(route)

expect(url).toBe('https://kitbag.dev/foo')
})

test('given route with host and path with excess forward slashes, returns forward slash after host', () => {
const route = createExternalRoute({
name: 'extra-delimiter-after-host',
path: '/foo',
host: 'https://kitbag.dev/',
})

const url = assembleUrl(route)

expect(url).toBe('https://kitbag.dev/foo')
})
Loading
Loading