From efdc7f50bc4d3fc608e5b2554243fab36ccafd45 Mon Sep 17 00:00:00 2001 From: mya-ake Date: Sun, 1 Jul 2018 12:47:48 +0900 Subject: [PATCH 1/3] Support setProps runs computed and watcher when prop is object (#761) --- packages/create-instance/add-props.js | 22 +++++ packages/create-instance/create-instance.js | 6 +- test/specs/mount.spec.js | 4 +- test/specs/wrapper/setProps.spec.js | 104 ++++++++++++++++++++ 4 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 packages/create-instance/add-props.js diff --git a/packages/create-instance/add-props.js b/packages/create-instance/add-props.js new file mode 100644 index 000000000..7eba63528 --- /dev/null +++ b/packages/create-instance/add-props.js @@ -0,0 +1,22 @@ +// @flow + +// SetProps on object prop child changes trigger computed or watcher +// https://github.com/vuejs/vue-test-utils/issues/761 +export function createProps (propsData: Object): Object { + return Object.keys(propsData).reduce((props, key) => { + const data = propsData[key] + if ( + typeof data === 'object' && + data !== null && + !Array.isArray(data) + ) { + props[key] = Object.assign( + Object.create(Object.getPrototypeOf(data)), + data + ) + } else { + props[key] = data + } + return props + }, {}) +} diff --git a/packages/create-instance/create-instance.js b/packages/create-instance/create-instance.js index ea0cc8af3..981b6d049 100644 --- a/packages/create-instance/create-instance.js +++ b/packages/create-instance/create-instance.js @@ -2,6 +2,7 @@ import { createSlotVNodes } from './add-slots' import addMocks from './add-mocks' +import { createProps } from './add-props' import { addEventLogger } from './log-events' import { createComponentStubs } from 'shared/stub-components' import { throwError, warn, vueVersion } from 'shared/util' @@ -128,11 +129,14 @@ export default function createInstance ( const slots = options.slots ? createSlotVNodes(h, options.slots) : undefined + const props = options.propsData + ? createProps(options.propsData) + : undefined return h( Constructor, { ref: 'vm', - props: options.propsData, + props, on: options.listeners, attrs: options.attrs }, diff --git a/test/specs/mount.spec.js b/test/specs/mount.spec.js index 906dfc26b..12fb06d68 100644 --- a/test/specs/mount.spec.js +++ b/test/specs/mount.spec.js @@ -52,9 +52,9 @@ describeRunIf(process.env.TEST_ENV !== 'node', 'mount', () => { const wrapper = mount(ComponentWithProps, { propsData: { prop1 }}) expect(wrapper.vm).to.be.an('object') if (wrapper.vm.$props) { - expect(wrapper.vm.$props.prop1).to.equal(prop1) + expect(wrapper.vm.$props.prop1).to.deep.equal(prop1) } else { - expect(wrapper.vm.$options.propsData.prop1).to.equal(prop1) + expect(wrapper.vm.$options.propsData.prop1).to.deep.equal(prop1) } }) 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('

') From 68bbebba0de73844077a007c17c386a9fd6145a7 Mon Sep 17 00:00:00 2001 From: mya-ake Date: Sun, 1 Jul 2018 18:36:05 +0900 Subject: [PATCH 2/3] Update setPorps processing --- packages/create-instance/add-props.js | 22 --------------------- packages/create-instance/create-instance.js | 6 +----- packages/test-utils/src/wrapper.js | 20 ++++++++++++++++++- test/specs/mount.spec.js | 4 ++-- 4 files changed, 22 insertions(+), 30 deletions(-) delete mode 100644 packages/create-instance/add-props.js diff --git a/packages/create-instance/add-props.js b/packages/create-instance/add-props.js deleted file mode 100644 index 7eba63528..000000000 --- a/packages/create-instance/add-props.js +++ /dev/null @@ -1,22 +0,0 @@ -// @flow - -// SetProps on object prop child changes trigger computed or watcher -// https://github.com/vuejs/vue-test-utils/issues/761 -export function createProps (propsData: Object): Object { - return Object.keys(propsData).reduce((props, key) => { - const data = propsData[key] - if ( - typeof data === 'object' && - data !== null && - !Array.isArray(data) - ) { - props[key] = Object.assign( - Object.create(Object.getPrototypeOf(data)), - data - ) - } else { - props[key] = data - } - return props - }, {}) -} diff --git a/packages/create-instance/create-instance.js b/packages/create-instance/create-instance.js index 981b6d049..ea0cc8af3 100644 --- a/packages/create-instance/create-instance.js +++ b/packages/create-instance/create-instance.js @@ -2,7 +2,6 @@ import { createSlotVNodes } from './add-slots' import addMocks from './add-mocks' -import { createProps } from './add-props' import { addEventLogger } from './log-events' import { createComponentStubs } from 'shared/stub-components' import { throwError, warn, vueVersion } from 'shared/util' @@ -129,14 +128,11 @@ export default function createInstance ( const slots = options.slots ? createSlotVNodes(h, options.slots) : undefined - const props = options.propsData - ? createProps(options.propsData) - : undefined return h( Constructor, { ref: 'vm', - props, + props: options.propsData, on: options.listeners, attrs: options.attrs }, diff --git a/packages/test-utils/src/wrapper.js b/packages/test-utils/src/wrapper.js index db4180acd..60a7cea99 100644 --- a/packages/test-utils/src/wrapper.js +++ b/packages/test-utils/src/wrapper.js @@ -659,7 +659,25 @@ export default class Wrapper implements BaseWrapper { } if (this.vm && this.vm._props) { - this.vm._props[key] = data[key] + // SetProps on object prop child changes trigger computed or watcher + // https://github.com/vuejs/vue-test-utils/issues/761 + if ( + typeof data[key] === 'object' && + data[key] !== null && + !Array.isArray(data[key]) + ) { + const newObj = mergeWith( + Object.create(Object.getPrototypeOf(data[key])), + data[key], + (objValue, srcValue) => { + return Array.isArray(srcValue) ? srcValue : undefined + } + ) + // $FlowIgnore : Problem with possibly null this.vm._props + this.vm._props[key] = newObj + } else { + this.vm._props[key] = data[key] + } } else { // $FlowIgnore : Problem with possibly null this.vm this.vm[key] = data[key] diff --git a/test/specs/mount.spec.js b/test/specs/mount.spec.js index 12fb06d68..906dfc26b 100644 --- a/test/specs/mount.spec.js +++ b/test/specs/mount.spec.js @@ -52,9 +52,9 @@ describeRunIf(process.env.TEST_ENV !== 'node', 'mount', () => { const wrapper = mount(ComponentWithProps, { propsData: { prop1 }}) expect(wrapper.vm).to.be.an('object') if (wrapper.vm.$props) { - expect(wrapper.vm.$props.prop1).to.deep.equal(prop1) + expect(wrapper.vm.$props.prop1).to.equal(prop1) } else { - expect(wrapper.vm.$options.propsData.prop1).to.deep.equal(prop1) + expect(wrapper.vm.$options.propsData.prop1).to.equal(prop1) } }) From 48dbae8218f0d5f9858218772c9ab21ffe690efb Mon Sep 17 00:00:00 2001 From: mya-ake Date: Sun, 1 Jul 2018 19:56:03 +0900 Subject: [PATCH 3/3] Support v2.0.8 and v2.1.10 --- packages/test-utils/src/wrapper.js | 41 +++++++++++++++--------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/packages/test-utils/src/wrapper.js b/packages/test-utils/src/wrapper.js index 60a7cea99..41e89f92d 100644 --- a/packages/test-utils/src/wrapper.js +++ b/packages/test-utils/src/wrapper.js @@ -658,31 +658,32 @@ export default class Wrapper implements BaseWrapper { ) } - if (this.vm && this.vm._props) { - // SetProps on object prop child changes trigger computed or watcher - // https://github.com/vuejs/vue-test-utils/issues/761 - if ( - typeof data[key] === 'object' && + // 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]) - ) { - const newObj = mergeWith( - Object.create(Object.getPrototypeOf(data[key])), - data[key], - (objValue, srcValue) => { - return Array.isArray(srcValue) ? srcValue : undefined - } - ) - // $FlowIgnore : Problem with possibly null this.vm._props - this.vm._props[key] = newObj - } else { - this.vm._props[key] = 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] = 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