mirror of
https://gitee.com/vuejs/vue.git
synced 2024-12-05 05:27:59 +08:00
props tests
This commit is contained in:
parent
3db3ca5623
commit
6a48b35473
@ -57,10 +57,19 @@ function initData (vm: Component) {
|
|||||||
}
|
}
|
||||||
// proxy data on instance
|
// proxy data on instance
|
||||||
const keys = Object.keys(data)
|
const keys = Object.keys(data)
|
||||||
|
const props = vm.$options.props
|
||||||
let i = keys.length
|
let i = keys.length
|
||||||
while (i--) {
|
while (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])
|
proxy(vm, keys[i])
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// observe data
|
// observe data
|
||||||
observe(data)
|
observe(data)
|
||||||
data.__ob__ && data.__ob__.vmCount++
|
data.__ob__ && data.__ob__.vmCount++
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/* @flow */
|
/* @flow */
|
||||||
|
|
||||||
import { hasOwn, isObject, isPlainObject } from 'shared/util'
|
import { hasOwn, isObject, isPlainObject, capitalize, hyphenate } from 'shared/util'
|
||||||
import { observe, observerState } from '../observer/index'
|
import { observe, observerState } from '../observer/index'
|
||||||
import { warn } from './debug'
|
import { warn } from './debug'
|
||||||
|
|
||||||
@ -14,8 +14,16 @@ type PropOptions = {
|
|||||||
export function validateProp (vm: Component, key: string, propsData: ?Object): any {
|
export function validateProp (vm: Component, key: string, propsData: ?Object): any {
|
||||||
if (!vm.$options.props || !propsData) return
|
if (!vm.$options.props || !propsData) return
|
||||||
const prop = vm.$options.props[key]
|
const prop = vm.$options.props[key]
|
||||||
const absent = hasOwn(propsData, key)
|
const absent = !hasOwn(propsData, key)
|
||||||
let value = 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
|
// check default value
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
value = getPropDefaultValue(vm, prop, key)
|
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 {
|
function getPropDefaultValue (vm: Component, prop: PropOptions, name: string): any {
|
||||||
// no default, return undefined
|
// no default, return undefined
|
||||||
if (!hasOwn(prop, 'default')) {
|
if (!hasOwn(prop, 'default')) {
|
||||||
// absent boolean value defaults to false
|
return undefined
|
||||||
return prop.type === Boolean
|
|
||||||
? false
|
|
||||||
: undefined
|
|
||||||
}
|
}
|
||||||
const def = prop.default
|
const def = prop.default
|
||||||
// warn against non-factory defaults for Object & Array
|
// warn against non-factory defaults for Object & Array
|
||||||
@ -75,7 +80,7 @@ function assertProp (
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (value == null) {
|
if (value == null && !prop.required) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let type = prop.type
|
let type = prop.type
|
||||||
@ -94,7 +99,7 @@ function assertProp (
|
|||||||
if (!valid) {
|
if (!valid) {
|
||||||
warn(
|
warn(
|
||||||
'Invalid prop: type check failed for prop "' + name + '".' +
|
'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) + '.',
|
', got ' + Object.prototype.toString.call(value).slice(8, -1) + '.',
|
||||||
vm
|
vm
|
||||||
)
|
)
|
||||||
|
@ -176,7 +176,7 @@ function extractProps (data: VNodeData, Ctor: Class<Component>): ?Object {
|
|||||||
const attrs = data.attrs
|
const attrs = data.attrs
|
||||||
const props = data.props
|
const props = data.props
|
||||||
const staticAttrs = data.staticAttrs
|
const staticAttrs = data.staticAttrs
|
||||||
if (!attrs && !props) {
|
if (!attrs && !props && !staticAttrs) {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
for (const key in propOptions) {
|
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