Skip to content

Commit b4331ff

Browse files
beyer-martineddyerburgh
authored andcommitted
feat: Add setValue method (#557)
1 parent 0437e26 commit b4331ff

File tree

10 files changed

+432
-0
lines changed

10 files changed

+432
-0
lines changed

Diff for: flow/wrapper.flow.js

+3
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ declare interface BaseWrapper { // eslint-disable-line no-undef
3232
setData(data: Object): void,
3333
setComputed(computed: Object): void,
3434
setMethods(methods: Object): void,
35+
setValue(value: any): void,
36+
setChecked(checked: boolean): void,
37+
setSelected(): void,
3538
setProps(data: Object): void,
3639
trigger(type: string, options: Object): void,
3740
destroy(): void

Diff for: packages/test-utils/src/error-wrapper.js

+12
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,18 @@ export default class ErrorWrapper implements BaseWrapper {
117117
throwError(`find did not return ${this.selector}, cannot call setProps() on empty Wrapper`)
118118
}
119119

120+
setValue (): void {
121+
throwError(`find did not return ${this.selector}, cannot call setValue() on empty Wrapper`)
122+
}
123+
124+
setChecked (): void {
125+
throwError(`find did not return ${this.selector}, cannot call setChecked() on empty Wrapper`)
126+
}
127+
128+
setSelected (): void {
129+
throwError(`find did not return ${this.selector}, cannot call setSelected() on empty Wrapper`)
130+
}
131+
120132
trigger (): void {
121133
throwError(`find did not return ${this.selector}, cannot call trigger() on empty Wrapper`)
122134
}

Diff for: packages/test-utils/src/wrapper-array.js

+18
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,24 @@ export default class WrapperArray implements BaseWrapper {
181181
this.wrappers.forEach(wrapper => wrapper.setProps(props))
182182
}
183183

184+
setValue (value: any): void {
185+
this.throwErrorIfWrappersIsEmpty('setValue')
186+
187+
this.wrappers.forEach(wrapper => wrapper.setValue(value))
188+
}
189+
190+
setChecked (checked: boolean): void {
191+
this.throwErrorIfWrappersIsEmpty('setChecked')
192+
193+
this.wrappers.forEach(wrapper => wrapper.setChecked(checked))
194+
}
195+
196+
setSelected (): void {
197+
this.throwErrorIfWrappersIsEmpty('setSelected')
198+
199+
throwError('setSelected must be called on a single wrapper, use at(i) to access a wrapper')
200+
}
201+
184202
trigger (event: string, options: Object): void {
185203
this.throwErrorIfWrappersIsEmpty('trigger')
186204

Diff for: packages/test-utils/src/wrapper.js

+108
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,114 @@ export default class Wrapper implements BaseWrapper {
543543
orderWatchers(this.vm || this.vnode.context.$root)
544544
}
545545

546+
/**
547+
* Sets element value and triggers input event
548+
*/
549+
setValue (value: any) {
550+
const el = this.element
551+
552+
if (!el) {
553+
throwError('cannot call wrapper.setValue() on a wrapper without an element')
554+
}
555+
556+
const tag = el.tagName
557+
const type = this.attributes().type
558+
const event = 'input'
559+
560+
if (tag === 'SELECT') {
561+
throwError('wrapper.setValue() cannot be called on a <select> element. Use wrapper.setSelected() instead')
562+
} else if (tag === 'INPUT' && type === 'checkbox') {
563+
throwError('wrapper.setValue() cannot be called on a <input type="checkbox" /> element. Use wrapper.setChecked() instead')
564+
} else if (tag === 'INPUT' && type === 'radio') {
565+
throwError('wrapper.setValue() cannot be called on a <input type="radio" /> element. Use wrapper.setChecked() instead')
566+
} else if (tag === 'INPUT' || tag === 'textarea') {
567+
// $FlowIgnore
568+
el.value = value
569+
this.trigger(event)
570+
} else {
571+
throwError('wrapper.setValue() cannot be called on this element')
572+
}
573+
}
574+
575+
/**
576+
* Checks radio button or checkbox element
577+
*/
578+
setChecked (checked: boolean) {
579+
if (typeof checked !== 'undefined') {
580+
if (typeof checked !== 'boolean') {
581+
throwError('wrapper.setChecked() must be passed a boolean')
582+
}
583+
} else {
584+
checked = true
585+
}
586+
587+
const el = this.element
588+
589+
if (!el) {
590+
throwError('cannot call wrapper.setChecked() on a wrapper without an element')
591+
}
592+
593+
const tag = el.tagName
594+
const type = this.attributes().type
595+
const event = 'change'
596+
597+
if (tag === 'SELECT') {
598+
throwError('wrapper.setChecked() cannot be called on a <select> element. Use wrapper.setSelected() instead')
599+
} else if (tag === 'INPUT' && type === 'checkbox') {
600+
// $FlowIgnore
601+
if (el.checked !== checked) {
602+
this.trigger('click')
603+
this.trigger(event)
604+
}
605+
} else if (tag === 'INPUT' && type === 'radio') {
606+
if (!checked) {
607+
throwError('wrapper.setChecked() cannot be called with parameter false on a <input type="radio" /> element.')
608+
} else {
609+
// $FlowIgnore
610+
if (!el.checked) {
611+
this.trigger('click')
612+
this.trigger(event)
613+
}
614+
}
615+
} else if (tag === 'INPUT' || tag === 'textarea') {
616+
throwError('wrapper.setChecked() cannot be called on "text" inputs. Use wrapper.setValue() instead')
617+
} else {
618+
throwError('wrapper.setChecked() cannot be called on this element')
619+
}
620+
}
621+
622+
/**
623+
* Selects <option></option> element
624+
*/
625+
setSelected () {
626+
const el = this.element
627+
628+
if (!el) {
629+
throwError('cannot call wrapper.setSelected() on a wrapper without an element')
630+
}
631+
632+
const tag = el.tagName
633+
const type = this.attributes().type
634+
const event = 'change'
635+
636+
if (tag === 'OPTION') {
637+
// $FlowIgnore
638+
el.selected = true
639+
// $FlowIgnore
640+
createWrapper(el.parentElement, this.options).trigger(event)
641+
} else if (tag === 'SELECT') {
642+
throwError('wrapper.setSelected() cannot be called on select. Call it on one of its options')
643+
} else if (tag === 'INPUT' && type === 'checkbox') {
644+
throwError('wrapper.setSelected() cannot be called on a <input type="checkbox" /> element. Use wrapper.setChecked() instead')
645+
} else if (tag === 'INPUT' && type === 'radio') {
646+
throwError('wrapper.setSelected() cannot be called on a <input type="radio" /> element. Use wrapper.setChecked() instead')
647+
} else if (tag === 'INPUT' || tag === 'textarea') {
648+
throwError('wrapper.setSelected() cannot be called on "text" inputs. Use wrapper.setValue() instead')
649+
} else {
650+
throwError('wrapper.setSelected() cannot be called on this element')
651+
}
652+
}
653+
546654
/**
547655
* Return text of wrapper element
548656
*/

Diff for: packages/test-utils/types/index.d.ts

+6
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ interface BaseWrapper {
7272
setData (data: object): void
7373
setMethods (data: object): void
7474
setProps (props: object): void
75+
76+
setValue (value: any): void
77+
setChecked (checked: boolean): void
78+
setSelected (): void
79+
7580
trigger (eventName: string, options?: object): void
7681
destroy (): void
7782
}
@@ -98,6 +103,7 @@ export interface Wrapper<V extends Vue> extends BaseWrapper {
98103
html (): string
99104
text (): string
100105
name (): string
106+
setSelected(): void
101107

102108
emitted (event?: string): { [name: string]: Array<Array<any>> }
103109
emittedByOrder (): Array<{ name: string, args: Array<any> }>

Diff for: packages/test-utils/types/test/wrapper.ts

+4
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ array = wrapper.findAll(ClassComponent)
6363
array = wrapper.findAll({ ref: 'myButton' })
6464
array = wrapper.findAll({ name: 'my-button' })
6565

66+
wrapper.setChecked(true)
67+
wrapper.setValue('some string')
68+
wrapper.setSelected()
69+
6670
let str: string = wrapper.html()
6771
str = wrapper.text()
6872
str = wrapper.name()

Diff for: test/resources/components/component-with-input.vue

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<template>
2+
<div>
3+
<input type="checkbox" v-model="checkboxVal">
4+
<input type="radio" v-model="radioVal" id="radioFoo" value="radioFooResult">
5+
<input type="radio" v-model="radioVal" id="radioBar" value="radioBarResult">
6+
<input type="text" v-model="textVal">
7+
<select v-model="selectVal">
8+
<option value="selectA"></option>
9+
<option value="selectB"></option>
10+
<option value="selectC"></option>
11+
</select>
12+
<label id="label-el"></label>
13+
14+
<span class="checkboxResult" v-if="checkboxVal">checkbox checked</span>
15+
<span class="counter">{{ counter }}</span>
16+
{{ textVal }}
17+
{{ selectVal }}
18+
{{ radioVal }}
19+
</div>
20+
</template>
21+
22+
<script>
23+
export default {
24+
name: 'component-with-input',
25+
data () {
26+
return {
27+
checkboxVal: undefined,
28+
textVal: undefined,
29+
radioVal: undefined,
30+
selectVal: undefined,
31+
counter: 0
32+
}
33+
},
34+
35+
watch: {
36+
checkboxVal () {
37+
this.counter++
38+
},
39+
radioVal () {
40+
this.counter++
41+
}
42+
}
43+
}
44+
</script>

Diff for: test/specs/wrapper/setChecked.spec.js

+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import ComponentWithInput from '~resources/components/component-with-input.vue'
2+
import { describeWithShallowAndMount } from '~resources/utils'
3+
4+
describeWithShallowAndMount('setChecked', (mountingMethod) => {
5+
it('sets element checked true with no option passed', () => {
6+
const wrapper = mountingMethod(ComponentWithInput)
7+
const input = wrapper.find('input[type="checkbox"]')
8+
input.setChecked()
9+
10+
expect(input.element.checked).to.equal(true)
11+
})
12+
13+
it('sets element checked equal to param passed', () => {
14+
const wrapper = mountingMethod(ComponentWithInput)
15+
const input = wrapper.find('input[type="checkbox"]')
16+
17+
input.setChecked(true)
18+
expect(input.element.checked).to.equal(true)
19+
20+
input.setChecked(false)
21+
expect(input.element.checked).to.equal(false)
22+
})
23+
24+
it('updates dom with checkbox v-model', () => {
25+
const wrapper = mountingMethod(ComponentWithInput)
26+
const input = wrapper.find('input[type="checkbox"]')
27+
28+
input.setChecked()
29+
expect(wrapper.text()).to.contain('checkbox checked')
30+
31+
input.setChecked(false)
32+
expect(wrapper.text()).to.not.contain('checkbox checked')
33+
})
34+
35+
it('changes state the right amount of times with checkbox v-model', () => {
36+
const wrapper = mountingMethod(ComponentWithInput)
37+
const input = wrapper.find('input[type="checkbox"]')
38+
39+
input.setChecked()
40+
input.setChecked(false)
41+
input.setChecked(false)
42+
input.setChecked(true)
43+
input.setChecked(false)
44+
input.setChecked(false)
45+
46+
expect(wrapper.find('.counter').text()).to.equal('4')
47+
})
48+
49+
it('updates dom with radio v-model', () => {
50+
const wrapper = mountingMethod(ComponentWithInput)
51+
52+
wrapper.find('#radioBar').setChecked()
53+
expect(wrapper.text()).to.contain('radioBarResult')
54+
55+
wrapper.find('#radioFoo').setChecked()
56+
expect(wrapper.text()).to.contain('radioFooResult')
57+
})
58+
59+
it('changes state the right amount of times with checkbox v-model', () => {
60+
const wrapper = mountingMethod(ComponentWithInput)
61+
const radioBar = wrapper.find('#radioBar')
62+
const radioFoo = wrapper.find('#radioFoo')
63+
64+
radioBar.setChecked()
65+
radioBar.setChecked()
66+
radioFoo.setChecked()
67+
radioBar.setChecked()
68+
radioBar.setChecked()
69+
radioFoo.setChecked()
70+
radioFoo.setChecked()
71+
72+
expect(wrapper.find('.counter').text()).to.equal('4')
73+
})
74+
75+
it('throws error if checked param is not boolean', () => {
76+
const message = 'wrapper.setChecked() must be passed a boolean'
77+
shouldThrowErrorOnElement('input[type="checkbox"]', message, 'asd')
78+
})
79+
80+
it('throws error if checked param is false on radio element', () => {
81+
const message = 'wrapper.setChecked() cannot be called with parameter false on a <input type="radio" /> element.'
82+
shouldThrowErrorOnElement('#radioFoo', message, false)
83+
})
84+
85+
it('throws error if wrapper does not contain element', () => {
86+
const wrapper = mountingMethod({ render: (h) => h('div') })
87+
const div = wrapper.find('div')
88+
div.element = null
89+
90+
const fn = () => div.setChecked()
91+
const message = '[vue-test-utils]: cannot call wrapper.setChecked() on a wrapper without an element'
92+
expect(fn).to.throw().with.property('message', message)
93+
})
94+
95+
it('throws error if element is select', () => {
96+
const message = 'wrapper.setChecked() cannot be called on a <select> element. Use wrapper.setSelected() instead'
97+
shouldThrowErrorOnElement('select', message)
98+
})
99+
100+
it('throws error if element is text like', () => {
101+
const message = 'wrapper.setChecked() cannot be called on "text" inputs. Use wrapper.setValue() instead'
102+
shouldThrowErrorOnElement('input[type="text"]', message)
103+
})
104+
105+
it('throws error if element is not valid', () => {
106+
const message = 'wrapper.setChecked() cannot be called on this element'
107+
shouldThrowErrorOnElement('#label-el', message)
108+
})
109+
110+
function shouldThrowErrorOnElement (selector, message, value) {
111+
const wrapper = mountingMethod(ComponentWithInput)
112+
const input = wrapper.find(selector)
113+
114+
const fn = () => input.setChecked(value)
115+
expect(fn).to.throw().with.property('message', '[vue-test-utils]: ' + message)
116+
}
117+
})

0 commit comments

Comments
 (0)