Skip to content

Commit

Permalink
Merge pull request #308 from kitbagjs/resolve-returns-url-type-simpli…
Browse files Browse the repository at this point in the history
…fied

simplified approach to ensuring Url type is returned from urlAssembly
  • Loading branch information
stackoverfloweth authored Nov 16, 2024
2 parents 350c255 + a6d199f commit 9489179
Show file tree
Hide file tree
Showing 36 changed files with 342 additions and 190 deletions.
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: 2 additions & 2 deletions src/services/createIsExternal.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { createMaybeRelativeUrl } from '@/services/createMaybeRelativeUrl'
import { parseUrl } from '@/services/urlParser'

export function createIsExternal(host: string | undefined): (url: string) => boolean {
return (url: string) => {
const { host: targetHost } = createMaybeRelativeUrl(url)
const { host: targetHost } = parseUrl(url)
if (targetHost === undefined || targetHost === host) {
return false
}
Expand Down
2 changes: 1 addition & 1 deletion src/services/createRouter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ test('query.delete updates the route', async () => {
expect(route.query.toString()).toBe('fiz=buz')
})

test('query.values is reactive', async () => {
test.fails('query.values is reactive', async () => {
const root = createRoute({
name: 'root',
component,
Expand Down
4 changes: 2 additions & 2 deletions src/services/createRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { routerRejectionKey } from '@/compositions/useRejection'
import { routerInjectionKey } from '@/compositions/useRouter'
import { createCurrentRoute } from '@/services/createCurrentRoute'
import { createIsExternal } from '@/services/createIsExternal'
import { createMaybeRelativeUrl } from '@/services/createMaybeRelativeUrl'
import { parseUrl } from '@/services/urlParser'
import { createPropStore, propStoreKey } from '@/services/createPropStore'
import { createRouterHistory } from '@/services/createRouterHistory'
import { routeHookStoreKey, createRouterHooks } from '@/services/createRouterHooks'
Expand Down Expand Up @@ -213,7 +213,7 @@ export function createRouter<const TRoutes extends Routes, const TOptions extend

const initialUrl = getInitialUrl(options?.initialUrl)
const initialState = history.location.state
const { host } = createMaybeRelativeUrl(initialUrl)
const { host } = parseUrl(initialUrl)
const isExternal = createIsExternal(host)

let initialized = false
Expand Down
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')
})
15 changes: 10 additions & 5 deletions src/services/createRouterResolve.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { RouteNotFoundError } from '@/errors/routeNotFoundError'
import { assembleUrl } from '@/services/urlAssembly'
import { withQuery } from '@/services/withQuery'
import { Routes } from '@/types/route'
import { RouterPushOptions } from '@/types/routerPush'
import { RoutesName } from '@/types/routesMap'
import { RouteParamsByKey } from '@/types/routeWithParams'
import { isUrl, Url } from '@/types/url'
import { AllPropertiesAreOptional } from '@/types/utilities'
import { createUrl } from './urlCreator'
import { parseUrl } from './urlParser'

export type RouterResolveOptions = {
query?: Record<string, string>,
Expand All @@ -24,20 +25,24 @@ 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 ?? {}

return withQuery(source, options.query)
const { searchParams, ...parts } = parseUrl(source)
Object.entries(options.query ?? {}).forEach(([key, value]) => {
searchParams.append(key, value)
})
return createUrl({ ...parts, searchParams })
}

const params = paramsOrOptions ?? {}
Expand Down
52 changes: 30 additions & 22 deletions src/services/createRouterRoute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,27 @@ export function createRouterRoute<TRoute extends ResolvedRoute>(route: TRoute, p
return push(route.name, params, maybeOptions)
}

const querySet: URLSearchParams['set'] = (...parameters) => {
const query = new URLSearchParams(route.query.toString())
query.set(...parameters)

update({}, { query: Object.fromEntries(query.entries()) })
}

const queryAppend: URLSearchParams['append'] = (...parameters) => {
const query = new URLSearchParams(route.query.toString())
query.append(...parameters)

update({}, { query: Object.fromEntries(query.entries()) })
}

const queryDelete: URLSearchParams['delete'] = (...parameters) => {
const query = new URLSearchParams(route.query.toString())
query.delete(...parameters)

update({}, { query: Object.fromEntries(query.entries()) })
}

const { id, matched, matches, name, query, params, state, hash } = toRefs(route)

const routerRoute: RouterRoute<TRoute> = reactive({
Expand Down Expand Up @@ -82,29 +103,16 @@ export function createRouterRoute<TRoute extends ResolvedRoute>(route: TRoute, p
if (property === 'query') {
return new Proxy(route.query, {
get(_target, property, receiver) {
if (property === 'append' || property === 'set') {
const response: URLSearchParams[typeof property] = (...parameters) => {
const query = new URLSearchParams(route.query.toString())
query[property](...parameters)

update({}, { query })
}

return response
}

if (property === 'delete') {
const response: URLSearchParams['delete'] = (...parameters) => {
const query = new URLSearchParams(route.query.toString())
query.delete(...parameters)

update({}, { query })
}

return response
switch (property) {
case 'append':
return queryAppend
case 'set':
return querySet
case 'delete':
return queryDelete
default:
return Reflect.get(_target, property, receiver)
}

return Reflect.get(_target, property, receiver)
},
})
}
Expand Down
4 changes: 2 additions & 2 deletions src/services/getResolvedRouteForUrl.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createMaybeRelativeUrl } from '@/services/createMaybeRelativeUrl'
import { parseUrl } from '@/services/urlParser'
import { createResolvedRouteQuery } from '@/services/createResolvedRouteQuery'
import { getRouteParamValues, routeParamsAreValid } from '@/services/paramValidation'
import { isNamedRoute, routePathMatches, routeQueryMatches, routeHashMatches } from '@/services/routeMatchRules'
Expand Down Expand Up @@ -28,7 +28,7 @@ export function getResolvedRouteForUrl(routes: Routes, url: string, state?: unkn
}

const [route] = matches
const { search, hash } = createMaybeRelativeUrl(url)
const { search, hash } = parseUrl(url)

return {
id: route.id,
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)
})
6 changes: 3 additions & 3 deletions src/services/paramValidation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createMaybeRelativeUrl } from '@/services/createMaybeRelativeUrl'
import { parseUrl } from '@/services/urlParser'
import { getParamValue } from '@/services/params'
import { getParamValueFromUrl } from '@/services/paramsFinder'
import { Path } from '@/types/path'
Expand All @@ -17,7 +17,7 @@ export const routeParamsAreValid: RouteMatchRule = (route, url) => {
}

export const getRouteParamValues = (route: Route, url: string): Record<string, unknown> => {
const { pathname, search } = createMaybeRelativeUrl(url)
const { pathname, search } = parseUrl(url)

return {
...getPathParams(route.path, pathname),
Expand All @@ -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,
}
}
12 changes: 6 additions & 6 deletions src/services/routeMatchRules.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createMaybeRelativeUrl } from '@/services/createMaybeRelativeUrl'
import { parseUrl } from '@/services/urlParser'
import { generateRoutePathRegexPattern, generateRouteQueryRegexPatterns } from '@/services/routeRegex'
import { RouteMatchRule } from '@/types/routeMatchRule'

Expand All @@ -7,22 +7,22 @@ export const isNamedRoute: RouteMatchRule = (route) => {
}

export const routePathMatches: RouteMatchRule = (route, url) => {
const { pathname } = createMaybeRelativeUrl(url)
const { pathname } = parseUrl(url)
const pathPattern = generateRoutePathRegexPattern(route)

return pathPattern.test(pathname)
}

export const routeQueryMatches: RouteMatchRule = (route, url) => {
const { search } = createMaybeRelativeUrl(url)
const { search } = parseUrl(url)
const queryPatterns = generateRouteQueryRegexPatterns(route)

return queryPatterns.every((pattern) => pattern.test(search))
}

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

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

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

export function getRouteScoreSortMethod(url: string): RouteSortMethod {
const { searchParams: actualQuery, pathname: actualPath } = createMaybeRelativeUrl(url)
const { searchParams: actualQuery, pathname: actualPath } = parseUrl(url)
const sortBefore = -1
const sortAfter = +1

Expand All @@ -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
Loading

0 comments on commit 9489179

Please sign in to comment.