]> git.ipfire.org Git - thirdparty/vuejs/router.git/commitdiff
fix: null should be preserved in relative navigations fix/optional-param-relative 2083/head
authorEduardo San Martin Morote <posva13@gmail.com>
Tue, 20 Jun 2023 15:52:04 +0000 (17:52 +0200)
committerEduardo San Martin Morote <posva13@gmail.com>
Fri, 15 Dec 2023 14:15:25 +0000 (15:15 +0100)
The fix is a bit more complicated that I anticipated, I will come back
to this later on as the currently documented version works perfectly.

- the nullish params are removed before being passed to the matcher
- The encodeParam function transform null into ''
- The applyToParams also works with arrays but it makes no sense to
  allow null in array params

Ideally, I would make the matcher a bit more permissive so the encoding
is kept at the router level. I think the matcher sholud be responsible
for removing the nullish parameters but that also means the encode
function should leave nullish values untouched. We might need an
intermediate Type for this shape of Params, it gets a little bit tedious
in terms of types, so I would like to avoid adding more types.

Close #1893

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

index a60db5393ebfda912ee00f4d0d99d2e495f93d44..50d60f0398834580edd8758076b0047bc089ae8e 100644 (file)
@@ -331,6 +331,19 @@ describe('Router', () => {
     expect(router.currentRoute.value.params).toEqual({})
   })
 
+  it('removes null/undefined optional params when current location has it on relative navigations', async () => {
+    const { router } = await newRouter()
+    const withParam = router.resolve({ name: 'optional', params: { p: 'a' } })
+    const implicitNull = router.resolve({ params: { p: null } }, withParam)
+    const implicitUndefined = router.resolve(
+      { params: { p: undefined } },
+      withParam
+    )
+
+    expect(implicitNull.params).toEqual({})
+    expect(implicitUndefined.params).toEqual({})
+  })
+
   it('keeps empty strings in optional params', async () => {
     const { router } = await newRouter()
     const route1 = router.resolve({ name: 'optional', params: { p: '' } })
index e46707080556528b6de5ba835a7d1038ee7141ca..75d76518f3b9e0411e094538b4d770758c4e7d15 100644 (file)
@@ -1,3 +1,4 @@
+import { assign } from './utils'
 import { warn } from './warning'
 
 /**
@@ -120,14 +121,34 @@ 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. If `text` is `null` or `undefined`, returns an empty
- * string instead.
+ * slash (`/`) character. If `text` is `null` or `undefined`, it keeps the value as is.
  *
  * @param text - string to encode
  * @returns encoded string
  */
-export function encodeParam(text: string | number | null | undefined): string {
-  return text == null ? '' : encodePath(text).replace(SLASH_RE, '%2F')
+export function encodeParam(
+  text: string | number | null | undefined
+): string | null | undefined {
+  return text == null ? text : encodePath(text).replace(SLASH_RE, '%2F')
+}
+
+/**
+ * Remove nullish values from an object. This function creates a copy of the object. Used for params and query.
+ *
+ * @param obj - plain object to remove nullish values from
+ * @returns a new object with only defined values
+ */
+export function withoutNullishValues(
+  obj: Record<string, unknown>
+): Record<string, unknown> {
+  const targetParams = assign({}, obj)
+  for (const key in targetParams) {
+    if (targetParams[key] == null) {
+      delete targetParams[key]
+    }
+  }
+
+  return targetParams
 }
 
 /**
index af90cb5cefed42ec78fed89e6371ea7846e46a45..4b04067ce9cb64384d3aeff6cdf78695cd3ad418 100644 (file)
@@ -13,10 +13,18 @@ export function isESModule(obj: any): obj is { default: RouteComponent } {
 
 export const assign = Object.assign
 
+export function applyToParams(
+  fn: (v: string | number | null | undefined) => string | null | undefined,
+  params: RouteParamsRaw | undefined
+): RouteParamsRaw
 export function applyToParams(
   fn: (v: string | number | null | undefined) => string,
   params: RouteParamsRaw | undefined
-): RouteParams {
+): RouteParams
+export function applyToParams(
+  fn: (v: string | number | null | undefined) => string | null | undefined,
+  params: RouteParamsRaw | undefined
+): RouteParams | RouteParamsRaw {
   const newParams: RouteParams = {}
 
   for (const key in params) {