]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(runtime-core): provide full props to props validator functions (#3258)
authornandi95 <41805560+nandi95@users.noreply.github.com>
Tue, 5 Dec 2023 09:14:17 +0000 (09:14 +0000)
committerGitHub <noreply@github.com>
Tue, 5 Dec 2023 09:14:17 +0000 (17:14 +0800)
packages/runtime-core/__tests__/componentProps.spec.ts
packages/runtime-core/src/componentProps.ts

index 885e80090a1417b2e4a33db28d541a8c6efbb177..3a16840b4f32f3218bcff1067689bb18b64968de 100644 (file)
@@ -282,6 +282,56 @@ describe('component props', () => {
     expect(root.innerHTML).toBe('<div id="b">2</div>')
   })
 
+  describe('validator', () => {
+    test('validator should be called with two arguments', async () => {
+      const mockFn = vi.fn((...args: any[]) => true)
+      const Comp = defineComponent({
+        props: {
+          foo: {
+            type: Number,
+            validator: (value, props) => mockFn(value, props)
+          },
+          bar: {
+            type: Number
+          }
+        },
+        template: `<div />`
+      })
+
+      // Note this one is using the main Vue render so it can compile template
+      // on the fly
+      const root = document.createElement('div')
+      domRender(h(Comp, { foo: 1, bar: 2 }), root)
+      expect(mockFn).toHaveBeenCalledWith(1, { foo: 1, bar: 2 })
+    })
+
+    test('validator should not be able to mutate other props', async () => {
+      const mockFn = vi.fn((...args: any[]) => true)
+      const Comp = defineComponent({
+        props: {
+          foo: {
+            type: Number,
+            validator: (value, props) => !!(props.bar = 1)
+          },
+          bar: {
+            type: Number,
+            validator: value => mockFn(value)
+          }
+        },
+        template: `<div />`
+      })
+
+      // Note this one is using the main Vue render so it can compile template
+      // on the fly
+      const root = document.createElement('div')
+      domRender(h(Comp, { foo: 1, bar: 2 }), root)
+      expect(
+        `Set operation on key "bar" failed: target is readonly.`
+      ).toHaveBeenWarnedLast()
+      expect(mockFn).toHaveBeenCalledWith(2)
+    })
+  })
+
   test('warn props mutation', () => {
     let instance: ComponentInternalInstance
     let setupProps: any
index bbe88bf7b82672948b4e4817ea8aa40288f2748b..4b7be8a8e73b9787cf36cb82d7706ee834fb3ae1 100644 (file)
@@ -2,7 +2,8 @@ import {
   toRaw,
   shallowReactive,
   trigger,
-  TriggerOpTypes
+  TriggerOpTypes,
+  shallowReadonly
 } from '@vue/reactivity'
 import {
   EMPTY_OBJ,
@@ -57,7 +58,7 @@ export interface PropOptions<T = any, D = T> {
   type?: PropType<T> | true | null
   required?: boolean
   default?: D | DefaultFactory<D> | null | undefined | object
-  validator?(value: unknown): boolean
+  validator?(value: unknown, props: Data): boolean
   /**
    * @internal
    */
@@ -634,6 +635,7 @@ function validateProps(
       key,
       resolvedValues[key],
       opt,
+      __DEV__ ? shallowReadonly(resolvedValues) : resolvedValues,
       !hasOwn(rawProps, key) && !hasOwn(rawProps, hyphenate(key))
     )
   }
@@ -646,6 +648,7 @@ function validateProp(
   name: string,
   value: unknown,
   prop: PropOptions,
+  props: Data,
   isAbsent: boolean
 ) {
   const { type, required, validator, skipCheck } = prop
@@ -675,7 +678,7 @@ function validateProp(
     }
   }
   // custom validator
-  if (validator && !validator(value)) {
+  if (validator && !validator(value, props)) {
     warn('Invalid prop: custom validator check failed for prop "' + name + '".')
   }
 }