Skip to content

Commit

Permalink
refactored route matching to include new logic for matching query
Browse files Browse the repository at this point in the history
  • Loading branch information
stackoverfloweth committed Jan 20, 2024
1 parent 2221550 commit e71e359
Show file tree
Hide file tree
Showing 17 changed files with 366 additions and 74 deletions.
2 changes: 1 addition & 1 deletion src/components/routerLink.browser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Route } from '@/types'
import { component, createRouter } from '@/utilities'

test('renders an anchor tag with the correct href and slot content', () => {
const path = 'path/:param'
const path = '/path/:param'
const param = 'param'
const content = 'hello world'
const href = path.replace(':param', param)
Expand Down
12 changes: 6 additions & 6 deletions src/components/routerView.browser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,18 @@ test('renders component for initial route', () => {
test('renders components for initial route', () => {
const childRoute = {
name: 'child',
path: 'child',
path: '/child',
component: { template: 'Child' },
} as const satisfies Route

const parentRoute = {
name: 'parent',
path: '',
path: '/parent',
children: [childRoute],
} as const satisfies Route

const router = createRouter([parentRoute], {
initialUrl: childRoute.path,
initialUrl: '/parent/child',
})

const root = {
Expand All @@ -62,19 +62,19 @@ test('renders components for initial route', () => {
test('updates components when route changes', async () => {
const childA = {
name: 'childA',
path: 'childA',
path: '/childA',
component: { template: 'ChildA' },
} as const satisfies Route

const childB = {
name: 'childB',
path: 'childB',
path: '/childB',
component: { template: 'ChildB' },
} as const satisfies Route

const childC = {
name: 'childC',
path: 'childC',
path: '/childC',
component: { template: 'ChildC' },
} as const satisfies Route

Expand Down
1 change: 1 addition & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export * from './middleware'
export * from './params'
export * from './register'
export * from './resolved'
export * from './routeMatchRule'
export * from './routeMethod'
export * from './routeMethods'
export * from './router'
Expand Down
4 changes: 4 additions & 0 deletions src/types/routeMatchRule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { Resolved } from '@/types/resolved'
import { Route } from '@/types/routes'

export type RouteMatchRule = (route: Resolved<Route>, url: string) => boolean
5 changes: 2 additions & 3 deletions src/utilities/createRouter.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { reactive, readonly, App, InjectionKey } from 'vue'
import { RouterLink, RouterView } from '@/components'
import { Resolved, Route, Routes, Router, RouterOptions, RouterPushOptions, RegisteredRouter, RouterReplaceOptions, RouterPush } from '@/types'
import { createRouteMethods, createRouterNavigation, resolveRoutes, routeMatch, getInitialUrl, resolveRoutesRegex, assembleUrl, flattenParentMatches } from '@/utilities'
import { createRouteMethods, createRouterNavigation, resolveRoutes, routeMatch, getInitialUrl, assembleUrl, flattenParentMatches } from '@/utilities'

export const routerInjectionKey: InjectionKey<RegisteredRouter> = Symbol()

export function createRouter<T extends Routes>(routes: T, options: RouterOptions = {}): Router<T> {
const resolved = resolveRoutes(routes)
const resolvedWithRegex = resolveRoutesRegex(resolved)
const navigation = createRouterNavigation({
onLocationUpdate,
})
Expand All @@ -27,7 +26,7 @@ export function createRouter<T extends Routes>(routes: T, options: RouterOptions
}

function getRoute(url: string): Resolved<Route> {
const route = routeMatch(resolvedWithRegex, url)
const route = routeMatch(resolved, url)

if (!route) {
// not found
Expand Down
3 changes: 2 additions & 1 deletion src/utilities/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ export * from './paramsFinder'
export * from './path'
export * from './query'
export * from './resolveRoutes'
export * from './resolveRoutesRegex'
export * from './routeMatch'
export * from './routeMatchRegexRules'
export * from './routeRegex'
export * from './routerNavigation'
export * from './string'
export * from './testHelpers'
export * from './updateBrowserUrl'
export * from './urlAssembly'
export * from './urlSplitter'
12 changes: 6 additions & 6 deletions src/utilities/paramValidation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ test('given route WITHOUT params, always return true', () => {
}
const [resolved] = resolveRoutes([route])

const response = routeParamsAreValid('/no-params', resolved)
const response = routeParamsAreValid(resolved, '/no-params')

expect(response).toBe(true)
})
Expand All @@ -24,7 +24,7 @@ test('given route with simple string param and value present, returns true', ()
}
const [resolved] = resolveRoutes([route])

const response = routeParamsAreValid('/simple/ABC', resolved)
const response = routeParamsAreValid(resolved, '/simple/ABC')

expect(response).toBe(true)
})
Expand All @@ -37,7 +37,7 @@ test('given route with OPTIONAL string param WITHOUT value present, returns true
}
const [resolved] = resolveRoutes([route])

const response = routeParamsAreValid('/simple/', resolved)
const response = routeParamsAreValid(resolved, '/simple/')

expect(response).toBe(true)
})
Expand All @@ -52,7 +52,7 @@ test('given route with non-string param with value that satisfies, returns true'
}
const [resolved] = resolveRoutes([route])

const response = routeParamsAreValid('/simple/123', resolved)
const response = routeParamsAreValid(resolved, '/simple/123')

expect(response).toBe(true)
})
Expand All @@ -67,7 +67,7 @@ test('given route with non-string param with value that does NOT satisfy, return
}
const [resolved] = resolveRoutes([route])

const response = routeParamsAreValid('/simple/fail', resolved)
const response = routeParamsAreValid(resolved, '/simple/fail')

expect(response).toBe(false)
})
Expand All @@ -82,7 +82,7 @@ test('given route with OPTIONAL non-string param with value that does NOT satisf
}
const [resolved] = resolveRoutes([route])

const response = routeParamsAreValid('/simple/fail', resolved)
const response = routeParamsAreValid(resolved, '/simple/fail')

expect(response).toBe(false)
})
4 changes: 2 additions & 2 deletions src/utilities/paramValidation.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Param, Resolved, Route } from '@/types'
import { Param, RouteMatchRule } from '@/types'
import { getParamValue, getParamValuesFromUrl } from '@/utilities'

export function routeParamsAreValid(url: string, route: Resolved<Route>): boolean {
export const routeParamsAreValid: RouteMatchRule = (route, url) => {
const params = Object.entries<Param[]>(route.params)

for (const [key, paramsTuple] of params) {
Expand Down
15 changes: 0 additions & 15 deletions src/utilities/resolveRoutesRegex.ts

This file was deleted.

32 changes: 21 additions & 11 deletions src/utilities/routeMatch.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { expect, test } from 'vitest'
import { Routes } from '@/types'
import { resolveRoutes } from '@/utilities/resolveRoutes'
import { resolveRoutesRegex } from '@/utilities/resolveRoutesRegex'
import { routeMatch } from '@/utilities/routeMatch'
import { component } from '@/utilities/testHelpers'

Expand All @@ -27,8 +26,7 @@ test('given path WITHOUT params, returns match', () => {
] as const satisfies Routes

const resolved = resolveRoutes(routes)
const resolvedWithRegex = resolveRoutesRegex(resolved)
const match = routeMatch(resolvedWithRegex, '/parent/child/grandchild')
const match = routeMatch(resolved, '/parent/child/grandchild')

expect(match?.name).toBe('grandchild')
})
Expand All @@ -54,8 +52,7 @@ test('given path to unnamed parent, without option to get to leaf, returns undef
] as const satisfies Routes

const resolved = resolveRoutes(routes)
const resolvedWithRegex = resolveRoutesRegex(resolved)
const match = routeMatch(resolvedWithRegex, '/unnamed')
const match = routeMatch(resolved, '/unnamed')

expect(match).toBeUndefined()
})
Expand All @@ -75,8 +72,7 @@ test('given path to unnamed parent, with option to get to leaf, returns availab
] as const satisfies Routes

const resolved = resolveRoutes(routes)
const resolvedWithRegex = resolveRoutesRegex(resolved)
const match = routeMatch(resolvedWithRegex, '/unnamed')
const match = routeMatch(resolved, '/unnamed')

expect(match?.name).toBe('unnamed-child-root')
})
Expand All @@ -103,8 +99,7 @@ test('given path that includes named parent and path to leaf, return first match
] as const satisfies Routes

