Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(setprops): allowed for setProps to be synced with nextTick intervals #1618

Merged
merged 2 commits into from
Aug 1, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion packages/test-utils/src/wrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -711,7 +711,17 @@ export default class Wrapper implements BaseWrapper {

// $FlowIgnore : Problem with possibly null this.vm
this.vm.$forceUpdate()
return nextTick()
return new Promise((resolve, reject) => {
lmiller1990 marked this conversation as resolved.
Show resolved Hide resolved
nextTick().then(() => {
const isUpdated = Object.keys(data).some(key => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this use .every?
Otherwise, setting new props where one but not all of the new values is the same as the old could falsely signal that the update succeeded.

return (
// $FlowIgnore : Problem with possibly null this.vm
this.vm[key] === data[key] || this.vm.$attrs[key] === data[key]
)
})
return !isUpdated ? this.setProps(data).then(resolve()) : resolve()
Copy link

@TeamDman TeamDman Sep 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be
.then(() => resolve())
instead of
.then(resolve())
?

Copy link

@TeamDman TeamDman Sep 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EDIT: ignore this, I forgot that this was overriding the method, so we know it's always returning a promise
Maybe the typedef could be updated


Additionally, the signature on setProps claims to potentially return void

interface BaseWrapper {
	setProps (props: object): Promise<void> | void
}

so I'm getting warnings on

? this.setProps(data).then(`
property 'then' does not exist on type 'void | Promise<void>'.
  Property 'then' does not exist on type 'void'

maybe I'm missing something, I'm just trying to backport the fix for my project

Janky backport function if anyone cares
/**
 * Bug: setProps doesn't play nice when there exists a watcher with immediate:true
 * https://github.com/vuejs/vue-test-utils/pull/1618
 */
function setPropsForSure(
	wrapper: Wrapper<CombinedVueInstance<any, any, any, any, any>>,
	data: any,
) {
	return new Promise(resolve => {
		wrapper.setProps(data);
		wrapper.vm.$nextTick().then(() => {
			const isUpdated = Object.keys(data).every(key => {
				return (
					// $FlowIgnore : Problem with possibly null this.vm
					wrapper.vm[key] === data[key] || wrapper.vm.$attrs[key] === data[key]
				);
			});
			if (isUpdated) {
				return resolve();
			}
			let result = setPropsForSure(wrapper, data);
			if (result) {
				result.then(() => resolve());
			} else {
				resolve();
			}
		});
	});
}

})
})
} catch (err) {
throw err
} finally {
Expand Down
75 changes: 75 additions & 0 deletions test/specs/wrapper/setProps.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,81 @@ describeWithShallowAndMount('setProps', mountingMethod => {
await wrapper.setProps({ prop1 })
expect(wrapper.vm.prop2).to.equal(prop1)
})

it('invokes watchers with immediate set to "true"', async () => {
const callback = sinon.spy()
const TestComponent = {
template: '<div />',
props: ['propA'],
mounted() {
this.$watch(
'propA',
function() {
callback()
},
{ immediate: true }
)
}
}
const wrapper = mountingMethod(TestComponent, {
propsData: { propA: 'none' }
})

expect(callback.calledOnce)
callback.resetHistory()

await wrapper.setProps({ propA: 'value' })
expect(wrapper.props().propA).to.equal('value')
expect(callback.calledOnce)
})

it('invokes watchers with immediate set to "true" with deep objects', async () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also added a deep equality test here to test the assign by reference in the code to make sure we don't get into some type of continual recursive loop. This looks to work as intended

const callback = sinon.spy()
const TestComponent = {
template: '<div />',
props: ['propA'],
mounted() {
this.$watch(
'propA',
function() {
callback()
},
{ immediate: true }
)
}
}
const wrapper = mountingMethod(TestComponent, {
propsData: {
propA: {
key: {
nestedKey: 'value'
},
key2: 'value2'
}
}
})

expect(callback.calledOnce)
callback.resetHistory()

await wrapper.setProps({
propA: {
key: {
nestedKey: 'newValue',
anotherNestedKey: 'value'
},
key2: 'value2'
}
})
expect(wrapper.props().propA).to.deep.equal({
key: {
nestedKey: 'newValue',
anotherNestedKey: 'value'
},
key2: 'value2'
})
expect(callback.calledOnce)
})
})

it('props and setProps should return the same reference when called with same object', () => {
Expand Down