diff --git a/packages/test-utils/src/wrapper.js b/packages/test-utils/src/wrapper.js index db4180acd..41e89f92d 100644 --- a/packages/test-utils/src/wrapper.js +++ b/packages/test-utils/src/wrapper.js @@ -658,13 +658,32 @@ export default class Wrapper implements BaseWrapper { ) } + // SetProps on object prop child changes trigger computed or watcher + // https://github.com/vuejs/vue-test-utils/issues/761 + let newData + if ( + typeof data[key] === 'object' && + data[key] !== null && + !Array.isArray(data[key]) + ) { + newData = mergeWith( + Object.create(Object.getPrototypeOf(data[key])), + data[key], + (objValue, srcValue) => { + return Array.isArray(srcValue) ? srcValue : undefined + } + ) + } else { + newData = data[key] + } + if (this.vm && this.vm._props) { - this.vm._props[key] = data[key] + this.vm._props[key] = newData } else { // $FlowIgnore : Problem with possibly null this.vm - this.vm[key] = data[key] + this.vm[key] = newData // $FlowIgnore : Problem with possibly null this.vm.$options - this.vm.$options.propsData[key] = data[key] + this.vm.$options.propsData[key] = newData } }) // $FlowIgnore : Problem with possibly null this.vm diff --git a/test/specs/wrapper/setProps.spec.js b/test/specs/wrapper/setProps.spec.js index a275f1ab7..407eacb4a 100644 --- a/test/specs/wrapper/setProps.spec.js +++ b/test/specs/wrapper/setProps.spec.js @@ -169,6 +169,110 @@ describeWithShallowAndMount('setProps', mountingMethod => { expect(wrapper.vm.data).to.equal('1,2,3,4,5') }) + it('runs computed when prop is object', () => { + class TestClass { + constructor (text) { + this._text = text + } + + get text () { + return this._text + } + + set text (text) { + this._text = text + } + } + + const TestComponent = { + template: `
+
{{ shallowObjText }}
+
{{ deepObjText }}
+
{{ classText }}
+
`, + props: { + shallowObj: Object, + deepObj: Object, + classInstance: TestClass + }, + computed: { + shallowObjText () { + return this.shallowObj.text + }, + deepObjText () { + return this.deepObj.obj.text + }, + classText () { + return this.classInstance.text + } + } + } + + const shallowObj = { + text: 'initial shallow' + } + const deepObj = { + obj: { + text: 'initial deep' + } + } + const classInstance = new TestClass('initial class') + const wrapper = mountingMethod(TestComponent, { + propsData: { shallowObj, deepObj, classInstance } + }) + const shallowWraper = wrapper.find('.shallow') + const deepWrapper = wrapper.find('.deep') + const classWrapper = wrapper.find('.class') + + expect(shallowWraper.text()).to.equal('initial shallow') + expect(deepWrapper.text()).to.equal('initial deep') + expect(classWrapper.text()).to.equal('initial class') + + shallowObj.text = 'updated shallow' + deepObj.obj.text = 'updated deep' + classInstance.text = 'updated class' + wrapper.setProps({ shallowObj, deepObj, classInstance }) + expect(shallowWraper.text()).to.equal('updated shallow') + expect(deepWrapper.text()).to.equal('updated deep') + expect(classWrapper.text()).to.equal('updated class') + }) + + it('runs watcher when prop is object', () => { + const TestComponent = { + template: `
+
{{ watchText }}
+
`, + props: { + obj: Object + }, + data: () => ({ + watchText: 'initial' + }), + watch: { + 'obj.text': 'execute' + }, + methods: { + execute () { + this.watchText = 'updated' + } + } + } + + const obj = { + text: 'initial shallow' + } + const wrapper = mountingMethod(TestComponent, { + propsData: { obj } + }) + const watchWrapper = wrapper.find('.watch') + + expect(watchWrapper.text()).to.equal('initial') + + obj.text = 'updated shallow' + wrapper.setProps({ obj }) + expect(watchWrapper.text()).to.equal('updated') + }) + it('throws an error if node is not a Vue instance', () => { const message = 'wrapper.setProps() can only be called on a Vue instance' const compiled = compileToFunctions('

')