const resolved = resolveRoutes(routes)
const resolvedWithRegex = resolveRoutesRegex(resolved)
const match = routeMatch(resolvedWithRegex, '/named-parent')
const match = routeMatch(resolved, '/named-parent')

expect(match?.name).toBe('namedGrandchild')
})
Expand All @@ -119,8 +114,23 @@ test('given route with simple string param WITHOUT value present, returns undefi
]

const resolved = resolveRoutes(routes)
const resolvedWithRegex = resolveRoutesRegex(resolved)
const response = routeMatch(resolvedWithRegex, '/simple/')
const response = routeMatch(resolved, '/simple/')

expect(response).toBeUndefined()
})

test.skip('given route with simple string query param WITHOUT value present, returns undefined', () => {
const routes: Routes = [
{
name: 'simple-params',
path: '',
query: 'simple=:simple',
component,
},
]

const resolved = resolveRoutes(routes)
const response = routeMatch(resolved, 'irrelevant?missing=params')

expect(response).toBeUndefined()
})
10 changes: 4 additions & 6 deletions src/utilities/routeMatch.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { Route } from '@/types'
import { Resolved } from '@/types/resolved'
import { routeParamsAreValid } from '@/utilities/paramValidation'
import { ResolvedWithRegex } from '@/utilities/resolveRoutesRegex'
import { routePathMatches, routeQueryMatches } from '@/utilities/routeMatchRegexRules'

export function routeMatch(routes: ResolvedWithRegex[], path: string): Resolved<Route> | undefined {
const { route } = routes.find(({ regexp, route }) => {
return regexp.test(path) && routeParamsAreValid(path, route)
}) ?? {}
export function routeMatch(routes: Resolved<Route>[], url: string): Resolved<Route> | undefined {
const rules = [routePathMatches, routeQueryMatches, routeParamsAreValid]

return route
return routes.find(route => rules.every(test => test(route, url)))
}
Loading

0 comments on commit e71e359

Please sign in to comment.