Skip to content

Commit

Permalink
fix(router): allow null | undefined for params
Browse files Browse the repository at this point in the history
null and undefined params are now automatically removed. Empty strings are stil kept because custom regexps can be an empty string
  • Loading branch information
posva committed Aug 5, 2021
1 parent f1c01ab commit ebca15a
Show file tree
Hide file tree
Showing 5 changed files with 36 additions and 7 deletions.
18 changes: 18 additions & 0 deletions __tests__/router.spec.ts
Expand Up @@ -34,6 +34,7 @@ const routes: RouteRecordRaw[] = [
{ path: '/to-foo-query', redirect: '/foo?a=2#b' },
{ path: '/to-p/:p', redirect: { name: 'Param' } },
{ path: '/p/:p', name: 'Param', component: components.Bar },
{ path: '/optional/:p?', name: 'optional', component: components.Bar },
{ path: '/repeat/:r+', name: 'repeat', component: components.Bar },
{ path: '/to-p/:p', redirect: to => `/p/${to.params.p}` },
{ path: '/redirect-with-param/:p', redirect: () => `/` },
Expand Down Expand Up @@ -240,6 +241,23 @@ describe('Router', () => {
expect(router.currentRoute.value).toMatchObject({ params: { p: '0' } })
})

it('casts null/undefined params to empty strings', async () => {
const { router } = await newRouter()
expect(
router.resolve({ name: 'optional', params: { p: undefined } })
).toMatchObject({
params: {},
})
expect(
router.resolve({ name: 'optional', params: { p: null } })
).toMatchObject({
params: {},
})
await router.push({ name: 'optional', params: { p: null } })
expect(router.currentRoute.value).toMatchObject({ params: {} })
await router.push({ name: 'optional', params: {} })
})

it('navigates to same route record but different query', async () => {
const { router } = await newRouter()
await router.push('/?q=1')
Expand Down
7 changes: 4 additions & 3 deletions src/encoding.ts
Expand Up @@ -120,13 +120,14 @@ export function encodePath(text: string | number): string {
/**
* Encode characters that need to be encoded on the path section of the URL as a
* param. This function encodes everything {@link encodePath} does plus the
* slash (`/`) character.
* slash (`/`) character. If `text` is `null` or `undefined`, returns an empty
* string instead.
*
* @param text - string to encode
* @returns encoded string
*/
export function encodeParam(text: string | number): string {
return encodePath(text).replace(SLASH_RE, '%2F')
export function encodeParam(text: string | number | null | undefined): string {
return text == null ? '' : encodePath(text).replace(SLASH_RE, '%2F')
}

/**
Expand Down
12 changes: 11 additions & 1 deletion src/router.ts
Expand Up @@ -12,6 +12,7 @@ import {
NavigationGuardWithThis,
RouteLocationOptions,
MatcherLocationRaw,
RouteParams,
} from './types'
import { RouterHistory, HistoryState, NavigationType } from './history/common'
import {
Expand Down Expand Up @@ -380,7 +381,9 @@ export function createRouter(options: RouterOptions): Router {
paramValue => '' + paramValue
)
const encodeParams = applyToParams.bind(null, encodeParam)
const decodeParams = applyToParams.bind(null, decode)
const decodeParams: (params: RouteParams | undefined) => RouteParams =
// @ts-expect-error: intentionally avoid the type check
applyToParams.bind(null, decode)

function addRoute(
parentOrRoute: RouteRecordName | RouteRecordRaw,
Expand Down Expand Up @@ -473,6 +476,13 @@ export function createRouter(options: RouterOptions): Router {
path: parseURL(parseQuery, rawLocation.path, currentLocation.path).path,
})
} else {
// remove any nullish param
const targetParams = assign({}, rawLocation.params)
for (const key in targetParams) {
if (targetParams[key] == null) {
delete targetParams[key]
}
}
// pass encoded values to the matcher so it can produce encoded path and fullPath
matcherLocation = assign({}, rawLocation, {
params: encodeParams(rawLocation.params),
Expand Down
4 changes: 2 additions & 2 deletions src/types/index.ts
Expand Up @@ -31,11 +31,11 @@ export type RouteParamValue = string
/**
* @internal
*/
export type RouteParamValueRaw = RouteParamValue | number
export type RouteParamValueRaw = RouteParamValue | number | null | undefined
export type RouteParams = Record<string, RouteParamValue | RouteParamValue[]>
export type RouteParamsRaw = Record<
string,
RouteParamValueRaw | RouteParamValueRaw[]
RouteParamValueRaw | Exclude<RouteParamValueRaw, null | undefined>[]
>

/**
Expand Down
2 changes: 1 addition & 1 deletion src/utils/index.ts
Expand Up @@ -10,7 +10,7 @@ export function isESModule(obj: any): obj is { default: RouteComponent } {
export const assign = Object.assign

export function applyToParams(
fn: (v: string | number) => string,
fn: (v: string | number | null | undefined) => string,
params: RouteParamsRaw | undefined
): RouteParams {
const newParams: RouteParams = {}
Expand Down

0 comments on commit ebca15a

Please sign in to comment.