Skip to content

Commit 619f934

Browse files
authored
fix: throw errors in setData and setProps for functional components (#438)
1 parent 5bfef56 commit 619f934

File tree

5 files changed

+87
-21
lines changed

5 files changed

+87
-21
lines changed

src/lib/create-functional-component.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export default function createFunctionalComponent (component: Component, mountin
5353
(mountingOptions.context && mountingOptions.context.children && mountingOptions.context.children.map(x => typeof x === 'function' ? x(h) : x)) || createFunctionalSlots(mountingOptions.slots, h)
5454
)
5555
},
56-
name: component.name
56+
name: component.name,
57+
_isFunctionalContainer: true
5758
}
5859
}

src/wrappers/vue-wrapper.js

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export default class VueWrapper extends Wrapper implements BaseWrapper {
4545
}))
4646
this.vm = vm
4747
this.isVueComponent = true
48+
this.isFunctionalComponent = vm.$options._isFunctionalContainer
4849
this._emitted = vm.__emitted
4950
this._emittedByOrder = vm.__emittedByOrder
5051
}

src/wrappers/wrapper.js

+15-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import getSelectorTypeOrThrow from '../lib/get-selector-type'
55
import {
66
REF_SELECTOR,
77
COMPONENT_SELECTOR,
8-
NAME_SELECTOR
8+
NAME_SELECTOR,
9+
FUNCTIONAL_OPTIONS
910
} from '../lib/consts'
1011
import {
1112
vmCtorMatchesName,
@@ -29,6 +30,7 @@ export default class Wrapper implements BaseWrapper {
2930
update: Function;
3031
options: WrapperOptions;
3132
version: number
33+
isFunctionalComponent: boolean
3234

3335
constructor (node: VNode | Element, update: Function, options: WrapperOptions) {
3436
if (node instanceof Element) {
@@ -38,6 +40,9 @@ export default class Wrapper implements BaseWrapper {
3840
this.vnode = node
3941
this.element = node.elm
4042
}
43+
if (this.vnode && (this.vnode[FUNCTIONAL_OPTIONS] || this.vnode.functionalContext)) {
44+
this.isFunctionalComponent = true
45+
}
4146
this.update = update
4247
this.options = options
4348
this.version = Number(`${Vue.version.split('.')[0]}.${Vue.version.split('.')[1]}`)
@@ -394,6 +399,10 @@ export default class Wrapper implements BaseWrapper {
394399
* Sets vm data
395400
*/
396401
setData (data: Object) {
402+
if (this.isFunctionalComponent) {
403+
throwError('wrapper.setData() canot be called on a functional component')
404+
}
405+
397406
if (!this.vm) {
398407
throwError('wrapper.setData() can only be called on a Vue instance')
399408
}
@@ -475,6 +484,9 @@ export default class Wrapper implements BaseWrapper {
475484
* Sets vm props
476485
*/
477486
setProps (data: Object) {
487+
if (this.isFunctionalComponent) {
488+
throwError('wrapper.setProps() canot be called on a functional component')
489+
}
478490
if (!this.isVueComponent || !this.vm) {
479491
throwError('wrapper.setProps() can only be called on a Vue instance')
480492
}
@@ -484,8 +496,8 @@ export default class Wrapper implements BaseWrapper {
484496
Object.keys(data).forEach((key) => {
485497
// Ignore properties that were not specified in the component options
486498
// $FlowIgnore : Problem with possibly null this.vm
487-
if (!this.vm.$options._propKeys.includes(key)) {
488-
return
499+
if (!this.vm.$options._propKeys || !this.vm.$options._propKeys.includes(key)) {
500+
throwError(`wrapper.setProps() called with ${key} property which is not defined on component`)
489501
}
490502

491503
// $FlowIgnore : Problem with possibly null this.vm

test/specs/wrapper/setData.spec.js

+34-9
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import { compileToFunctions } from 'vue-template-compiler'
22
import ComponentWithVIf from '~resources/components/component-with-v-if.vue'
33
import ComponentWithWatch from '~resources/components/component-with-watch.vue'
4-
import { describeWithShallowAndMount } from '~resources/test-utils'
4+
import {
5+
describeWithShallowAndMount,
6+
vueVersion
7+
} from '~resources/test-utils'
58

6-
describeWithShallowAndMount('setData', (method) => {
9+
describeWithShallowAndMount('setData', (mountingMethod) => {
710
let info
811

912
beforeEach(() => {
@@ -15,7 +18,7 @@ describeWithShallowAndMount('setData', (method) => {
1518
})
1619

1720
it('sets component data and updates nested vm nodes when called on Vue instance', () => {
18-
const wrapper = method(ComponentWithVIf)
21+
const wrapper = mountingMethod(ComponentWithVIf)
1922
expect(wrapper.findAll('.child.ready').length).to.equal(0)
2023
wrapper.setData({ ready: true })
2124
expect(wrapper.findAll('.child.ready').length).to.equal(1)
@@ -30,35 +33,57 @@ describeWithShallowAndMount('setData', (method) => {
3033
}
3134
}
3235
}
33-
const wrapper = method(Component)
36+
const wrapper = mountingMethod(Component)
3437
wrapper.setData({ show: true })
3538
wrapper.update()
3639
expect(wrapper.element).to.equal(wrapper.vm.$el)
3740
expect(wrapper.hasClass('some-class')).to.be.true
3841
})
3942

4043
it('runs watch function when data is updated', () => {
41-
const wrapper = method(ComponentWithWatch)
44+
const wrapper = mountingMethod(ComponentWithWatch)
4245
const data1 = 'testest'
4346
wrapper.setData({ data1 })
4447
expect(wrapper.vm.data2).to.equal(data1)
4548
})
4649

4750
it('runs watch function after all props are updated', () => {
48-
const wrapper = method(ComponentWithWatch)
51+
const wrapper = mountingMethod(ComponentWithWatch)
4952
const data1 = 'testest'
5053
wrapper.setData({ data2: 'newProp', data1 })
5154
expect(info.args[0][0]).to.equal(data1)
5255
})
5356

54-
it('throws an error if node is not a Vue instance', () => {
57+
it('throws error if node is not a Vue instance', () => {
5558
const message = 'wrapper.setData() can only be called on a Vue instance'
5659
const compiled = compileToFunctions('<div><p></p></div>')
57-
const wrapper = method(compiled)
60+
const wrapper = mountingMethod(compiled)
5861
const p = wrapper.find('p')
5962
expect(() => p.setData({ ready: true })).throw(Error, message)
6063
})
6164

65+
it('throws error when called on functional vnode', () => {
66+
const AFunctionalComponent = {
67+
render: (h, context) => h('div', context.prop1),
68+
functional: true
69+
}
70+
const message = '[vue-test-utils]: wrapper.setData() canot be called on a functional component'
71+
const fn = () => mountingMethod(AFunctionalComponent).setData({ data1: 'data' })
72+
expect(fn).to.throw().with.property('message', message)
73+
// find on functional components isn't supported in Vue < 2.3
74+
if (vueVersion < 2.3) {
75+
return
76+
}
77+
const TestComponent = {
78+
template: '<div><a-functional-component /></div>',
79+
components: {
80+
AFunctionalComponent
81+
}
82+
}
83+
const fn2 = () => mountingMethod(TestComponent).find(AFunctionalComponent).setData({ data1: 'data' })
84+
expect(fn2).to.throw().with.property('message', message)
85+
})
86+
6287
it('should not run watchers if data updated is null', () => {
6388
const TestComponent = {
6489
template: `
@@ -76,7 +101,7 @@ describeWithShallowAndMount('setData', (method) => {
76101
}
77102
}
78103
}
79-
const wrapper = method(TestComponent)
104+
const wrapper = mountingMethod(TestComponent)
80105
wrapper.setData({ message: null })
81106
expect(wrapper.text()).to.equal('There is no message yet')
82107
})

test/specs/wrapper/setProps.spec.js

+35-8
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import { compileToFunctions } from 'vue-template-compiler'
22
import ComponentWithProps from '~resources/components/component-with-props.vue'
33
import ComponentWithWatch from '~resources/components/component-with-watch.vue'
4-
import { describeWithShallowAndMount } from '~resources/test-utils'
4+
import {
5+
describeWithShallowAndMount,
6+
vueVersion
7+
} from '~resources/test-utils'
58

69
describeWithShallowAndMount('setProps', (mountingMethod) => {
710
let info
@@ -24,6 +27,37 @@ describeWithShallowAndMount('setProps', (mountingMethod) => {
2427
expect(wrapper.find('.prop-2').element.textContent).to.equal(prop2)
2528
})
2629

30+
it('throws error if component does not include props key', () => {
31+
const TestComponent = {
32+
template: '<div></div>'
33+
}
34+
const message = '[vue-test-utils]: wrapper.setProps() called with prop1 property which is not defined on component'
35+
const fn = () => mountingMethod(TestComponent).setProps({ prop1: 'prop' })
36+
expect(fn).to.throw().with.property('message', message)
37+
})
38+
39+
it('throws error when called on functional vnode', () => {
40+
const AFunctionalComponent = {
41+
render: (h, context) => h('div', context.prop1),
42+
functional: true
43+
}
44+
const message = '[vue-test-utils]: wrapper.setProps() canot be called on a functional component'
45+
const fn = () => mountingMethod(AFunctionalComponent).setProps({ prop1: 'prop' })
46+
expect(fn).to.throw().with.property('message', message)
47+
// find on functional components isn't supported in Vue < 2.3
48+
if (vueVersion < 2.3) {
49+
return
50+
}
51+
const TestComponent = {
52+
template: '<div><a-functional-component /></div>',
53+
components: {
54+
AFunctionalComponent
55+
}
56+
}
57+
const fn2 = () => mountingMethod(TestComponent).find(AFunctionalComponent).setProps({ prop1: 'prop' })
58+
expect(fn2).to.throw().with.property('message', message)
59+
})
60+
2761
it('sets component props, and updates DOM when propsData was not initially passed', () => {
2862
const prop1 = 'prop 1'
2963
const prop2 = 'prop s'
@@ -33,13 +67,6 @@ describeWithShallowAndMount('setProps', (mountingMethod) => {
3367
expect(wrapper.find('.prop-2').element.textContent).to.equal(prop2)
3468
})
3569

36-
it('does not add properties not defined in component', () => {
37-
const undefinedProp = 'some value'
38-
const wrapper = mountingMethod(ComponentWithProps)
39-
wrapper.setProps({ undefinedProp })
40-
expect(wrapper.props().undefinedProp).to.be.undefined
41-
})
42-
4370
it('runs watch function when prop is updated', () => {
4471
const wrapper = mountingMethod(ComponentWithWatch)
4572
const prop1 = 'testest'

0 commit comments

Comments
 (0)