From b4331ff229da3a6679aa5451af602bb958cf66d5 Mon Sep 17 00:00:00 2001 From: Martin Beyer Date: Sat, 9 Jun 2018 02:57:20 -0300 Subject: [PATCH] feat: Add setValue method (#557) --- flow/wrapper.flow.js | 3 + packages/test-utils/src/error-wrapper.js | 12 ++ packages/test-utils/src/wrapper-array.js | 18 +++ packages/test-utils/src/wrapper.js | 108 ++++++++++++++++ packages/test-utils/types/index.d.ts | 6 + packages/test-utils/types/test/wrapper.ts | 4 + .../components/component-with-input.vue | 44 +++++++ test/specs/wrapper/setChecked.spec.js | 117 ++++++++++++++++++ test/specs/wrapper/setSelected.spec.js | 62 ++++++++++ test/specs/wrapper/setValue.spec.js | 58 +++++++++ 10 files changed, 432 insertions(+) create mode 100644 test/resources/components/component-with-input.vue create mode 100644 test/specs/wrapper/setChecked.spec.js create mode 100644 test/specs/wrapper/setSelected.spec.js create mode 100644 test/specs/wrapper/setValue.spec.js diff --git a/flow/wrapper.flow.js b/flow/wrapper.flow.js index 77edd9f05..218345ea0 100644 --- a/flow/wrapper.flow.js +++ b/flow/wrapper.flow.js @@ -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 diff --git a/packages/test-utils/src/error-wrapper.js b/packages/test-utils/src/error-wrapper.js index 1599747ae..51603ae04 100644 --- a/packages/test-utils/src/error-wrapper.js +++ b/packages/test-utils/src/error-wrapper.js @@ -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`) } diff --git a/packages/test-utils/src/wrapper-array.js b/packages/test-utils/src/wrapper-array.js index 6e27fd017..76b011d3b 100644 --- a/packages/test-utils/src/wrapper-array.js +++ b/packages/test-utils/src/wrapper-array.js @@ -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') diff --git a/packages/test-utils/src/wrapper.js b/packages/test-utils/src/wrapper.js index d95678e33..392f5a0b9 100644 --- a/packages/test-utils/src/wrapper.js +++ b/packages/test-utils/src/wrapper.js @@ -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 element. Use wrapper.setChecked() instead') + } else if (tag === 'INPUT' && type === 'radio') { + throwError('wrapper.setValue() cannot be called on a 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 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 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 element. Use wrapper.setChecked() instead') + } else if (tag === 'INPUT' && type === 'radio') { + throwError('wrapper.setSelected() cannot be called on a 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 */ diff --git a/packages/test-utils/types/index.d.ts b/packages/test-utils/types/index.d.ts index 48d1a4d90..cce3e239c 100644 --- a/packages/test-utils/types/index.d.ts +++ b/packages/test-utils/types/index.d.ts @@ -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 } @@ -98,6 +103,7 @@ export interface Wrapper extends BaseWrapper { html (): string text (): string name (): string + setSelected(): void emitted (event?: string): { [name: string]: Array> } emittedByOrder (): Array<{ name: string, args: Array }> diff --git a/packages/test-utils/types/test/wrapper.ts b/packages/test-utils/types/test/wrapper.ts index c14a5338d..a4ce5710c 100644 --- a/packages/test-utils/types/test/wrapper.ts +++ b/packages/test-utils/types/test/wrapper.ts @@ -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() diff --git a/test/resources/components/component-with-input.vue b/test/resources/components/component-with-input.vue new file mode 100644 index 000000000..12fad1655 --- /dev/null +++ b/test/resources/components/component-with-input.vue @@ -0,0 +1,44 @@ + + + diff --git a/test/specs/wrapper/setChecked.spec.js b/test/specs/wrapper/setChecked.spec.js new file mode 100644 index 000000000..116d06a7b --- /dev/null +++ b/test/specs/wrapper/setChecked.spec.js @@ -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 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 element. Use wrapper.setChecked() instead' + shouldThrowErrorOnElement('input[type="radio"]', message) + }) + + it('throws error if element is radio', () => { + const message = 'wrapper.setSelected() cannot be called on a element. Use wrapper.setChecked() instead' + shouldThrowErrorOnElement('input[type="checkbox"]', message) + }) + + it('throws error if element is text like', () => { + const message = 'wrapper.setSelected() 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.setSelected() 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.setSelected(value) + expect(fn).to.throw().with.property('message', '[vue-test-utils]: ' + message) + } +}) diff --git a/test/specs/wrapper/setValue.spec.js b/test/specs/wrapper/setValue.spec.js new file mode 100644 index 000000000..f913575c0 --- /dev/null +++ b/test/specs/wrapper/setValue.spec.js @@ -0,0 +1,58 @@ +import ComponentWithInput from '~resources/components/component-with-input.vue' +import { describeWithShallowAndMount } from '~resources/utils' + +describeWithShallowAndMount('setValue', (mountingMethod) => { + it('sets element value', () => { + const wrapper = mountingMethod(ComponentWithInput) + const input = wrapper.find('input[type="text"]') + input.setValue('foo') + + expect(input.element.value).to.equal('foo') + }) + + it('updates dom with v-model', () => { + const wrapper = mountingMethod(ComponentWithInput) + const input = wrapper.find('input[type="text"]') + + input.setValue('input text awesome binding') + + expect(wrapper.text()).to.contain('input text awesome binding') + }) + + 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.setValue('') + const message = '[vue-test-utils]: cannot call wrapper.setValue() on a wrapper without an element' + expect(fn).to.throw().with.property('message', message) + }) + + it('throws error if element is select', () => { + const message = 'wrapper.setValue() cannot be called on a element. Use wrapper.setChecked() instead' + shouldThrowErrorOnElement('input[type="radio"]', message) + }) + + it('throws error if element is checkbox', () => { + const message = 'wrapper.setValue() cannot be called on a element. Use wrapper.setChecked() instead' + shouldThrowErrorOnElement('input[type="checkbox"]', message) + }) + + it('throws error if element is not valid', () => { + const message = 'wrapper.setValue() cannot be called on this element' + shouldThrowErrorOnElement('#label-el', message) + }) + + function shouldThrowErrorOnElement (selector, message) { + const wrapper = mountingMethod(ComponentWithInput) + const input = wrapper.find(selector) + + const fn = () => input.setValue('') + expect(fn).to.throw().with.property('message', '[vue-test-utils]: ' + message) + } +})