Skip to content

Commit 6dc4c64

Browse files
authored
Merge pull request #230 from vuejs/add-set-data
Add setData
2 parents c3bee18 + fc49c02 commit 6dc4c64

File tree

4 files changed

+126
-2
lines changed

4 files changed

+126
-2
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ text | ✅ |
8787
trigger | ✅ | returns `nextTick`. You can do `await wrapper.find('button').trigger('click')`
8888
setProps | ✅ |
8989
props | ✅
90-
setData | ❌ | has PR
90+
setData | ✅ |
9191
destroy | ✅ | renamed to `unmount` to match Vue 3 lifecycle hook name.
9292
props | ✅
9393
contains | ⚰️| use `find`

src/utils.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,31 @@ export function mergeGlobalProperties(
4040
export function isFunctionalComponent(component: any) {
4141
return typeof component === 'function'
4242
}
43+
44+
// https://stackoverflow.com/a/48218209
45+
export const mergeDeep = (
46+
target: Record<string, any>,
47+
source: Record<string, any>
48+
) => {
49+
const isObject = (obj: unknown): obj is Object =>
50+
obj && typeof obj === 'object'
51+
52+
if (!isObject(target) || !isObject(source)) {
53+
return source
54+
}
55+
56+
Object.keys(source).forEach((key) => {
57+
const targetValue = target[key]
58+
const sourceValue = source[key]
59+
60+
if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
61+
target[key] = targetValue.concat(sourceValue)
62+
} else if (isObject(targetValue) && isObject(sourceValue)) {
63+
target[key] = mergeDeep(Object.assign({}, targetValue), sourceValue)
64+
} else {
65+
target[key] = sourceValue
66+
}
67+
})
68+
69+
return target
70+
}

src/vueWrapper.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
import { createWrapperError } from './errorWrapper'
1212
import { TriggerOptions } from './createDomEvent'
1313
import { find, matches } from './utils/find'
14-
import { isFunctionalComponent } from './utils'
14+
import { isFunctionalComponent, mergeDeep } from './utils'
1515

1616
export class VueWrapper<T extends ComponentPublicInstance> {
1717
private componentVM: T
@@ -226,6 +226,11 @@ export class VueWrapper<T extends ComponentPublicInstance> {
226226
return Array.from(results).map((element) => new DOMWrapper(element))
227227
}
228228

229+
setData(data: Record<string, any>): Promise<void> {
230+
mergeDeep(this.componentVM.$data, data)
231+
return nextTick()
232+
}
233+
229234
setProps(props: Record<string, any>): Promise<void> {
230235
// if this VM's parent is not the root or if setProps does not exist, error out
231236
if (this.vm.$parent !== this.rootVM || !this.__setProps) {

tests/setData.spec.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { defineComponent, ref } from 'vue'
2+
3+
import { mount } from '../src'
4+
5+
describe('setData', () => {
6+
it('sets component data', async () => {
7+
const Component = {
8+
template: '<div>{{ foo }}</div>',
9+
data: () => ({ foo: 'bar' })
10+
}
11+
12+
const wrapper = mount(Component)
13+
expect(wrapper.html()).toContain('bar')
14+
15+
await wrapper.setData({ foo: 'qux' })
16+
expect(wrapper.html()).toContain('qux')
17+
})
18+
19+
it('causes nested nodes to re-render', async () => {
20+
const Component = {
21+
template: `<div><div v-if="show" id="show">Show</div></div>`,
22+
data: () => ({ show: false })
23+
}
24+
25+
const wrapper = mount(Component)
26+
27+
expect(wrapper.find('#show').exists()).toBe(false)
28+
29+
await wrapper.setData({ show: true })
30+
31+
expect(wrapper.find('#show').exists()).toBe(true)
32+
})
33+
34+
it('updates a single property of a complex object', async () => {
35+
const Component = {
36+
template: `<div>{{ complexObject.string }}. bar: {{ complexObject.foo.bar }}</div>`,
37+
data: () => ({
38+
complexObject: {
39+
string: 'will not change',
40+
foo: {
41+
bar: 'old val'
42+
}
43+
}
44+
})
45+
}
46+
47+
const wrapper = mount(Component)
48+
49+
expect(wrapper.html()).toContain('will not change. bar: old val')
50+
51+
await wrapper.setData({
52+
complexObject: {
53+
foo: {
54+
bar: 'new val'
55+
}
56+
}
57+
})
58+
59+
expect(wrapper.html()).toContain('will not change. bar: new val')
60+
})
61+
62+
it('does not set new properties', async () => {
63+
jest.spyOn(console, 'warn').mockImplementationOnce(() => {})
64+
65+
const Component = {
66+
template: `<div>{{ foo || 'fallback' }}</div>`
67+
}
68+
69+
const wrapper = mount(Component)
70+
71+
expect(wrapper.html()).toContain('fallback')
72+
73+
expect(() => wrapper.setData({ foo: 'bar' })).toThrowError(
74+
'Cannot add property foo'
75+
)
76+
})
77+
78+
it('does not modify composition API setup data', async () => {
79+
const Component = defineComponent({
80+
template: `<div>Count is: {{ count }}</div>`,
81+
setup: () => ({ count: ref(1) })
82+
})
83+
const wrapper = mount(Component)
84+
85+
expect(wrapper.html()).toContain('Count is: 1')
86+
87+
expect(() => wrapper.setData({ count: 2 })).toThrowError(
88+
'Cannot add property count'
89+
)
90+
})
91+
})

0 commit comments

Comments
 (0)