Skip to content

Commit 8e27692

Browse files
authored
feat(runtime-core): provide full props to props validator functions (#3258)
1 parent a817883 commit 8e27692

File tree

2 files changed

+56
-3
lines changed

2 files changed

+56
-3
lines changed

packages/runtime-core/__tests__/componentProps.spec.ts

+50
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,56 @@ describe('component props', () => {
282282
expect(root.innerHTML).toBe('<div id="b">2</div>')
283283
})
284284

285+
describe('validator', () => {
286+
test('validator should be called with two arguments', async () => {
287+
const mockFn = vi.fn((...args: any[]) => true)
288+
const Comp = defineComponent({
289+
props: {
290+
foo: {
291+
type: Number,
292+
validator: (value, props) => mockFn(value, props)
293+
},
294+
bar: {
295+
type: Number
296+
}
297+
},
298+
template: `<div />`
299+
})
300+
301+
// Note this one is using the main Vue render so it can compile template
302+
// on the fly
303+
const root = document.createElement('div')
304+
domRender(h(Comp, { foo: 1, bar: 2 }), root)
305+
expect(mockFn).toHaveBeenCalledWith(1, { foo: 1, bar: 2 })
306+
})
307+
308+
test('validator should not be able to mutate other props', async () => {
309+
const mockFn = vi.fn((...args: any[]) => true)
310+
const Comp = defineComponent({
311+
props: {
312+
foo: {
313+
type: Number,
314+
validator: (value, props) => !!(props.bar = 1)
315+
},
316+
bar: {
317+
type: Number,
318+
validator: value => mockFn(value)
319+
}
320+
},
321+
template: `<div />`
322+
})
323+
324+
// Note this one is using the main Vue render so it can compile template
325+
// on the fly
326+
const root = document.createElement('div')
327+
domRender(h(Comp, { foo: 1, bar: 2 }), root)
328+
expect(
329+
`Set operation on key "bar" failed: target is readonly.`
330+
).toHaveBeenWarnedLast()
331+
expect(mockFn).toHaveBeenCalledWith(2)
332+
})
333+
})
334+
285335
test('warn props mutation', () => {
286336
let instance: ComponentInternalInstance
287337
let setupProps: any

packages/runtime-core/src/componentProps.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import {
22
toRaw,
33
shallowReactive,
44
trigger,
5-
TriggerOpTypes
5+
TriggerOpTypes,
6+
shallowReadonly
67
} from '@vue/reactivity'
78
import {
89
EMPTY_OBJ,
@@ -57,7 +58,7 @@ export interface PropOptions<T = any, D = T> {
5758
type?: PropType<T> | true | null
5859
required?: boolean
5960
default?: D | DefaultFactory<D> | null | undefined | object
60-
validator?(value: unknown): boolean
61+
validator?(value: unknown, props: Data): boolean
6162
/**
6263
* @internal
6364
*/
@@ -634,6 +635,7 @@ function validateProps(
634635
key,
635636
resolvedValues[key],
636637
opt,
638+
__DEV__ ? shallowReadonly(resolvedValues) : resolvedValues,
637639
!hasOwn(rawProps, key) && !hasOwn(rawProps, hyphenate(key))
638640
)
639641
}
@@ -646,6 +648,7 @@ function validateProp(
646648
name: string,
647649
value: unknown,
648650
prop: PropOptions,
651+
props: Data,
649652
isAbsent: boolean
650653
) {
651654
const { type, required, validator, skipCheck } = prop
@@ -675,7 +678,7 @@ function validateProp(
675678
}
676679
}
677680
// custom validator
678-
if (validator && !validator(value)) {
681+
if (validator && !validator(value, props)) {
679682
warn('Invalid prop: custom validator check failed for prop "' + name + '".')
680683
}
681684
}

0 commit comments

Comments
 (0)