Skip to content

Commit efdc7f5

Browse files
committed
Support setProps runs computed and watcher when prop is object (vuejs#761)
1 parent 221038b commit efdc7f5

File tree

4 files changed

+133
-3
lines changed

4 files changed

+133
-3
lines changed

packages/create-instance/add-props.js

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// @flow
2+
3+
// SetProps on object prop child changes trigger computed or watcher
4+
// https://github.com/vuejs/vue-test-utils/issues/761
5+
export function createProps (propsData: Object): Object {
6+
return Object.keys(propsData).reduce((props, key) => {
7+
const data = propsData[key]
8+
if (
9+
typeof data === 'object' &&
10+
data !== null &&
11+
!Array.isArray(data)
12+
) {
13+
props[key] = Object.assign(
14+
Object.create(Object.getPrototypeOf(data)),
15+
data
16+
)
17+
} else {
18+
props[key] = data
19+
}
20+
return props
21+
}, {})
22+
}

packages/create-instance/create-instance.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { createSlotVNodes } from './add-slots'
44
import addMocks from './add-mocks'
5+
import { createProps } from './add-props'
56
import { addEventLogger } from './log-events'
67
import { createComponentStubs } from 'shared/stub-components'
78
import { throwError, warn, vueVersion } from 'shared/util'
@@ -128,11 +129,14 @@ export default function createInstance (
128129
const slots = options.slots
129130
? createSlotVNodes(h, options.slots)
130131
: undefined
132+
const props = options.propsData
133+
? createProps(options.propsData)
134+
: undefined
131135
return h(
132136
Constructor,
133137
{
134138
ref: 'vm',
135-
props: options.propsData,
139+
props,
136140
on: options.listeners,
137141
attrs: options.attrs
138142
},

test/specs/mount.spec.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ describeRunIf(process.env.TEST_ENV !== 'node', 'mount', () => {
5252
const wrapper = mount(ComponentWithProps, { propsData: { prop1 }})
5353
expect(wrapper.vm).to.be.an('object')
5454
if (wrapper.vm.$props) {
55-
expect(wrapper.vm.$props.prop1).to.equal(prop1)
55+
expect(wrapper.vm.$props.prop1).to.deep.equal(prop1)
5656
} else {
57-
expect(wrapper.vm.$options.propsData.prop1).to.equal(prop1)
57+
expect(wrapper.vm.$options.propsData.prop1).to.deep.equal(prop1)
5858
}
5959
})
6060

test/specs/wrapper/setProps.spec.js

+104
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,110 @@ describeWithShallowAndMount('setProps', mountingMethod => {
169169
expect(wrapper.vm.data).to.equal('1,2,3,4,5')
170170
})
171171

172+
it('runs computed when prop is object', () => {
173+
class TestClass {
174+
constructor (text) {
175+
this._text = text
176+
}
177+
178+
get text () {
179+
return this._text
180+
}
181+
182+
set text (text) {
183+
this._text = text
184+
}
185+
}
186+
187+
const TestComponent = {
188+
template: `<div id="app">
189+
<div class="shallow">{{ shallowObjText }}</div>
190+
<div class="deep">{{ deepObjText }}</div>
191+
<div class="class">{{ classText }}</div>
192+
</div>`,
193+
props: {
194+
shallowObj: Object,
195+
deepObj: Object,
196+
classInstance: TestClass
197+
},
198+
computed: {
199+
shallowObjText () {
200+
return this.shallowObj.text
201+
},
202+
deepObjText () {
203+
return this.deepObj.obj.text
204+
},
205+
classText () {
206+
return this.classInstance.text
207+
}
208+
}
209+
}
210+
211+
const shallowObj = {
212+
text: 'initial shallow'
213+
}
214+
const deepObj = {
215+
obj: {
216+
text: 'initial deep'
217+
}
218+
}
219+
const classInstance = new TestClass('initial class')
220+
const wrapper = mountingMethod(TestComponent, {
221+
propsData: { shallowObj, deepObj, classInstance }
222+
})
223+
const shallowWraper = wrapper.find('.shallow')
224+
const deepWrapper = wrapper.find('.deep')
225+
const classWrapper = wrapper.find('.class')
226+
227+
expect(shallowWraper.text()).to.equal('initial shallow')
228+
expect(deepWrapper.text()).to.equal('initial deep')
229+
expect(classWrapper.text()).to.equal('initial class')
230+
231+
shallowObj.text = 'updated shallow'
232+
deepObj.obj.text = 'updated deep'
233+
classInstance.text = 'updated class'
234+
wrapper.setProps({ shallowObj, deepObj, classInstance })
235+
expect(shallowWraper.text()).to.equal('updated shallow')
236+
expect(deepWrapper.text()).to.equal('updated deep')
237+
expect(classWrapper.text()).to.equal('updated class')
238+
})
239+
240+
it('runs watcher when prop is object', () => {
241+
const TestComponent = {
242+
template: `<div id="app">
243+
<div class="watch">{{ watchText }}</div>
244+
</div>`,
245+
props: {
246+
obj: Object
247+
},
248+
data: () => ({
249+
watchText: 'initial'
250+
}),
251+
watch: {
252+
'obj.text': 'execute'
253+
},
254+
methods: {
255+
execute () {
256+
this.watchText = 'updated'
257+
}
258+
}
259+
}
260+
261+
const obj = {
262+
text: 'initial shallow'
263+
}
264+
const wrapper = mountingMethod(TestComponent, {
265+
propsData: { obj }
266+
})
267+
const watchWrapper = wrapper.find('.watch')
268+
269+
expect(watchWrapper.text()).to.equal('initial')
270+
271+
obj.text = 'updated shallow'
272+
wrapper.setProps({ obj })
273+
expect(watchWrapper.text()).to.equal('updated')
274+
})
275+
172276
it('throws an error if node is not a Vue instance', () => {
173277
const message = 'wrapper.setProps() can only be called on a Vue instance'
174278
const compiled = compileToFunctions('<div><p></p></div>')

0 commit comments

Comments
 (0)