]> git.ipfire.org Git - thirdparty/vuejs/router.git/commitdiff
fix(router): allow null | undefined for params
authorEduardo San Martin Morote <posva13@gmail.com>
Thu, 5 Aug 2021 09:28:10 +0000 (11:28 +0200)
committerEduardo San Martin Morote <posva13@gmail.com>
Thu, 5 Aug 2021 09:28:10 +0000 (11:28 +0200)
null and undefined params are now automatically removed. Empty strings are stil kept because custom regexps can be an empty string

__tests__/router.spec.ts
src/encoding.ts
src/router.ts
src/types/index.ts
src/utils/index.ts

index 957b9b89230fa4047f99ada61312930f5828f0f8..6bf25b089aa4970208d7fa66b7b6975b7e0dcbdd 100644 (file)
@@ -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: () => `/` },
@@ -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')
index 5207d417ac3b7049c255306882020861999d8838..248a7506398df87333ebbe76ad1204e566ec499a 100644 (file)
@@ -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')
 }
 
 /**
index f2ac74d62d60851236b8b0aa4dde5f3f28ed2f1a..f32fb0e27729ffc64662f2ecf6a7923c70227aa7 100644 (file)
@@ -12,6 +12,7 @@ import {
   NavigationGuardWithThis,
   RouteLocationOptions,
   MatcherLocationRaw,
+  RouteParams,
 } from './types'
 import { RouterHistory, HistoryState, NavigationType } from './history/common'
 import {
@@ -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,
@@ -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),
index 9bfad6bc15abab14ba642d27cb1c8a6d90400a8a..49c4399151c67a2d7040a7db56af45ca54df1171 100644 (file)
@@ -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>[]
 >
 
 /**
index 35a2b8d06773f36fe46f55c560af9a57b2c909c6..3e3b9218987d7deb87200828c0c8a00aecc40008 100644 (file)
@@ -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 = {}