import Vue from 'vue'
import { hasSymbol } from 'core/util/env'
import testObjectOption from '../../../helpers/test-object-option'
describe('Options props', () => {
testObjectOption('props')
it('array syntax', done => {
const vm = new Vue({
data: {
b: 'bar'
},
template: '',
components: {
test: {
props: ['b'],
template: '
{{b}}
'
}
}
}).$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')
expect('Avoid mutating a prop directly').toHaveBeenWarned()
}).then(done)
})
it('object syntax', done => {
const vm = new Vue({
data: {
b: 'bar'
},
template: '',
components: {
test: {
props: { b: String },
template: '{{b}}
'
}
}
}).$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')
expect('Avoid mutating a prop directly').toHaveBeenWarned()
}).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: '',
components: {
test: {
props: {
a: {
default: 'A' // absent
},
b: {
default: 'B' // undefined
}
},
template: '{{a}}{{b}}
'
}
}
}).$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: '{{ a.b }}
'
}).$mount()
expect(vm.$el.textContent).toBe('1')
vm.a.b = 2
waitForUpdate(() => {
expect(vm.$el.textContent).toBe('2')
}).then(done)
})
it('default value Function', () => {
const func = () => 132
const vm = new Vue({
props: {
a: {
type: Function,
default: func
}
},
propsData: {
a: undefined
}
})
expect(vm.a).toBe(func)
})
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: '',
components: {
test: {
props: { a: { required: true }},
template: '{{a}}
'
}
}
}).$mount()
expect('Missing required prop: "a"').toHaveBeenWarned()
})
describe('assertions', () => {
function makeInstance (value, type, validator, required) {
return new Vue({
template: '',
data: {
val: value
},
components: {
test: {
template: '',
props: {
test: {
type,
validator,
required
}
}
}
}
}).$mount()
}
it('string', () => {
makeInstance('hello', String)
expect(console.error.calls.count()).toBe(0)
makeInstance(123, String)
expect('Expected String with value "123", got Number with value 123').toHaveBeenWarned()
})
it('number', () => {
makeInstance(123, Number)
expect(console.error.calls.count()).toBe(0)
makeInstance('123', Number)
expect('Expected Number with value 123, got String with value "123"').toHaveBeenWarned()
})
it('number & boolean', () => {
makeInstance(123, Number)
expect(console.error.calls.count()).toBe(0)
makeInstance(false, Number)
expect('Expected Number, got Boolean with value false').toHaveBeenWarned()
})
it('string & boolean', () => {
makeInstance('hello', String)
expect(console.error.calls.count()).toBe(0)
makeInstance(true, String)
expect('Expected String, got Boolean with value true').toHaveBeenWarned()
})
it('boolean', () => {
makeInstance(true, Boolean)
expect(console.error.calls.count()).toBe(0)
makeInstance('123', Boolean)
expect('Expected Boolean, got String with value "123"').toHaveBeenWarned()
})
it('function', () => {
makeInstance(() => {}, Function)
expect(console.error.calls.count()).toBe(0)
makeInstance(123, Function)
expect('Expected Function, got Number with value 123').toHaveBeenWarned()
})
it('object', () => {
makeInstance({}, Object)
expect(console.error.calls.count()).toBe(0)
makeInstance([], Object)
expect('Expected Object, got Array').toHaveBeenWarned()
})
it('array', () => {
makeInstance([], Array)
expect(console.error.calls.count()).toBe(0)
makeInstance({}, Array)
expect('Expected Array, got Object').toHaveBeenWarned()
})
it('primitive wrapper objects', () => {
/* eslint-disable no-new-wrappers */
makeInstance(new String('s'), String)
expect(console.error.calls.count()).toBe(0)
makeInstance(new Number(1), Number)
expect(console.error.calls.count()).toBe(0)
makeInstance(new Boolean(true), Boolean)
expect(console.error.calls.count()).toBe(0)
/* eslint-enable no-new-wrappers */
})
if (hasSymbol) {
it('symbol', () => {
makeInstance(Symbol('foo'), Symbol)
expect(console.error.calls.count()).toBe(0)
makeInstance({}, Symbol)
expect('Expected Symbol, got Object').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 with value "123", got Number with value 123').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('optional prop of any type (type: true or prop: true)', () => {
makeInstance(1, true)
expect(console.error.calls.count()).toBe(0)
makeInstance('any', true)
expect(console.error.calls.count()).toBe(0)
makeInstance({}, true)
expect(console.error.calls.count()).toBe(0)
makeInstance(undefined, true)
expect(console.error.calls.count()).toBe(0)
makeInstance(null, true)
expect(console.error.calls.count()).toBe(0)
})
})
it('should work with v-bind', () => {
const vm = new Vue({
template: ``,
components: {
test: {
props: ['a', 'b'],
template: '{{ a }} {{ b }}
'
}
}
}).$mount()
expect(vm.$el.textContent).toBe('1 2')
})
it('should warn data fields already defined as a prop', () => {
new Vue({
template: '',
components: {
test: {
template: '',
data: function () {
return { a: 123 }
},
props: {
a: null
}
}
}
}).$mount()
expect('already declared as a prop').toHaveBeenWarned()
})
it('should warn methods already defined as a prop', () => {
new Vue({
template: '',
components: {
test: {
template: '',
props: {
a: null
},
methods: {
a () {
}
}
}
}
}).$mount()
expect(`Method "a" has already been defined as a prop`).toHaveBeenWarned()
expect(`Avoid mutating a prop directly`).toHaveBeenWarned()
})
it('treat boolean props properly', () => {
const vm = new Vue({
template: '',
components: {
comp: {
template: '',
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: '',
components: {
test: {
props: {
prop: {
type: Boolean,
default: true
}
},
template: '{{prop}}
'
}
}
}).$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: '',
data: {
a: a
},
components: {
comp: {
template: '',
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: '',
components: {
test: {
template: '',
props: {
prop: {
type: String
}
}
}
}
}).$mount()
expect(console.error.calls.count()).toBe(0)
})
// #3453
it('should not fire watcher on object/array props when parent re-renders', done => {
const spy = jasmine.createSpy()
const vm = new Vue({
data: {
arr: []
},
template: 'hi',
components: {
test: {
props: ['prop'],
watch: {
prop: spy
},
template: '
'
}
}
}).$mount()
vm.$forceUpdate()
waitForUpdate(() => {
expect(spy).not.toHaveBeenCalled()
}).then(done)
})
// #4090
it('should not trigger watcher on default value', done => {
const spy = jasmine.createSpy()
const vm = new Vue({
template: ``,
data: {
a: 1,
b: undefined
},
components: {
test: {
template: '{{ value }}
',
props: {
value: { type: Number },
test: {
type: Object,
default: () => ({})
}
},
watch: {
test: spy
}
}
}
}).$mount()
vm.a++
waitForUpdate(() => {
expect(spy).not.toHaveBeenCalled()
vm.b = {}
}).then(() => {
expect(spy.calls.count()).toBe(1)
}).then(() => {
vm.b = undefined
}).then(() => {
expect(spy.calls.count()).toBe(2)
vm.a++
}).then(() => {
expect(spy.calls.count()).toBe(2)
}).then(done)
})
it('warn reserved props', () => {
const specialAttrs = ['key', 'ref', 'slot', 'is', 'slot-scope']
new Vue({
props: specialAttrs
})
specialAttrs.forEach(attr => {
expect(`"${attr}" is a reserved attribute`).toHaveBeenWarned()
})
})
it('should consider order when casting [Boolean, String] multi-type props', () => {
const vm = new Vue({
template: '',
components: {
test: {
template: '',
props: {
booleanOrString: [Boolean, String],
stringOrBoolean: [String, Boolean]
}
}
}
}).$mount()
expect(vm.$refs.test.$props.booleanOrString).toBe(true)
expect(vm.$refs.test.$props.stringOrBoolean).toBe('')
})
})