mirror of
https://gitee.com/vuejs/vue.git
synced 2024-12-04 13:07:40 +08:00
props tests
This commit is contained in:
parent
3db3ca5623
commit
6a48b35473
@ -57,9 +57,18 @@ function initData (vm: Component) {
|
||||
}
|
||||
// proxy data on instance
|
||||
const keys = Object.keys(data)
|
||||
const props = vm.$options.props
|
||||
let i = keys.length
|
||||
while (i--) {
|
||||
proxy(vm, keys[i])
|
||||
if (props && hasOwn(props, keys[i])) {
|
||||
process.env.NODE_ENV !== 'production' && warn(
|
||||
`The data property "${keys[i]}" is already declared as a prop. ` +
|
||||
`Use prop default value instead.`,
|
||||
vm
|
||||
)
|
||||
} else {
|
||||
proxy(vm, keys[i])
|
||||
}
|
||||
}
|
||||
// observe data
|
||||
observe(data)
|
||||
|
@ -1,6 +1,6 @@
|
||||
/* @flow */
|
||||
|
||||
import { hasOwn, isObject, isPlainObject } from 'shared/util'
|
||||
import { hasOwn, isObject, isPlainObject, capitalize, hyphenate } from 'shared/util'
|
||||
import { observe, observerState } from '../observer/index'
|
||||
import { warn } from './debug'
|
||||
|
||||
@ -14,8 +14,16 @@ type PropOptions = {
|
||||
export function validateProp (vm: Component, key: string, propsData: ?Object): any {
|
||||
if (!vm.$options.props || !propsData) return
|
||||
const prop = vm.$options.props[key]
|
||||
const absent = hasOwn(propsData, key)
|
||||
const absent = !hasOwn(propsData, key)
|
||||
let value = propsData[key]
|
||||
// handle boolean props
|
||||
if (prop.type === Boolean) {
|
||||
if (absent && !hasOwn(prop, 'default')) {
|
||||
value = false
|
||||
} else if (value === '' || value === hyphenate(key)) {
|
||||
value = true
|
||||
}
|
||||
}
|
||||
// check default value
|
||||
if (value === undefined) {
|
||||
value = getPropDefaultValue(vm, prop, key)
|
||||
@ -37,10 +45,7 @@ export function validateProp (vm: Component, key: string, propsData: ?Object): a
|
||||
function getPropDefaultValue (vm: Component, prop: PropOptions, name: string): any {
|
||||
// no default, return undefined
|
||||
if (!hasOwn(prop, 'default')) {
|
||||
// absent boolean value defaults to false
|
||||
return prop.type === Boolean
|
||||
? false
|
||||
: undefined
|
||||
return undefined
|
||||
}
|
||||
const def = prop.default
|
||||
// warn against non-factory defaults for Object & Array
|
||||
@ -75,7 +80,7 @@ function assertProp (
|
||||
)
|
||||
return
|
||||
}
|
||||
if (value == null) {
|
||||
if (value == null && !prop.required) {
|
||||
return
|
||||
}
|
||||
let type = prop.type
|
||||
@ -94,7 +99,7 @@ function assertProp (
|
||||
if (!valid) {
|
||||
warn(
|
||||
'Invalid prop: type check failed for prop "' + name + '".' +
|
||||
' Expected ' + expectedTypes.join(', ') +
|
||||
' Expected ' + expectedTypes.map(capitalize).join(', ') +
|
||||
', got ' + Object.prototype.toString.call(value).slice(8, -1) + '.',
|
||||
vm
|
||||
)
|
||||
|
@ -176,7 +176,7 @@ function extractProps (data: VNodeData, Ctor: Class<Component>): ?Object {
|
||||
const attrs = data.attrs
|
||||
const props = data.props
|
||||
const staticAttrs = data.staticAttrs
|
||||
if (!attrs && !props) {
|
||||
if (!attrs && !props && !staticAttrs) {
|
||||
return res
|
||||
}
|
||||
for (const key in propOptions) {
|
||||
|
@ -0,0 +1,350 @@
|
||||
import Vue from 'vue'
|
||||
|
||||
describe('Options props', () => {
|
||||
it('array syntax', done => {
|
||||
const vm = new Vue({
|
||||
data: {
|
||||
b: 'bar'
|
||||
},
|
||||
template: '<test v-bind:b="b" v-ref:child></test>',
|
||||
components: {
|
||||
test: {
|
||||
props: ['b'],
|
||||
template: '<div>{{b}}</div>'
|
||||
}
|
||||
}
|
||||
}).$mount()
|
||||
expect(vm.$el.innerHTML).toBe('bar')
|
||||
vm.b = 'baz'
|
||||
waitForUpdate(() => {
|
||||
expect(vm.$el.innerHTML).toBe('baz')
|
||||
vm.$refs.child.b = 'qux'
|
||||
}).then(() => {
|
||||
expect(vm.$el.innerHTML).toBe('qux')
|
||||
}).then(done)
|
||||
})
|
||||
|
||||
it('object syntax', done => {
|
||||
const vm = new Vue({
|
||||
data: {
|
||||
b: 'bar'
|
||||
},
|
||||
template: '<test v-bind:b="b" v-ref:child></test>',
|
||||
components: {
|
||||
test: {
|
||||
props: { b: String },
|
||||
template: '<div>{{b}}</div>'
|
||||
}
|
||||
}
|
||||
}).$mount()
|
||||
expect(vm.$el.innerHTML).toBe('bar')
|
||||
vm.b = 'baz'
|
||||
waitForUpdate(() => {
|
||||
expect(vm.$el.innerHTML).toBe('baz')
|
||||
vm.$refs.child.b = 'qux'
|
||||
}).then(() => {
|
||||
expect(vm.$el.innerHTML).toBe('qux')
|
||||
}).then(done)
|
||||
})
|
||||
|
||||
it('warn mixed syntax', () => {
|
||||
new Vue({
|
||||
props: [{ b: String }]
|
||||
})
|
||||
expect('props must be strings when using array syntax').toHaveBeenWarned()
|
||||
})
|
||||
|
||||
it('default values', () => {
|
||||
const vm = new Vue({
|
||||
data: {
|
||||
b: undefined
|
||||
},
|
||||
template: '<test :b="b"></test>',
|
||||
components: {
|
||||
test: {
|
||||
props: {
|
||||
a: {
|
||||
default: 'A' // absent
|
||||
},
|
||||
b: {
|
||||
default: 'B' // undefined
|
||||
}
|
||||
},
|
||||
template: '<div>{{a}}{{b}}</div>'
|
||||
}
|
||||
}
|
||||
}).$mount()
|
||||
expect(vm.$el.textContent).toBe('AB')
|
||||
})
|
||||
|
||||
it('default value reactivity', done => {
|
||||
const vm = new Vue({
|
||||
props: {
|
||||
a: {
|
||||
default: () => ({ b: 1 })
|
||||
}
|
||||
},
|
||||
propsData: {
|
||||
a: undefined
|
||||
},
|
||||
template: '<div>{{ a.b }}</div>'
|
||||
}).$mount()
|
||||
expect(vm.$el.textContent).toBe('1')
|
||||
vm.a.b = 2
|
||||
waitForUpdate(() => {
|
||||
expect(vm.$el.textContent).toBe('2')
|
||||
}).then(done)
|
||||
})
|
||||
|
||||
it('warn object/array default values', () => {
|
||||
new Vue({
|
||||
props: {
|
||||
a: {
|
||||
default: { b: 1 }
|
||||
}
|
||||
},
|
||||
propsData: {
|
||||
a: undefined
|
||||
}
|
||||
})
|
||||
expect('Props with type Object/Array must use a factory function').toHaveBeenWarned()
|
||||
})
|
||||
|
||||
it('warn missing required', () => {
|
||||
new Vue({
|
||||
template: '<test></test>',
|
||||
components: {
|
||||
test: {
|
||||
props: { a: { required: true }},
|
||||
template: '<div>{{a}}</div>'
|
||||
}
|
||||
}
|
||||
}).$mount()
|
||||
expect('Missing required prop: "a"').toHaveBeenWarned()
|
||||
})
|
||||
|
||||
describe('assertions', () => {
|
||||
function makeInstance (value, type, validator, required) {
|
||||
return new Vue({
|
||||
template: '<test :test="val"></test>',
|
||||
data: {
|
||||
val: value
|
||||
},
|
||||
components: {
|
||||
test: {
|
||||
template: '<div></div>',
|
||||
props: {
|
||||
test: {
|
||||
type,
|
||||
validator,
|
||||
required
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}).$mount()
|
||||
}
|
||||
|
||||
it('string', () => {
|
||||
makeInstance('hello', String)
|
||||
expect(console.error.calls.count()).toBe(0)
|
||||
makeInstance(123, String)
|
||||
expect('Expected String').toHaveBeenWarned()
|
||||
})
|
||||
|
||||
it('number', () => {
|
||||
makeInstance(123, Number)
|
||||
expect(console.error.calls.count()).toBe(0)
|
||||
makeInstance('123', Number)
|
||||
expect('Expected Number').toHaveBeenWarned()
|
||||
})
|
||||
|
||||
it('boolean', () => {
|
||||
makeInstance(true, Boolean)
|
||||
expect(console.error.calls.count()).toBe(0)
|
||||
makeInstance('123', Boolean)
|
||||
expect('Expected Boolean').toHaveBeenWarned()
|
||||
})
|
||||
|
||||
it('function', () => {
|
||||
makeInstance(() => {}, Function)
|
||||
expect(console.error.calls.count()).toBe(0)
|
||||
makeInstance(123, Function)
|
||||
expect('Expected Function').toHaveBeenWarned()
|
||||
})
|
||||
|
||||
it('object', () => {
|
||||
makeInstance({}, Object)
|
||||
expect(console.error.calls.count()).toBe(0)
|
||||
makeInstance([], Object)
|
||||
expect('Expected Object').toHaveBeenWarned()
|
||||
})
|
||||
|
||||
it('array', () => {
|
||||
makeInstance([], Array)
|
||||
expect(console.error.calls.count()).toBe(0)
|
||||
makeInstance({}, Array)
|
||||
expect('Expected Array').toHaveBeenWarned()
|
||||
})
|
||||
|
||||
it('custom constructor', () => {
|
||||
function Class () {}
|
||||
makeInstance(new Class(), Class)
|
||||
expect(console.error.calls.count()).toBe(0)
|
||||
makeInstance({}, Class)
|
||||
expect('type check failed').toHaveBeenWarned()
|
||||
})
|
||||
|
||||
it('multiple types', () => {
|
||||
makeInstance([], [Array, Number, Boolean])
|
||||
expect(console.error.calls.count()).toBe(0)
|
||||
makeInstance({}, [Array, Number, Boolean])
|
||||
expect('Expected Array, Number, Boolean, got Object').toHaveBeenWarned()
|
||||
})
|
||||
|
||||
it('custom validator', () => {
|
||||
makeInstance(123, null, v => v === 123)
|
||||
expect(console.error.calls.count()).toBe(0)
|
||||
makeInstance(123, null, v => v === 234)
|
||||
expect('custom validator check failed').toHaveBeenWarned()
|
||||
})
|
||||
|
||||
it('type check + custom validator', () => {
|
||||
makeInstance(123, Number, v => v === 123)
|
||||
expect(console.error.calls.count()).toBe(0)
|
||||
makeInstance(123, Number, v => v === 234)
|
||||
expect('custom validator check failed').toHaveBeenWarned()
|
||||
makeInstance(123, String, v => v === 123)
|
||||
expect('Expected String').toHaveBeenWarned()
|
||||
})
|
||||
|
||||
it('multiple types + custom validator', () => {
|
||||
makeInstance(123, [Number, String, Boolean], v => v === 123)
|
||||
expect(console.error.calls.count()).toBe(0)
|
||||
makeInstance(123, [Number, String, Boolean], v => v === 234)
|
||||
expect('custom validator check failed').toHaveBeenWarned()
|
||||
makeInstance(123, [String, Boolean], v => v === 123)
|
||||
expect('Expected String, Boolean').toHaveBeenWarned()
|
||||
})
|
||||
|
||||
it('optional with type + null/undefined', () => {
|
||||
makeInstance(undefined, String)
|
||||
expect(console.error.calls.count()).toBe(0)
|
||||
makeInstance(null, String)
|
||||
expect(console.error.calls.count()).toBe(0)
|
||||
})
|
||||
|
||||
it('required with type + null/undefined', () => {
|
||||
makeInstance(undefined, String, null, true)
|
||||
expect(console.error.calls.count()).toBe(1)
|
||||
expect('Expected String').toHaveBeenWarned()
|
||||
makeInstance(null, Boolean, null, true)
|
||||
expect(console.error.calls.count()).toBe(2)
|
||||
expect('Expected Boolean').toHaveBeenWarned()
|
||||
})
|
||||
})
|
||||
|
||||
it('should warn data fields already defined as a prop', () => {
|
||||
new Vue({
|
||||
template: '<test a="1"></test>',
|
||||
components: {
|
||||
test: {
|
||||
template: '<div></div>',
|
||||
data: function () {
|
||||
return { a: 123 }
|
||||
},
|
||||
props: {
|
||||
a: null
|
||||
}
|
||||
}
|
||||
}
|
||||
}).$mount()
|
||||
expect('already declared as a prop').toHaveBeenWarned()
|
||||
})
|
||||
|
||||
it('treat boolean props properly', () => {
|
||||
const vm = new Vue({
|
||||
template: '<comp v-ref:child prop-a prop-b="prop-b"></comp>',
|
||||
components: {
|
||||
comp: {
|
||||
template: '<div></div>',
|
||||
props: {
|
||||
propA: Boolean,
|
||||
propB: Boolean,
|
||||
propC: Boolean
|
||||
}
|
||||
}
|
||||
}
|
||||
}).$mount()
|
||||
expect(vm.$refs.child.propA).toBe(true)
|
||||
expect(vm.$refs.child.propB).toBe(true)
|
||||
expect(vm.$refs.child.propC).toBe(false)
|
||||
})
|
||||
|
||||
it('should respect default value of a Boolean prop', function () {
|
||||
const vm = new Vue({
|
||||
template: '<test></test>',
|
||||
components: {
|
||||
test: {
|
||||
props: {
|
||||
prop: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
template: '<div>{{prop}}</div>'
|
||||
}
|
||||
}
|
||||
}).$mount()
|
||||
expect(vm.$el.textContent).toBe('true')
|
||||
})
|
||||
|
||||
it('non reactive values passed down as prop should not be converted', done => {
|
||||
const a = Object.freeze({
|
||||
nested: {
|
||||
msg: 'hello'
|
||||
}
|
||||
})
|
||||
const parent = new Vue({
|
||||
template: '<comp :a="a.nested"></comp>',
|
||||
data: {
|
||||
a: a
|
||||
},
|
||||
components: {
|
||||
comp: {
|
||||
template: '<div></div>',
|
||||
props: ['a']
|
||||
}
|
||||
}
|
||||
}).$mount()
|
||||
const child = parent.$children[0]
|
||||
expect(child.a.msg).toBe('hello')
|
||||
expect(child.a.__ob__).toBeUndefined() // should not be converted
|
||||
parent.a = Object.freeze({
|
||||
nested: {
|
||||
msg: 'yo'
|
||||
}
|
||||
})
|
||||
waitForUpdate(() => {
|
||||
expect(child.a.msg).toBe('yo')
|
||||
expect(child.a.__ob__).toBeUndefined()
|
||||
}).then(done)
|
||||
})
|
||||
|
||||
it('should not warn for non-required, absent prop', function () {
|
||||
new Vue({
|
||||
template: '<test></test>',
|
||||
components: {
|
||||
test: {
|
||||
template: '<div></div>',
|
||||
props: {
|
||||
prop: {
|
||||
type: String
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}).$mount()
|
||||
expect(console.error.calls.count()).toBe(0)
|
||||
})
|
||||
})
|
Loading…
Reference in New Issue
Block a user