Skip to content

Commit

Permalink
feat: Add setValue method (#557)
Browse files Browse the repository at this point in the history
  • Loading branch information
beyer-martin authored and eddyerburgh committed Jun 9, 2018
1 parent 0437e26 commit b4331ff
Show file tree
Hide file tree
Showing 10 changed files with 432 additions and 0 deletions.
3 changes: 3 additions & 0 deletions flow/wrapper.flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ declare interface BaseWrapper { // eslint-disable-line no-undef
setData(data: Object): void,
setComputed(computed: Object): void,
setMethods(methods: Object): void,
setValue(value: any): void,
setChecked(checked: boolean): void,
setSelected(): void,
setProps(data: Object): void,
trigger(type: string, options: Object): void,
destroy(): void
Expand Down
12 changes: 12 additions & 0 deletions packages/test-utils/src/error-wrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,18 @@ export default class ErrorWrapper implements BaseWrapper {
throwError(`find did not return ${this.selector}, cannot call setProps() on empty Wrapper`)
}

setValue (): void {
throwError(`find did not return ${this.selector}, cannot call setValue() on empty Wrapper`)
}

setChecked (): void {
throwError(`find did not return ${this.selector}, cannot call setChecked() on empty Wrapper`)
}

setSelected (): void {
throwError(`find did not return ${this.selector}, cannot call setSelected() on empty Wrapper`)
}

trigger (): void {
throwError(`find did not return ${this.selector}, cannot call trigger() on empty Wrapper`)
}
Expand Down
18 changes: 18 additions & 0 deletions packages/test-utils/src/wrapper-array.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,24 @@ export default class WrapperArray implements BaseWrapper {
this.wrappers.forEach(wrapper => wrapper.setProps(props))
}

setValue (value: any): void {
this.throwErrorIfWrappersIsEmpty('setValue')

this.wrappers.forEach(wrapper => wrapper.setValue(value))
}

setChecked (checked: boolean): void {
this.throwErrorIfWrappersIsEmpty('setChecked')

this.wrappers.forEach(wrapper => wrapper.setChecked(checked))
}

setSelected (): void {
this.throwErrorIfWrappersIsEmpty('setSelected')

throwError('setSelected must be called on a single wrapper, use at(i) to access a wrapper')
}

trigger (event: string, options: Object): void {
this.throwErrorIfWrappersIsEmpty('trigger')

Expand Down
108 changes: 108 additions & 0 deletions packages/test-utils/src/wrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,114 @@ export default class Wrapper implements BaseWrapper {
orderWatchers(this.vm || this.vnode.context.$root)
}

/**
* Sets element value and triggers input event
*/
setValue (value: any) {
const el = this.element

if (!el) {
throwError('cannot call wrapper.setValue() on a wrapper without an element')
}

const tag = el.tagName
const type = this.attributes().type
const event = 'input'

if (tag === 'SELECT') {
throwError('wrapper.setValue() cannot be called on a <select> element. Use wrapper.setSelected() instead')
} else if (tag === 'INPUT' && type === 'checkbox') {
throwError('wrapper.setValue() cannot be called on a <input type="checkbox" /> element. Use wrapper.setChecked() instead')
} else if (tag === 'INPUT' && type === 'radio') {
throwError('wrapper.setValue() cannot be called on a <input type="radio" /> element. Use wrapper.setChecked() instead')
} else if (tag === 'INPUT' || tag === 'textarea') {
// $FlowIgnore
el.value = value
this.trigger(event)
} else {
throwError('wrapper.setValue() cannot be called on this element')
}
}

/**
* Checks radio button or checkbox element
*/
setChecked (checked: boolean) {
if (typeof checked !== 'undefined') {
if (typeof checked !== 'boolean') {
throwError('wrapper.setChecked() must be passed a boolean')
}
} else {
checked = true
}

const el = this.element

if (!el) {
throwError('cannot call wrapper.setChecked() on a wrapper without an element')
}

const tag = el.tagName
const type = this.attributes().type
const event = 'change'

if (tag === 'SELECT') {
throwError('wrapper.setChecked() cannot be called on a <select> element. Use wrapper.setSelected() instead')
} else if (tag === 'INPUT' && type === 'checkbox') {
// $FlowIgnore
if (el.checked !== checked) {
this.trigger('click')
this.trigger(event)
}
} else if (tag === 'INPUT' && type === 'radio') {
if (!checked) {
throwError('wrapper.setChecked() cannot be called with parameter false on a <input type="radio" /> element.')
} else {
// $FlowIgnore
if (!el.checked) {
this.trigger('click')
this.trigger(event)
}
}
} else if (tag === 'INPUT' || tag === 'textarea') {
throwError('wrapper.setChecked() cannot be called on "text" inputs. Use wrapper.setValue() instead')
} else {
throwError('wrapper.setChecked() cannot be called on this element')
}
}

/**
* Selects <option></option> element
*/
setSelected () {
const el = this.element

if (!el) {
throwError('cannot call wrapper.setSelected() on a wrapper without an element')
}

const tag = el.tagName
const type = this.attributes().type
const event = 'change'

if (tag === 'OPTION') {
// $FlowIgnore
el.selected = true
// $FlowIgnore
createWrapper(el.parentElement, this.options).trigger(event)
} else if (tag === 'SELECT') {
throwError('wrapper.setSelected() cannot be called on select. Call it on one of its options')
} else if (tag === 'INPUT' && type === 'checkbox') {
throwError('wrapper.setSelected() cannot be called on a <input type="checkbox" /> element. Use wrapper.setChecked() instead')
} else if (tag === 'INPUT' && type === 'radio') {
throwError('wrapper.setSelected() cannot be called on a <input type="radio" /> element. Use wrapper.setChecked() instead')
} else if (tag === 'INPUT' || tag === 'textarea') {
throwError('wrapper.setSelected() cannot be called on "text" inputs. Use wrapper.setValue() instead')
} else {
throwError('wrapper.setSelected() cannot be called on this element')
}
}

/**
* Return text of wrapper element
*/
Expand Down
6 changes: 6 additions & 0 deletions packages/test-utils/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ interface BaseWrapper {
setData (data: object): void
setMethods (data: object): void
setProps (props: object): void

setValue (value: any): void
setChecked (checked: boolean): void
setSelected (): void

trigger (eventName: string, options?: object): void
destroy (): void
}
Expand All @@ -98,6 +103,7 @@ export interface Wrapper<V extends Vue> extends BaseWrapper {
html (): string
text (): string
name (): string
setSelected(): void

emitted (event?: string): { [name: string]: Array<Array<any>> }
emittedByOrder (): Array<{ name: string, args: Array<any> }>
Expand Down
4 changes: 4 additions & 0 deletions packages/test-utils/types/test/wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ array = wrapper.findAll(ClassComponent)
array = wrapper.findAll({ ref: 'myButton' })
array = wrapper.findAll({ name: 'my-button' })

wrapper.setChecked(true)
wrapper.setValue('some string')
wrapper.setSelected()

let str: string = wrapper.html()
str = wrapper.text()
str = wrapper.name()
Expand Down
44 changes: 44 additions & 0 deletions test/resources/components/component-with-input.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<template>
<div>
<input type="checkbox" v-model="checkboxVal">
<input type="radio" v-model="radioVal" id="radioFoo" value="radioFooResult">
<input type="radio" v-model="radioVal" id="radioBar" value="radioBarResult">
<input type="text" v-model="textVal">
<select v-model="selectVal">
<option value="selectA"></option>
<option value="selectB"></option>
<option value="selectC"></option>
</select>
<label id="label-el"></label>

<span class="checkboxResult" v-if="checkboxVal">checkbox checked</span>
<span class="counter">{{ counter }}</span>
{{ textVal }}
{{ selectVal }}
{{ radioVal }}
</div>
</template>

<script>
export default {
name: 'component-with-input',
data () {
return {
checkboxVal: undefined,
textVal: undefined,
radioVal: undefined,
selectVal: undefined,
counter: 0
}
},
watch: {
checkboxVal () {
this.counter++
},
radioVal () {
this.counter++
}
}
}
</script>
117 changes: 117 additions & 0 deletions test/specs/wrapper/setChecked.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import ComponentWithInput from '~resources/components/component-with-input.vue'
import { describeWithShallowAndMount } from '~resources/utils'

describeWithShallowAndMount('setChecked', (mountingMethod) => {
it('sets element checked true with no option passed', () => {
const wrapper = mountingMethod(ComponentWithInput)
const input = wrapper.find('input[type="checkbox"]')
input.setChecked()

expect(input.element.checked).to.equal(true)
})

it('sets element checked equal to param passed', () => {
const wrapper = mountingMethod(ComponentWithInput)
const input = wrapper.find('input[type="checkbox"]')

input.setChecked(true)
expect(input.element.checked).to.equal(true)

input.setChecked(false)
expect(input.element.checked).to.equal(false)
})

it('updates dom with checkbox v-model', () => {
const wrapper = mountingMethod(ComponentWithInput)
const input = wrapper.find('input[type="checkbox"]')

input.setChecked()
expect(wrapper.text()).to.contain('checkbox checked')

input.setChecked(false)
expect(wrapper.text()).to.not.contain('checkbox checked')
})

it('changes state the right amount of times with checkbox v-model', () => {
const wrapper = mountingMethod(ComponentWithInput)
const input = wrapper.find('input[type="checkbox"]')

input.setChecked()
input.setChecked(false)
input.setChecked(false)
input.setChecked(true)
input.setChecked(false)
input.setChecked(false)

expect(wrapper.find('.counter').text()).to.equal('4')
})

it('updates dom with radio v-model', () => {
const wrapper = mountingMethod(ComponentWithInput)

wrapper.find('#radioBar').setChecked()
expect(wrapper.text()).to.contain('radioBarResult')

wrapper.find('#radioFoo').setChecked()
expect(wrapper.text()).to.contain('radioFooResult')
})

it('changes state the right amount of times with checkbox v-model', () => {
const wrapper = mountingMethod(ComponentWithInput)
const radioBar = wrapper.find('#radioBar')
const radioFoo = wrapper.find('#radioFoo')

radioBar.setChecked()
radioBar.setChecked()
radioFoo.setChecked()
radioBar.setChecked()
radioBar.setChecked()
radioFoo.setChecked()
radioFoo.setChecked()

expect(wrapper.find('.counter').text()).to.equal('4')
})

it('throws error if checked param is not boolean', () => {
const message = 'wrapper.setChecked() must be passed a boolean'
shouldThrowErrorOnElement('input[type="checkbox"]', message, 'asd')
})

it('throws error if checked param is false on radio element', () => {
const message = 'wrapper.setChecked() cannot be called with parameter false on a <input type="radio" /> element.'
shouldThrowErrorOnElement('#radioFoo', message, false)
})

it('throws error if wrapper does not contain element', () => {
const wrapper = mountingMethod({ render: (h) => h('div') })
const div = wrapper.find('div')
div.element = null

const fn = () => div.setChecked()
const message = '[vue-test-utils]: cannot call wrapper.setChecked() on a wrapper without an element'
expect(fn).to.throw().with.property('message', message)
})

it('throws error if element is select', () => {
const message = 'wrapper.setChecked() cannot be called on a <select> element. Use wrapper.setSelected() instead'
shouldThrowErrorOnElement('select', message)
})

it('throws error if element is text like', () => {
const message = 'wrapper.setChecked() cannot be called on "text" inputs. Use wrapper.setValue() instead'
shouldThrowErrorOnElement('input[type="text"]', message)
})

it('throws error if element is not valid', () => {
const message = 'wrapper.setChecked() cannot be called on this element'
shouldThrowErrorOnElement('#label-el', message)
})

function shouldThrowErrorOnElement (selector, message, value) {
const wrapper = mountingMethod(ComponentWithInput)
const input = wrapper.find(selector)

const fn = () => input.setChecked(value)
expect(fn).to.throw().with.property('message', '[vue-test-utils]: ' + message)
}
})
Loading

0 comments on commit b4331ff

Please sign in to comment.