From df1974a8a88226ea9b0ecb49dc4a7837f1c02083 Mon Sep 17 00:00:00 2001 From: Ankur Kumar Date: Sat, 7 Oct 2017 18:45:16 +0530 Subject: [PATCH 0001/1136] docs: update example repo links (#80) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 73c12fcfa..b26565c3e 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,8 @@ Refer to [documentation](https://vue-test-utils.vuejs.org/) ## Examples -- [example with Jest](https://github.com/eddyerburgh/vue-test-utils-jest-example) -- [example with Mocha](https://github.com/eddyerburgh/vue-test-utils-mocha-example) +- [example with Jest](https://github.com/vuejs/vue-test-utils-jest-example) +- [example with Mocha](https://github.com/vuejs/vue-test-utils-mocha-webpack-example) - [example with tape](https://github.com/eddyerburgh/vue-test-utils-tape-example) - [example with AVA](https://github.com/eddyerburgh/vue-test-utils-ava-example) From da84f1b350a9907aa9c425b9b1ec58a738164db4 Mon Sep 17 00:00:00 2001 From: Lachlan Date: Wed, 11 Oct 2017 02:50:00 +0900 Subject: [PATCH 0002/1136] docs: fix code samples in using-with-vuex (#82) * Fix incorrect code in example code * Remove whitespace * Update sample code --- docs/en/guides/using-with-vuex.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/en/guides/using-with-vuex.md b/docs/en/guides/using-with-vuex.md index 46ad358a6..c1b82f5b7 100644 --- a/docs/en/guides/using-with-vuex.md +++ b/docs/en/guides/using-with-vuex.md @@ -67,7 +67,7 @@ describe('Actions.vue', () => { it('calls store action actionInput when input value is input and an input even is fired', () => { const wrapper = mount(Actions, { store }) - const input = wrapper.find('input')[0] + const input = wrapper.find('input') input.element.value = 'input' input.trigger('input') expect(actions.actionInput.calledOnce).toBe(true) @@ -75,7 +75,7 @@ describe('Actions.vue', () => { it('does not call store action actionInput when input value is not input and an input even is fired', () => { const wrapper = mount(Actions, { store }) - const input = wrapper.find('input')[0] + const input = wrapper.find('input') input.element.value = 'not input' input.trigger('input') expect(actions.actionInput.calledOnce).toBe(false) @@ -83,7 +83,7 @@ describe('Actions.vue', () => { it('calls store action actionClick when button is clicked', () => { const wrapper = mount(Actions, { store }) - wrapper.find('button')[0].trigger('click') + wrapper.find('button').trigger('click') expect(actions.actionClick.calledOnce).toBe(true) }) }) @@ -159,13 +159,13 @@ describe('Getters.vue', () => { it('Renders state.inputValue in first p tag', () => { const wrapper = mount(Actions, { store }) - const p = wrapper.find('p')[0] + const p = wrapper.find('p') expect(p.text()).toBe(getters.inputValue()) }) it('Renders state.clicks in second p tag', () => { const wrapper = mount(Actions, { store }) - const p = wrapper.find('p')[1] + const p = wrapper.findAll('p').at(1) expect(p.text()).toBe(getters.clicks().toString()) }) }) @@ -245,14 +245,14 @@ describe('Modules.vue', () => { it('calls store action moduleActionClick when button is clicked', () => { const wrapper = mount(Modules, { store }) - const button = wrapper.find('button')[0] + const button = wrapper.find('button') button.trigger('click') expect(actions.moduleActionClick.calledOnce).toBe(true) }) it('Renders state.inputValue in first p tag', () => { const wrapper = mount(Modules, { store }) - const p = wrapper.find('p')[0] + const p = wrapper.find('p') expect(p.text()).toBe(state.module.clicks.toString()) }) }) From eef37924edcbaa585aedc99e6e592b21bc6e441d Mon Sep 17 00:00:00 2001 From: Sean Ray Date: Wed, 11 Oct 2017 08:53:11 -0400 Subject: [PATCH 0003/1136] docs: clarify nextTick section of getting-started guide (#86) * Clarify nextTick section of getting-started guide Following up on #83, clarify getting started guide to explain that `nextTick` is not necessary when testing DOM updates, but it is still needed for operations that require advancing the event loop. * Addressing getting started update PR notes - Putting change in a *Note: * - Changing wording, "to test DOM updates in your tests" felt awkward. --- docs/en/guides/getting-started.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/guides/getting-started.md b/docs/en/guides/getting-started.md index c732ac1a6..5c023d6dc 100644 --- a/docs/en/guides/getting-started.md +++ b/docs/en/guides/getting-started.md @@ -106,7 +106,9 @@ it('button click should increment the count', () => { Vue batches pending DOM updates and applies them asynchronously to prevent unnecessary re-renders caused by multiple data mutations. This is why in practice we often have to use `Vue.nextTick` to wait until Vue has performed the actual DOM update after we trigger some state change. -To simplify usage, `vue-test-utils` applies all updates synchronously so you don't need to use `Vue.nextTick` in your tests. +To simplify usage, `vue-test-utils` applies all updates synchronously so you don't need to use `Vue.nextTick` to wait for DOM updates in your tests. + +*Note: `nextTick` is still necessary when you need to explictly advance the event loop, for operations such as asynchronous callbacks or promise resolution.* ## What's Next From 6ef437d784c63884dd7130cbea51cd5c4d1cb7e7 Mon Sep 17 00:00:00 2001 From: Edd Yerburgh Date: Wed, 11 Oct 2017 21:14:15 +0100 Subject: [PATCH 0004/1136] feat(setComputed): add setComputed method (#84) --- flow/wrapper.js | 1 + src/wrappers/error-wrapper.js | 4 +++ src/wrappers/wrapper-array.js | 6 +++++ src/wrappers/wrapper.js | 21 ++++++++++++++- .../components/component-with-computed.vue | 19 ++++++++++++++ .../specs/mount/Wrapper/setComputed.spec.js | 26 +++++++++++++++++++ .../unit/specs/wrappers/error-wrapper.spec.js | 7 +++++ .../unit/specs/wrappers/wrapper-array.spec.js | 11 +++++++- types/index.d.ts | 1 + types/test/wrapper.ts | 1 + 10 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 test/resources/components/component-with-computed.vue create mode 100644 test/unit/specs/mount/Wrapper/setComputed.spec.js diff --git a/flow/wrapper.js b/flow/wrapper.js index de1613430..9cf25f48e 100644 --- a/flow/wrapper.js +++ b/flow/wrapper.js @@ -24,6 +24,7 @@ declare interface BaseWrapper { // eslint-disable-line no-undef name(): string | void, text(): string | void, setData(data: Object): void, + setComputed(computed: Object): void, setMethods(methods: Object): void, setProps(data: Object): void, trigger(type: string, options: Object): void, diff --git a/src/wrappers/error-wrapper.js b/src/wrappers/error-wrapper.js index aa25f48b1..434ddd1f4 100644 --- a/src/wrappers/error-wrapper.js +++ b/src/wrappers/error-wrapper.js @@ -76,6 +76,10 @@ export default class ErrorWrapper implements BaseWrapper { throwError(`find did not return ${this.selector}, cannot call text() on empty Wrapper`) } + setComputed (): void { + throwError(`find did not return ${this.selector}, cannot call setComputed() on empty Wrapper`) + } + setData (): void { throwError(`find did not return ${this.selector}, cannot call setData() on empty Wrapper`) } diff --git a/src/wrappers/wrapper-array.js b/src/wrappers/wrapper-array.js index 493ee5d03..113ff9f7b 100644 --- a/src/wrappers/wrapper-array.js +++ b/src/wrappers/wrapper-array.js @@ -119,6 +119,12 @@ export default class WrapperArray implements BaseWrapper { } } + setComputed (computed: Object): void { + this.throwErrorIfWrappersIsEmpty('setComputed') + + this.wrappers.forEach(wrapper => wrapper.setComputed(computed)) + } + setData (data: Object): void { this.throwErrorIfWrappersIsEmpty('setData') diff --git a/src/wrappers/wrapper.js b/src/wrappers/wrapper.js index 9db7de867..a1e6eee73 100644 --- a/src/wrappers/wrapper.js +++ b/src/wrappers/wrapper.js @@ -283,7 +283,26 @@ export default class Wrapper implements BaseWrapper { } /** - * Sets vm data + * Sets vm computed + */ + setComputed (computed: Object) { + if (!this.isVueComponent) { + throwError('wrapper.setComputed() can only be called on a Vue instance') + } + + Object.keys(computed).forEach((key) => { + // $FlowIgnore : Problem with possibly null this.vm + if (!this.vm._computedWatchers[key]) { + throwError(`wrapper.setComputed() was passed a value that does not exist as a computed property on the Vue instance. Property ${key} does not exist on the Vue instance`) + } + // $FlowIgnore : Problem with possibly null this.vm + this.vm._computedWatchers[key].value = computed[key] + }) + this.update() + } + + /** + * Sets vm methods */ setMethods (methods: Object) { if (!this.isVueComponent) { diff --git a/test/resources/components/component-with-computed.vue b/test/resources/components/component-with-computed.vue new file mode 100644 index 000000000..311b0bcb0 --- /dev/null +++ b/test/resources/components/component-with-computed.vue @@ -0,0 +1,19 @@ + + + diff --git a/test/unit/specs/mount/Wrapper/setComputed.spec.js b/test/unit/specs/mount/Wrapper/setComputed.spec.js new file mode 100644 index 000000000..729aba584 --- /dev/null +++ b/test/unit/specs/mount/Wrapper/setComputed.spec.js @@ -0,0 +1,26 @@ +import { compileToFunctions } from 'vue-template-compiler' +import mount from '~src/mount' +import ComponentWithComputed from '~resources/components/component-with-computed.vue' + +describe('setComputed', () => { + it('sets component computed props and updates when called on Vue instance', () => { + const wrapper = mount(ComponentWithComputed) + expect(wrapper.text()).to.contain('message') + wrapper.setComputed({ reversedMessage: 'custom' }) + expect(wrapper.text()).to.contain('custom') + }) + + it('throws an error if computed watcher does not exist', () => { + const message = 'wrapper.setComputed() was passed a value that does not exist as a computed property on the Vue instance. Property noExist does not exist on the Vue instance' + const wrapper = mount(ComponentWithComputed) + expect(() => wrapper.setComputed({ noExist: '' })).throw(Error, message) + }) + + it('throws an error if node is not a Vue instance', () => { + const message = 'wrapper.setComputed() can only be called on a Vue instance' + const compiled = compileToFunctions('

') + const wrapper = mount(compiled) + const p = wrapper.find('p') + expect(() => p.setComputed({ ready: true })).throw(Error, message) + }) +}) diff --git a/test/unit/specs/wrappers/error-wrapper.spec.js b/test/unit/specs/wrappers/error-wrapper.spec.js index 59492f0a7..2e2e8ffc1 100644 --- a/test/unit/specs/wrappers/error-wrapper.spec.js +++ b/test/unit/specs/wrappers/error-wrapper.spec.js @@ -113,6 +113,13 @@ describe('ErrorWrapper', () => { expect(() => error.text()).to.throw().with.property('message', message) }) + it('setComputed throws error when called', () => { + const selector = 'div' + const message = `[vue-test-utils]: find did not return ${selector}, cannot call setComputed() on empty Wrapper` + const error = new ErrorWrapper(selector) + expect(() => error.setComputed()).to.throw().with.property('message', message) + }) + it('setData throws error when called', () => { const selector = 'div' const message = `[vue-test-utils]: find did not return ${selector}, cannot call setData() on empty Wrapper` diff --git a/test/unit/specs/wrappers/wrapper-array.spec.js b/test/unit/specs/wrappers/wrapper-array.spec.js index b949f5ec2..7946f36e5 100644 --- a/test/unit/specs/wrappers/wrapper-array.spec.js +++ b/test/unit/specs/wrappers/wrapper-array.spec.js @@ -206,7 +206,16 @@ describe('WrapperArray', () => { expect(() => wrapperArray.text()).to.throw().with.property('message', message) }) - it('setData calls setMethods on each wrapper', () => { + it('setComputed calls setMethods on each wrapper', () => { + const setComputed = sinon.stub() + const computed = {} + const wrapperArray = new WrapperArray([{ setComputed }, { setComputed }]) + wrapperArray.setComputed(computed) + expect(setComputed.calledTwice).to.equal(true) + expect(setComputed.calledWith(computed)).to.equal(true) + }) + + it('setMethods calls setMethods on each wrapper', () => { const setMethods = sinon.stub() const methods = {} const wrapperArray = new WrapperArray([{ setMethods }, { setMethods }]) diff --git a/types/index.d.ts b/types/index.d.ts index d5264497e..030268318 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -43,6 +43,7 @@ interface BaseWrapper { isVueInstance (): boolean update (): void + setComputed (computed: object): void setData (data: object): void setMethods (data: object): void setProps (props: object): void diff --git a/types/test/wrapper.ts b/types/test/wrapper.ts index 4be42ab5b..aea6334ec 100644 --- a/types/test/wrapper.ts +++ b/types/test/wrapper.ts @@ -30,6 +30,7 @@ const emittedByOrder = wrapper.emittedByOrder() const name: string = emittedByOrder[0].name wrapper.update() +wrapper.setComputed({computedProp: true}) wrapper.setData({ foo: 'bar' }) wrapper.setMethods({checked: true}) wrapper.setProps({ checked: true }) From 438603cf166378c3eb8c7776af014ff13792a4ef Mon Sep 17 00:00:00 2001 From: re-fort Date: Thu, 12 Oct 2017 05:08:54 +0000 Subject: [PATCH 0005/1136] fix: restore temporary change to original config after adding attrs/listeners (#87) --- src/lib/add-attrs.js | 3 ++- src/lib/add-listeners.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib/add-attrs.js b/src/lib/add-attrs.js index 78d79789f..d7a6b87ec 100644 --- a/src/lib/add-attrs.js +++ b/src/lib/add-attrs.js @@ -1,11 +1,12 @@ import Vue from 'vue' export default function addAttrs (vm, attrs) { + const originalVueConfig = Vue.config Vue.config.silent = true if (attrs) { vm.$attrs = attrs } else { vm.$attrs = {} } - Vue.config.silent = false + Vue.config.silent = originalVueConfig.silent } diff --git a/src/lib/add-listeners.js b/src/lib/add-listeners.js index a32abb308..b6989f610 100644 --- a/src/lib/add-listeners.js +++ b/src/lib/add-listeners.js @@ -1,11 +1,12 @@ import Vue from 'vue' export default function addListeners (vm, listeners) { + const originalVueConfig = Vue.config Vue.config.silent = true if (listeners) { vm.$listeners = listeners } else { vm.$listeners = {} } - Vue.config.silent = false + Vue.config.silent = originalVueConfig.silent } From de7e35161401328574fdc51227a4f2cc372bdfd7 Mon Sep 17 00:00:00 2001 From: Edd Yerburgh Date: Thu, 12 Oct 2017 19:11:38 +0100 Subject: [PATCH 0006/1136] fix: support setComputed on Vue 2.1 (#90) * feat(setComputed): add setComputed method * fix: support setComputed on Vue 2.1 --- flow/wrapper.js | 1 + src/wrappers/wrapper.js | 26 +++++++++++++++---- .../specs/mount/Wrapper/setComputed.spec.js | 1 + 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/flow/wrapper.js b/flow/wrapper.js index 9cf25f48e..ee6cae350 100644 --- a/flow/wrapper.js +++ b/flow/wrapper.js @@ -33,5 +33,6 @@ declare interface BaseWrapper { // eslint-disable-line no-undef declare type WrapperOptions = { // eslint-disable-line no-undef attachedToDocument: boolean, + version: number, error?: string } diff --git a/src/wrappers/wrapper.js b/src/wrappers/wrapper.js index a1e6eee73..5df404037 100644 --- a/src/wrappers/wrapper.js +++ b/src/wrappers/wrapper.js @@ -1,5 +1,6 @@ // @flow +import Vue from 'vue' import { isValidSelector } from '../lib/validators' import findVueComponents, { vmCtorMatchesName } from '../lib/find-vue-components' import findMatchingVNodes from '../lib/find-matching-vnodes' @@ -17,12 +18,14 @@ export default class Wrapper implements BaseWrapper { element: HTMLElement; update: Function; options: WrapperOptions; + version: number constructor (vnode: VNode, update: Function, options: WrapperOptions) { this.vnode = vnode this.element = vnode.elm this.update = update this.options = options + this.version = Number(`${Vue.version.split('.')[0]}.${Vue.version.split('.')[1]}`) } at () { @@ -291,12 +294,25 @@ export default class Wrapper implements BaseWrapper { } Object.keys(computed).forEach((key) => { - // $FlowIgnore : Problem with possibly null this.vm - if (!this.vm._computedWatchers[key]) { - throwError(`wrapper.setComputed() was passed a value that does not exist as a computed property on the Vue instance. Property ${key} does not exist on the Vue instance`) + if (this.version > 2.1) { + // $FlowIgnore : Problem with possibly null this.vm + if (!this.vm._computedWatchers[key]) { + throwError(`wrapper.setComputed() was passed a value that does not exist as a computed property on the Vue instance. Property ${key} does not exist on the Vue instance`) + } + // $FlowIgnore : Problem with possibly null this.vm + this.vm._computedWatchers[key].value = computed[key] + } else { + // $FlowIgnore : Problem with possibly null this.vm + if (!this.vm._watchers.some(w => w.getter.name === key)) { + throwError(`wrapper.setComputed() was passed a value that does not exist as a computed property on the Vue instance. Property ${key} does not exist on the Vue instance`) + } + // $FlowIgnore : Problem with possibly null this.vm + this.vm._watchers.forEach((watcher) => { + if (watcher.getter.name === key) { + watcher.value = computed[key] + } + }) } - // $FlowIgnore : Problem with possibly null this.vm - this.vm._computedWatchers[key].value = computed[key] }) this.update() } diff --git a/test/unit/specs/mount/Wrapper/setComputed.spec.js b/test/unit/specs/mount/Wrapper/setComputed.spec.js index 729aba584..9c6bcd5cf 100644 --- a/test/unit/specs/mount/Wrapper/setComputed.spec.js +++ b/test/unit/specs/mount/Wrapper/setComputed.spec.js @@ -6,6 +6,7 @@ describe('setComputed', () => { it('sets component computed props and updates when called on Vue instance', () => { const wrapper = mount(ComponentWithComputed) expect(wrapper.text()).to.contain('message') + debugger wrapper.setComputed({ reversedMessage: 'custom' }) expect(wrapper.text()).to.contain('custom') }) From 148967f70ff6522b83855341691526d367ba2b53 Mon Sep 17 00:00:00 2001 From: Edd Yerburgh Date: Thu, 12 Oct 2017 19:37:54 +0100 Subject: [PATCH 0007/1136] feat: add TransitionStub component (#91) * feat(setComputed): add setComputed method * fix: support setComputed on Vue 2.1 * feat(transitions): add TransitionStub component * fix: remove version from flow definition --- flow/wrapper.js | 1 - src/components/TransitionStub.js | 116 ++++++++++++++++++ src/lib/util.js | 4 + .../components/component-with-transition.vue | 14 +++ test/unit/specs/TransitionStub.spec.js | 33 +++++ 5 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 src/components/TransitionStub.js create mode 100644 test/resources/components/component-with-transition.vue create mode 100644 test/unit/specs/TransitionStub.spec.js diff --git a/flow/wrapper.js b/flow/wrapper.js index ee6cae350..9cf25f48e 100644 --- a/flow/wrapper.js +++ b/flow/wrapper.js @@ -33,6 +33,5 @@ declare interface BaseWrapper { // eslint-disable-line no-undef declare type WrapperOptions = { // eslint-disable-line no-undef attachedToDocument: boolean, - version: number, error?: string } diff --git a/src/components/TransitionStub.js b/src/components/TransitionStub.js new file mode 100644 index 000000000..a7361958b --- /dev/null +++ b/src/components/TransitionStub.js @@ -0,0 +1,116 @@ +/* eslint-disable */ +import { warn } from '../lib/util' + +function getRealChild (vnode: ?VNode): ?VNode { + const compOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions + if (compOptions && compOptions.Ctor.options.abstract) { + return getRealChild(getFirstComponentChild(compOptions.children)) + } else { + return vnode + } +} + +function getFirstComponentChild (children: ?Array): ?VNode { + if (Array.isArray(children)) { + for (let i = 0; i < children.length; i++) { + const c = children[i] + if (c && (c.componentOptions || isAsyncPlaceholder(c))) { + return c + } + } + } +} + +function isAsyncPlaceholder (node: VNode): boolean { + return node.isComment && node.asyncFactory +} +const camelizeRE = /-(\w)/g +export const camelize = (str: string): string => { + return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '') +} + +function extractTransitionData (comp: Component): Object { + const data = {} + const options: ComponentOptions = comp.$options + // props + for (const key in options.propsData) { + data[key] = comp[key] + } + // events. + // extract listeners and pass them directly to the transition methods + const listeners: ?Object = options._parentListeners + for (const key in listeners) { + data[camelize(key)] = listeners[key] + } + return data +} + +function hasParentTransition (vnode: VNode): ?boolean { + while ((vnode = vnode.parent)) { + if (vnode.data.transition) { + return true + } + } +} + +export default { + render (h: Function) { + let children: ?Array = this.$options._renderChildren + if (!children) { + return + } + + // filter out text nodes (possible whitespaces) + children = children.filter((c: VNode) => c.tag || isAsyncPlaceholder(c)) + /* istanbul ignore if */ + if (!children.length) { + return + } + + // warn multiple elements + if (children.length > 1) { + warn( + ' can only be used on a single element. Use ' + + ' for lists.', + this.$parent + ) + } + + const mode: string = this.mode + + // warn invalid mode + if (mode && mode !== 'in-out' && mode !== 'out-in' + ) { + warn( + 'invalid mode: ' + mode, + this.$parent + ) + } + + const rawChild: VNode = children[0] + + // if this is a component root node and the component's + // parent container node also has transition, skip. + if (hasParentTransition(this.$vnode)) { + return rawChild + } + + // apply transition data to child + // use getRealChild() to ignore abstract components e.g. keep-alive + const child: ?VNode = getRealChild(rawChild) + + if (!child) { + return rawChild + } + + (child.data || (child.data = {})).transition = extractTransitionData(this) + + // mark v-show + // so that the transition module can hand over the control to the directive + if (child.data.directives && child.data.directives.some(d => d.name === 'show')) { + child.data.show = true + } + + return rawChild + } +} diff --git a/src/lib/util.js b/src/lib/util.js index e478b1a50..65f865903 100644 --- a/src/lib/util.js +++ b/src/lib/util.js @@ -3,3 +3,7 @@ export function throwError (msg: string): void { throw new Error(`[vue-test-utils]: ${msg}`) } + +export function warn (msg: string): void { + console.error(`[vue-test-utils]: ${msg}`) +} diff --git a/test/resources/components/component-with-transition.vue b/test/resources/components/component-with-transition.vue new file mode 100644 index 000000000..ea52f9381 --- /dev/null +++ b/test/resources/components/component-with-transition.vue @@ -0,0 +1,14 @@ + + + diff --git a/test/unit/specs/TransitionStub.spec.js b/test/unit/specs/TransitionStub.spec.js new file mode 100644 index 000000000..d02e948a9 --- /dev/null +++ b/test/unit/specs/TransitionStub.spec.js @@ -0,0 +1,33 @@ +import ComponentWithTransition from '~resources/components/component-with-transition.vue' +import TransitionStub from '~src/components/TransitionStub' +import mount from '~src/mount' + +describe.only('TransitionStub', () => { + it('update synchronously when used as stubs for Transition', () => { + const wrapper = mount(ComponentWithTransition, { + stubs: { + 'transition': TransitionStub + } + }) + expect(wrapper.text()).contains('a') + wrapper.setData({ a: 'b' }) + expect(wrapper.text()).contains('b') + }) + + it('logs error when has multiple children', () => { + const TestComponent = { + template: ` +
+ ` + } + const msg = '[vue-test-utils]: can only be used on a single element. Use for lists.' + const error = sinon.stub(console, 'error') + mount(TestComponent, { + stubs: { + 'transition': TransitionStub + } + }) + expect(error.args[0][0]).to.equal(msg) + error.restore() + }) +}) From bacc1fd110c3f8938568397b30a1a582b988399c Mon Sep 17 00:00:00 2001 From: Edd Yerburgh Date: Thu, 12 Oct 2017 19:50:32 +0100 Subject: [PATCH 0008/1136] test: only run unit tests in compatibility script --- test/test.sh | 3 ++- test/unit/specs/TransitionStub.spec.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/test/test.sh b/test/test.sh index e9b848039..887e20d4e 100755 --- a/test/test.sh +++ b/test/test.sh @@ -7,7 +7,8 @@ test_version_number(){ echo "running unit tests with Vue $1" yarn install yarn add vue@$1 vue-template-compiler@$1 - yarn test + yarn test:unit + yarn test:unit:karma } test_version_number "2.0.8" diff --git a/test/unit/specs/TransitionStub.spec.js b/test/unit/specs/TransitionStub.spec.js index d02e948a9..5e24aa9dd 100644 --- a/test/unit/specs/TransitionStub.spec.js +++ b/test/unit/specs/TransitionStub.spec.js @@ -2,7 +2,7 @@ import ComponentWithTransition from '~resources/components/component-with-transi import TransitionStub from '~src/components/TransitionStub' import mount from '~src/mount' -describe.only('TransitionStub', () => { +describe('TransitionStub', () => { it('update synchronously when used as stubs for Transition', () => { const wrapper = mount(ComponentWithTransition, { stubs: { From fd4d7c941cd8539bd2a547dd84683752c01dac05 Mon Sep 17 00:00:00 2001 From: Edd Yerburgh Date: Thu, 12 Oct 2017 20:06:19 +0100 Subject: [PATCH 0009/1136] feat(transition): add TransitionGroupStub --- src/components/TransitionGroupStub.js | 10 ++++++ .../component-with-transition-group.vue | 15 +++++++++ test/unit/specs/TransitionGroupStub.spec.js | 33 +++++++++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 src/components/TransitionGroupStub.js create mode 100644 test/resources/components/component-with-transition-group.vue create mode 100644 test/unit/specs/TransitionGroupStub.spec.js diff --git a/src/components/TransitionGroupStub.js b/src/components/TransitionGroupStub.js new file mode 100644 index 000000000..17fcb1be6 --- /dev/null +++ b/src/components/TransitionGroupStub.js @@ -0,0 +1,10 @@ +// @flow + +export default { + render (h: Function) { + const tag: string = this.tag || this.$vnode.data.tag || 'span' + const children: Array = this.$slots.default || [] + + return h(tag, null, children) + } +} diff --git a/test/resources/components/component-with-transition-group.vue b/test/resources/components/component-with-transition-group.vue new file mode 100644 index 000000000..bec0f2ebc --- /dev/null +++ b/test/resources/components/component-with-transition-group.vue @@ -0,0 +1,15 @@ + + + diff --git a/test/unit/specs/TransitionGroupStub.spec.js b/test/unit/specs/TransitionGroupStub.spec.js new file mode 100644 index 000000000..18d9b7ed4 --- /dev/null +++ b/test/unit/specs/TransitionGroupStub.spec.js @@ -0,0 +1,33 @@ +import ComponentWithTransitionGroup from '~resources/components/component-with-transition-group.vue' +import TransitionGroupStub from '~src/components/TransitionGroupStub' +import mount from '~src/mount' + +describe('TransitionGroupStub', () => { + it('update synchronously when used as stubs for Transition', () => { + const wrapper = mount(ComponentWithTransitionGroup, { + stubs: { + 'transition-group': TransitionGroupStub + } + }) + expect(wrapper.text()).contains('a') + wrapper.setData({ a: 'b' }) + expect(wrapper.text()).contains('b') + }) + + // it('logs error when has multiple children', () => { + // const TestComponent = { + // template: ` + //
+ // ` + // } + // const msg = '[vue-test-utils]: can only be used on a single element. Use for lists.' + // const error = sinon.stub(console, 'error') + // mount(TestComponent, { + // stubs: { + // 'transition': TransitionStub + // } + // }) + // expect(error.args[0][0]).to.equal(msg) + // error.restore() + // }) +}) From 936ce4ad9e74245123ed775331f54716260d5458 Mon Sep 17 00:00:00 2001 From: Edd Yerburgh Date: Thu, 12 Oct 2017 20:11:39 +0100 Subject: [PATCH 0010/1136] feat(transition): add TransitionStub to export --- src/index.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index 1759425cb..f1ed854b1 100644 --- a/src/index.js +++ b/src/index.js @@ -1,9 +1,13 @@ import shallow from './shallow' import mount from './mount' import createLocalVue from './create-local-vue' +import TransitionStub from './components/TransitionStub' +import TransitionGroupStub from './components/TransitionGroupStub' export default { createLocalVue, mount, - shallow + shallow, + TransitionStub, + TransitionGroupStub } From a2aaff74af2d0366f8f0e405e2cddca1247284da Mon Sep 17 00:00:00 2001 From: Edd Yerburgh Date: Thu, 12 Oct 2017 20:19:44 +0100 Subject: [PATCH 0011/1136] refactor: add .flow extension to declaration files --- flow/{modules.js => modules.flow.js} | 0 flow/{options.js => options.flow.js} | 0 flow/{vue.js => vue.flow.js} | 0 flow/{wrapper.js => wrapper.flow.js} | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename flow/{modules.js => modules.flow.js} (100%) rename flow/{options.js => options.flow.js} (100%) rename flow/{vue.js => vue.flow.js} (100%) rename flow/{wrapper.js => wrapper.flow.js} (100%) diff --git a/flow/modules.js b/flow/modules.flow.js similarity index 100% rename from flow/modules.js rename to flow/modules.flow.js diff --git a/flow/options.js b/flow/options.flow.js similarity index 100% rename from flow/options.js rename to flow/options.flow.js diff --git a/flow/vue.js b/flow/vue.flow.js similarity index 100% rename from flow/vue.js rename to flow/vue.flow.js diff --git a/flow/wrapper.js b/flow/wrapper.flow.js similarity index 100% rename from flow/wrapper.js rename to flow/wrapper.flow.js From 8766dbedafe409c96ed11a2f2e62b4f7f18cd858 Mon Sep 17 00:00:00 2001 From: Edd Yerburgh Date: Thu, 12 Oct 2017 21:13:37 +0100 Subject: [PATCH 0012/1136] docs: use localVue in using-with-vuex --- docs/en/guides/using-with-vuex.md | 75 ++++++++++++++++--------------- 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/docs/en/guides/using-with-vuex.md b/docs/en/guides/using-with-vuex.md index c1b82f5b7..ec9a769a2 100644 --- a/docs/en/guides/using-with-vuex.md +++ b/docs/en/guides/using-with-vuex.md @@ -1,5 +1,7 @@ # Using with Vuex +In this guide, we'll see how to test Vuex in components with vue-test-utils. + ## Mocking Actions Let’s look at some code. @@ -35,20 +37,20 @@ export default{ For the purposes of this test, we don’t care what the actions do, or what the store looks like. We just need to know that these actions are being fired when they should, and that they are fired with the expected value. -To test this, we need to pass a mock store to Vue when we mount our component. +To test this, we need to pass a mock store to Vue when we shallow our component. + +Instead of passing the store to the base Vue constructor, we can pass it to a - [localVue](../api/options.md#localvue). A localVue is a scoped Vue constructor that we can make changes to without affecting the global Vue constructor. Let’s see what this looks like: ``` js -import Vue from 'vue' -import { mount } from 'vue-test-utils' -import sinon from 'sinon' -import { expect } from 'chai' +import { shallow, createLocalVue } from 'vue-test-utils' import Vuex from 'vuex' -import 'babel-polyfill' import Actions from '../../../src/components/Actions' -Vue.use(Vuex) +const localVue = createLocalVue() + +localVue.use(Vuex) describe('Actions.vue', () => { let actions @@ -56,8 +58,8 @@ describe('Actions.vue', () => { beforeEach(() => { actions = { - actionClick: sinon.stub(), - actionInput: sinon.stub() + actionClick: jest.fn(), + actionInput: jest.fn() } store = new Vuex.Store({ state: {}, @@ -66,25 +68,25 @@ describe('Actions.vue', () => { }) it('calls store action actionInput when input value is input and an input even is fired', () => { - const wrapper = mount(Actions, { store }) + const wrapper = shallow(Actions, { store, localVue }) const input = wrapper.find('input') input.element.value = 'input' input.trigger('input') - expect(actions.actionInput.calledOnce).toBe(true) + expect(actions.actionInput).toHaveBeenCalled() }) it('does not call store action actionInput when input value is not input and an input even is fired', () => { - const wrapper = mount(Actions, { store }) + const wrapper = shallow(Actions, { store, localVue }) const input = wrapper.find('input') input.element.value = 'not input' input.trigger('input') - expect(actions.actionInput.calledOnce).toBe(false) + expect(actions.actionInput).not.toHaveBeenCalled() }) it('calls store action actionClick when button is clicked', () => { - const wrapper = mount(Actions, { store }) + const wrapper = shallow(Actions, { store, localVue }) wrapper.find('button').trigger('click') - expect(actions.actionClick.calledOnce).toBe(true) + expect(actions.actionClick).toHaveBeenCalled() }) }) ``` @@ -93,13 +95,13 @@ What’s happening here? First we tell Vue to use Vuex with the Vue.use method. We then make a mock store by calling new Vuex.store with our mock values. We only pass it the actions, since that’s all we care about. -The actions are [sinon stubs](http://sinonjs.org/). The stubs give us methods to assert whether the actions were called or not. +The actions are [jest mock functions](https://facebook.github.io/jest/docs/en/mock-functions.html). These mock functions give us methods to assert whether the actions were called or not. We can then assert in our tests that the action stub was called when expected. Now the way we define the store might look a bit foreign to you. -We’re using beforeEach to ensure we have a clean store before each test. beforeEach is a mocha hook that’s called before each test. In our test, we are reassigning the store variables value. If we didn’t do this, the sinon stubs would need to be automatically reset. It also lets us change the state in our tests, without it affecting later tests. +We’re using beforeEach to ensure we have a clean store before each test. beforeEach is a mocha hook that’s called before each test. In our test, we are reassigning the store variables value. If we didn’t do this, the mock functions would need to be automatically reset. It also lets us change the state in our tests, without it affecting later tests. The most important thing to note in this test is that **we create a mock Vuex store and then pass it to vue-test-utils**. @@ -133,14 +135,13 @@ This is a fairly simple component. It renders the result of the getters clicks a Let’s see the test: ``` js -import 'babel-polyfill' -import Vue from 'vue' -import { mount } from 'vue-test-utils' -import { expect } from 'chai' +import { shallow, createLocalVue } from 'vue-test-utils' import Vuex from 'vuex' import Actions from '../../../src/components/Getters' -Vue.use(Vuex) +const localVue = createLocalVue() + +localVue.use(Vuex) describe('Getters.vue', () => { let getters @@ -158,19 +159,19 @@ describe('Getters.vue', () => { }) it('Renders state.inputValue in first p tag', () => { - const wrapper = mount(Actions, { store }) + const wrapper = shallow(Actions, { store, localVue }) const p = wrapper.find('p') expect(p.text()).toBe(getters.inputValue()) }) it('Renders state.clicks in second p tag', () => { - const wrapper = mount(Actions, { store }) + const wrapper = shallow(Actions, { store, localVue }) const p = wrapper.findAll('p').at(1) expect(p.text()).toBe(getters.clicks().toString()) }) }) ``` -This test is similar to our actions test. We create a mock store before each test, pass it as an option when we call mount, and assert that the value returned by our mock getters is being rendered. +This test is similar to our actions test. We create a mock store before each test, pass it as an option when we call shallow, and assert that the value returned by our mock getters is being rendered. This is great, but what if we want to check our getters are returning the correct part of our state? @@ -209,16 +210,14 @@ Simple component that includes one action and one getter. And the test: ``` js -import Vue from 'vue' -import { mount } from 'vue-test-utils' -import sinon from 'sinon' -import { expect } from 'chai' +import { shallow, createLocalVue } from 'vue-test-utils' import Vuex from 'vuex' -import 'babel-polyfill' import Modules from '../../../src/components/Modules' import module from '../../../src/store/module' -Vue.use(Vuex) +const localVue = createLocalVue() + +localVue.use(Vuex) describe('Modules.vue', () => { let actions @@ -233,7 +232,7 @@ describe('Modules.vue', () => { } actions = { - moduleActionClick: sinon.stub() + moduleActionClick: jest.fn() } store = new Vuex.Store({ @@ -244,18 +243,22 @@ describe('Modules.vue', () => { }) it('calls store action moduleActionClick when button is clicked', () => { - const wrapper = mount(Modules, { store }) + const wrapper = shallow(Modules, { store, localVue }) const button = wrapper.find('button') button.trigger('click') - expect(actions.moduleActionClick.calledOnce).toBe(true) + expect(actions.moduleActionClick).toHaveBeenCalled() }) it('Renders state.inputValue in first p tag', () => { - const wrapper = mount(Modules, { store }) + const wrapper = shallow(Modules, { store, localVue }) const p = wrapper.find('p') expect(p.text()).toBe(state.module.clicks.toString()) }) }) ``` -To have a look at what the module file looks like, [check out the repo](https://github.com/eddyerburgh/mock-vuex-in-vue-unit-tests-tutorial). +### Resources + +- [Example project for this guide](https://github.com/eddyerburgh/vue-test-utils-vuex-example) +- [localVue](../api/options.md#localvue) +- [createLocalVue](../api/createLocalVue.md) From 03e2a37b1ecc9f66c01cede46f270e8f42b8bb6f Mon Sep 17 00:00:00 2001 From: Edd Yerburgh Date: Thu, 12 Oct 2017 21:36:40 +0100 Subject: [PATCH 0013/1136] docs: add using-with-vue-router --- docs/en/SUMMARY.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/en/SUMMARY.md b/docs/en/SUMMARY.md index dcd941af2..d0e1c133c 100644 --- a/docs/en/SUMMARY.md +++ b/docs/en/SUMMARY.md @@ -6,7 +6,8 @@ * [Choosing a test runner](guides/choosing-a-test-runner.md) * [Testing SFCs with Jest](guides/testing-SFCs-with-jest.md) * [Testing SFCs with Mocha + webpack](guides/testing-SFCs-with-mocha-webpack.md) - * [Using with Vuex](guides/using-with-vuex.md) + * [Using with vue-router](guides/using-with-vuex.md) + * [Using with vuex](guides/using-with-vuex.md) * [API](api/README.md) * [mount](api/mount.md) * [shallow](api/shallow.md) From 032fe20c6ec349984295b1d546b538796ad6550c Mon Sep 17 00:00:00 2001 From: Edd Yerburgh Date: Thu, 12 Oct 2017 21:42:27 +0100 Subject: [PATCH 0014/1136] docs: add using with vue router --- docs/en/guides/using-with-vue-router.md | 71 +++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 docs/en/guides/using-with-vue-router.md diff --git a/docs/en/guides/using-with-vue-router.md b/docs/en/guides/using-with-vue-router.md new file mode 100644 index 000000000..444404f0c --- /dev/null +++ b/docs/en/guides/using-with-vue-router.md @@ -0,0 +1,71 @@ +# Using with vue-router + +## Installing vue-router in tests + +You should never install vue-router on the Vue base constructor in tests. Installing vue-router adds `$route` and `$router` as read-only properties on Vue prototype. + +To avoid this, we can create a localVue, and install vue-router on that. + +```js +import VueRouter from 'vue-router' +const localVue = createLocalVue() + +localVue.use(VueRouter) + +shallow(Component, { + localVue +}) +``` + +## Testing components that use router-link or router-view + +When you install vue-router, the router-link and router-view components are registered. This means we can use them anywhere in our application without needing to import them. + +When we run tests, we need to make these vue-router components available to the component we're mounting. There are two methods to do this. + +### Using stubs + +```js +shallow(Component, { + stubs: ['router-link', 'router-view'] +}) +``` + +### Installing vue-router with localVue + +```js +import VueRouter from 'vue-router' +const localVue = createLocalVue() + +localVue.use(VueRouter) + +shallow(Component, { + localVue +}) +``` + +## Mocking $route and $router + +Sometimes you want to test that a component does something with parameters from the `$route` and `$router` objects. To do that, you can pass custom mocks to the Vue instance. + +```js +const $route = { + path: '/some/path' +} + +const wrapper = shallow(Component, { + mocks: { + $route + } +}) + +wrapper.vm.$router // /some/path +``` + +## Common gotchas + +Installing vue-router adds `$route` and `$router` as read-only properties on Vue prototype. + +This means any future tests that try to mock $route or `$router` will fail. + +To avoid this, never install vue-router when you're running tests. From 72eb01514692581fd08cff63d35f262c6a7b526b Mon Sep 17 00:00:00 2001 From: Edd Yerburgh Date: Thu, 12 Oct 2017 21:45:57 +0100 Subject: [PATCH 0015/1136] test: add flow to TransitionStub --- src/components/TransitionStub.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/components/TransitionStub.js b/src/components/TransitionStub.js index a7361958b..c4bcdd20c 100644 --- a/src/components/TransitionStub.js +++ b/src/components/TransitionStub.js @@ -1,8 +1,9 @@ -/* eslint-disable */ +// @flow + import { warn } from '../lib/util' function getRealChild (vnode: ?VNode): ?VNode { - const compOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions + const compOptions = vnode && vnode.componentOptions if (compOptions && compOptions.Ctor.options.abstract) { return getRealChild(getFirstComponentChild(compOptions.children)) } else { @@ -31,7 +32,7 @@ export const camelize = (str: string): string => { function extractTransitionData (comp: Component): Object { const data = {} - const options: ComponentOptions = comp.$options + const options = comp.$options // props for (const key in options.propsData) { data[key] = comp[key] @@ -71,8 +72,7 @@ export default { if (children.length > 1) { warn( ' can only be used on a single element. Use ' + - ' for lists.', - this.$parent + ' for lists.' ) } @@ -82,8 +82,7 @@ export default { if (mode && mode !== 'in-out' && mode !== 'out-in' ) { warn( - 'invalid mode: ' + mode, - this.$parent + 'invalid mode: ' + mode ) } From 5056e79aaccc8b7d99e0824f780b5c3b8d25b626 Mon Sep 17 00:00:00 2001 From: Edd Yerburgh Date: Thu, 12 Oct 2017 21:46:46 +0100 Subject: [PATCH 0016/1136] build: 1.0.0-beta.2 --- dist/vue-test-utils.amd.js | 201 ++++++++++++++++++++++++++++++++++-- dist/vue-test-utils.iife.js | 201 ++++++++++++++++++++++++++++++++++-- dist/vue-test-utils.js | 201 ++++++++++++++++++++++++++++++++++-- dist/vue-test-utils.umd.js | 201 ++++++++++++++++++++++++++++++++++-- 4 files changed, 764 insertions(+), 40 deletions(-) diff --git a/dist/vue-test-utils.amd.js b/dist/vue-test-utils.amd.js index 9d70b88be..b15adefaa 100644 --- a/dist/vue-test-utils.amd.js +++ b/dist/vue-test-utils.amd.js @@ -2541,6 +2541,10 @@ function throwError (msg) { throw new Error(("[vue-test-utils]: " + msg)) } +function warn (msg) { + console.error(("[vue-test-utils]: " + msg)); +} + // var LIFECYCLE_HOOKS = [ @@ -2917,6 +2921,12 @@ WrapperArray.prototype.throwErrorIfWrappersIsEmpty = function throwErrorIfWrappe } }; +WrapperArray.prototype.setComputed = function setComputed (computed) { + this.throwErrorIfWrappersIsEmpty('setComputed'); + + this.wrappers.forEach(function (wrapper) { return wrapper.setComputed(computed); }); +}; + WrapperArray.prototype.setData = function setData (data) { this.throwErrorIfWrappersIsEmpty('setData'); @@ -3020,6 +3030,10 @@ ErrorWrapper.prototype.text = function text () { throwError(("find did not return " + (this.selector) + ", cannot call text() on empty Wrapper")); }; +ErrorWrapper.prototype.setComputed = function setComputed () { + throwError(("find did not return " + (this.selector) + ", cannot call setComputed() on empty Wrapper")); +}; + ErrorWrapper.prototype.setData = function setData () { throwError(("find did not return " + (this.selector) + ", cannot call setData() on empty Wrapper")); }; @@ -3047,6 +3061,7 @@ var Wrapper = function Wrapper (vnode, update, options) { this.element = vnode.elm; this.update = update; this.options = options; + this.version = Number(((Vue.version.split('.')[0]) + "." + (Vue.version.split('.')[1]))); }; Wrapper.prototype.at = function at () { @@ -3242,9 +3257,7 @@ Wrapper.prototype.findAll = function findAll (selector) { * Returns HTML of element as a string */ Wrapper.prototype.html = function html () { - var tmp = document.createElement('div'); - tmp.appendChild(this.element); - return tmp.innerHTML + return this.element.outerHTML }; /** @@ -3274,7 +3287,7 @@ Wrapper.prototype.is = function is (selector) { * Checks if node is empty */ Wrapper.prototype.isEmpty = function isEmpty () { - return this.vnode.children === undefined + return this.vnode.children === undefined || this.vnode.children.length === 0 }; /** @@ -3313,7 +3326,41 @@ Wrapper.prototype.setData = function setData (data) { }; /** - * Sets vm data + * Sets vm computed + */ +Wrapper.prototype.setComputed = function setComputed (computed) { + var this$1 = this; + + if (!this.isVueComponent) { + throwError('wrapper.setComputed() can only be called on a Vue instance'); + } + + Object.keys(computed).forEach(function (key) { + if (this$1.version > 2.1) { + // $FlowIgnore : Problem with possibly null this.vm + if (!this$1.vm._computedWatchers[key]) { + throwError(("wrapper.setComputed() was passed a value that does not exist as a computed property on the Vue instance. Property " + key + " does not exist on the Vue instance")); + } + // $FlowIgnore : Problem with possibly null this.vm + this$1.vm._computedWatchers[key].value = computed[key]; + } else { + // $FlowIgnore : Problem with possibly null this.vm + if (!this$1.vm._watchers.some(function (w) { return w.getter.name === key; })) { + throwError(("wrapper.setComputed() was passed a value that does not exist as a computed property on the Vue instance. Property " + key + " does not exist on the Vue instance")); + } + // $FlowIgnore : Problem with possibly null this.vm + this$1.vm._watchers.forEach(function (watcher) { + if (watcher.getter.name === key) { + watcher.value = computed[key]; + } + }); + } + }); + this.update(); +}; + +/** + * Sets vm methods */ Wrapper.prototype.setMethods = function setMethods (methods) { var this$1 = this; @@ -3439,6 +3486,7 @@ function logEvents (vm, emitted, emittedByOrder) { function update () { this._update(this._render()); + this.$children.forEach(function (child) { return update.call(child); }); } var VueWrapper = (function (Wrapper$$1) { @@ -3450,7 +3498,11 @@ var VueWrapper = (function (Wrapper$$1) { get: function () { return vm._vnode; }, set: function () {} })); - + // $FlowIgnore + Object.defineProperty(this, 'element', ({ + get: function () { return vm.$el; }, + set: function () {} + })); this.vm = vm; this.isVueComponent = true; this._emitted = Object.create(null); @@ -3519,23 +3571,25 @@ function addMocks (mockedProperties, Vue$$1) { } function addAttrs (vm, attrs) { + var originalVueConfig = Vue.config; Vue.config.silent = true; if (attrs) { vm.$attrs = attrs; } else { vm.$attrs = {}; } - Vue.config.silent = false; + Vue.config.silent = originalVueConfig.silent; } function addListeners (vm, listeners) { + var originalVueConfig = Vue.config; Vue.config.silent = true; if (listeners) { vm.$listeners = listeners; } else { vm.$listeners = {}; } - Vue.config.silent = false; + Vue.config.silent = originalVueConfig.silent; } function addProvide (component, options) { @@ -3648,7 +3702,7 @@ function mount (component, options) { throwError( 'window is undefined, vue-test-utils needs to be run in a browser environment.\n' + 'You can run the tests in node using jsdom + jsdom-global.\n' + - 'See https://vue-test-utils.vuejs.org/en/guides/general-tips.html for more details.' + 'See https://vue-test-utils.vuejs.org/en/guides/common-tips.html for more details.' ); } @@ -3721,10 +3775,137 @@ function createLocalVue () { return instance } +// + +function getRealChild (vnode) { + var compOptions = vnode && vnode.componentOptions; + if (compOptions && compOptions.Ctor.options.abstract) { + return getRealChild(getFirstComponentChild(compOptions.children)) + } else { + return vnode + } +} + +function getFirstComponentChild (children) { + if (Array.isArray(children)) { + for (var i = 0; i < children.length; i++) { + var c = children[i]; + if (c && (c.componentOptions || isAsyncPlaceholder(c))) { + return c + } + } + } +} + +function isAsyncPlaceholder (node) { + return node.isComment && node.asyncFactory +} +var camelizeRE = /-(\w)/g; +var camelize = function (str) { + return str.replace(camelizeRE, function (_, c) { return c ? c.toUpperCase() : ''; }) +}; + +function extractTransitionData (comp) { + var data = {}; + var options = comp.$options; + // props + for (var key in options.propsData) { + data[key] = comp[key]; + } + // events. + // extract listeners and pass them directly to the transition methods + var listeners = options._parentListeners; + for (var key$1 in listeners) { + data[camelize(key$1)] = listeners[key$1]; + } + return data +} + +function hasParentTransition (vnode) { + while ((vnode = vnode.parent)) { + if (vnode.data.transition) { + return true + } + } +} + +var TransitionStub = { + render: function render (h) { + var children = this.$options._renderChildren; + if (!children) { + return + } + + // filter out text nodes (possible whitespaces) + children = children.filter(function (c) { return c.tag || isAsyncPlaceholder(c); }); + /* istanbul ignore if */ + if (!children.length) { + return + } + + // warn multiple elements + if (children.length > 1) { + warn( + ' can only be used on a single element. Use ' + + ' for lists.' + ); + } + + var mode = this.mode; + + // warn invalid mode + if (mode && mode !== 'in-out' && mode !== 'out-in' + ) { + warn( + 'invalid mode: ' + mode + ); + } + + var rawChild = children[0]; + + // if this is a component root node and the component's + // parent container node also has transition, skip. + if (hasParentTransition(this.$vnode)) { + return rawChild + } + + // apply transition data to child + // use getRealChild() to ignore abstract components e.g. keep-alive + var child = getRealChild(rawChild); + + if (!child) { + return rawChild + } + + (child.data || (child.data = {})).transition = extractTransitionData(this); + + // mark v-show + // so that the transition module can hand over the control to the directive + if (child.data.directives && child.data.directives.some(function (d) { return d.name === 'show'; })) { + child.data.show = true; + } + + return rawChild + } +}; + +// + +var TransitionGroupStub = { + render: function render (h) { + var tag = this.tag || this.$vnode.data.tag || 'span'; + var children = this.$slots.default || []; + + return h(tag, null, children) + } +}; + var index = { createLocalVue: createLocalVue, mount: mount, - shallow: shallow + shallow: shallow, + TransitionStub: TransitionStub, + TransitionGroupStub: TransitionGroupStub }; return index; diff --git a/dist/vue-test-utils.iife.js b/dist/vue-test-utils.iife.js index 83e1d9d92..c219a0118 100644 --- a/dist/vue-test-utils.iife.js +++ b/dist/vue-test-utils.iife.js @@ -2542,6 +2542,10 @@ function throwError (msg) { throw new Error(("[vue-test-utils]: " + msg)) } +function warn (msg) { + console.error(("[vue-test-utils]: " + msg)); +} + // var LIFECYCLE_HOOKS = [ @@ -2918,6 +2922,12 @@ WrapperArray.prototype.throwErrorIfWrappersIsEmpty = function throwErrorIfWrappe } }; +WrapperArray.prototype.setComputed = function setComputed (computed) { + this.throwErrorIfWrappersIsEmpty('setComputed'); + + this.wrappers.forEach(function (wrapper) { return wrapper.setComputed(computed); }); +}; + WrapperArray.prototype.setData = function setData (data) { this.throwErrorIfWrappersIsEmpty('setData'); @@ -3021,6 +3031,10 @@ ErrorWrapper.prototype.text = function text () { throwError(("find did not return " + (this.selector) + ", cannot call text() on empty Wrapper")); }; +ErrorWrapper.prototype.setComputed = function setComputed () { + throwError(("find did not return " + (this.selector) + ", cannot call setComputed() on empty Wrapper")); +}; + ErrorWrapper.prototype.setData = function setData () { throwError(("find did not return " + (this.selector) + ", cannot call setData() on empty Wrapper")); }; @@ -3048,6 +3062,7 @@ var Wrapper = function Wrapper (vnode, update, options) { this.element = vnode.elm; this.update = update; this.options = options; + this.version = Number(((Vue.version.split('.')[0]) + "." + (Vue.version.split('.')[1]))); }; Wrapper.prototype.at = function at () { @@ -3243,9 +3258,7 @@ Wrapper.prototype.findAll = function findAll (selector) { * Returns HTML of element as a string */ Wrapper.prototype.html = function html () { - var tmp = document.createElement('div'); - tmp.appendChild(this.element); - return tmp.innerHTML + return this.element.outerHTML }; /** @@ -3275,7 +3288,7 @@ Wrapper.prototype.is = function is (selector) { * Checks if node is empty */ Wrapper.prototype.isEmpty = function isEmpty () { - return this.vnode.children === undefined + return this.vnode.children === undefined || this.vnode.children.length === 0 }; /** @@ -3314,7 +3327,41 @@ Wrapper.prototype.setData = function setData (data) { }; /** - * Sets vm data + * Sets vm computed + */ +Wrapper.prototype.setComputed = function setComputed (computed) { + var this$1 = this; + + if (!this.isVueComponent) { + throwError('wrapper.setComputed() can only be called on a Vue instance'); + } + + Object.keys(computed).forEach(function (key) { + if (this$1.version > 2.1) { + // $FlowIgnore : Problem with possibly null this.vm + if (!this$1.vm._computedWatchers[key]) { + throwError(("wrapper.setComputed() was passed a value that does not exist as a computed property on the Vue instance. Property " + key + " does not exist on the Vue instance")); + } + // $FlowIgnore : Problem with possibly null this.vm + this$1.vm._computedWatchers[key].value = computed[key]; + } else { + // $FlowIgnore : Problem with possibly null this.vm + if (!this$1.vm._watchers.some(function (w) { return w.getter.name === key; })) { + throwError(("wrapper.setComputed() was passed a value that does not exist as a computed property on the Vue instance. Property " + key + " does not exist on the Vue instance")); + } + // $FlowIgnore : Problem with possibly null this.vm + this$1.vm._watchers.forEach(function (watcher) { + if (watcher.getter.name === key) { + watcher.value = computed[key]; + } + }); + } + }); + this.update(); +}; + +/** + * Sets vm methods */ Wrapper.prototype.setMethods = function setMethods (methods) { var this$1 = this; @@ -3440,6 +3487,7 @@ function logEvents (vm, emitted, emittedByOrder) { function update () { this._update(this._render()); + this.$children.forEach(function (child) { return update.call(child); }); } var VueWrapper = (function (Wrapper$$1) { @@ -3451,7 +3499,11 @@ var VueWrapper = (function (Wrapper$$1) { get: function () { return vm._vnode; }, set: function () {} })); - + // $FlowIgnore + Object.defineProperty(this, 'element', ({ + get: function () { return vm.$el; }, + set: function () {} + })); this.vm = vm; this.isVueComponent = true; this._emitted = Object.create(null); @@ -3520,23 +3572,25 @@ function addMocks (mockedProperties, Vue$$1) { } function addAttrs (vm, attrs) { + var originalVueConfig = Vue.config; Vue.config.silent = true; if (attrs) { vm.$attrs = attrs; } else { vm.$attrs = {}; } - Vue.config.silent = false; + Vue.config.silent = originalVueConfig.silent; } function addListeners (vm, listeners) { + var originalVueConfig = Vue.config; Vue.config.silent = true; if (listeners) { vm.$listeners = listeners; } else { vm.$listeners = {}; } - Vue.config.silent = false; + Vue.config.silent = originalVueConfig.silent; } function addProvide (component, options) { @@ -3649,7 +3703,7 @@ function mount (component, options) { throwError( 'window is undefined, vue-test-utils needs to be run in a browser environment.\n' + 'You can run the tests in node using jsdom + jsdom-global.\n' + - 'See https://vue-test-utils.vuejs.org/en/guides/general-tips.html for more details.' + 'See https://vue-test-utils.vuejs.org/en/guides/common-tips.html for more details.' ); } @@ -3722,10 +3776,137 @@ function createLocalVue () { return instance } +// + +function getRealChild (vnode) { + var compOptions = vnode && vnode.componentOptions; + if (compOptions && compOptions.Ctor.options.abstract) { + return getRealChild(getFirstComponentChild(compOptions.children)) + } else { + return vnode + } +} + +function getFirstComponentChild (children) { + if (Array.isArray(children)) { + for (var i = 0; i < children.length; i++) { + var c = children[i]; + if (c && (c.componentOptions || isAsyncPlaceholder(c))) { + return c + } + } + } +} + +function isAsyncPlaceholder (node) { + return node.isComment && node.asyncFactory +} +var camelizeRE = /-(\w)/g; +var camelize = function (str) { + return str.replace(camelizeRE, function (_, c) { return c ? c.toUpperCase() : ''; }) +}; + +function extractTransitionData (comp) { + var data = {}; + var options = comp.$options; + // props + for (var key in options.propsData) { + data[key] = comp[key]; + } + // events. + // extract listeners and pass them directly to the transition methods + var listeners = options._parentListeners; + for (var key$1 in listeners) { + data[camelize(key$1)] = listeners[key$1]; + } + return data +} + +function hasParentTransition (vnode) { + while ((vnode = vnode.parent)) { + if (vnode.data.transition) { + return true + } + } +} + +var TransitionStub = { + render: function render (h) { + var children = this.$options._renderChildren; + if (!children) { + return + } + + // filter out text nodes (possible whitespaces) + children = children.filter(function (c) { return c.tag || isAsyncPlaceholder(c); }); + /* istanbul ignore if */ + if (!children.length) { + return + } + + // warn multiple elements + if (children.length > 1) { + warn( + ' can only be used on a single element. Use ' + + ' for lists.' + ); + } + + var mode = this.mode; + + // warn invalid mode + if (mode && mode !== 'in-out' && mode !== 'out-in' + ) { + warn( + 'invalid mode: ' + mode + ); + } + + var rawChild = children[0]; + + // if this is a component root node and the component's + // parent container node also has transition, skip. + if (hasParentTransition(this.$vnode)) { + return rawChild + } + + // apply transition data to child + // use getRealChild() to ignore abstract components e.g. keep-alive + var child = getRealChild(rawChild); + + if (!child) { + return rawChild + } + + (child.data || (child.data = {})).transition = extractTransitionData(this); + + // mark v-show + // so that the transition module can hand over the control to the directive + if (child.data.directives && child.data.directives.some(function (d) { return d.name === 'show'; })) { + child.data.show = true; + } + + return rawChild + } +}; + +// + +var TransitionGroupStub = { + render: function render (h) { + var tag = this.tag || this.$vnode.data.tag || 'span'; + var children = this.$slots.default || []; + + return h(tag, null, children) + } +}; + var index = { createLocalVue: createLocalVue, mount: mount, - shallow: shallow + shallow: shallow, + TransitionStub: TransitionStub, + TransitionGroupStub: TransitionGroupStub }; return index; diff --git a/dist/vue-test-utils.js b/dist/vue-test-utils.js index eeb7b42a6..71c438cd0 100644 --- a/dist/vue-test-utils.js +++ b/dist/vue-test-utils.js @@ -12,6 +12,10 @@ function throwError (msg) { throw new Error(("[vue-test-utils]: " + msg)) } +function warn (msg) { + console.error(("[vue-test-utils]: " + msg)); +} + // var LIFECYCLE_HOOKS = [ @@ -388,6 +392,12 @@ WrapperArray.prototype.throwErrorIfWrappersIsEmpty = function throwErrorIfWrappe } }; +WrapperArray.prototype.setComputed = function setComputed (computed) { + this.throwErrorIfWrappersIsEmpty('setComputed'); + + this.wrappers.forEach(function (wrapper) { return wrapper.setComputed(computed); }); +}; + WrapperArray.prototype.setData = function setData (data) { this.throwErrorIfWrappersIsEmpty('setData'); @@ -491,6 +501,10 @@ ErrorWrapper.prototype.text = function text () { throwError(("find did not return " + (this.selector) + ", cannot call text() on empty Wrapper")); }; +ErrorWrapper.prototype.setComputed = function setComputed () { + throwError(("find did not return " + (this.selector) + ", cannot call setComputed() on empty Wrapper")); +}; + ErrorWrapper.prototype.setData = function setData () { throwError(("find did not return " + (this.selector) + ", cannot call setData() on empty Wrapper")); }; @@ -518,6 +532,7 @@ var Wrapper = function Wrapper (vnode, update, options) { this.element = vnode.elm; this.update = update; this.options = options; + this.version = Number(((Vue.version.split('.')[0]) + "." + (Vue.version.split('.')[1]))); }; Wrapper.prototype.at = function at () { @@ -713,9 +728,7 @@ Wrapper.prototype.findAll = function findAll (selector) { * Returns HTML of element as a string */ Wrapper.prototype.html = function html () { - var tmp = document.createElement('div'); - tmp.appendChild(this.element); - return tmp.innerHTML + return this.element.outerHTML }; /** @@ -745,7 +758,7 @@ Wrapper.prototype.is = function is (selector) { * Checks if node is empty */ Wrapper.prototype.isEmpty = function isEmpty () { - return this.vnode.children === undefined + return this.vnode.children === undefined || this.vnode.children.length === 0 }; /** @@ -784,7 +797,41 @@ Wrapper.prototype.setData = function setData (data) { }; /** - * Sets vm data + * Sets vm computed + */ +Wrapper.prototype.setComputed = function setComputed (computed) { + var this$1 = this; + + if (!this.isVueComponent) { + throwError('wrapper.setComputed() can only be called on a Vue instance'); + } + + Object.keys(computed).forEach(function (key) { + if (this$1.version > 2.1) { + // $FlowIgnore : Problem with possibly null this.vm + if (!this$1.vm._computedWatchers[key]) { + throwError(("wrapper.setComputed() was passed a value that does not exist as a computed property on the Vue instance. Property " + key + " does not exist on the Vue instance")); + } + // $FlowIgnore : Problem with possibly null this.vm + this$1.vm._computedWatchers[key].value = computed[key]; + } else { + // $FlowIgnore : Problem with possibly null this.vm + if (!this$1.vm._watchers.some(function (w) { return w.getter.name === key; })) { + throwError(("wrapper.setComputed() was passed a value that does not exist as a computed property on the Vue instance. Property " + key + " does not exist on the Vue instance")); + } + // $FlowIgnore : Problem with possibly null this.vm + this$1.vm._watchers.forEach(function (watcher) { + if (watcher.getter.name === key) { + watcher.value = computed[key]; + } + }); + } + }); + this.update(); +}; + +/** + * Sets vm methods */ Wrapper.prototype.setMethods = function setMethods (methods) { var this$1 = this; @@ -910,6 +957,7 @@ function logEvents (vm, emitted, emittedByOrder) { function update () { this._update(this._render()); + this.$children.forEach(function (child) { return update.call(child); }); } var VueWrapper = (function (Wrapper$$1) { @@ -921,7 +969,11 @@ var VueWrapper = (function (Wrapper$$1) { get: function () { return vm._vnode; }, set: function () {} })); - + // $FlowIgnore + Object.defineProperty(this, 'element', ({ + get: function () { return vm.$el; }, + set: function () {} + })); this.vm = vm; this.isVueComponent = true; this._emitted = Object.create(null); @@ -990,23 +1042,25 @@ function addMocks (mockedProperties, Vue$$1) { } function addAttrs (vm, attrs) { + var originalVueConfig = Vue.config; Vue.config.silent = true; if (attrs) { vm.$attrs = attrs; } else { vm.$attrs = {}; } - Vue.config.silent = false; + Vue.config.silent = originalVueConfig.silent; } function addListeners (vm, listeners) { + var originalVueConfig = Vue.config; Vue.config.silent = true; if (listeners) { vm.$listeners = listeners; } else { vm.$listeners = {}; } - Vue.config.silent = false; + Vue.config.silent = originalVueConfig.silent; } function addProvide (component, options) { @@ -1119,7 +1173,7 @@ function mount (component, options) { throwError( 'window is undefined, vue-test-utils needs to be run in a browser environment.\n' + 'You can run the tests in node using jsdom + jsdom-global.\n' + - 'See https://vue-test-utils.vuejs.org/en/guides/general-tips.html for more details.' + 'See https://vue-test-utils.vuejs.org/en/guides/common-tips.html for more details.' ); } @@ -1192,10 +1246,137 @@ function createLocalVue () { return instance } +// + +function getRealChild (vnode) { + var compOptions = vnode && vnode.componentOptions; + if (compOptions && compOptions.Ctor.options.abstract) { + return getRealChild(getFirstComponentChild(compOptions.children)) + } else { + return vnode + } +} + +function getFirstComponentChild (children) { + if (Array.isArray(children)) { + for (var i = 0; i < children.length; i++) { + var c = children[i]; + if (c && (c.componentOptions || isAsyncPlaceholder(c))) { + return c + } + } + } +} + +function isAsyncPlaceholder (node) { + return node.isComment && node.asyncFactory +} +var camelizeRE = /-(\w)/g; +var camelize = function (str) { + return str.replace(camelizeRE, function (_, c) { return c ? c.toUpperCase() : ''; }) +}; + +function extractTransitionData (comp) { + var data = {}; + var options = comp.$options; + // props + for (var key in options.propsData) { + data[key] = comp[key]; + } + // events. + // extract listeners and pass them directly to the transition methods + var listeners = options._parentListeners; + for (var key$1 in listeners) { + data[camelize(key$1)] = listeners[key$1]; + } + return data +} + +function hasParentTransition (vnode) { + while ((vnode = vnode.parent)) { + if (vnode.data.transition) { + return true + } + } +} + +var TransitionStub = { + render: function render (h) { + var children = this.$options._renderChildren; + if (!children) { + return + } + + // filter out text nodes (possible whitespaces) + children = children.filter(function (c) { return c.tag || isAsyncPlaceholder(c); }); + /* istanbul ignore if */ + if (!children.length) { + return + } + + // warn multiple elements + if (children.length > 1) { + warn( + ' can only be used on a single element. Use ' + + ' for lists.' + ); + } + + var mode = this.mode; + + // warn invalid mode + if (mode && mode !== 'in-out' && mode !== 'out-in' + ) { + warn( + 'invalid mode: ' + mode + ); + } + + var rawChild = children[0]; + + // if this is a component root node and the component's + // parent container node also has transition, skip. + if (hasParentTransition(this.$vnode)) { + return rawChild + } + + // apply transition data to child + // use getRealChild() to ignore abstract components e.g. keep-alive + var child = getRealChild(rawChild); + + if (!child) { + return rawChild + } + + (child.data || (child.data = {})).transition = extractTransitionData(this); + + // mark v-show + // so that the transition module can hand over the control to the directive + if (child.data.directives && child.data.directives.some(function (d) { return d.name === 'show'; })) { + child.data.show = true; + } + + return rawChild + } +}; + +// + +var TransitionGroupStub = { + render: function render (h) { + var tag = this.tag || this.$vnode.data.tag || 'span'; + var children = this.$slots.default || []; + + return h(tag, null, children) + } +}; + var index = { createLocalVue: createLocalVue, mount: mount, - shallow: shallow + shallow: shallow, + TransitionStub: TransitionStub, + TransitionGroupStub: TransitionGroupStub }; module.exports = index; diff --git a/dist/vue-test-utils.umd.js b/dist/vue-test-utils.umd.js index 93eaf82ed..e3f01aa69 100644 --- a/dist/vue-test-utils.umd.js +++ b/dist/vue-test-utils.umd.js @@ -2545,6 +2545,10 @@ function throwError (msg) { throw new Error(("[vue-test-utils]: " + msg)) } +function warn (msg) { + console.error(("[vue-test-utils]: " + msg)); +} + // var LIFECYCLE_HOOKS = [ @@ -2921,6 +2925,12 @@ WrapperArray.prototype.throwErrorIfWrappersIsEmpty = function throwErrorIfWrappe } }; +WrapperArray.prototype.setComputed = function setComputed (computed) { + this.throwErrorIfWrappersIsEmpty('setComputed'); + + this.wrappers.forEach(function (wrapper) { return wrapper.setComputed(computed); }); +}; + WrapperArray.prototype.setData = function setData (data) { this.throwErrorIfWrappersIsEmpty('setData'); @@ -3024,6 +3034,10 @@ ErrorWrapper.prototype.text = function text () { throwError(("find did not return " + (this.selector) + ", cannot call text() on empty Wrapper")); }; +ErrorWrapper.prototype.setComputed = function setComputed () { + throwError(("find did not return " + (this.selector) + ", cannot call setComputed() on empty Wrapper")); +}; + ErrorWrapper.prototype.setData = function setData () { throwError(("find did not return " + (this.selector) + ", cannot call setData() on empty Wrapper")); }; @@ -3051,6 +3065,7 @@ var Wrapper = function Wrapper (vnode, update, options) { this.element = vnode.elm; this.update = update; this.options = options; + this.version = Number(((Vue.version.split('.')[0]) + "." + (Vue.version.split('.')[1]))); }; Wrapper.prototype.at = function at () { @@ -3246,9 +3261,7 @@ Wrapper.prototype.findAll = function findAll (selector) { * Returns HTML of element as a string */ Wrapper.prototype.html = function html () { - var tmp = document.createElement('div'); - tmp.appendChild(this.element); - return tmp.innerHTML + return this.element.outerHTML }; /** @@ -3278,7 +3291,7 @@ Wrapper.prototype.is = function is (selector) { * Checks if node is empty */ Wrapper.prototype.isEmpty = function isEmpty () { - return this.vnode.children === undefined + return this.vnode.children === undefined || this.vnode.children.length === 0 }; /** @@ -3317,7 +3330,41 @@ Wrapper.prototype.setData = function setData (data) { }; /** - * Sets vm data + * Sets vm computed + */ +Wrapper.prototype.setComputed = function setComputed (computed) { + var this$1 = this; + + if (!this.isVueComponent) { + throwError('wrapper.setComputed() can only be called on a Vue instance'); + } + + Object.keys(computed).forEach(function (key) { + if (this$1.version > 2.1) { + // $FlowIgnore : Problem with possibly null this.vm + if (!this$1.vm._computedWatchers[key]) { + throwError(("wrapper.setComputed() was passed a value that does not exist as a computed property on the Vue instance. Property " + key + " does not exist on the Vue instance")); + } + // $FlowIgnore : Problem with possibly null this.vm + this$1.vm._computedWatchers[key].value = computed[key]; + } else { + // $FlowIgnore : Problem with possibly null this.vm + if (!this$1.vm._watchers.some(function (w) { return w.getter.name === key; })) { + throwError(("wrapper.setComputed() was passed a value that does not exist as a computed property on the Vue instance. Property " + key + " does not exist on the Vue instance")); + } + // $FlowIgnore : Problem with possibly null this.vm + this$1.vm._watchers.forEach(function (watcher) { + if (watcher.getter.name === key) { + watcher.value = computed[key]; + } + }); + } + }); + this.update(); +}; + +/** + * Sets vm methods */ Wrapper.prototype.setMethods = function setMethods (methods) { var this$1 = this; @@ -3443,6 +3490,7 @@ function logEvents (vm, emitted, emittedByOrder) { function update () { this._update(this._render()); + this.$children.forEach(function (child) { return update.call(child); }); } var VueWrapper = (function (Wrapper$$1) { @@ -3454,7 +3502,11 @@ var VueWrapper = (function (Wrapper$$1) { get: function () { return vm._vnode; }, set: function () {} })); - + // $FlowIgnore + Object.defineProperty(this, 'element', ({ + get: function () { return vm.$el; }, + set: function () {} + })); this.vm = vm; this.isVueComponent = true; this._emitted = Object.create(null); @@ -3523,23 +3575,25 @@ function addMocks (mockedProperties, Vue$$1) { } function addAttrs (vm, attrs) { + var originalVueConfig = Vue.config; Vue.config.silent = true; if (attrs) { vm.$attrs = attrs; } else { vm.$attrs = {}; } - Vue.config.silent = false; + Vue.config.silent = originalVueConfig.silent; } function addListeners (vm, listeners) { + var originalVueConfig = Vue.config; Vue.config.silent = true; if (listeners) { vm.$listeners = listeners; } else { vm.$listeners = {}; } - Vue.config.silent = false; + Vue.config.silent = originalVueConfig.silent; } function addProvide (component, options) { @@ -3652,7 +3706,7 @@ function mount (component, options) { throwError( 'window is undefined, vue-test-utils needs to be run in a browser environment.\n' + 'You can run the tests in node using jsdom + jsdom-global.\n' + - 'See https://vue-test-utils.vuejs.org/en/guides/general-tips.html for more details.' + 'See https://vue-test-utils.vuejs.org/en/guides/common-tips.html for more details.' ); } @@ -3725,10 +3779,137 @@ function createLocalVue () { return instance } +// + +function getRealChild (vnode) { + var compOptions = vnode && vnode.componentOptions; + if (compOptions && compOptions.Ctor.options.abstract) { + return getRealChild(getFirstComponentChild(compOptions.children)) + } else { + return vnode + } +} + +function getFirstComponentChild (children) { + if (Array.isArray(children)) { + for (var i = 0; i < children.length; i++) { + var c = children[i]; + if (c && (c.componentOptions || isAsyncPlaceholder(c))) { + return c + } + } + } +} + +function isAsyncPlaceholder (node) { + return node.isComment && node.asyncFactory +} +var camelizeRE = /-(\w)/g; +var camelize = function (str) { + return str.replace(camelizeRE, function (_, c) { return c ? c.toUpperCase() : ''; }) +}; + +function extractTransitionData (comp) { + var data = {}; + var options = comp.$options; + // props + for (var key in options.propsData) { + data[key] = comp[key]; + } + // events. + // extract listeners and pass them directly to the transition methods + var listeners = options._parentListeners; + for (var key$1 in listeners) { + data[camelize(key$1)] = listeners[key$1]; + } + return data +} + +function hasParentTransition (vnode) { + while ((vnode = vnode.parent)) { + if (vnode.data.transition) { + return true + } + } +} + +var TransitionStub = { + render: function render (h) { + var children = this.$options._renderChildren; + if (!children) { + return + } + + // filter out text nodes (possible whitespaces) + children = children.filter(function (c) { return c.tag || isAsyncPlaceholder(c); }); + /* istanbul ignore if */ + if (!children.length) { + return + } + + // warn multiple elements + if (children.length > 1) { + warn( + ' can only be used on a single element. Use ' + + ' for lists.' + ); + } + + var mode = this.mode; + + // warn invalid mode + if (mode && mode !== 'in-out' && mode !== 'out-in' + ) { + warn( + 'invalid mode: ' + mode + ); + } + + var rawChild = children[0]; + + // if this is a component root node and the component's + // parent container node also has transition, skip. + if (hasParentTransition(this.$vnode)) { + return rawChild + } + + // apply transition data to child + // use getRealChild() to ignore abstract components e.g. keep-alive + var child = getRealChild(rawChild); + + if (!child) { + return rawChild + } + + (child.data || (child.data = {})).transition = extractTransitionData(this); + + // mark v-show + // so that the transition module can hand over the control to the directive + if (child.data.directives && child.data.directives.some(function (d) { return d.name === 'show'; })) { + child.data.show = true; + } + + return rawChild + } +}; + +// + +var TransitionGroupStub = { + render: function render (h) { + var tag = this.tag || this.$vnode.data.tag || 'span'; + var children = this.$slots.default || []; + + return h(tag, null, children) + } +}; + var index = { createLocalVue: createLocalVue, mount: mount, - shallow: shallow + shallow: shallow, + TransitionStub: TransitionStub, + TransitionGroupStub: TransitionGroupStub }; return index; From 131ab9f1aead3f6e81a0f4ab3d7fdc7c68e1295e Mon Sep 17 00:00:00 2001 From: Edd Yerburgh Date: Thu, 12 Oct 2017 21:46:47 +0100 Subject: [PATCH 0017/1136] release: 1.0.0-beta.2 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 295dae01f..eaa995c71 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "vue-test-utils", - "version": "1.0.0-beta.1", + "version": "1.0.0-beta.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index e9fcbdd47..4a06d6df5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vue-test-utils", - "version": "1.0.0-beta.1", + "version": "1.0.0-beta.2", "description": "Utilities for testing Vue components.", "main": "dist/vue-test-utils.js", "types": "types/index.d.ts", From d2c0273b1f77e601c87ef9f0367014c2d6fe1f28 Mon Sep 17 00:00:00 2001 From: kazuya kawaguchi Date: Fri, 13 Oct 2017 13:32:53 +0900 Subject: [PATCH 0018/1136] Improve docs (#92) * docs: fix links * docs: add vue-router using link to TOC of README * docs: fix spelling inconsistency --- docs/en/README.md | 1 + docs/en/SUMMARY.md | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/en/README.md b/docs/en/README.md index 0f5dd0923..bbf722e6f 100644 --- a/docs/en/README.md +++ b/docs/en/README.md @@ -8,6 +8,7 @@ * [Choosing a test runner](guides/choosing-a-test-runner.md) * [Testing SFCs with Jest](guides/testing-SFCs-with-jest.md) * [Testing SFCs with Mocha + webpack](guides/testing-SFCs-with-mocha-webpack.md) + * [Using with Vue Router](guides/using-with-router.md) * [Using with Vuex](guides/using-with-vuex.md) * [API](api/README.md) * [createLocalVue](api/createLocalVue.md) diff --git a/docs/en/SUMMARY.md b/docs/en/SUMMARY.md index d0e1c133c..24e186ce3 100644 --- a/docs/en/SUMMARY.md +++ b/docs/en/SUMMARY.md @@ -6,8 +6,8 @@ * [Choosing a test runner](guides/choosing-a-test-runner.md) * [Testing SFCs with Jest](guides/testing-SFCs-with-jest.md) * [Testing SFCs with Mocha + webpack](guides/testing-SFCs-with-mocha-webpack.md) - * [Using with vue-router](guides/using-with-vuex.md) - * [Using with vuex](guides/using-with-vuex.md) + * [Using with Vue Router](guides/using-with-router.md) + * [Using with Vuex](guides/using-with-vuex.md) * [API](api/README.md) * [mount](api/mount.md) * [shallow](api/shallow.md) From 5260aaa7564f96de9a942725e063ec450649c2e9 Mon Sep 17 00:00:00 2001 From: Ankur Kumar Date: Fri, 13 Oct 2017 11:34:46 +0530 Subject: [PATCH 0019/1136] docs: Update readme (#93) * docs: Update readme * Add badges * Update install commands to get latest version * docs: add CI badge, remove others --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b26565c3e..36ab9d097 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ -# vue-test-utils +# vue-test-utils [![Build Status](https://circleci.com/gh/vuejs/vue-test-utils/tree/dev.png?style=shield)](https://circleci.com/gh/vuejs/vue-test-utils) ## Currently in beta To use vue-test-utils beta: ``` // npm -npm install --save-dev vue-test-utils@1.0.0-beta.1 +npm install --save-dev vue-test-utils@latest // yarn -yarn add --dev vue-test-utils@1.0.0-beta.1 +yarn add --dev vue-test-utils@latest ``` ## Intro From 02b13e85f381c8e28c77604130ad9bea11750b75 Mon Sep 17 00:00:00 2001 From: kazuya kawaguchi Date: Fri, 13 Oct 2017 18:43:52 +0900 Subject: [PATCH 0020/1136] docs: more improvements (#94) --- docs/en/README.md | 2 +- docs/en/SUMMARY.md | 2 +- docs/en/guides/using-with-vue-router.md | 22 +++++++++++----------- docs/en/guides/using-with-vuex.md | 12 ++++++------ 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/docs/en/README.md b/docs/en/README.md index bbf722e6f..2140a591a 100644 --- a/docs/en/README.md +++ b/docs/en/README.md @@ -8,7 +8,7 @@ * [Choosing a test runner](guides/choosing-a-test-runner.md) * [Testing SFCs with Jest](guides/testing-SFCs-with-jest.md) * [Testing SFCs with Mocha + webpack](guides/testing-SFCs-with-mocha-webpack.md) - * [Using with Vue Router](guides/using-with-router.md) + * [Using with Vue Router](guides/using-with-vue-router.md) * [Using with Vuex](guides/using-with-vuex.md) * [API](api/README.md) * [createLocalVue](api/createLocalVue.md) diff --git a/docs/en/SUMMARY.md b/docs/en/SUMMARY.md index 24e186ce3..6d5dafcd5 100644 --- a/docs/en/SUMMARY.md +++ b/docs/en/SUMMARY.md @@ -6,7 +6,7 @@ * [Choosing a test runner](guides/choosing-a-test-runner.md) * [Testing SFCs with Jest](guides/testing-SFCs-with-jest.md) * [Testing SFCs with Mocha + webpack](guides/testing-SFCs-with-mocha-webpack.md) - * [Using with Vue Router](guides/using-with-router.md) + * [Using with Vue Router](guides/using-with-vue-router.md) * [Using with Vuex](guides/using-with-vuex.md) * [API](api/README.md) * [mount](api/mount.md) diff --git a/docs/en/guides/using-with-vue-router.md b/docs/en/guides/using-with-vue-router.md index 444404f0c..d69353af0 100644 --- a/docs/en/guides/using-with-vue-router.md +++ b/docs/en/guides/using-with-vue-router.md @@ -1,10 +1,10 @@ -# Using with vue-router +# Using with Vue Router -## Installing vue-router in tests +## Installing Vue Router in tests -You should never install vue-router on the Vue base constructor in tests. Installing vue-router adds `$route` and `$router` as read-only properties on Vue prototype. +You should never install Vue Router on the Vue base constructor in tests. Installing Vue Router adds `$route` and `$router` as read-only properties on Vue prototype. -To avoid this, we can create a localVue, and install vue-router on that. +To avoid this, we can create a localVue, and install Vue Router on that. ```js import VueRouter from 'vue-router' @@ -17,9 +17,9 @@ shallow(Component, { }) ``` -## Testing components that use router-link or router-view +## Testing components that use `router-link` or `router-view` -When you install vue-router, the router-link and router-view components are registered. This means we can use them anywhere in our application without needing to import them. +When you install Vue Router, the `router-link` and `router-view` components are registered. This means we can use them anywhere in our application without needing to import them. When we run tests, we need to make these vue-router components available to the component we're mounting. There are two methods to do this. @@ -31,7 +31,7 @@ shallow(Component, { }) ``` -### Installing vue-router with localVue +### Installing Vue Router with localVue ```js import VueRouter from 'vue-router' @@ -44,7 +44,7 @@ shallow(Component, { }) ``` -## Mocking $route and $router +## Mocking `$route` and `$router` Sometimes you want to test that a component does something with parameters from the `$route` and `$router` objects. To do that, you can pass custom mocks to the Vue instance. @@ -64,8 +64,8 @@ wrapper.vm.$router // /some/path ## Common gotchas -Installing vue-router adds `$route` and `$router` as read-only properties on Vue prototype. +Installing Vue Router adds `$route` and `$router` as read-only properties on Vue prototype. -This means any future tests that try to mock $route or `$router` will fail. +This means any future tests that try to mock `$route` or `$router` will fail. -To avoid this, never install vue-router when you're running tests. +To avoid this, never install Vue Router when you're running tests. diff --git a/docs/en/guides/using-with-vuex.md b/docs/en/guides/using-with-vuex.md index ec9a769a2..7d42728a4 100644 --- a/docs/en/guides/using-with-vuex.md +++ b/docs/en/guides/using-with-vuex.md @@ -1,6 +1,6 @@ # Using with Vuex -In this guide, we'll see how to test Vuex in components with vue-test-utils. +In this guide, we'll see how to test Vuex in components with `vue-test-utils`. ## Mocking Actions @@ -91,9 +91,9 @@ describe('Actions.vue', () => { }) ``` -What’s happening here? First we tell Vue to use Vuex with the Vue.use method. This is just a wrapper around Vue.use. +What’s happening here? First we tell Vue to use Vuex with the `Vue.use` method. This is just a wrapper around `Vue.use`. -We then make a mock store by calling new Vuex.store with our mock values. We only pass it the actions, since that’s all we care about. +We then make a mock store by calling new `Vuex.store` with our mock values. We only pass it the actions, since that’s all we care about. The actions are [jest mock functions](https://facebook.github.io/jest/docs/en/mock-functions.html). These mock functions give us methods to assert whether the actions were called or not. @@ -101,7 +101,7 @@ We can then assert in our tests that the action stub was called when expected. Now the way we define the store might look a bit foreign to you. -We’re using beforeEach to ensure we have a clean store before each test. beforeEach is a mocha hook that’s called before each test. In our test, we are reassigning the store variables value. If we didn’t do this, the mock functions would need to be automatically reset. It also lets us change the state in our tests, without it affecting later tests. +We’re using `beforeEach` to ensure we have a clean store before each test. `beforeEach` is a mocha hook that’s called before each test. In our test, we are reassigning the store variables value. If we didn’t do this, the mock functions would need to be automatically reset. It also lets us change the state in our tests, without it affecting later tests. The most important thing to note in this test is that **we create a mock Vuex store and then pass it to vue-test-utils**. @@ -130,7 +130,7 @@ export default{ ``` -This is a fairly simple component. It renders the result of the getters clicks and inputValue. Again, we don’t really care about what those getters returns – just that the result of them is being rendered correctly. +This is a fairly simple component. It renders the result of the getters `clicks` and `inputValue`. Again, we don’t really care about what those getters returns – just that the result of them is being rendered correctly. Let’s see the test: @@ -171,7 +171,7 @@ describe('Getters.vue', () => { }) }) ``` -This test is similar to our actions test. We create a mock store before each test, pass it as an option when we call shallow, and assert that the value returned by our mock getters is being rendered. +This test is similar to our actions test. We create a mock store before each test, pass it as an option when we call `shallow`, and assert that the value returned by our mock getters is being rendered. This is great, but what if we want to check our getters are returning the correct part of our state? From 2f288044d79d4d5aaf59c4bc269784890ff724b5 Mon Sep 17 00:00:00 2001 From: kazuya kawaguchi Date: Fri, 13 Oct 2017 20:35:11 +0900 Subject: [PATCH 0021/1136] docs: fix links (#96) --- docs/en/api/mount.md | 2 +- docs/en/api/shallow.md | 8 ++++---- docs/en/api/wrapper/find.md | 2 +- docs/en/guides/common-tips.md | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/en/api/mount.md b/docs/en/api/mount.md index 35dcfef5d..aaddd3e2f 100644 --- a/docs/en/api/mount.md +++ b/docs/en/api/mount.md @@ -1,4 +1,4 @@ -# mount(component,{,options}]) +# mount(component {, options}]) - **Arguments:** diff --git a/docs/en/api/shallow.md b/docs/en/api/shallow.md index fc75ac961..fa16e9d24 100644 --- a/docs/en/api/shallow.md +++ b/docs/en/api/shallow.md @@ -1,4 +1,4 @@ -# shallow(component,{,options}]) +# shallow(component {, options}]) - **Arguments:** @@ -19,15 +19,15 @@ - **Options:** -See [options](/docs/en/api/options.md) +See [options](./options.md) - **Usage:** -Returns [`Wrapper`](/docs/en/api/wrapper/README.md) of first DOM node or Vue component matching selector. +Returns [`Wrapper`](./wrapper/README.md) of first DOM node or Vue component matching selector. Stubs all child components. -Use any valid [selector](/docs/en/api/selectors.md). +Use any valid [selector](./selectors.md). **Without options:** diff --git a/docs/en/api/wrapper/find.md b/docs/en/api/wrapper/find.md index e43f22799..1d121c591 100644 --- a/docs/en/api/wrapper/find.md +++ b/docs/en/api/wrapper/find.md @@ -24,4 +24,4 @@ const bar = wrapper.find(Bar) expect(bar.is(Bar)).toBe(true) ``` -- **See also:** [Wrapper](/docs/en/api/wrapper/README.md) +- **See also:** [Wrapper](README.md) diff --git a/docs/en/guides/common-tips.md b/docs/en/guides/common-tips.md index 978bf3586..abd7500a7 100644 --- a/docs/en/guides/common-tips.md +++ b/docs/en/guides/common-tips.md @@ -58,7 +58,7 @@ expect(wrapper.emitted().foo.length).toBe(2) expect(wrapper.emitted().foo[1]).toEqual([123]) ``` -You can also get an Array of the events in their emit order by calling [wrapper.emittedByOrder()](../api/emittedByOrder.md). +You can also get an Array of the events in their emit order by calling [wrapper.emittedByOrder()](../api/wrapper/emittedByOrder.md). ## Manipulating Component State @@ -86,7 +86,7 @@ mount(Component, { You can also update the props of an already-mounted component with the `wrapper.setProps({})` method. -*For a full list of options, please see the [mount options section](./api/options.md) of the docs.* +*For a full list of options, please see the [mount options section](../api/options.md) of the docs.* ## Applying Global Plugins and Mixins From da24ff4a866c8aa7dceabcbfcc596e928266c0b3 Mon Sep 17 00:00:00 2001 From: kazuya kawaguchi Date: Sat, 14 Oct 2017 02:56:03 +0900 Subject: [PATCH 0022/1136] docs: fix typo (#98) --- docs/en/guides/using-with-vuex.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/guides/using-with-vuex.md b/docs/en/guides/using-with-vuex.md index 7d42728a4..85f40d7fd 100644 --- a/docs/en/guides/using-with-vuex.md +++ b/docs/en/guides/using-with-vuex.md @@ -67,7 +67,7 @@ describe('Actions.vue', () => { }) }) - it('calls store action actionInput when input value is input and an input even is fired', () => { + it('calls store action actionInput when input value is input and an input event is fired', () => { const wrapper = shallow(Actions, { store, localVue }) const input = wrapper.find('input') input.element.value = 'input' @@ -75,7 +75,7 @@ describe('Actions.vue', () => { expect(actions.actionInput).toHaveBeenCalled() }) - it('does not call store action actionInput when input value is not input and an input even is fired', () => { + it('does not call store action actionInput when input value is not input and an input event is fired', () => { const wrapper = shallow(Actions, { store, localVue }) const input = wrapper.find('input') input.element.value = 'not input' From b80b9699cd7f61e910aa06f939f3b3bb8758ecc9 Mon Sep 17 00:00:00 2001 From: Paul Sherman Date: Sat, 14 Oct 2017 02:16:38 -0500 Subject: [PATCH 0023/1136] feat(createLocalVue): allow additional arguments in use (#100) * Allow additional arguments in use * Whitespace --- src/create-local-vue.js | 4 ++-- test/unit/specs/create-local-vue.spec.js | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/create-local-vue.js b/src/create-local-vue.js index 61afd5ef8..b17863358 100644 --- a/src/create-local-vue.js +++ b/src/create-local-vue.js @@ -30,10 +30,10 @@ function createLocalVue (): Component { // compat for vue-router < 2.7.1 where it does not allow multiple installs const use = instance.use - instance.use = (plugin) => { + instance.use = (plugin, ...rest) => { plugin.installed = false plugin.install.installed = false - use.call(instance, plugin) + use.call(instance, plugin, ...rest) } return instance } diff --git a/test/unit/specs/create-local-vue.spec.js b/test/unit/specs/create-local-vue.spec.js index cc5e77c72..3443fed30 100644 --- a/test/unit/specs/create-local-vue.spec.js +++ b/test/unit/specs/create-local-vue.spec.js @@ -95,4 +95,15 @@ describe('createLocalVue', () => { const freshWrapper = mount(Component) expect(typeof freshWrapper.vm.$route).to.equal('undefined') }) + + it('use can take additional arguments', () => { + const localVue = createLocalVue() + const pluginOptions = { foo: 'bar' } + const plugin = { + install: function (_Vue, options) { + expect(options).to.equal(pluginOptions) + } + } + localVue.use(plugin, pluginOptions) + }) }) From 73b6bc97c98e9d2b920098da3c359dc98545fd5f Mon Sep 17 00:00:00 2001 From: Alexander Sokolov Date: Sun, 15 Oct 2017 10:06:21 +0300 Subject: [PATCH 0024/1136] docs: add missed links to README.md's (#101) * Update README.md * Update README.md --- docs/en/README.md | 20 +++++++++++++++++--- docs/en/guides/README.md | 1 + 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/docs/en/README.md b/docs/en/README.md index 2140a591a..c848bc583 100644 --- a/docs/en/README.md +++ b/docs/en/README.md @@ -11,13 +11,24 @@ * [Using with Vue Router](guides/using-with-vue-router.md) * [Using with Vuex](guides/using-with-vuex.md) * [API](api/README.md) - * [createLocalVue](api/createLocalVue.md) * [mount](api/mount.md) * [shallow](api/shallow.md) * [Mounting Options](api/options.md) + - [context](api/options.md#context) + - [slots](api/options.md#slots) + - [stubs](api/options.md#stubs) + - [mocks](api/options.md#mocks) + - [localVue](api/options.md#localvue) + - [attachToDocument](api/options.md#attachtodocument) + - [attrs](api/options.md#attrs) + - [listeners](api/options.md#listeners) + - [clone](api/options.md#clone) * [Wrapper](api/wrapper/README.md) * [contains](api/wrapper/contains.md) + * [emitted](api/wrapper/emitted.md) + * [emittedByOrder](api/wrapper/emittedByOrder.md) * [find](api/wrapper/find.md) + * [findAll](api/wrapper/findAll.md) * [hasAttribute](api/wrapper/hasAttribute.md) * [hasClass](api/wrapper/hasClass.md) * [hasProp](api/wrapper/hasProp.md) @@ -27,11 +38,12 @@ * [isEmpty](api/wrapper/isEmpty.md) * [isVueInstance](api/wrapper/isVueInstance.md) * [name](api/wrapper/name.md) - * [update](api/wrapper/update.md) * [setData](api/wrapper/setData.md) + * [setMethods](api/wrapper/setMethods.md) * [setProps](api/wrapper/setProps.md) * [text](api/wrapper/text.md) * [trigger](api/wrapper/trigger.md) + * [update](api/wrapper/update.md) * [WrapperArray](api/wrapper-array/README.md) * [at](api/wrapper-array/at.md) * [contains](api/wrapper-array/contains.md) @@ -42,8 +54,10 @@ * [is](api/wrapper-array/is.md) * [isEmpty](api/wrapper-array/isEmpty.md) * [isVueInstance](api/wrapper-array/isVueInstance.md) - * [update](api/wrapper-array/update.md) * [setData](api/wrapper-array/setData.md) + * [setMethods](api/wrapper-array/setMethods.md) * [setProps](api/wrapper-array/setProps.md) * [trigger](api/wrapper-array/trigger.md) + * [update](api/wrapper-array/update.md) * [Selectors](api/selectors.md) + * [createLocalVue](api/createLocalVue.md) diff --git a/docs/en/guides/README.md b/docs/en/guides/README.md index 8e8b6119d..b347c3734 100644 --- a/docs/en/guides/README.md +++ b/docs/en/guides/README.md @@ -6,4 +6,5 @@ * [Using with Jest](./using-with-jest.md) * [Testing SFCs with Jest](./testing-SFCs-with-jest.md) * [Testing SFCs with Mocha + webpack](./testing-SFCs-with-mocha-webpack.md) +* [Using with Vue Router](./using-with-vue-router.md) * [Using with Vuex](./using-with-vuex.md) From 020fc4d8db944abca6107298c57a39ea74be375b Mon Sep 17 00:00:00 2001 From: Lachlan Date: Mon, 16 Oct 2017 14:04:45 +0900 Subject: [PATCH 0025/1136] docs: fix example and add example using setProps (#102) * Fix example and add example using setProps * Remove unnecessary sentence --- docs/en/api/wrapper/setProps.md | 40 ++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/docs/en/api/wrapper/setProps.md b/docs/en/api/wrapper/setProps.md index 67ff3f3b1..7a55124c4 100644 --- a/docs/en/api/wrapper/setProps.md +++ b/docs/en/api/wrapper/setProps.md @@ -1,13 +1,13 @@ # setProps(props) -Sets `Wrapper` `vm` props and forces update. - -**Note the Wrapper must contain a Vue instance.** - - **Arguments:** - `{Object} props` -- **Example:** +- **Usage:** + +Sets `Wrapper` `vm` props and forces update. + +**Note the Wrapper must contain a Vue instance.** ```js import { mount } from 'vue-test-utils' @@ -16,5 +16,33 @@ import Foo from './Foo.vue' const wrapper = mount(Foo) wrapper.setProps({ foo: 'bar' }) -expect(wrapper.props().foo).toBe('bar') +expect(wrapper.vm.foo).to.equal('bar') +``` + +You can also pass a `propsData` object, which will initialize the Vue instance with passed values. + +``` js +// Foo.vue +export default { + props: { + foo: { + type: String, + required: true + } + } +} +``` + +``` js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo, { + propsData: { + foo: 'bar' + } +}) + +expect(wrapper.vm.foo).to.equal('bar') ``` From de89293bb759af6804242dcb714e9f15a7f0a1e2 Mon Sep 17 00:00:00 2001 From: Nathaniel Blackburn Date: Tue, 17 Oct 2017 15:22:54 +0100 Subject: [PATCH 0026/1136] feat: add support for CSS modules in hasClass (#106) * Added support for $style in hasClass Checks the className against $style where available. * Reworked to not redefine variables * Added unit test * Linting fixes * Fixed unit test --- src/wrappers/wrapper.js | 11 +++++++++-- .../components/component-with-css-modules.vue | 15 +++++++++++++++ test/unit/specs/mount/Wrapper/hasClass.spec.js | 7 +++++++ 3 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 test/resources/components/component-with-css-modules.vue diff --git a/src/wrappers/wrapper.js b/src/wrappers/wrapper.js index 5df404037..96a4ac8d4 100644 --- a/src/wrappers/wrapper.js +++ b/src/wrappers/wrapper.js @@ -98,11 +98,18 @@ export default class Wrapper implements BaseWrapper { * Asserts wrapper has a class name */ hasClass (className: string) { - if (typeof className !== 'string') { + let targetClass = className + + if (typeof targetClass !== 'string') { throwError('wrapper.hasClass() must be passed a string') } - return !!(this.element && this.element.classList.contains(className)) + // if $style is available and has a matching target, use that instead. + if (this.vm && this.vm.$style && this.vm.$style[targetClass]) { + targetClass = this.vm.$style[targetClass] + } + + return !!(this.element && this.element.classList.contains(targetClass)) } /** diff --git a/test/resources/components/component-with-css-modules.vue b/test/resources/components/component-with-css-modules.vue new file mode 100644 index 000000000..946af93fe --- /dev/null +++ b/test/resources/components/component-with-css-modules.vue @@ -0,0 +1,15 @@ + + + + + diff --git a/test/unit/specs/mount/Wrapper/hasClass.spec.js b/test/unit/specs/mount/Wrapper/hasClass.spec.js index d5851d002..ee05d1c0c 100644 --- a/test/unit/specs/mount/Wrapper/hasClass.spec.js +++ b/test/unit/specs/mount/Wrapper/hasClass.spec.js @@ -1,3 +1,4 @@ +import ComponentWithCssModules from '~resources/components/component-with-css-modules.vue' import { compileToFunctions } from 'vue-template-compiler' import mount from '~src/mount' @@ -39,4 +40,10 @@ describe('hasClass', () => { expect(fn).to.throw().with.property('message', message) }) }) + + it('returns true when element contains class name mapped in css modules', () => { + const wrapper = mount(ComponentWithCssModules) + + expect(wrapper.hasClass('color-red')).to.equal(true) + }) }) From 5f2c45ba20db646562c153719c937ebc32d75349 Mon Sep 17 00:00:00 2001 From: Edd Yerburgh Date: Wed, 18 Oct 2017 17:23:02 +0100 Subject: [PATCH 0027/1136] feat(error): check for window and throw error if undefined --- src/lib/warn-if-no-window.js | 9 +++++++++ src/mount.js | 10 +--------- src/shallow.js | 1 + 3 files changed, 11 insertions(+), 9 deletions(-) create mode 100644 src/lib/warn-if-no-window.js diff --git a/src/lib/warn-if-no-window.js b/src/lib/warn-if-no-window.js new file mode 100644 index 000000000..54127ffad --- /dev/null +++ b/src/lib/warn-if-no-window.js @@ -0,0 +1,9 @@ +import { throwError } from './util' + +if (typeof window === 'undefined') { + throwError( + 'window is undefined, vue-test-utils needs to be run in a browser environment.\n' + + 'You can run the tests in node using jsdom + jsdom-global.\n' + + 'See https://vue-test-utils.vuejs.org/en/guides/common-tips.html for more details.' + ) +} diff --git a/src/mount.js b/src/mount.js index aecfe1691..903a5de41 100644 --- a/src/mount.js +++ b/src/mount.js @@ -1,9 +1,9 @@ // @flow +import './lib/warn-if-no-window' import Vue from 'vue' import VueWrapper from './wrappers/vue-wrapper' import createInstance from './lib/create-instance' -import { throwError } from './lib/util' import cloneDeep from 'lodash/cloneDeep' import createElement from './lib/create-element' import './lib/matches-polyfill' @@ -11,14 +11,6 @@ import './lib/matches-polyfill' Vue.config.productionTip = false export default function mount (component: Component, options: Options = {}): VueWrapper { - if (!window) { - throwError( - 'window is undefined, vue-test-utils needs to be run in a browser environment.\n' + - 'You can run the tests in node using jsdom + jsdom-global.\n' + - 'See https://vue-test-utils.vuejs.org/en/guides/common-tips.html for more details.' - ) - } - const componentToMount = options.clone === false ? component : cloneDeep(component) // Remove cached constructor delete componentToMount._Ctor diff --git a/src/shallow.js b/src/shallow.js index a02541a4d..395c1e119 100644 --- a/src/shallow.js +++ b/src/shallow.js @@ -1,5 +1,6 @@ // @flow +import './lib/warn-if-no-window' import Vue from 'vue' import cloneDeep from 'lodash/cloneDeep' import { From 5fa01e62659154d9ba76e132a7ae5bd7f427b8f8 Mon Sep 17 00:00:00 2001 From: kazuya kawaguchi Date: Thu, 19 Oct 2017 15:00:50 +0900 Subject: [PATCH 0028/1136] docs: add translation for Japanese (#95) * setup japanese translation * translate README.md * translate SUMMARY.md * translate getting-started.md * translate common-gotchas.md * update getting-started.md * translate /api/wrapper/README.md * update /api/wrapper/README.md * translate /api/README.md * translate /api/wrapper/contains.md * translate /api/wrapper/exist.md * translate /api/wrapper/find.md * translate /api/wrapper/findAll.md * translate /api/wrapper/hasAttribute.md * translate /api/wrapper/hasClass.md * translate /api/wrapper/hasProp.md * translate /api/wrapper/hasStyle.md * update /api/wrapper/hasProp.md * translate /api/wrapper/html.md * translate /api/wrapper/is.md: * translate api/wrapper/isEmpty.md * translate /api/wrapper/isVueInstance.md * translate /api/wrapper/setData.md * translate /api/wrapper/setData.md * translate /api/wrapper/setMethods.md * translate /api/wrapper/setProps.md * translate /api/wrapper/text.md * translate /api/wrapper/trigger.md * translate /api/wrapper/update.md * update common-gotchas.md * translate /api/createLocalVue.md * translate /api/mount.md * improve japanese * translate /api/selectors.md * improve japanese * translate /api/wrapper-array/shallow.md * translate /api/wrapper-array/README.md * translate api/wrapper-array/at.md * translate /api/wrapper-array/contains.md * translate /api/wrapper-array * translate api/options.md * improve README.md * improves api/options.md mount.md README.md * improve ja docs * translate docs/guides/ * improved to contents of feedback * `using-with-vuex` is does not translate to wait for updates * tweak translation * add vuex section * add ja * pick up from en getting-started.md NOTE: pick up from https://github.com/vuejs/vue-test-utils/commit/eef37924edcbaa585aedc99e6e592b21bc6e441d * sync 1.0.0 beta-2 docs * docs: add TOC vue-router * translate vue-router using section * translate vuex using section * docs: translate note at getting started section * docs: tweak translation * docs: pick up from #94 * docs: fix links * docs: fix typo in test descriptions * docs: fix review comments * docs: pick up from https://github.com/vuejs/vue-test-utils/commit/020fc4d8db944abca6107298c57a39ea74be375b * docs: update mounting component translation --- docs/LANGS.md | 1 + docs/ja/README.md | 49 ++++ docs/ja/SUMMARY.md | 61 ++++ docs/ja/api/README.md | 49 ++++ docs/ja/api/createLocalVue.md | 28 ++ docs/ja/api/mount.md | 136 +++++++++ docs/ja/api/options.md | 165 +++++++++++ docs/ja/api/selectors.md | 43 +++ docs/ja/api/shallow.md | 124 ++++++++ docs/ja/api/wrapper-array/README.md | 11 + docs/ja/api/wrapper-array/at.md | 21 ++ docs/ja/api/wrapper-array/contains.md | 24 ++ docs/ja/api/wrapper-array/hasAttribute.md | 21 ++ docs/ja/api/wrapper-array/hasClass.md | 20 ++ docs/ja/api/wrapper-array/hasProp.md | 24 ++ docs/ja/api/wrapper-array/hasStyle.md | 24 ++ docs/ja/api/wrapper-array/is.md | 20 ++ docs/ja/api/wrapper-array/isEmpty.md | 17 ++ docs/ja/api/wrapper-array/isVueInstance.md | 18 ++ docs/ja/api/wrapper-array/setData.md | 22 ++ docs/ja/api/wrapper-array/setMethods.md | 27 ++ docs/ja/api/wrapper-array/setProps.md | 22 ++ docs/ja/api/wrapper-array/trigger.md | 26 ++ docs/ja/api/wrapper-array/update.md | 20 ++ docs/ja/api/wrapper/README.md | 16 ++ docs/ja/api/wrapper/contains.md | 23 ++ docs/ja/api/wrapper/emitted.md | 33 +++ docs/ja/api/wrapper/emittedByOrder.md | 28 ++ docs/ja/api/wrapper/exists.md | 21 ++ docs/ja/api/wrapper/find.md | 27 ++ docs/ja/api/wrapper/findAll.md | 27 ++ docs/ja/api/wrapper/hasAttribute.md | 37 +++ docs/ja/api/wrapper/hasClass.md | 21 ++ docs/ja/api/wrapper/hasProp.md | 25 ++ docs/ja/api/wrapper/hasStyle.md | 24 ++ docs/ja/api/wrapper/html.md | 16 ++ docs/ja/api/wrapper/is.md | 19 ++ docs/ja/api/wrapper/isEmpty.md | 16 ++ docs/ja/api/wrapper/isVueInstance.md | 16 ++ docs/ja/api/wrapper/name.md | 18 ++ docs/ja/api/wrapper/setData.md | 20 ++ docs/ja/api/wrapper/setMethods.md | 24 ++ docs/ja/api/wrapper/setProps.md | 49 ++++ docs/ja/api/wrapper/text.md | 16 ++ docs/ja/api/wrapper/trigger.md | 38 +++ docs/ja/api/wrapper/update.md | 19 ++ docs/ja/guides/README.md | 9 + docs/ja/guides/choosing-a-test-runner.md | 44 +++ docs/ja/guides/common-tips.md | 136 +++++++++ docs/ja/guides/getting-started.md | 116 ++++++++ docs/ja/guides/testing-SFCs-with-jest.md | 178 ++++++++++++ .../guides/testing-SFCs-with-mocha-webpack.md | 180 ++++++++++++ docs/ja/guides/using-with-vue-router.md | 71 +++++ docs/ja/guides/using-with-vuex.md | 266 ++++++++++++++++++ 54 files changed, 2506 insertions(+) create mode 100644 docs/ja/README.md create mode 100644 docs/ja/SUMMARY.md create mode 100644 docs/ja/api/README.md create mode 100644 docs/ja/api/createLocalVue.md create mode 100644 docs/ja/api/mount.md create mode 100644 docs/ja/api/options.md create mode 100644 docs/ja/api/selectors.md create mode 100644 docs/ja/api/shallow.md create mode 100644 docs/ja/api/wrapper-array/README.md create mode 100644 docs/ja/api/wrapper-array/at.md create mode 100644 docs/ja/api/wrapper-array/contains.md create mode 100644 docs/ja/api/wrapper-array/hasAttribute.md create mode 100644 docs/ja/api/wrapper-array/hasClass.md create mode 100644 docs/ja/api/wrapper-array/hasProp.md create mode 100644 docs/ja/api/wrapper-array/hasStyle.md create mode 100644 docs/ja/api/wrapper-array/is.md create mode 100644 docs/ja/api/wrapper-array/isEmpty.md create mode 100644 docs/ja/api/wrapper-array/isVueInstance.md create mode 100644 docs/ja/api/wrapper-array/setData.md create mode 100644 docs/ja/api/wrapper-array/setMethods.md create mode 100644 docs/ja/api/wrapper-array/setProps.md create mode 100644 docs/ja/api/wrapper-array/trigger.md create mode 100644 docs/ja/api/wrapper-array/update.md create mode 100644 docs/ja/api/wrapper/README.md create mode 100644 docs/ja/api/wrapper/contains.md create mode 100644 docs/ja/api/wrapper/emitted.md create mode 100644 docs/ja/api/wrapper/emittedByOrder.md create mode 100644 docs/ja/api/wrapper/exists.md create mode 100644 docs/ja/api/wrapper/find.md create mode 100644 docs/ja/api/wrapper/findAll.md create mode 100644 docs/ja/api/wrapper/hasAttribute.md create mode 100644 docs/ja/api/wrapper/hasClass.md create mode 100644 docs/ja/api/wrapper/hasProp.md create mode 100644 docs/ja/api/wrapper/hasStyle.md create mode 100644 docs/ja/api/wrapper/html.md create mode 100644 docs/ja/api/wrapper/is.md create mode 100644 docs/ja/api/wrapper/isEmpty.md create mode 100644 docs/ja/api/wrapper/isVueInstance.md create mode 100644 docs/ja/api/wrapper/name.md create mode 100644 docs/ja/api/wrapper/setData.md create mode 100644 docs/ja/api/wrapper/setMethods.md create mode 100644 docs/ja/api/wrapper/setProps.md create mode 100644 docs/ja/api/wrapper/text.md create mode 100644 docs/ja/api/wrapper/trigger.md create mode 100644 docs/ja/api/wrapper/update.md create mode 100644 docs/ja/guides/README.md create mode 100644 docs/ja/guides/choosing-a-test-runner.md create mode 100644 docs/ja/guides/common-tips.md create mode 100644 docs/ja/guides/getting-started.md create mode 100644 docs/ja/guides/testing-SFCs-with-jest.md create mode 100644 docs/ja/guides/testing-SFCs-with-mocha-webpack.md create mode 100644 docs/ja/guides/using-with-vue-router.md create mode 100644 docs/ja/guides/using-with-vuex.md diff --git a/docs/LANGS.md b/docs/LANGS.md index 25b60e8a0..58d8021f6 100644 --- a/docs/LANGS.md +++ b/docs/LANGS.md @@ -1 +1,2 @@ * [English](en/) +* [日本語](ja/) diff --git a/docs/ja/README.md b/docs/ja/README.md new file mode 100644 index 000000000..029e01c74 --- /dev/null +++ b/docs/ja/README.md @@ -0,0 +1,49 @@ +# vue-test-utils + +`vue-test-utils`は Vue.js 向けの公式単体テストライブラリです。 + +* [ガイド](guides/README.md) + * [はじめる](guides/getting-started.md) + * [一般的なヒント](guides/common-tips.md) + * [テストランナを選ぶ](guides/choosing-a-test-runner.md) + * [Jest による単一ファイルコンポーネントのテスト](guides/testing-SFCs-with-jest.md) + * [Mocha + webpack による単一ファイルコンポーネントのテスト](guides/testing-SFCs-with-mocha-webpack.md) + * [Vue Router と一緒に使う](guides/using-with-vue-router.md) + * [Vuex と一緒に使う](guides/using-with-vuex.md) +* [API](api/README.md) + * [createLocalVue](api/createLocalVue.md) + * [mount](api/mount.md) + * [shallow](api/shallow.md) + * [マウンティングオプション](api/options.md) + * [Wrapper](api/wrapper/README.md) + * [contains](api/wrapper/contains.md) + * [find](api/wrapper/find.md) + * [hasAttribute](api/wrapper/hasAttribute.md) + * [hasClass](api/wrapper/hasClass.md) + * [hasProp](api/wrapper/hasProp.md) + * [hasStyle](api/wrapper/hasStyle.md) + * [html](api/wrapper/html.md) + * [is](api/wrapper/is.md) + * [isEmpty](api/wrapper/isEmpty.md) + * [isVueInstance](api/wrapper/isVueInstance.md) + * [name](api/wrapper/name.md) + * [update](api/wrapper/update.md) + * [setData](api/wrapper/setData.md) + * [setProps](api/wrapper/setProps.md) + * [text](api/wrapper/text.md) + * [trigger](api/wrapper/trigger.md) + * [WrapperArray](api/wrapper-array/README.md) + * [at](api/wrapper-array/at.md) + * [contains](api/wrapper-array/contains.md) + * [hasAttribute](api/wrapper-array/hasAttribute.md) + * [hasClass](api/wrapper-array/hasClass.md) + * [hasProp](api/wrapper-array/hasProp.md) + * [hasStyle](api/wrapper-array/hasStyle.md) + * [is](api/wrapper-array/is.md) + * [isEmpty](api/wrapper-array/isEmpty.md) + * [isVueInstance](api/wrapper-array/isVueInstance.md) + * [update](api/wrapper-array/update.md) + * [setData](api/wrapper-array/setData.md) + * [setProps](api/wrapper-array/setProps.md) + * [trigger](api/wrapper-array/trigger.md) + * [セレクタ](api/selectors.md) diff --git a/docs/ja/SUMMARY.md b/docs/ja/SUMMARY.md new file mode 100644 index 000000000..0a0da8b4e --- /dev/null +++ b/docs/ja/SUMMARY.md @@ -0,0 +1,61 @@ +## 目次 + +* [ガイド](guides/README.md) + * [はじめる](guides/getting-started.md) + * [一般的なヒント](guides/common-tips.md) + * [テストランナを選ぶ](guides/choosing-a-test-runner.md) + * [Jest による単一ファイルコンポーネントのテスト](guides/testing-SFCs-with-jest.md) + * [Mocha + webpack による単一ファイルコンポーネントのテスト](guides/testing-SFCs-with-mocha-webpack.md) + * [Vue Router と一緒に使う](guides/using-with-vue-router.md) + * [Vuex と一緒に使う](guides/using-with-vuex.md) +* [API](api/README.md) + * [mount](api/mount.md) + * [shallow](api/shallow.md) + * [マウンティングオプション](api/options.md) + - [context](api/options.md#context) + - [slots](api/options.md#slots) + - [stubs](api/options.md#stubs) + - [mocks](api/options.md#mocks) + - [localVue](api/options.md#localvue) + - [attachToDocument](api/options.md#attachtodocument) + - [attrs](api/options.md#attrs) + - [listeners](api/options.md#listeners) + - [clone](api/options.md#clone) + * [Wrapper](api/wrapper/README.md) + * [contains](api/wrapper/contains.md) + * [emitted](api/wrapper/emitted.md) + * [emittedByOrder](api/wrapper/emittedByOrder.md) + * [find](api/wrapper/find.md) + * [findAll](api/wrapper/findAll.md) + * [hasAttribute](api/wrapper/hasAttribute.md) + * [hasClass](api/wrapper/hasClass.md) + * [hasProp](api/wrapper/hasProp.md) + * [hasStyle](api/wrapper/hasStyle.md) + * [html](api/wrapper/html.md) + * [is](api/wrapper/is.md) + * [isEmpty](api/wrapper/isEmpty.md) + * [isVueInstance](api/wrapper/isVueInstance.md) + * [name](api/wrapper/name.md) + * [setData](api/wrapper/setData.md) + * [setMethods](api/wrapper/setMethods.md) + * [setProps](api/wrapper/setProps.md) + * [text](api/wrapper/text.md) + * [trigger](api/wrapper/trigger.md) + * [update](api/wrapper/update.md) + * [WrapperArray](api/wrapper-array/README.md) + * [at](api/wrapper-array/at.md) + * [contains](api/wrapper-array/contains.md) + * [hasAttribute](api/wrapper-array/hasAttribute.md) + * [hasClass](api/wrapper-array/hasClass.md) + * [hasProp](api/wrapper-array/hasProp.md) + * [hasStyle](api/wrapper-array/hasStyle.md) + * [is](api/wrapper-array/is.md) + * [isEmpty](api/wrapper-array/isEmpty.md) + * [isVueInstance](api/wrapper-array/isVueInstance.md) + * [setData](api/wrapper-array/setData.md) + * [setMethods](api/wrapper-array/setMethods.md) + * [setProps](api/wrapper-array/setProps.md) + * [trigger](api/wrapper-array/trigger.md) + * [update](api/wrapper-array/update.md) + * [セレクタ](api/selectors.md) + * [createLocalVue](api/createLocalVue.md) diff --git a/docs/ja/api/README.md b/docs/ja/api/README.md new file mode 100644 index 000000000..231254061 --- /dev/null +++ b/docs/ja/api/README.md @@ -0,0 +1,49 @@ +# API + +* [mount](./mount.md) +* [shallow](./shallow.md) +* [Mounting Options](./options.md) + - [context](./options.md#context) + - [slots](./options.md#slots) + - [stubs](./options.md#stubs) + - [mocks](./options.md#mocks) + - [localVue](./options.md#localvue) + - [attachToDocument](./options.md#attachtodocument) + - [attrs](./options.md#attrs) + - [listeners](./options.md#listeners) + - [clone](./options.md#clone) +* [Wrapper](./wrapper/README.md) + * [contains](./wrapper/contains.md) + * [emitted](./wrapper/emitted.md) + * [emittedByOrder](./wrapper/emittedByOrder.md) + * [find](./wrapper/find.md) + * [hasAttribute](./wrapper/hasAttribute.md) + * [hasClass](./wrapper/hasClass.md) + * [hasProp](./wrapper/hasProp.md) + * [hasStyle](./wrapper/hasStyle.md) + * [html](./wrapper/html.md) + * [is](./wrapper/is.md) + * [isEmpty](./wrapper/isEmpty.md) + * [isVueInstance](./wrapper/isVueInstance.md) + * [name](./wrapper/name.md) + * [update](./wrapper/update.md) + * [setData](./wrapper/setData.md) + * [setProps](./wrapper/setProps.md) + * [text](./wrapper/text.md) + * [trigger](./wrapper/trigger.md) +* [WrapperArray](./wrapper-array/README.md) + * [at](./wrapper-array/at.md) + * [contains](./wrapper-array/contains.md) + * [hasAttribute](./wrapper-array/hasAttribute.md) + * [hasClass](./wrapper-array/hasClass.md) + * [hasProp](./wrapper-array/hasProp.md) + * [hasStyle](./wrapper-array/hasStyle.md) + * [is](./wrapper-array/is.md) + * [isEmpty](./wrapper-array/isEmpty.md) + * [isVueInstance](./wrapper-array/isVueInstance.md) + * [update](./wrapper-array/update.md) + * [setData](./wrapper-array/setData.md) + * [setProps](./wrapper-array/setProps.md) + * [trigger](./wrapper-array/trigger.md) +* [createLocalVue](./createLocalVue.md) +* [Selectors](./selectors.md) diff --git a/docs/ja/api/createLocalVue.md b/docs/ja/api/createLocalVue.md new file mode 100644 index 000000000..d075af895 --- /dev/null +++ b/docs/ja/api/createLocalVue.md @@ -0,0 +1,28 @@ +# createLocalVue() + +- **戻り値:** + - `{Component}` + +- **使い方:** + +`createLocalVue` は、グローバル Vue クラスを汚染することなくコンポーネント、ミックスイン、プラグインを追加するための Vue クラスを返します。 + +`options.localVue` と一緒に使用してください。 + +```js +import { createLocalVue, shallow } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const localVue = createLocalVue() +const wrapper = shallow(Foo, { + localVue, + intercept: { foo: true } +}) +expect(wrapper.vm.foo).toBe(true) + +const freshWrapper = shallow(Foo) +expect(freshWrapper.vm.foo).toBe(false) +``` + +- **参照:** [一般的なヒント](../guides/common-tips.md#グローバルプラグインとミックスインの適用) diff --git a/docs/ja/api/mount.md b/docs/ja/api/mount.md new file mode 100644 index 000000000..177cf90b7 --- /dev/null +++ b/docs/ja/api/mount.md @@ -0,0 +1,136 @@ +# mount(component {, options}]) + +- **引数:** + + - `{Component} component` + - `{Object} options` + +- **戻り値:** `{Wrapper}` + +- **オプション:** + +[オプション](./options.md)を参照してください。 + +- **使い方:** + +最初の DOM ノードまたは Vue コンポーネント一致セレクタの [`Wrapper`](./wrapper/README.md) を返します。 + +有効な[セレクタ](./selectors.md)を使用してください。 + +**オプションなし:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +describe('Foo', () => { + it('renders a div', () => { + const wrapper = mount(Foo) + expect(wrapper.contains('div')).toBe(true) + }) +}) +``` +**Vueオプションを使用:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +describe('Foo', () => { + it('renders a div', () => { + const wrapper = mount(Foo, { + propsData: { + color: 'red' + } + }) + expect(wrapper.hasProp('color', 'red')).toBe(true) + }) +}) +``` + +**DOMへのアタッチ:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +describe('Foo', () => { + it('renders a div', () => { + const wrapper = mount(Foo, { + attachToDocument: true + }) + expect(wrapper.contains('div')).toBe(true) + }) +}) +``` +**デフォルトおよび名前付きスロット:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' +import Bar from './Bar.vue' +import FooBar from './FooBar.vue' + +describe('Foo', () => { + it('renders a div', () => { + const wrapper = mount(Foo, { + slots: { + default: [Bar, FooBar], + fooBar: FooBar, // と一致する, + foo: '
' + } + }) + expect(wrapper.contains('div')).toBe(true) + }) +}) +``` + +**グローバルプロパティのスタブ:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +describe('Foo', () => { + it('renders a div', () => { + const $route = { path: 'http://www.example-path.com' } + const wrapper = mount(Foo, { + intercept: { + $route + } + }) + expect(wrapper.vm.$route.path).toBe($route.path) + }) +}) +``` + +**コンポーネントのスタブ:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' +import Bar from './Bar.vue' +import Faz from './Faz.vue' + +describe('Foo', () => { + it('renders a div', () => { + const wrapper = mount(Foo, { + stub: { + Bar: '
, + foo: '
' + } +}) +expect(wrapper.find('div')).toBe(true) +``` + +### `stubs` + +- type: `{ [name: string]: Component | boolean } | Array` + +子のコンポーネントをスタブします。スタブまたはオブジェクトに対するコンポーネント名の配列になります。 + +例: + +```js +import Foo from './Foo.vue' + +mount(Component, { + stubs: ['registered-component'] +}) + +shallow(Component, { + stubs: { + // 特定の実装によるスタブ + 'registered-component': Foo, + // デフォルトのスタブを作成します + 'another-component': true + } +}) +``` + +### `mocks` + +- 型: `Object` + +インスタンスに追加のプロパティを追加します。グローバル注入をモックするのに便利です。 + +例: + +```js +import { expect } from 'chai' + +const $route = { path: 'http://www.example-path.com' } +const wrapper = shallow(Component, { + mocks: { + $route + } +}) +expect(wrapper.vm.$route.path).toBe($route.path) +``` + +### `localVue` + +- 型: `Vue` + +コンポーネントのマウント時に使用する [createLocalVue](./createLocalVue.md) によって作成された Vue のローカルコピーです。この Vue のコピーにプラグインをインストールすると、元の `Vue` コピーが汚染されなくなります。 + +例: + +```js +import { createLocalVue, mount } from 'vue-test-utils' +import VueRouter from 'vue-router' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const localVue = createLocalVue() +localVue.use(VueRouter) + +const routes = [ + { path: '/foo', component: Foo } +] + +const router = new VueRouter({ + routes +}) + +const wrapper = mount(Component, { + localVue, + router +}) +expect(wrapper.vm.$route).toBeInstanceOf(Object) +``` + +### `attachToDocument` + +- 型: `boolean` +- デフォルト: `false` + +`true` に設定されている場合、描画時にコンポーネントは DOM にアタッチされます。これは複数の要素や CSS セレクタをチェックするための [`hasStyle`](./wrapper/hasStyle.md) とも使用できます。 + +### `attrs` + +- 型: `Object` + +コンポーネントインスタンスの `$attrs` オブジェクトを設定します。 + +### `listeners` + +- 型: `Object` + +コンポーネントインスタンスの `$listeners` オブジェクトを設定します。 + +### `clone` + +- 型: `boolean` +- デフォルト: `true` + +`true` に設定されている場合、マウント前にコンポーネントを複製し、元のコンポーネントの定義を変更することはありません。 + +`options.mocks` (`Object`): Vue インスタンスにグローバルを追加します。 + +`options.localVue` (`Object`): `mount` で使う Vue クラスです。[createLocalVue](./createLocalVue.md)を参照してください。 diff --git a/docs/ja/api/selectors.md b/docs/ja/api/selectors.md new file mode 100644 index 000000000..7b9ae8ab5 --- /dev/null +++ b/docs/ja/api/selectors.md @@ -0,0 +1,43 @@ +# セレクタ + +多くのメソッドがセレクタを引数とします。セレクタは、CSS セレクタまたは Vue コンポーネントのいずれかです。 + +## CSS セレクタ + +マウントは有効な CSS セレクタを処理します。 + +- タグセレクタ (div, foo, bar) +- クラスセレクタ (.foo, .bar) +- 属性セレクタ ([foo], [foo="bar"]) +- idセレクタ (#foo, #bar) +- 疑似セレクタ (div:first-of-type) + +これらを組み合わせることも可能です: + +- 直接子孫を組み合わせる (div > #bar > .foo) +- 一般子孫セレクタを組み合わせる (div #bar .foo) +- 隣接する兄弟のセレクタ (div + .foo) +- 一般兄弟セレクタ (div ~ .foo) + +## Vue コンポーネント + +Vue コンポーネントもセレクタとして有効です。 + +vue-test-utils は `name` プロパティを使用して、一致する Vue コンポーネントのインスタンスツリーを検索します。 + +```js +// Foo.vue + +export default{ + name: 'FooComponent' +} +``` + +```js +import { shallow } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = shallow(Foo) +expect(wrapper.is(Foo)).toBe(true) +``` diff --git a/docs/ja/api/shallow.md b/docs/ja/api/shallow.md new file mode 100644 index 000000000..ed5cda807 --- /dev/null +++ b/docs/ja/api/shallow.md @@ -0,0 +1,124 @@ +# shallow(component {, options}]) + +- **引数:** + + - `{Component} component` + - `{Object} options` + - `{boolean} attachToDocument` + - `{Object} context` + - `{Object} slots` + - `{Array|Component|String} default` + - `{Array|Component|String} named` + - `{Object} intercept` + - `{Object|Array} stubs` + - `{boolean} clone` + - `{Object} children` + - `{Vue} localVue` + +- **戻り値:** `{Wrapper}` + +- **オプション:** + +[オプション](./options.md)を参照してください。 + +- **使い方:** + +最初の DOM ノードまたは Vue コンポーネント一致セレクタの [`Wrapper`](./wrapper/) を返します。 + +全ての子コンポーネントをスタブします。 + +有効な[セレクタ](./selectors.md)を使用してください。 + +**オプションなし:** + +```js +import { shallow } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +describe('Foo', () => { + it('renders a div', () => { + const wrapper = shallow(Foo) + expect(wrapper.contains('div')).toBe(true) + }) +}) +``` + +**Vueオプションを使用:** + +```js +import { shallow } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +describe('Foo', () => { + it('renders a div', () => { + const wrapper = shallow(Foo, { + propsData: { + color: 'red' + } + }) + expect(wrapper.hasProp('color', 'red')).toBe(true) + }) +}) +``` + +**DOMへのアタッチ:** + +```js +import { shallow } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +describe('Foo', () => { + it('renders a div', () => { + const wrapper = shallow(Foo, { + attachToDocument: true + }) + expect(wrapper.contains('div')).toBe(true) + }) +}) +``` + +**デフォルトおよび名前付きスロット:** + +```js +import { shallow } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' +import Bar from './Bar.vue' +import FooBar from './FooBar.vue' + +describe('Foo', () => { + it('renders a div', () => { + const wrapper = shallow(Foo, { + slots: { + default: [Bar, FooBar], + fooBar: FooBar, // と一致する, + foo: '
' + } + }) + expect(wrapper.find('div')).toBe(true) + }) +}) +``` + +**グローバルプロパティのスタブ:** + +```js +import { shallow } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +describe('Foo', () => { + it('renders a div', () => { + const $route = { path: 'http://www.example-path.com' } + const wrapper = shallow(Foo, { + intercept: { + $route + } + }) + expect(wrapper.vm.$route.path).toBe($route.path) + }) +}) +``` diff --git a/docs/ja/api/wrapper-array/README.md b/docs/ja/api/wrapper-array/README.md new file mode 100644 index 000000000..e6fd7056e --- /dev/null +++ b/docs/ja/api/wrapper-array/README.md @@ -0,0 +1,11 @@ +# WrapperArray + +`WrapperArray` は、[Wrapper](../wrapper/README.md) の配列と `Wrapper` をテストするメソッドを含むオブジェクトです。 + +- **プロパティ:** + +`length` `number`: `WrapperArray` に含まれる `Wrappers` の数 + + - **メソッド:** + +ドキュメントの WrapperArray セクションには、詳細なメソッドのリストがあります。 diff --git a/docs/ja/api/wrapper-array/at.md b/docs/ja/api/wrapper-array/at.md new file mode 100644 index 000000000..2497040ad --- /dev/null +++ b/docs/ja/api/wrapper-array/at.md @@ -0,0 +1,21 @@ +# at(index) + +渡された `index` の `Wrapper` を返します。ゼロベースの番号付けを使用します(つまり、最初のアイテムはインデックス 0 になります)。 + +- **引数:** + - `{number} index` + +- **戻り値:** `{Wrapper}` + +- **例:** + +```js +import { shallow } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = shallow(Foo) +const divArray = wrapper.findAll('div') +const secondDiv = divArray.at(1) +expect(secondDiv.is('p')).toBe(true) +``` diff --git a/docs/ja/api/wrapper-array/contains.md b/docs/ja/api/wrapper-array/contains.md new file mode 100644 index 000000000..47273e2f5 --- /dev/null +++ b/docs/ja/api/wrapper-array/contains.md @@ -0,0 +1,24 @@ +# contains(selector) + +`WrapperArray` のすべての Wrapper にセレクタが含まれているか検証します。 + +有効な[セレクタ](../selectors.md)を使用してください。 + +- **引数:** + - `{string|Component} selector` + +- **戻り値:** `{boolean}` + +- **例:** + +```js +import { shallow } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' +import Bar from './Bar.vue' + +const wrapper = shallow(Foo) +const divArray = wrapper.findAll('div') +expect(divArray.contains('p')).toBe(true) +expect(divArray.contains(Bar)).toBe(true) +``` diff --git a/docs/ja/api/wrapper-array/hasAttribute.md b/docs/ja/api/wrapper-array/hasAttribute.md new file mode 100644 index 000000000..d9fe16810 --- /dev/null +++ b/docs/ja/api/wrapper-array/hasAttribute.md @@ -0,0 +1,21 @@ +# hasAttribute(attribute, value) + +`WrapperArray` の全ての `Wrapper` が `value` と一致する属性を持っているか検証します。 + +- **引数:** + - `{string} attribute` + - `{string} value` + +- **戻り値:** `{boolean}` + +- **例:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +const divArray = wrapper.findAll('div') +expect(divArray.hasAttribute('id', 'foo')).toBe(true) +``` diff --git a/docs/ja/api/wrapper-array/hasClass.md b/docs/ja/api/wrapper-array/hasClass.md new file mode 100644 index 000000000..e396c32c2 --- /dev/null +++ b/docs/ja/api/wrapper-array/hasClass.md @@ -0,0 +1,20 @@ +# hasClass(className) + +`WrapperArray` のすべての `Wrapper` に DOM ノードが `className` を含むクラスを持っているか検証します。 + +- **引数:** + - `{string} className` + +- **戻り値:** `{boolean}` + +- **例:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +const divArray = wrapper.findAll('div') +expect(divArray.hasClass('bar')).toBe(true) +``` diff --git a/docs/ja/api/wrapper-array/hasProp.md b/docs/ja/api/wrapper-array/hasProp.md new file mode 100644 index 000000000..5d73c5b17 --- /dev/null +++ b/docs/ja/api/wrapper-array/hasProp.md @@ -0,0 +1,24 @@ +# hasProp(prop, value) + +`vm` が `value` に一致する `prop` を持つ `WrapperArray` において全て `Wrapper` か検証します。 + +**WrapperにはVueインスタンスを含む必要があることに注意してください。** + +- **引数:** + - `{string} prop` + - `{any} value` + +- **戻り値:** `{boolean}` + +- **例:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' +import Bar from './Bar.vue' + +const wrapper = mount(Foo) +const barArray = wrapper.findAll(Bar) +expect(barArray.hasProp('bar', 10)).toBe(true) +``` diff --git a/docs/ja/api/wrapper-array/hasStyle.md b/docs/ja/api/wrapper-array/hasStyle.md new file mode 100644 index 000000000..822580101 --- /dev/null +++ b/docs/ja/api/wrapper-array/hasStyle.md @@ -0,0 +1,24 @@ +# hasStyle(style, value) + +`WrapperArray` の全ての `Wrapper` の DOM ノードに style 属性とマッチする値を持っているか検証します。 + +`Wrapper` DOM ノードに `string` にマッチする `style` 値がある場合 `true` を返します。 + +**`jsdom`で実行しているときにのみインラインスタイルを検出しますので注意してください。** +- **引数:** + - `{string} style` + - `{string} value` + +- **戻り値:** `{boolean}` + +- **例:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +const divArray = wrapper.findAll('div') +expect(divArray.hasStyle('color', 'red')).toBe(true) +``` diff --git a/docs/ja/api/wrapper-array/is.md b/docs/ja/api/wrapper-array/is.md new file mode 100644 index 000000000..e667e8746 --- /dev/null +++ b/docs/ja/api/wrapper-array/is.md @@ -0,0 +1,20 @@ +# is(selector) + +`WrapperArray` の全ての `Wrapper` のDOMノード、もしくは[セレクタ](../selectors.md)が `vm` とマッチするか検証します。 + +- **引数:** + - `{string|Component} selector` + +- **戻り値:** `{boolean}` + +- **例:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +const divArray = wrapper.find('div') +expect(divArray.is('div')).toBe(true) +``` diff --git a/docs/ja/api/wrapper-array/isEmpty.md b/docs/ja/api/wrapper-array/isEmpty.md new file mode 100644 index 000000000..2fd029ed2 --- /dev/null +++ b/docs/ja/api/wrapper-array/isEmpty.md @@ -0,0 +1,17 @@ +# isEmpty() + +`WrapperArray` のすべての `Wrapper` に子ノードを含んでいないか検証します。 + +- **戻り値:** `{boolean}` + +- **例:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +const divArray = wrapper.findAll('div') +expect(divArray.isEmpty()).toBe(true) +``` diff --git a/docs/ja/api/wrapper-array/isVueInstance.md b/docs/ja/api/wrapper-array/isVueInstance.md new file mode 100644 index 000000000..718fb69ff --- /dev/null +++ b/docs/ja/api/wrapper-array/isVueInstance.md @@ -0,0 +1,18 @@ +# isVueInstance() + +`WrapperArray` の全ての `Wrapper` が Vue インスタンスであるか検証します。 + +- **戻り値:** `{boolean}` + +- **例:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' +import Bar from './Bar.vue' + +const wrapper = mount(Foo) +const barArray = wrapper.findAll(Bar) +expect(barArray.isVueInstance()).toBe(true) +``` diff --git a/docs/ja/api/wrapper-array/setData.md b/docs/ja/api/wrapper-array/setData.md new file mode 100644 index 000000000..37be339a5 --- /dev/null +++ b/docs/ja/api/wrapper-array/setData.md @@ -0,0 +1,22 @@ +# setData(data) + +`WrapperArray` の `Wrapper` ごとに `Wrapper` に `vm` データをセットし、強制的に更新します。 + +**すべての `Wrapper` は Vue インスタンスを含んでいなければならないことに注意してください。** + +- **引数:** + - `{Object} data` + +- **例:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' +import Bar from './Bar.vue' + +const wrapper = mount(Foo) +const barArray = wrapper.findAll(Bar) +barArray.setData({ foo: 'bar' }) +expect(barArray.at(0).vm.foo).toBe('bar') +``` diff --git a/docs/ja/api/wrapper-array/setMethods.md b/docs/ja/api/wrapper-array/setMethods.md new file mode 100644 index 000000000..4375ab3ce --- /dev/null +++ b/docs/ja/api/wrapper-array/setMethods.md @@ -0,0 +1,27 @@ + +# setMethods(methods) + +`WrapperArray` の `Wrapper` ごとに `Wrapper` に `vm` メソッドをセットし、強制的に更新します。 + +**すべての `Wrapper` は Vue インスタンスを含んでいなければならないことに注意してください。** + +- **引数:** + - `{Object} methods` + +- **例:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import sinon from 'sinon' +import Foo from './Foo.vue' +import Bar from './Bar.vue' + +const wrapper = mount(Foo) +const barArray = wrapper.findAll(Bar) +const clickMethodStub = sinon.stub() + +barArray.setMethods({ clickMethod: clickMethodStub }) +barArray.at(0).trigger('click') +expect(clickMethodStub.called).toBe(true) +``` diff --git a/docs/ja/api/wrapper-array/setProps.md b/docs/ja/api/wrapper-array/setProps.md new file mode 100644 index 000000000..827778f82 --- /dev/null +++ b/docs/ja/api/wrapper-array/setProps.md @@ -0,0 +1,22 @@ +# setProps(props) + +`WrapperArray` の `Wrapper` ごとに `Wrapper` に `vm` プロパティをセットし、強制的に更新します。 + +**すべての `Wrapper` は Vue インスタンスを含んでいなければならないことに注意してください。** + +- **引数:** + - `{Object} props` + +- **例:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' +import Bar from './Bar.vue' + +const wrapper = mount(Foo) +const barArray = wrapper.findAll(Bar) +barArray.setProps({ foo: 'bar' }) +expect(barArray.at(0).vm.foo).toBe('bar') +``` diff --git a/docs/ja/api/wrapper-array/trigger.md b/docs/ja/api/wrapper-array/trigger.md new file mode 100644 index 000000000..d32fc57a7 --- /dev/null +++ b/docs/ja/api/wrapper-array/trigger.md @@ -0,0 +1,26 @@ +# trigger(eventName) + +`WrapperArray` の DOM ノードのすべての `Wrapper` でイベントを発火します。 + +**すべての `Wrapper` は Vue インスタンスを含んでいなければならないことに注意してください。** + +- **引数:** + - `{string} eventName + +- **例:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import sinon from 'sinon' +import Foo from './Foo.vue' + +const clickHandler = sinon.stub() +const wrapper = mount(Foo, { + propsData: { clickHandler } +}) + +const divArray = wrapper.findAll('div') +divArray.trigger('click') +expect(clickHandler.called).toBe(true) +``` diff --git a/docs/ja/api/wrapper-array/update.md b/docs/ja/api/wrapper-array/update.md new file mode 100644 index 000000000..d5af25ace --- /dev/null +++ b/docs/ja/api/wrapper-array/update.md @@ -0,0 +1,20 @@ +# update() + +`WrapperArray` の各 `Wrapper` のルート Vue コンポーネントを強制的に再描画します。 + +ラップされた Vue コンポーネント配列において呼び出された場合、各 Vue コンポーネントは強制的に再描画します。 + +- **例:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +const divArray = wrapper.findAll('div') +expect(divArray.at(0).vm.bar).toBe('bar') +divArray.at(0).vm.bar = 'new value' +divArray.update() +expect(divArray.at(0).vm.bar).toBe('new value') +``` diff --git a/docs/ja/api/wrapper/README.md b/docs/ja/api/wrapper/README.md new file mode 100644 index 000000000..cc1c94218 --- /dev/null +++ b/docs/ja/api/wrapper/README.md @@ -0,0 +1,16 @@ +# Wrapper + +vue-test-utils はラッパベースの API です。 + +`Wrapper` は、マウントされたコンポーネントと仮想 DOM 、またはコンポーネントと仮想 DOM をテストするメソッドを含むオブジェクトです。 + +- **プロパティ:** + +`vm` `Component`:これは vue のインスタンスです。`wrapper.vm` を使って [vm のプロパティとインスタンスメソッド](https://jp.vuejs.org/v2/api/#インスタンスプロパティ)にアクセスできます。これは、Vue コンポーネントラッパにのみ存在します。 +`element` `HTMLElement`: ラッパのルート DOM +`options` `Object`: `mount` または `shallow` に渡された vue-test-utils オプションを含むオブジェクト +`options.attachedToDom` `Boolean`: `mount` か `shallow` に渡された場合は True です。 + +- **メソッド:** + +ドキュメントの wrapper セクションにはメソッドの詳細が一覧になっています。 diff --git a/docs/ja/api/wrapper/contains.md b/docs/ja/api/wrapper/contains.md new file mode 100644 index 000000000..54d1462b2 --- /dev/null +++ b/docs/ja/api/wrapper/contains.md @@ -0,0 +1,23 @@ +# contains(selector) + +`Wrapper` に要素、もしくは[セレクタ](../selectors.md)で指定したコンポーネントを含んでいるか検証します。 + +- **引数:** + - `{string|Component} selector` + +- **戻り値:** `{boolean}` + +- **例:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' +import Bar from './Bar.vue' + +const wrapper = mount(Foo) +expect(wrapper.contains('p')).toBe(true) +expect(wrapper.contains(Bar)).toBe(true) +``` + +- **参照:** [セレクタ](../selectors.md) diff --git a/docs/ja/api/wrapper/emitted.md b/docs/ja/api/wrapper/emitted.md new file mode 100644 index 000000000..46d683de6 --- /dev/null +++ b/docs/ja/api/wrapper/emitted.md @@ -0,0 +1,33 @@ +# emitted() + +`Wrapper` `vm` によって生成されたカスタムイベントを含むオブジェクトを返します。 + +- **戻り値:** `{ [name: string]: Array> }` + +- **例:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' + +const wrapper = mount(Component) + +wrapper.vm.$emit('foo') +wrapper.vm.$emit('foo', 123) + +/* +wrapper.emitted() は次のオブジェクトを返します: +{ + foo: [[], [123]] +} +*/ + +// イベントが発行されたか検証します +expect(wrapper.emitted().foo).toBeTruthy() + +// イベントの数を検証します +expect(wrapper.emitted().foo.length).toBe(2) + +// イベントのペイロードを検証します +expect(wrapper.emitted().foo[1]).toEqual([123]) +``` diff --git a/docs/ja/api/wrapper/emittedByOrder.md b/docs/ja/api/wrapper/emittedByOrder.md new file mode 100644 index 000000000..27c3665d7 --- /dev/null +++ b/docs/ja/api/wrapper/emittedByOrder.md @@ -0,0 +1,28 @@ +# emittedByOrder() + +`Wrapper` `vm` によって生成されたカスタムイベントを含む配列を返します。 + +- **戻り値:** `Array<{ name: string, args: Array }>` + +- **例:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' + +const wrapper = mount(Component) + +wrapper.vm.$emit('foo') +wrapper.vm.$emit('bar', 123) + +/* +wrapper.emittedByOrder() は次の配列を返します +[ + { name: 'foo', args: [] }, + { name: 'bar', args: [123] } +] +*/ + +// イベントの発行順序を検証します +expect(wrapper.emittedByOrder().map(e => e.name)).toEqual(['foo', 'bar']) +``` diff --git a/docs/ja/api/wrapper/exists.md b/docs/ja/api/wrapper/exists.md new file mode 100644 index 000000000..a09ab6e87 --- /dev/null +++ b/docs/ja/api/wrapper/exists.md @@ -0,0 +1,21 @@ +# exists() + +`Wrapper` か `WrapperArray` が存在するか検証します。 + +`Wrapper` か `WrapperArray` が空だった場合は false を返します。 + +- **戻り値:** `{boolean}` + +- **例:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +expect(wrapper.exists()).toBe(true) +expect(wrapper.find('does-not-exist').exists()).toBe(false) +expect(wrapper.findAll('div').exists()).toBe(true) +expect(wrapper.findAll('does-not-exist').exists()).toBe(false) +``` diff --git a/docs/ja/api/wrapper/find.md b/docs/ja/api/wrapper/find.md new file mode 100644 index 000000000..5050d0ccb --- /dev/null +++ b/docs/ja/api/wrapper/find.md @@ -0,0 +1,27 @@ +# find(selector) + +最初の DOM ノードの [Wrapper](./README.md)、またはセレクタで一致した Vue コンポーネントを返します。 + +有効な[セレクタ](../selectors.md)を使用してください。 + +- **引数:** + - `{string|Component} selector` + +- **戻り値:** `{Wrapper}` + +- **例:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' +import Bar from './Bar.vue' + +const wrapper = mount(Foo) +const div = wrapper.find('div') +expect(div.is('div')).toBe(true) +const bar = wrapper.find(Bar) +expect(bar.is(Bar)).toBe(true) +``` + +- **参照:** [Wrapper](./README.md) diff --git a/docs/ja/api/wrapper/findAll.md b/docs/ja/api/wrapper/findAll.md new file mode 100644 index 000000000..a5278a6b8 --- /dev/null +++ b/docs/ja/api/wrapper/findAll.md @@ -0,0 +1,27 @@ +# findAll(selector) + +[ラッパ (Wrapper)](./README.md) の[`WrapperArray`](../wrapper-array/README.md)を返します。 + +有効な[セレクタ](../selectors.md)を使用してください。 + +- **引数:** + - `{string|Component} selector` + +- **戻り値:** `{WrapperArray}` + +- **例:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' +import Bar from './Bar.vue' + +const wrapper = mount(Foo) +const div = wrapper.findAll('div').at(0) +expect(div.is('div')).toBe(true) +const bar = wrapper.findAll(Bar).at(0) +expect(bar.is(Bar)).toBe(true) +``` + +- **参照:** [Wrapper](./README.md) diff --git a/docs/ja/api/wrapper/hasAttribute.md b/docs/ja/api/wrapper/hasAttribute.md new file mode 100644 index 000000000..ba1e84243 --- /dev/null +++ b/docs/ja/api/wrapper/hasAttribute.md @@ -0,0 +1,37 @@ +# hasAttribute(attribute, value) + +`Wrapper` DOM ノードが値と一致した属性を持つか検証します。 + +`Wrapper` DOM ノードが値と一致する属性が含まれている場合は `true` を返します。 + +- **引数:** + - `{string} attribute` + - `{string} value` + +- **戻り値:** `{boolean}` + +- **例:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +expect(wrapper.hasAttribute('id', 'foo')).toBe(true) +``` + +- **代替:** + +`Wrapper.element` から属性を取得して、値に基づく検証を得ることができます。 + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +expect(wrapper.element.getAttribute('id')).toBe('foo') +``` + +これにより、更に有益な検証エラーを生み出せます。 diff --git a/docs/ja/api/wrapper/hasClass.md b/docs/ja/api/wrapper/hasClass.md new file mode 100644 index 000000000..0b19d99d7 --- /dev/null +++ b/docs/ja/api/wrapper/hasClass.md @@ -0,0 +1,21 @@ +# hasClass(className) + +`Wrapper` DOM ノードが `className` を含むクラスを持つか検証します。 + +`Wrapper` DOM ノードにクラスが含まれている場合は `true` を返します。 + +- **引数:** + - `{string} className` + +- **戻り値:** `{boolean}` + +- **例:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +expect(wrapper.hasClass('bar')).toBe(true) +``` diff --git a/docs/ja/api/wrapper/hasProp.md b/docs/ja/api/wrapper/hasProp.md new file mode 100644 index 000000000..986bdcee0 --- /dev/null +++ b/docs/ja/api/wrapper/hasProp.md @@ -0,0 +1,25 @@ +# hasProp(prop, value) + +`Wrapper` `vm`が `value` と一致する `prop` を持っているか検証します。 + +`Wrapper` `vm`が `value` と一致する `prop` を持つ場合は `true` を返します。 + + +**Wrapper には Vue インスタンスが含まれている必要があることに注意してください。** + +- **引数:** + - `{string} prop` + - `{any} value` + +- **戻り値:** `{boolean}` + +- **例:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +expect(wrapper.hasProp('bar', 10)).toBe(true) +``` diff --git a/docs/ja/api/wrapper/hasStyle.md b/docs/ja/api/wrapper/hasStyle.md new file mode 100644 index 000000000..1a7bd7126 --- /dev/null +++ b/docs/ja/api/wrapper/hasStyle.md @@ -0,0 +1,24 @@ +# hasStyle(style, value) + +`Wrapper` DOM ノードが値に一致するにスタイルを持つか検証します。 + +`Wrapper` DOM ノードが `string` に一致する `stuyle` を持つ場合は、`true` を返します。 + +**`jsdom` で実行しているときのみ、インラインスタイルを検出します。** + +- **引数:** + - `{string} style` + - `{string} value` + +- **戻り値:** `{boolean}` + +- **例:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +expect(wrapper.hasStyle('color', 'red')).toBe(true) +``` diff --git a/docs/ja/api/wrapper/html.md b/docs/ja/api/wrapper/html.md new file mode 100644 index 000000000..cc0d2dba3 --- /dev/null +++ b/docs/ja/api/wrapper/html.md @@ -0,0 +1,16 @@ +# html() + +`Wrapper` DOM ノードの HTML を文字列として返します。 + +- **戻り値:** `{string}` + +- **例:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +expect(wrapper.html()).toBe('

Foo

') +``` diff --git a/docs/ja/api/wrapper/is.md b/docs/ja/api/wrapper/is.md new file mode 100644 index 000000000..73ad1a0cb --- /dev/null +++ b/docs/ja/api/wrapper/is.md @@ -0,0 +1,19 @@ +# is(selector) + +`Wrapper` DOM ノード、または `vm` が[セレクタ](../selectors.md)と一致しているか検証します。 + +- **引数:** + - `{string|Component} selector` + +- **戻り値:** `{boolean}` + +- **例:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +expect(wrapper.is('div')).toBe(true) +``` diff --git a/docs/ja/api/wrapper/isEmpty.md b/docs/ja/api/wrapper/isEmpty.md new file mode 100644 index 000000000..05151f600 --- /dev/null +++ b/docs/ja/api/wrapper/isEmpty.md @@ -0,0 +1,16 @@ +# isEmpty() + +`Wrapper` が子ノードを含んでいないか検証します。 + +- **戻り値:** `{boolean}` + +- **例:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +expect(wrapper.isEmpty()).toBe(true) +``` diff --git a/docs/ja/api/wrapper/isVueInstance.md b/docs/ja/api/wrapper/isVueInstance.md new file mode 100644 index 000000000..911fb3c2c --- /dev/null +++ b/docs/ja/api/wrapper/isVueInstance.md @@ -0,0 +1,16 @@ +# isVueInstance() + +`Wrapper` が Vue インスタンスか検証します。 + +- **戻り値:** `{boolean}` + +- **例:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +expect(wrapper.isVueInstance()).toBe(true) +``` diff --git a/docs/ja/api/wrapper/name.md b/docs/ja/api/wrapper/name.md new file mode 100644 index 000000000..c42d42686 --- /dev/null +++ b/docs/ja/api/wrapper/name.md @@ -0,0 +1,18 @@ +# name() + +`Wrapper` に Vue インスタンスが含まれている場合はコンポーネント名を返し、そうでない場合は `Wrapper` DOM ノードのタグ名を返します。 + +- **戻り値:** `{string}` + +- **例:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +expect(wrapper.name()).toBe('Foo') +const p = wrapper.find('p') +expect(p.name()).toBe('p') +``` diff --git a/docs/ja/api/wrapper/setData.md b/docs/ja/api/wrapper/setData.md new file mode 100644 index 000000000..b4e56f607 --- /dev/null +++ b/docs/ja/api/wrapper/setData.md @@ -0,0 +1,20 @@ +# setData(data) + +`Wrapper` `vm` データを設定し、更新を強制します。 + +**Wrapper には Vue インスタンスを含む必要があることに注意してください** + +- **引数:** + - `{Object} data` + +- **例:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +wrapper.setData({ foo: 'bar' }) +expect(wrapper.vm.foo).toBe('bar') +``` diff --git a/docs/ja/api/wrapper/setMethods.md b/docs/ja/api/wrapper/setMethods.md new file mode 100644 index 000000000..f1f566d38 --- /dev/null +++ b/docs/ja/api/wrapper/setMethods.md @@ -0,0 +1,24 @@ +# setMethods(methods) + +`Wrapper` `vm` メソッドを設定し、更新を強制します。 + +**Wrapper には Vue インスタンスを含む必要があることに注意してください** + +- **引数:** + - `{Object} methods` + +- **例:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import sinon from 'sinon' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +const clickMethodStub = sinon.stub() + +wrapper.setMethods({ clickMethod: clickMethodStub }) +wrapper.find('button').trigger('click') +expect(clickMethodStub.called).toBe(true) +``` diff --git a/docs/ja/api/wrapper/setProps.md b/docs/ja/api/wrapper/setProps.md new file mode 100644 index 000000000..f7fc3df52 --- /dev/null +++ b/docs/ja/api/wrapper/setProps.md @@ -0,0 +1,49 @@ +# setProps(props) + +- **引数:** + - `{Object} props` + +- **使用方法:** + +`Wrapper` `vm` プロパティを設定し更新を強制します。 + +**Wrapper には Vue インスタンスを含む必要があることに注意してください** + + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +wrapper.setProps({ foo: 'bar' }) +expect(wrapper.vm.foo).to.equal('bar') +``` + +渡された値で Vue インスタンス を初期化する `propsData` オブジェクトを渡すことができます。 + +``` js +// Foo.vue +export default { + props: { + foo: { + type: String, + required: true + } + } +} +``` + +``` js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo, { + propsData: { + foo: 'bar' + } +}) + +expect(wrapper.vm.foo).to.equal('bar') +``` diff --git a/docs/ja/api/wrapper/text.md b/docs/ja/api/wrapper/text.md new file mode 100644 index 000000000..bab036823 --- /dev/null +++ b/docs/ja/api/wrapper/text.md @@ -0,0 +1,16 @@ +# text() + +`Wrapper` のテキスト内容を返します。 + +- **戻り値:** `{string}` + +- **例:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +expect(wrapper.text()).toBe('bar') +``` diff --git a/docs/ja/api/wrapper/trigger.md b/docs/ja/api/wrapper/trigger.md new file mode 100644 index 000000000..e75a4ff17 --- /dev/null +++ b/docs/ja/api/wrapper/trigger.md @@ -0,0 +1,38 @@ +# trigger(eventName) + +`Wrapper` DOM ノードのイベントを発火します。 + +Triggerは `options` オブジェクト形式で行います。`options` オブジェクトのプロパティがイベントに追加されます。 + +`options` で `preventDefault: true` とすることで、イベントに対して preventDefault を実行することができます。 + +- **引数:** + - `{string} eventName` + - `{Object} options` + - `{boolean} preventDefault` + +- **例:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import sinon from 'sinon' +import Foo from './Foo' + +const clickHandler = sinon.stub() +const wrapper = mount(Foo, { + propsData: { clickHandler } +}) + +wrapper.trigger('click') + +wrapper.trigger('click', { + button: 0 +}) + +wrapper.trigger('click', { + preventDefault: true +}) + +expect(clickHandler.called).toBe(true) +``` diff --git a/docs/ja/api/wrapper/update.md b/docs/ja/api/wrapper/update.md new file mode 100644 index 000000000..24111368f --- /dev/null +++ b/docs/ja/api/wrapper/update.md @@ -0,0 +1,19 @@ +# update() + +Vue コンポーネントを強制的に再描画します。 + +`vm` を含む `Wrapper` で呼び出された場合、`Wrapper` `vm` を強制的に再描画します。 + +- **例:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +expect(wrapper.vm.bar).toBe('bar') +wrapper.vm.bar = 'new value' +wrapper.update() +expect(wrapper.vm.bar).toBe('new value') +``` diff --git a/docs/ja/guides/README.md b/docs/ja/guides/README.md new file mode 100644 index 000000000..ce9946627 --- /dev/null +++ b/docs/ja/guides/README.md @@ -0,0 +1,9 @@ +# ガイド + +* [はじめる](./getting-started.md) +* [よくある落とし穴](./common-tips.md) +* [テストランナを選ぶ](./choosing-a-test-runner.md) +* [Jest と一緒に使う](./using-with-jest.md) +* [Jest による単一ファイルコンポーネントのテスト](./testing-SFCs-with-jest.md) +* [Mocha + webpack による単一ファイルコンポーネントのテスト](./testing-SFCs-with-mocha-webpack.md) +* [Vuex と一緒に使う](./using-with-vuex.md) diff --git a/docs/ja/guides/choosing-a-test-runner.md b/docs/ja/guides/choosing-a-test-runner.md new file mode 100644 index 000000000..040881db9 --- /dev/null +++ b/docs/ja/guides/choosing-a-test-runner.md @@ -0,0 +1,44 @@ +# テストランナを選ぶ + +テストランナは、テストを実行するプログラムです。 + +多くの一般的な JavaScript テストランナがあり、`vue-test-utils` はそれらすべてで動作します。テストランナにとらわれません。 + +ですが、テストランナを選択する際には、機能セット、パフォーマンス、および単一ファイルコンポーネント (SFC) の事前コンパイルのサポートなどを考慮すべきです。既存のライブラリを慎重に比較した上で、以下の2つのテストランナをお勧めします: + +- [Jest](https://facebook.github.io/jest/docs/en/getting-started.html#content) は最も充実したテストランナです。最小の設定が必要で、デフォルトで JSDOM を設定し、組み込みの検証を提供し、コマンドラインのユーザーエクスペリエンスが優れています。ただし、テストで SFC コンポーネントをインポートできるようにするには、プリプロセッサが必要です。最も一般的な SFC 機能を処理できる `jest-vue` プリプロセッサを作成しましたが、現在 `vue-loader` と 100% 同じ機能を持っていません。 + +- [mocha-webpack](https://github.com/zinserjan/mocha-webpack) は webpack + Mocha のラッパですが、より合理的なインタフェースと watch モードを備えています。この設定のメリットは、webpack + `vue-loader` を使用して完全な SFC サポートを得ることができるということですが、より多くの設定を行う必要があります。 + +## ブラウザ環境 + +`vue-test-utils` はブラウザ環境に依存します。技術的には、実際のブラウザで実行することはできますが、異なるプラットフォーム上で実際のブラウザを起動するという複雑さのため、お勧めできません。代わりに、[JSDOM](https://github.com/tmpvar/jsdom) を使用して仮想ブラウザ環境で Node.js でテストを実行することをお勧めします。 + +Jest テストランナーは JSDOM を自動的に設定します。他のテストランナーの場合は、[jsdom-global](https://github.com/rstacruz/jsdom-global) を使用してテスト用の JSDOM を手動で設定できます: + +``` bash +npm install --save-dev jsdom jsdom-global +``` +--- +``` js +// テストのセットアップと登録 +require('jsdom-global')() +``` + +## 単一ファイルコンポーネントをテストする + +単一ファイルコンポーネントは、ノードまたはブラウザで実行する前に事前コンパイルが必要です。コンパイルを実行するには、Jest プリプロセッサを使用する方法と webpack を直接使用する方法が推奨されます。 + +`jest-vue` プリプロセッサは基本的な SFC 機能をサポートしていますが、現在 `vue-loader` でのみサポートされているスタイルブロックやカスタムブロックは扱いません。これらの機能やその他の Webpack 固有の設定に依存する場合は、webpack + `vue-loader` ベースの設定を使用する必要があります。 + +さまざまな設定については、次のガイドをお読みください: +- [Jest による単一ファイルコンポーネントのテスト](./testing-SFCs-with-jest.md) +- [Mocha + webpack による単一ファイルコンポーネントのテスト](./testing-SFCs-with-mocha-webpack.md) + +## リソース + +- [テストランナの性能比較](https://github.com/eddyerburgh/vue-unit-test-perf-comparison) +- [Jest のプロジェクト例](https://github.com/vuejs/vue-test-utils-jest-example) +- [Mocha のプロジェクト例](https://github.com/vuejs/vue-test-utils-mocha-webpack-example) +- [tape のプロジェクト例](https://github.com/eddyerburgh/vue-test-utils-tape-example) +- [AVA のプロジェクト例](https://github.com/eddyerburgh/vue-test-utils-ava-example) diff --git a/docs/ja/guides/common-tips.md b/docs/ja/guides/common-tips.md new file mode 100644 index 000000000..aab57634f --- /dev/null +++ b/docs/ja/guides/common-tips.md @@ -0,0 +1,136 @@ +# 一般的なヒント + +## 何をテストするかを知る + +UI コンポーネントでは、コンポーネントの内部実装の詳細に集中しすぎて脆弱なテストが発生する可能性があるため、完全なラインベースのカバレッジを目指すことはお勧めしません。 + +代わりに、コンポーネントのパブリックインターフェイスを検証するテストを作成し、内部をブラックボックスとして扱うことをお勧めします。単一のテストケースでは、コンポーネントに提供された入力(ユーザーのやり取りやプロパティの変更)によって、期待される出力(結果の描画またはカスタムイベントの出力)が行われることが示されます。 + +たとえば、ボタンがクリックされるたびに表示カウンタを 1 ずつインクリメントする `Counter` コンポーネントの場合、そのテストケースはクリックをシミュレートし、描画された出力が 1 つ増加したのか検証します。カウンタは値をインクリメントし、入力と出力のみを扱います。 + +このアプローチの利点は、コンポーネントのパブリックインターフェイスが同じままである限り、コンポーネントの内部実装が時間の経過とともにどのように変化してもテストは合格になります。 + +このトピックは、[Matt O'Connell による偉大なプレゼンテーション](http://slides.com/mattoconnell/deck#/)で詳細に説明されています。 + +## Shallow 描画 + +単体テストでは、通常、単体テストとしてテスト対象のコンポーネントに焦点を当て、子コンポーネントの動作を間接的に検証することを避けたいと考えています。 + +さらに、多くの子コンポーネントを含むコンポーネントの場合、描画されたツリー全体が非常に大きくなる可能性があります。すべての子コンポーネントを繰り返し描画すると、テストが遅くなる可能性があります。 + +`vue-test-utils` を使うと、`shallow` メソッドを使って子コンポーネントを(スタブによって)描画せずにコンポーネントをマウントすることができます: + +```js +import { shallow } from 'vue-test-utils' + +const wrapper = shallow(Component) // Component インスタンスを含む Wrapper を返します。 +wrapper.vm // マウントされた Vue インスタンス +``` + +## イベントの発行を検証する + +マウントされた各ラッパは、基になる Vue インスタンスによって発行されたすべてのイベントを自動的に記録します。`wrapper.emitted()` を使って、記録されたイベントを取り出すことができます: + +``` js +wrapper.vm.$emit('foo') +wrapper.vm.$emit('foo', 123) + +/* +wrapper.emitted()は次のオブジェクトを返します: +{ + foo: [[], [123]] +} +*/ +``` + +次に、これらのデータに基づいて検証することもできます。 + +``` js +import { expect } from 'chai' + +// イベントが発行されたか検証する +expect(wrapper.emitted().foo).toBeTruthy() + +// イベント数を検証する +expect(wrapper.emitted().foo.length).toBe(2) + +// イベントのペイロードを検証する +expect(wrapper.emitted().foo[1]).toEqual([123]) +``` + +また、[wrapper.emittedByOrder()](../api/wrapper/emittedByOrder.md) を呼び出すことで、発行順序のイベントの配列を取得することもできます。 + +## コンポーネントの状態を操作する + +ラッパの `setData` メソッドまたは `setProps` メソッドを使って、コンポーネントの状態を直接操作することができます。: + +```js +wrapper.setData({ count: 10 }) + +wrapper.setProps({ foo: 'bar' }) +``` + +## プロパティをモックする + +Vue に組み込まれた `propsData` オプションを使用してコンポーネントにプロパティを渡すことができます: + +```js +import { mount } from 'vue-test-utils' + +mount(Component, { + propsData: { + aProp: 'some value' + } +}) +``` + +`wrapper.setProps({})` メソッドを使用して、すでにマウントされているコンポーネントのプロパティを更新することもできます。 + +*オプションの完全なリストについては、ドキュメントの[マウントオプションのセクション](../api/options.md)を参照してください。* + +## グローバルプラグインとミックスインの適用 + +コンポーネントの中には、グローバルプラグインやミックスインによって注入された機能 (例: `vuex`、`vue-router` など)に依存するものもあります。 + +特定のアプリケーションでコンポーネントのテストを作成している場合は、同じグローバルプラグインとミックスインをテストのエントリに設定できます。しかし、異なるアプリケーション間で共有される可能性のあるジェネリックコンポーネントスイートをテストする場合など、グローバルな `Vue` コンストラクタを汚染することなく、より孤立した設定でコンポーネントをテストする方が良い場合もあります。[createLocalVue](../api/createLocalVue.md) メソッドを使用すると、次のことができます: + +``` js +import createLocalVue from 'vue-test-utils' + +// 拡張された Vue コンストラクタを作成する +const localVue = createLocalVue() + +// プラグインをインストールする +localVue.use(MyPlugin) + +// localVue をマウントオプションに渡す +mount(Component, { + localVue +}) +``` + +## モックの注入 + +単純なモックを注入するための別の戦略として `mocks` オプションで行うことができます: + +```js +import { mount } from 'vue-test-utils' + +const $route = { + path: '/', + hash: '', + params: { id: '123' }, + query: { q: 'hello' } +} + +mount(Component, { + mocks: { + $route // コンポーネントをマウントする前に、モックした $route オブジェクトを Vue インスタンスに追加します。 + } +}) +``` + +## ルーティングの扱い + +定義によるルーティングは、アプリケーションの全体的な構造と関連し、複数のコンポーネントが関係するため、統合テストまたはエンドツーエンドテストによってよくテストされます。 +`vue-router` 機能に依存する個々のコンポーネントについては、上記の手法を使ってモックすることができます。 diff --git a/docs/ja/guides/getting-started.md b/docs/ja/guides/getting-started.md new file mode 100644 index 000000000..ef679d2d5 --- /dev/null +++ b/docs/ja/guides/getting-started.md @@ -0,0 +1,116 @@ +# はじめる + +## セットアップ + +`vue-test-utils` の使い方を体験したい場合は、基本設定としてデモリポジトリをクローンし、依存関係をインストールしてください。 + +``` bash +git clone https://github.com/vuejs/vue-test-utils-getting-started +cd vue-test-utils-getting-started +npm install +``` + +プロジェクトには単純なコンポーネント、`counter.js` が含まれています。 + +```js +// counter.js + +export default { + template: ` +
+ {{ count }} + +
+ `, + + data () { + return { + count: 0 + } + }, + + methods: { + increment () { + this.count++ + } + } +} +``` + +### マウンティングコンポーネント + +`vue-test-utils` は Vue コンポーネントを隔離してマウントし、必要な入力(プロパティ、注入、そしてユーザイベント)をモックし、そして出力(描画結果、カスタムイベントの発行)を検証することでテストします。 + +マウントされたコンポーネントは [Wrapper](./api/wrapper.md) の内部に返されます。これは、基の Vue コンポーネントインスタンスを操作、トラバース、クエリ処理するための多くの便利なメソッドを公開しています。 + +`mount` メソッドを使ってラッパを作成することができます。`test.js` というファイルを作りましょう: + +```js +// test.js + +// test utils から mount() メソッドをインポート +// テストするコンポーネント +import { mount } from 'vue-test-utils' +import Counter from './counter' + +//コンポーネントがマウントされ、ラッパが作成されます。 +const wrapper = mount(Counter) + +// wrapper.vmを 介して実際の Vue インスタンスにアクセスできます +const vm = wrapper.vm + +// ラッパをより深く調べるためにコンソールに記録してみましょう。 +// vue-test-utils でのあなたの冒険はここから始まります。 +console.log(wrapper) +``` + +### コンポーネントの描画された HTML 出力をテストする + +ラッパが完成したので、コンポーネントの描画された HTML 出力が、期待されるものと一致することを確認します。 + +```js +import { mount } from 'vue-test-utils' +import Counter from './counter' + +describe('Counter', () => { + // コンポーネントがマウントされ、ラッパが作成されます。 + const wrapper = mount(Counter) + + it('renders the correct markup', () => { + expect(wrapper.html()).toContain('0') + }) + + // 要素の存在を確認することも簡単です + it('has a button', () => { + expect(wrapper.contains('button')).toBe(true) + }) +}) +``` + +次に、`npm test` でテストを実行します。テストが合格になるはずです。 + +### ユーザのインタラクションをシミュレーションする + +ユーザがボタンをクリックすると、カウンタがカウントをインクリメントする必要があります。この振る舞いをシミュレートするには、まず**button 要素のラッパ**を返す `wrapper.find()` を使ってボタンを見つける必要があります。ボタンのラッパで `.trigger()` を呼び出すことでクリックをシミュレートできます: + +```js +it('button click should increment the count', () => { + expect(wrapper.vm.count).toBe(0) + const button = wrapper.find('button') + button.trigger('click') + expect(wrapper.vm.count).toBe(1) +}) +``` + +### `nextTick` はどうですか? + +Vue は保留した DOM 更新をまとめて処理し、非同期に適用して、複数のデータのミューテーションに起因する不要な再描画を防ぎます。実際には、Vue が何らかの状態変更をトリガした後に Vue が実際の DOM 更新を実行するまで待つために、`Vue.nextTick` を使用しなければならないからです。 + +使い方を簡単にするため、 `vue-test-utils` はすべての更新を同期的に適用するので、テストで DOM を更新するために `Vue.nextTick` を使う必要はありません。 + +*注意: 非同期コールバックやプロミスの解決などの操作のために、イベントループを明示的に進める必要がある場合は、まだ `nextTick` が必要です。* + +## 次は何をするのか + +- [テストランナを選ぶ](./choosing-a-test-runner.md)で `vue-test-utils` をプロジェクトに組み込む +- [テストを書くときの一般的なヒント](./common-tips.md)についてもっと知る diff --git a/docs/ja/guides/testing-SFCs-with-jest.md b/docs/ja/guides/testing-SFCs-with-jest.md new file mode 100644 index 000000000..bf544a2bd --- /dev/null +++ b/docs/ja/guides/testing-SFCs-with-jest.md @@ -0,0 +1,178 @@ +# Jest を使用した単一ファイルコンポーネントのテスト + +> このセットアップのサンプルプロジェクトは、 [GitHub](https://github.com/vuejs/vue-test-utils-jest-example) にあります。 + +Jest は Facebook が開発したテストランナであり、ユニットテストソリューションの提供を目指しています。 Jest の詳細については、[公式ドキュメント](https://facebook.github.io/jest/)を参照してください。 + +## Jest のセットアップ + +既に、webpack、vue-loader、および Babel が正しく設定されている設定から始めると仮定します。例: `vue-cli` によって雛形生成された `webpack-simple` テンプレートです。 + +まず Jest と `vue-test-utils` をインストールします: + +```bash +$ npm install --save-dev jest vue-test-utils +``` + +次に、`package.json` にスクリプトを定義する必要があります。 + +```json +// package.json +{ + "scripts": { + "test": "jest" + } +} +``` + +## Jest における単一ファイルコンポーネントの処理 + +Jest に `*.vue` ファイルの処理方法を教えるために、`jest-vue` プリプロセッサをインストールして設定する必要があります。: + +``` bash +npm install --save-dev jest-vue +``` + +次に、`package.json` に `jest` ブロックを作成します: + +``` json +{ + // ... + "jest": { + "moduleFileExtensions": [ + "js", + "json", + // *.vue ファイルを処理するように Jest に指示する + "vue" + ], + "transform": { + // jest-vue で *.vue ファイルを処理する + ".*\\.(vue)$": "/node_modules/jest-vue" + }, + "mapCoverage": true + } +} +``` + +> **注意:** `jest-vue` は現在、カスタムブロックのサポートやスタイルのロードなど、`vue-loader` のすべての機能をサポートしていません。さらに、コード分割などのWebpack固有の機能はサポートされていません。それらを使用するには、[Mocha + webpackによる単一ファイルコンポーネントのテスト](./testing-SFCs-with-mocha-webpack.md)のガイドをお読みください。 + +## Webpack エイリアスの処理 + +webpack 設定で、`@` を `/src` のエイリアスにしたいといった場合、`moduleNameMapper`オプションを使って Jest の設定を追加する必要があります + +``` json +{ + // ... + "jest": { + // ... + // ソースコードにある @ を src へと割当てる + "moduleNameMapper": { + "^@/(.*)$": "/src/$1" + } + } +} +``` + +## Jest のための Babel の設定 + +Node の最新バージョンではすでにほとんどの ES2015 機能がサポートされていますが、テストでは ES Modules 構文と stage-x 機能を使用することができます。そのために、`babel-jest` をインストールする必要があります。 + +``` bash +npm install --save-dev babel-jest +``` + +次に、Jest に `babel-jest` で JavaScript テストファイルを処理するよう、`package.json` の `jest.transform` の中にエントリを追加する必要があります: + +``` json +{ + // ... + "jest": { + // ... + "transform": { + // ... + // babel-jest で js を処理する + "^.+\\.js$": "/node_modules/babel-jest" + }, + // ... + } +} +``` + +> デフォルトでは、`babel-jest` はインストールしさえすれば自動的に設定します。しかし、`*.vue` ファイルのための変換を明示的に追加したため、`babel-jest` も明示的に設定する必要があります。 + +webpack で `babel-preset-env` を使用するとした場合、webpack は ES Modules 処理方法を既に知っているので、デフォルトの Babel 設定は ES Modules のトランスパイルを無効にします。ただし、Jest テストは Node で直接実行されるため、テスト用に有効にする必要があります。 + +また、`babel-preset-env` に、使用している Node のバージョンを指定するように指示することもできます。これにより不要な機能をスキップし、テストがより速く起動します。 + +これらのオプションをテストのためだけに適用するには、 `env.test` の下に別の設定をします(これは `babel-jest` によって自動的に選択されます)。 + +例 `.babelrc`: + +``` json +{ + "presets": [ + ["env", { "modules": false }] + ], + "env": { + "test": { + "presets": [ + ["env", { "targets": { "node": "current" }}] + ] + } + } +} +``` + +### スナップショットテスト + +[`vue-server-renderer`](https://github.com/vuejs/vue/tree/dev/packages/vue-server-renderer) を使ってコンポーネントを文字列に描画して保存することができます。[Jest のスナップショットテスト](https://facebook.github.io/jest/docs/en/snapshot-testing.html) のスナップショットとして表示されます。 + +`vue-server-renderer` の描画結果には、いくつかの SSR (Server-Side Rendering: サーバサイドレンダリング) 固有の属性が含まれており、空白を無視するため、diff をスキャンするのが難しくなります。カスタムシリアライザを使用して、保存されたスナップショットを改善することができます。 + +``` bash +npm install --save-dev jest-serializer-vue +``` + +次に `package.json` で設定します: + +``` json +{ + // ... + "jest": { + // ... + // スナップショットのシリアライザ + "snapshotSerializers": [ + "/node_modules/jest-serializer-vue" + ] + } +} +``` + +### テストファイルの配置 + +デフォルトでは、Jest はプロジェクト全体で `.spec.js` または `.test.js` 拡張子を持つすべてのファイルを再帰的に取得します。これがあなたのニーズに合わない場合は、`package.json` ファイルの config セクションで[testRegex を変更する](https://facebook.github.io/jest/docs/en/configuration.html#testregex-string)ことが可能です。 + +Jestは、テスト対象のコードのすぐ隣に`__tests__`ディレクトリを作成することを推奨していますが、適切にテストを構造化することは自由です。 Jestがスナップショットテストを実行するテストファイルの隣に`__snapshots__`ディレクトリを作成することに注意してください。 + +### Spec の例 + +あなたが Jasmine をよく知っているなら、Jest の [assertion API](https://facebook.github.io/jest/docs/en/expect.html#content)は自宅のように感じるはずです。 + +```js +import { mount } from 'vue-test-utils' +import Component from './component' + +describe('Component', () => { + test('is a Vue instance', () => { + const wrapper = mount(Component) + expect(wrapper.isVueInstance()).toBeTruthy() + }) +}) +``` + +### リソース + +- [このセットアップのプロジェクト例](https://github.com/vuejs/vue-test-utils-jest-example) +- [Vue Conf 2017のスライド](https://github.com/codebryo/vue-testing-with-jest-conf17) +- [Jest](https://facebook.github.io/jest/) +- [Babel preset env](https://github.com/babel/babel-preset-env) diff --git a/docs/ja/guides/testing-SFCs-with-mocha-webpack.md b/docs/ja/guides/testing-SFCs-with-mocha-webpack.md new file mode 100644 index 000000000..1fa5f4877 --- /dev/null +++ b/docs/ja/guides/testing-SFCs-with-mocha-webpack.md @@ -0,0 +1,180 @@ +# Mocha + webpack による単一ファイルコンポーネントのテスト + +> このセットアップのサンプルプロジェクトは、 [GitHub](https://github.com/vuejs/vue-test-utils-mocha-webpack-example) にあります。 + +単一ファイルコンポーネントをテストするためのもう一つの戦略は、webpack を使用してすべてのテストをコンパイルし、それをテストランナで実行することです。このアプローチの利点は、すべての webpack と `vue-loader` 機能を完全にサポートしていることです。ソースコードに妥協する必要はありません。 + +技術的に好きなテストランナを使用して結びつけることができますが、この特定の作業に非常に合理的な経験を提供するために [`mocha-webpack`](https://github.com/zinserjan/mocha-webpack) を見つけました。 + +## `mocha-webpack` の設定 + +既に、webpack、vue-loader、および Babel が正しく設定されている設定から始めると仮定します。例: `vue-cli` によって雛形生成された `webpack-simple` テンプレートです。 + +最初に行うことは、テストの依存関係をインストールすることです: + +``` bash +npm install --save-dev vue-test-utils mocha mocha-webpack +``` + +次に、`package.json` にスクリプトを定義する必要があります。 + +```json +// package.json +{ + "scripts": { + "test": "mocha-webpack --webpack-config webpack.config.js --require test/setup.js test/**/*.spec.js" + } +} +``` + +ここで注意すべき点がいくつかあります: + +- `--webpack-config` フラグはテストに使う webpack 設定ファイルを指定します。ほとんどの場合、これは実際のプロジェクトで使用する設定と同じですが、小さな調整が 1 つあります。これについては後で話します。 + +- `--require` フラグは、テストの前に `test/setup.js` ファイルが実行されることを保証します。このテストでは、テストを実行するためのグローバル環境を設定できます。 + +- 最後の引数は、テストバンドルに含まれるテストファイルのグロブです。 + +### 別の webpack の設定 + +#### NPM 依存関係の外部化 + +私たちのテストでは、いくつかの NPM 依存関係をインポートする可能性があります。これらのモジュールの中には、ブラウザの使用を念頭に置いて記述されているものもあれば、webpack によって適切にバンドルされていないものもあります。依存関係を外部化することにより、テストの起動速度が大幅に向上するというもう一つの考慮すべき点としてあります。`webpack-node-externals` を使って NPM の依存関係をすべて外部化することができます: + +```js +// webpack.config.js +const nodeExternals = require('webpack-node-externals') + +module.exports = { + // ... + externals: [nodeExternals()] +} +``` + +#### ソースマップ + +ソースマップは、`mocha-webpack` によってピックアップされるようにインライン化する必要があります。推奨設定は次のとおりです: + +``` js +module.exports = { + // ... + devtool: 'inline-cheap-module-source-map' +} +``` + +IDE 経由でデバッグする場合は、以下を追加することをお勧めします: + +``` js +module.exports = { + // ... + output: { + // ... + // ソースマップで絶対パスを使用する(IDE 経由のデバッグで重要) + devtoolModuleFilenameTemplate: '[absolute-resource-path]', + devtoolFallbackModuleFilenameTemplate: '[absolute-resource-path]?[hash]' + } +} +``` + +### ブラウザ環境の設定 + +`vue-test-utils` はブラウザ環境を必要とします。`jsdom-global`を 使って Node.js でシミュレーションすることができます: + +```bash +npm install --save-dev jsdom jsdom-global +``` + +次に、`test/setup.js` の中で: + +``` js +require('jsdom-global')() +``` + +これにより、node にブラウザ環境が追加され、 `vue-test-utils` が正しく動作するようになります。 + +### 検証ライブラリのピッキング + +[Chai](http://chaijs.com/) は Mocha と並んで一般的に使用される一般的な検証ライブラリです。また、スパイとスタブを作成するための [Sinon](http://sinonjs.org/) をチェックしてみてください。 + +あるいは、Jest の一部である `expect` を使うことができ、Jest のドキュメントにある[まったく同じAPI](http://facebook.github.io/jest/docs/en/expect.html#content)を公開しています。 + +ここで `expect` を使用してグローバルに利用できるようにして、すべてのテストでインポートする必要はありません。 + +``` bash +npm install --save-dev expect +``` + +次に、`test/setup.js` の中で: + +``` js +require('jsdom-global')() + +global.expect = require('expect') +``` + +### テストのための Babel の最適化 + +JavaScript を処理するには `babel-loader` を使用しています。`.babelrc` ファイルを使ってあなたのアプリでそれを使用しているならば、Babel を設定しておくべきです。`babel-loader` は自動的に同じ設定ファイルを使います。 + +注意すべき点の 1 つは、Node バージョン 6 以降を使用している場合、ES2015 の大部分の機能を既にサポートしているため、使用している Node のバージョンではサポートされていない機能のみをトランスパイルする Babel の [env オプション](https://babeljs.io/docs/usage/babelrc/#env-option)を設定できます。(例えば`stage-2`や flow 構文のサポートなど) + +### テストを追加する + +`Counter.vue` という名前の `src` ファイルを作成します。 + +``` html + + + +``` + +次のコードを使って `test/Counter.spec.js` という名前のテストファイルを作成します。 + +```js +import { shallow } from 'vue-test-utils' +import Counter from '../src/Counter.vue' + +describe('Counter.vue', () => { + it('increments count when button is clicked', () => { + const wrapper = shallow(Counter) + wrapper.find('button').trigger('click') + expect(wrapper.find('div').text()).toMatch('1') + }) +}) +``` + +これでテストを実行できます: + +``` +npm run unit +``` + +やったー!テストを実行している! + +### リソース + +- [この設定のプロジェクトの例](https://github.com/vuejs/vue-test-utils-mocha-webpack-example) +- [Mocha](https://mochajs.org/) +- [mocha-webpack](http://zinserjan.github.io/mocha-webpack/) +- [Chai](http://chaijs.com/) +- [Sinon](http://sinonjs.org/) +- [jest/expect](http://facebook.github.io/jest/docs/en/expect.html#content) diff --git a/docs/ja/guides/using-with-vue-router.md b/docs/ja/guides/using-with-vue-router.md new file mode 100644 index 000000000..60f3b3e2a --- /dev/null +++ b/docs/ja/guides/using-with-vue-router.md @@ -0,0 +1,71 @@ +# Vue Router と一緒に使用する + +## テストへ Vue Router のインストール + +テストで Vue のコンストラクタベースの Vue Router をインストールしないでください。Vue Router をインストールすると Vue のプロトタイプの読み取り専用プロパティとして `$route` と `$router` が追加されます。 + +これを回避するために、localeVue を作成し、その上に Vue Router をインストールすることができます。 + +```js +import VueRouter from 'vue-router' +const localVue = createLocalVue() + +localVue.use(VueRouter) + +shallow(Component, { + localVue +}) +``` + +## `router-link` または `router-view` を使用するコンポーネントテスト + +Vue Router をインストールする時、`router-link` と `router-view` コンポーネントが登録されます。これは、それらをアプリケーションにインポートする必要がなく、アプリケーションのどこでも使用することができます。 + +テストを実行する際には、マウントしているコンポーネントにこれら Vue Router のコンポーネントを使用できるようにする必要があります。これらを行うには 2 つの方法があります。 + +### スタブを使用する + +```js +shallow(Component, { + stubs: ['router-link', 'router-view'] +}) +``` + +### localVue による Vue Router のインストール + +```js +import VueRouter from 'vue-router' +const localVue = createLocalVue() + +localVue.use(VueRouter) + +shallow(Component, { + localVue +}) +``` + +## `$route` と `$router` のモック + +時々、コンポーネントが `$route` と `$router` オブジェクトから引数によって何かをするテストをしたいときがあります。これをするためには、Vue インスタンスにカスタムモックを渡すことができます。 + +```js +const $route = { + path: '/some/path' +} + +const wrapper = shallow(Component, { + mocks: { + $route + } +}) + +wrapper.vm.$router // /some/path +``` + +## よくある落とし穴 + +Vue Router をインストールすると Vue のプロトタイプに読み取り専用プロパティとして `$route` と `$router` が追加されます。 + +これは、`$route` または `$router` をモックを試みるテストが将来失敗することを意味します。 + +これを回避するために、テストを実行するときに、Vue Router をインストールしないでください。 diff --git a/docs/ja/guides/using-with-vuex.md b/docs/ja/guides/using-with-vuex.md new file mode 100644 index 000000000..126eff821 --- /dev/null +++ b/docs/ja/guides/using-with-vuex.md @@ -0,0 +1,266 @@ +# Vuex と一緒に使用する + +このガイドでは、`vue-test-utils` でコンポーネントで Vuex をテストする方法について、見ていきます。 + +## アクションのモック + +それではいくつかのコードを見ていきましょう。 + +これはテストしたいコンポーネントです。これは Vuex のアクションを呼び出します。 + +``` html + + + +``` + +このテストの目的のために、アクションが何をしているのか、またはストアがどのように見えるかは気にしません。これらのアクションが必要なときに発行されていること、そして期待された値によって発行されていることを知ることが必要です。 + +これをテストするためには、私たちのコンポーネントを shallow するときに Vue にモックストアを渡す必要があります。 + +ストアを Vue コンストラクタベースに渡す代わりに、[localVue](../api/options.md#localvue) に渡すことができます。localeVue はグローバルな Vue コンストラクタに影響を与えずに、変更を加えることができるスコープ付き Vue コンストラクタです。 + +これがどのように見えるか見ていきましょう: + +``` js +import { shallow, createLocalVue } from 'vue-test-utils' +import Vuex from 'vuex' +import Actions from '../../../src/components/Actions' + +const localVue = createLocalVue() + +localVue.use(Vuex) + +describe('Actions.vue', () => { + let actions + let store + + beforeEach(() => { + actions = { + actionClick: jest.fn(), + actionInput: jest.fn() + } + store = new Vuex.Store({ + state: {}, + actions + }) + }) + + it('calls store action actionInput when input value is input and an input event is fired', () => { + const wrapper = shallow(Actions, { store, localVue }) + const input = wrapper.find('input') + input.element.value = 'input' + input.trigger('input') + expect(actions.actionInput).toHaveBeenCalled() + }) + + it('does not call store action actionInput when input value is not input and an input event is fired', () => { + const wrapper = shallow(Actions, { store, localVue }) + const input = wrapper.find('input') + input.element.value = 'not input' + input.trigger('input') + expect(actions.actionInput).not.toHaveBeenCalled() + }) + + it('calls store action actionClick when button is clicked', () => { + const wrapper = shallow(Actions, { store, localVue }) + wrapper.find('button').trigger('click') + expect(actions.actionClick).toHaveBeenCalled() + }) +}) +``` + +ここでは何が起こっているでしょうか?まず、Vue に `Vue.use` メソッドを使用して Vuex を使用するように指示しています。これは、単なる `Vue.use` のラッパです。 + +次に、新しい `Vuex.store` をモックした値で呼び出すことによってモックのストアを作成します。それをアクションに渡すだけです。それが気にしなければならないことの全てだからです。 + +アクションは、[Jest のモック関数](https://facebook.github.io/jest/docs/en/mock-functions.html)です。これらモック関数は、アクションが呼び出された、または呼び出されていない、かどうかを検証するメソッドを提供します。 + +アクションのスタブが期待どおりに呼び出されたことを検討することができます。 + +今、ストアを定義する方法が、あなたには少し異質に見えるかもしれません。 + +各テストより前にストアをクリーンに保証するために、`beforeEach` を使用しています。`beforeEach` は各テストより前に呼び出される Mocha のフックです。このテストでは、ストア変数に値を再度割り当てています。これをしていない場合は、モック関数は自動的にリセットされる必要があります。また、テストにおいて状態を変更することもできますが、この方法は、後のテストで影響を与えることはないです。 + +このテストで最も重要なことは、**モック Vuex ストアを作成し、それを vue-test-utils に渡す** ことです。 + +素晴らしい!今、アクションをモック化できるので、ゲッタのモックについて見ていきましょう。 + +## ゲッタのモック + + +``` html + + + +``` + +これは、かなり単純なコンポーネントです。ゲッタによる `clicks` の結果と `inputValue` を描画します。また、これらゲッタが返す値については実際に気にしません。それらの結果が正しく描画されているかだけです。 + +テストを見てみましょう: + +``` js +import { shallow, createLocalVue } from 'vue-test-utils' +import Vuex from 'vuex' +import Actions from '../../../src/components/Getters' + +const localVue = createLocalVue() + +localVue.use(Vuex) + +describe('Getters.vue', () => { + let getters + let store + + beforeEach(() => { + getters = { + clicks: () => 2, + inputValue: () => 'input' + } + + store = new Vuex.Store({ + getters + }) + }) + + it('Renders state.inputValue in first p tag', () => { + const wrapper = shallow(Actions, { store, localVue }) + const p = wrapper.find('p') + expect(p.text()).toBe(getters.inputValue()) + }) + + it('Renders state.clicks in second p tag', () => { + const wrapper = shallow(Actions, { store, localVue }) + const p = wrapper.findAll('p').at(1) + expect(p.text()).toBe(getters.clicks().toString()) + }) +}) +``` + +このテストはアクションのテストに似ています。各テストの前にモックストアを作成し、`shallow` を呼び出すときにオプションを渡し、そしてモックゲッタから返された値を描画されているのを検証します。 + +これは素晴らしいですが、もしゲッタが状態の正しい部分を返しているのを確認したい場合はどうしますか? + +## モジュールによるモック + +[モジュール](https://vuex.vuejs.org/en/modules.html)はストアを管理しやすい塊に分けるために便利です。それらはゲッタもエクスポートします。テストではこれらを使用することができます。 + +コンポーネントを見てみましょう: + +``` html + + + +``` + +1 つのアクションと 1 つのゲッタを含む単純なコンポーネントです。 + +そしてテストは以下のようになります: + +``` js +import { shallow, createLocalVue } from 'vue-test-utils' +import Vuex from 'vuex' +import Modules from '../../../src/components/Modules' +import module from '../../../src/store/module' + +const localVue = createLocalVue() + +localVue.use(Vuex) + +describe('Modules.vue', () => { + let actions + let state + let store + + beforeEach(() => { + state = { + module: { + clicks: 2 + } + } + + actions = { + moduleActionClick: jest.fn() + } + + store = new Vuex.Store({ + state, + actions, + getters: module.getters + }) + }) + + it('calls store action moduleActionClick when button is clicked', () => { + const wrapper = shallow(Modules, { store, localVue }) + const button = wrapper.find('button') + button.trigger('click') + expect(actions.moduleActionClick).toHaveBeenCalled() + }) + + it('Renders state.inputValue in first p tag', () => { + const wrapper = shallow(Modules, { store, localVue }) + const p = wrapper.find('p') + expect(p.text()).toBe(state.module.clicks.toString()) + }) +}) +``` + +### リソース + +- [このガイド向けの例](https://github.com/eddyerburgh/vue-test-utils-vuex-example) +- [localVue](../api/options.md#localvue) +- [createLocalVue](../api/createLocalVue.md) From e671676643fa7e80909d20c10f50a36edfe52393 Mon Sep 17 00:00:00 2001 From: Lai Jimmy Date: Fri, 20 Oct 2017 15:19:52 +0200 Subject: [PATCH 0029/1136] fix: add support for vue.extended components + add test (#112) * fix: add support for vue.extended components + add test * Update test --- package.json | 2 +- src/lib/create-instance.js | 2 +- test/unit/specs/mount.spec.js | 12 ++++++++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 4a06d6df5..31eda46ba 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "sinon": "^2.3.2", "sinon-chai": "^2.10.0", "typescript": "^2.4.1", - "vue": "^2.4.4", + "vue": "^2.5.2", "vue-loader": "^13.0.5", "vue-router": "^2.7.0", "vue-template-compiler": "^2.4.4", diff --git a/src/lib/create-instance.js b/src/lib/create-instance.js index 67328ecf8..7c3fcd94f 100644 --- a/src/lib/create-instance.js +++ b/src/lib/create-instance.js @@ -42,7 +42,7 @@ export default function createConstructor (component: Component, options: Option compileTemplate(component) } - const Constructor = vue.extend(component) + const Constructor = vue.extend(component.extend ? component.options : component) if (options.mocks) { addMocks(options.mocks, Constructor) diff --git a/test/unit/specs/mount.spec.js b/test/unit/specs/mount.spec.js index 959edc544..0082d2d7b 100644 --- a/test/unit/specs/mount.spec.js +++ b/test/unit/specs/mount.spec.js @@ -1,3 +1,4 @@ +import Vue from 'vue' import { compileToFunctions } from 'vue-template-compiler' import mount from '~src/mount' import ComponentWithProps from '~resources/components/component-with-props.vue' @@ -41,6 +42,17 @@ describe('mount', () => { } }) + it('returns new VueWrapper with mounted Vue instance initialized with Vue.extend with props, if passed as propsData', () => { + const prop1 = { test: 'TEST' } + const wrapper = mount(Vue.extend(ComponentWithProps), { propsData: { prop1 }}) + expect(wrapper.vm).to.be.an('object') + if (wrapper.vm.$props) { + expect(wrapper.vm.$props.prop1).to.equal(prop1) + } else { + expect(wrapper.vm.$options.propsData.prop1).to.equal(prop1) + } + }) + it('does not use cached component', () => { ComponentWithMixin.methods.someMethod = sinon.stub() mount(ComponentWithMixin) From a5c0e049f1fca0bd19c6cd9c5411901421ec4fba Mon Sep 17 00:00:00 2001 From: Blake Newman Date: Fri, 20 Oct 2017 17:44:01 +0100 Subject: [PATCH 0030/1136] fix: Fix clone issue with vue.extend components (#115) - Moving the previous extend fix logic out to `mount` and `shallow` hooks fixes cloning of component ```js import { mount } from 'vue-test-utils' import ContainerTest from 'container/Test.vue' const options = { mocks: { $route: 'test' } } const wrapper = mount(ContainerTest, options) console.log(wrapper.vm.$route) // test const wrapper2 = mount(ContainerTest) console.log(wrapper2.vm.$route) // test ``` ```js import { mount } from 'vue-test-utils' import ContainerTest from 'container/Test.vue' const options = { mocks: { $route: 'test' } } const wrapper = mount(ContainerTest, options) console.log(wrapper.vm.$route) // test const wrapper2 = mount(ContainerTest) console.log(wrapper2.vm.$route) // undefined ``` --- src/lib/create-instance.js | 2 +- src/mount.js | 2 +- src/shallow.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/create-instance.js b/src/lib/create-instance.js index 7c3fcd94f..67328ecf8 100644 --- a/src/lib/create-instance.js +++ b/src/lib/create-instance.js @@ -42,7 +42,7 @@ export default function createConstructor (component: Component, options: Option compileTemplate(component) } - const Constructor = vue.extend(component.extend ? component.options : component) + const Constructor = vue.extend(component) if (options.mocks) { addMocks(options.mocks, Constructor) diff --git a/src/mount.js b/src/mount.js index 903a5de41..c33163b80 100644 --- a/src/mount.js +++ b/src/mount.js @@ -11,7 +11,7 @@ import './lib/matches-polyfill' Vue.config.productionTip = false export default function mount (component: Component, options: Options = {}): VueWrapper { - const componentToMount = options.clone === false ? component : cloneDeep(component) + const componentToMount = options.clone === false ? component : cloneDeep(component.extend ? component.options : component) // Remove cached constructor delete componentToMount._Ctor diff --git a/src/shallow.js b/src/shallow.js index 395c1e119..12a922552 100644 --- a/src/shallow.js +++ b/src/shallow.js @@ -12,7 +12,7 @@ import type VueWrapper from './wrappers/vue-wrapper' export default function shallow (component: Component, options: Options = {}): VueWrapper { const vue = options.localVue || Vue - const clonedComponent = cloneDeep(component) + const clonedComponent = cloneDeep(component.extend ? component.options : component) if (clonedComponent.components) { stubAllComponents(clonedComponent) From 000549e9246a727aa97ce0f58dace3392f787c10 Mon Sep 17 00:00:00 2001 From: Edd Yerburgh Date: Sun, 22 Oct 2017 21:24:20 +0100 Subject: [PATCH 0031/1136] build: use VueTemplateCompiler global --- build/build.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/build/build.js b/build/build.js index 8d232ed09..577f118f5 100644 --- a/build/build.js +++ b/build/build.js @@ -49,7 +49,11 @@ rollup({ name: 'globals', dest: resolve('dist/vue-test-utils.iife.js'), moduleName: 'vueTestUtils', - format: 'iife' + format: 'iife', + globals: { + 'vue': 'Vue', + 'vue-template-compiler': 'VueTemplateCompiler' + } }) }) .then(() => success('IIFE build successful')) @@ -71,7 +75,11 @@ rollup({ }).then((bundle) => { bundle.write({ dest: resolve('dist/vue-test-utils.amd.js'), - format: 'amd' + format: 'amd', + globals: { + 'vue': 'Vue', + 'vue-template-compiler': 'VueTemplateCompiler' + } }) }) .then(() => success('AMD build successful')) @@ -94,6 +102,10 @@ rollup({ bundle.write({ dest: resolve('dist/vue-test-utils.umd.js'), format: 'umd', + globals: { + 'vue': 'Vue', + 'vue-template-compiler': 'VueTemplateCompiler' + }, moduleName: 'vueTestUtils' }) }) From 24c0ae67570bf5a95f74274771e8e495615074ef Mon Sep 17 00:00:00 2001 From: Edd Yerburgh Date: Sun, 22 Oct 2017 21:27:11 +0100 Subject: [PATCH 0032/1136] docs: fix lint error --- docs/ja/guides/getting-started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ja/guides/getting-started.md b/docs/ja/guides/getting-started.md index ef679d2d5..094b59df1 100644 --- a/docs/ja/guides/getting-started.md +++ b/docs/ja/guides/getting-started.md @@ -53,7 +53,7 @@ export default { import { mount } from 'vue-test-utils' import Counter from './counter' -//コンポーネントがマウントされ、ラッパが作成されます。 +// コンポーネントがマウントされ、ラッパが作成されます。 const wrapper = mount(Counter) // wrapper.vmを 介して実際の Vue インスタンスにアクセスできます From 8d17a8014f8d58a1df3b9bed858ce3c172945d83 Mon Sep 17 00:00:00 2001 From: Edd Yerburgh Date: Sun, 22 Oct 2017 21:28:13 +0100 Subject: [PATCH 0033/1136] build: 1.0.0-beta.3 --- dist/vue-test-utils.amd.js | 56 ++++++++++++++++++++--------------- dist/vue-test-utils.iife.js | 58 ++++++++++++++++++++++--------------- dist/vue-test-utils.js | 36 ++++++++++++++--------- dist/vue-test-utils.umd.js | 58 ++++++++++++++++++++++--------------- 4 files changed, 124 insertions(+), 84 deletions(-) diff --git a/dist/vue-test-utils.amd.js b/dist/vue-test-utils.amd.js index b15adefaa..801ffdf1f 100644 --- a/dist/vue-test-utils.amd.js +++ b/dist/vue-test-utils.amd.js @@ -2,6 +2,24 @@ define(['vue', 'vue-template-compiler'], function (Vue, vueTemplateCompiler) { ' Vue = Vue && 'default' in Vue ? Vue['default'] : Vue; +// + +function throwError (msg) { + throw new Error(("[vue-test-utils]: " + msg)) +} + +function warn (msg) { + console.error(("[vue-test-utils]: " + msg)); +} + +if (typeof window === 'undefined') { + throwError( + 'window is undefined, vue-test-utils needs to be run in a browser environment.\n' + + 'You can run the tests in node using jsdom + jsdom-global.\n' + + 'See https://vue-test-utils.vuejs.org/en/guides/common-tips.html for more details.' + ); +} + /** * Removes all key-value entries from the list cache. * @@ -2537,16 +2555,6 @@ var cloneDeep_1 = cloneDeep; // -function throwError (msg) { - throw new Error(("[vue-test-utils]: " + msg)) -} - -function warn (msg) { - console.error(("[vue-test-utils]: " + msg)); -} - -// - var LIFECYCLE_HOOKS = [ 'beforeCreate', 'created', @@ -3134,11 +3142,18 @@ Wrapper.prototype.hasAttribute = function hasAttribute (attribute, value) { * Asserts wrapper has a class name */ Wrapper.prototype.hasClass = function hasClass (className) { - if (typeof className !== 'string') { + var targetClass = className; + + if (typeof targetClass !== 'string') { throwError('wrapper.hasClass() must be passed a string'); } - return !!(this.element && this.element.classList.contains(className)) + // if $style is available and has a matching target, use that instead. + if (this.vm && this.vm.$style && this.vm.$style[targetClass]) { + targetClass = this.vm.$style[targetClass]; + } + + return !!(this.element && this.element.classList.contains(targetClass)) }; /** @@ -3698,15 +3713,7 @@ Vue.config.productionTip = false; function mount (component, options) { if ( options === void 0 ) options = {}; - if (!window) { - throwError( - 'window is undefined, vue-test-utils needs to be run in a browser environment.\n' + - 'You can run the tests in node using jsdom + jsdom-global.\n' + - 'See https://vue-test-utils.vuejs.org/en/guides/common-tips.html for more details.' - ); - } - - var componentToMount = options.clone === false ? component : cloneDeep_1(component); + var componentToMount = options.clone === false ? component : cloneDeep_1(component.extend ? component.options : component); // Remove cached constructor delete componentToMount._Ctor; @@ -3727,7 +3734,7 @@ function shallow (component, options) { if ( options === void 0 ) options = {}; var vue = options.localVue || Vue; - var clonedComponent = cloneDeep_1(component); + var clonedComponent = cloneDeep_1(component.extend ? component.options : component); if (clonedComponent.components) { stubAllComponents(clonedComponent); @@ -3768,9 +3775,12 @@ function createLocalVue () { // compat for vue-router < 2.7.1 where it does not allow multiple installs var use = instance.use; instance.use = function (plugin) { + var rest = [], len = arguments.length - 1; + while ( len-- > 0 ) rest[ len ] = arguments[ len + 1 ]; + plugin.installed = false; plugin.install.installed = false; - use.call(instance, plugin); + use.call.apply(use, [ instance, plugin ].concat( rest )); }; return instance } diff --git a/dist/vue-test-utils.iife.js b/dist/vue-test-utils.iife.js index c219a0118..ec68c240d 100644 --- a/dist/vue-test-utils.iife.js +++ b/dist/vue-test-utils.iife.js @@ -3,6 +3,24 @@ var vueTestUtils = (function (Vue,vueTemplateCompiler) { Vue = Vue && 'default' in Vue ? Vue['default'] : Vue; +// + +function throwError (msg) { + throw new Error(("[vue-test-utils]: " + msg)) +} + +function warn (msg) { + console.error(("[vue-test-utils]: " + msg)); +} + +if (typeof window === 'undefined') { + throwError( + 'window is undefined, vue-test-utils needs to be run in a browser environment.\n' + + 'You can run the tests in node using jsdom + jsdom-global.\n' + + 'See https://vue-test-utils.vuejs.org/en/guides/common-tips.html for more details.' + ); +} + /** * Removes all key-value entries from the list cache. * @@ -2538,16 +2556,6 @@ var cloneDeep_1 = cloneDeep; // -function throwError (msg) { - throw new Error(("[vue-test-utils]: " + msg)) -} - -function warn (msg) { - console.error(("[vue-test-utils]: " + msg)); -} - -// - var LIFECYCLE_HOOKS = [ 'beforeCreate', 'created', @@ -3135,11 +3143,18 @@ Wrapper.prototype.hasAttribute = function hasAttribute (attribute, value) { * Asserts wrapper has a class name */ Wrapper.prototype.hasClass = function hasClass (className) { - if (typeof className !== 'string') { + var targetClass = className; + + if (typeof targetClass !== 'string') { throwError('wrapper.hasClass() must be passed a string'); } - return !!(this.element && this.element.classList.contains(className)) + // if $style is available and has a matching target, use that instead. + if (this.vm && this.vm.$style && this.vm.$style[targetClass]) { + targetClass = this.vm.$style[targetClass]; + } + + return !!(this.element && this.element.classList.contains(targetClass)) }; /** @@ -3699,15 +3714,7 @@ Vue.config.productionTip = false; function mount (component, options) { if ( options === void 0 ) options = {}; - if (!window) { - throwError( - 'window is undefined, vue-test-utils needs to be run in a browser environment.\n' + - 'You can run the tests in node using jsdom + jsdom-global.\n' + - 'See https://vue-test-utils.vuejs.org/en/guides/common-tips.html for more details.' - ); - } - - var componentToMount = options.clone === false ? component : cloneDeep_1(component); + var componentToMount = options.clone === false ? component : cloneDeep_1(component.extend ? component.options : component); // Remove cached constructor delete componentToMount._Ctor; @@ -3728,7 +3735,7 @@ function shallow (component, options) { if ( options === void 0 ) options = {}; var vue = options.localVue || Vue; - var clonedComponent = cloneDeep_1(component); + var clonedComponent = cloneDeep_1(component.extend ? component.options : component); if (clonedComponent.components) { stubAllComponents(clonedComponent); @@ -3769,9 +3776,12 @@ function createLocalVue () { // compat for vue-router < 2.7.1 where it does not allow multiple installs var use = instance.use; instance.use = function (plugin) { + var rest = [], len = arguments.length - 1; + while ( len-- > 0 ) rest[ len ] = arguments[ len + 1 ]; + plugin.installed = false; plugin.install.installed = false; - use.call(instance, plugin); + use.call.apply(use, [ instance, plugin ].concat( rest )); }; return instance } @@ -3911,4 +3921,4 @@ var index = { return index; -}(Vue,vueTemplateCompiler)); +}(Vue,VueTemplateCompiler)); diff --git a/dist/vue-test-utils.js b/dist/vue-test-utils.js index 71c438cd0..897e5d445 100644 --- a/dist/vue-test-utils.js +++ b/dist/vue-test-utils.js @@ -16,6 +16,14 @@ function warn (msg) { console.error(("[vue-test-utils]: " + msg)); } +if (typeof window === 'undefined') { + throwError( + 'window is undefined, vue-test-utils needs to be run in a browser environment.\n' + + 'You can run the tests in node using jsdom + jsdom-global.\n' + + 'See https://vue-test-utils.vuejs.org/en/guides/common-tips.html for more details.' + ); +} + // var LIFECYCLE_HOOKS = [ @@ -605,11 +613,18 @@ Wrapper.prototype.hasAttribute = function hasAttribute (attribute, value) { * Asserts wrapper has a class name */ Wrapper.prototype.hasClass = function hasClass (className) { - if (typeof className !== 'string') { + var targetClass = className; + + if (typeof targetClass !== 'string') { throwError('wrapper.hasClass() must be passed a string'); } - return !!(this.element && this.element.classList.contains(className)) + // if $style is available and has a matching target, use that instead. + if (this.vm && this.vm.$style && this.vm.$style[targetClass]) { + targetClass = this.vm.$style[targetClass]; + } + + return !!(this.element && this.element.classList.contains(targetClass)) }; /** @@ -1169,15 +1184,7 @@ Vue.config.productionTip = false; function mount (component, options) { if ( options === void 0 ) options = {}; - if (!window) { - throwError( - 'window is undefined, vue-test-utils needs to be run in a browser environment.\n' + - 'You can run the tests in node using jsdom + jsdom-global.\n' + - 'See https://vue-test-utils.vuejs.org/en/guides/common-tips.html for more details.' - ); - } - - var componentToMount = options.clone === false ? component : cloneDeep(component); + var componentToMount = options.clone === false ? component : cloneDeep(component.extend ? component.options : component); // Remove cached constructor delete componentToMount._Ctor; @@ -1198,7 +1205,7 @@ function shallow (component, options) { if ( options === void 0 ) options = {}; var vue = options.localVue || Vue; - var clonedComponent = cloneDeep(component); + var clonedComponent = cloneDeep(component.extend ? component.options : component); if (clonedComponent.components) { stubAllComponents(clonedComponent); @@ -1239,9 +1246,12 @@ function createLocalVue () { // compat for vue-router < 2.7.1 where it does not allow multiple installs var use = instance.use; instance.use = function (plugin) { + var rest = [], len = arguments.length - 1; + while ( len-- > 0 ) rest[ len ] = arguments[ len + 1 ]; + plugin.installed = false; plugin.install.installed = false; - use.call(instance, plugin); + use.call.apply(use, [ instance, plugin ].concat( rest )); }; return instance } diff --git a/dist/vue-test-utils.umd.js b/dist/vue-test-utils.umd.js index e3f01aa69..faf387be8 100644 --- a/dist/vue-test-utils.umd.js +++ b/dist/vue-test-utils.umd.js @@ -1,11 +1,29 @@ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('vue'), require('vue-template-compiler')) : typeof define === 'function' && define.amd ? define(['vue', 'vue-template-compiler'], factory) : - (global.vueTestUtils = factory(global.Vue,global.vueTemplateCompiler)); + (global.vueTestUtils = factory(global.Vue,global.VueTemplateCompiler)); }(this, (function (Vue,vueTemplateCompiler) { 'use strict'; Vue = Vue && 'default' in Vue ? Vue['default'] : Vue; +// + +function throwError (msg) { + throw new Error(("[vue-test-utils]: " + msg)) +} + +function warn (msg) { + console.error(("[vue-test-utils]: " + msg)); +} + +if (typeof window === 'undefined') { + throwError( + 'window is undefined, vue-test-utils needs to be run in a browser environment.\n' + + 'You can run the tests in node using jsdom + jsdom-global.\n' + + 'See https://vue-test-utils.vuejs.org/en/guides/common-tips.html for more details.' + ); +} + /** * Removes all key-value entries from the list cache. * @@ -2541,16 +2559,6 @@ var cloneDeep_1 = cloneDeep; // -function throwError (msg) { - throw new Error(("[vue-test-utils]: " + msg)) -} - -function warn (msg) { - console.error(("[vue-test-utils]: " + msg)); -} - -// - var LIFECYCLE_HOOKS = [ 'beforeCreate', 'created', @@ -3138,11 +3146,18 @@ Wrapper.prototype.hasAttribute = function hasAttribute (attribute, value) { * Asserts wrapper has a class name */ Wrapper.prototype.hasClass = function hasClass (className) { - if (typeof className !== 'string') { + var targetClass = className; + + if (typeof targetClass !== 'string') { throwError('wrapper.hasClass() must be passed a string'); } - return !!(this.element && this.element.classList.contains(className)) + // if $style is available and has a matching target, use that instead. + if (this.vm && this.vm.$style && this.vm.$style[targetClass]) { + targetClass = this.vm.$style[targetClass]; + } + + return !!(this.element && this.element.classList.contains(targetClass)) }; /** @@ -3702,15 +3717,7 @@ Vue.config.productionTip = false; function mount (component, options) { if ( options === void 0 ) options = {}; - if (!window) { - throwError( - 'window is undefined, vue-test-utils needs to be run in a browser environment.\n' + - 'You can run the tests in node using jsdom + jsdom-global.\n' + - 'See https://vue-test-utils.vuejs.org/en/guides/common-tips.html for more details.' - ); - } - - var componentToMount = options.clone === false ? component : cloneDeep_1(component); + var componentToMount = options.clone === false ? component : cloneDeep_1(component.extend ? component.options : component); // Remove cached constructor delete componentToMount._Ctor; @@ -3731,7 +3738,7 @@ function shallow (component, options) { if ( options === void 0 ) options = {}; var vue = options.localVue || Vue; - var clonedComponent = cloneDeep_1(component); + var clonedComponent = cloneDeep_1(component.extend ? component.options : component); if (clonedComponent.components) { stubAllComponents(clonedComponent); @@ -3772,9 +3779,12 @@ function createLocalVue () { // compat for vue-router < 2.7.1 where it does not allow multiple installs var use = instance.use; instance.use = function (plugin) { + var rest = [], len = arguments.length - 1; + while ( len-- > 0 ) rest[ len ] = arguments[ len + 1 ]; + plugin.installed = false; plugin.install.installed = false; - use.call(instance, plugin); + use.call.apply(use, [ instance, plugin ].concat( rest )); }; return instance } From d2d78ad74daf36a054eed4ff0bad12ca19b37798 Mon Sep 17 00:00:00 2001 From: Edd Yerburgh Date: Sun, 22 Oct 2017 21:28:14 +0100 Subject: [PATCH 0034/1136] release: 1.0.0-beta.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 31eda46ba..d5980294b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vue-test-utils", - "version": "1.0.0-beta.2", + "version": "1.0.0-beta.3", "description": "Utilities for testing Vue components.", "main": "dist/vue-test-utils.js", "types": "types/index.d.ts", From 08f58ee2c23567443c1d2c6696ae2ee04b83645a Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 24 Oct 2017 06:33:12 +0200 Subject: [PATCH 0035/1136] Docs: add a guide page for dom events (#121) --- docs/en/README.md | 1 + docs/en/SUMMARY.md | 1 + docs/en/guides/dom-events.md | 196 +++++++++++++++++++++++++++++++++++ 3 files changed, 198 insertions(+) create mode 100644 docs/en/guides/dom-events.md diff --git a/docs/en/README.md b/docs/en/README.md index c848bc583..7c4bdf55f 100644 --- a/docs/en/README.md +++ b/docs/en/README.md @@ -5,6 +5,7 @@ * [Guides](guides/README.md) * [Getting Started](guides/getting-started.md) * [Common Tips](guides/common-tips.md) + * [Mouse, Key and other DOM Events](guides/dom-events.md) * [Choosing a test runner](guides/choosing-a-test-runner.md) * [Testing SFCs with Jest](guides/testing-SFCs-with-jest.md) * [Testing SFCs with Mocha + webpack](guides/testing-SFCs-with-mocha-webpack.md) diff --git a/docs/en/SUMMARY.md b/docs/en/SUMMARY.md index 6d5dafcd5..1143c9e95 100644 --- a/docs/en/SUMMARY.md +++ b/docs/en/SUMMARY.md @@ -3,6 +3,7 @@ * [Guides](guides/README.md) * [Getting Started](guides/getting-started.md) * [Common Tips](guides/common-tips.md) + * [Mouse, Key and other DOM Events](guides/dom-events.md) * [Choosing a test runner](guides/choosing-a-test-runner.md) * [Testing SFCs with Jest](guides/testing-SFCs-with-jest.md) * [Testing SFCs with Mocha + webpack](guides/testing-SFCs-with-mocha-webpack.md) diff --git a/docs/en/guides/dom-events.md b/docs/en/guides/dom-events.md new file mode 100644 index 000000000..83d9e8ad7 --- /dev/null +++ b/docs/en/guides/dom-events.md @@ -0,0 +1,196 @@ +# Testing Key, Mouse and other DOM events + +## Trigger events + +The `Wrapper` expose a `trigger` method. It can be used to trigger DOM events. + +```js +const wrapper = mount(MyButton) + +wrapper.trigger('click') +``` + +You should be aware, that find returns a wrapper as well. Assuming `MyComponent` contains a button, the following code clicks the button. + +```js +const wrapper = mount(MyComponent) + +wrapper.find('button').trigger('click') +``` + +## Options + +The trigger method takes an optional `options` object. The properties in the `options` object are added to the Event. + +You can run preventDefault on the event by passing `preventDefault: true` in `options`. + +```js +const wrapper = mount(MyButton) + +wrapper.trigger('click', {preventDefault: true}) +``` + + +## Mouse Click Example + +**Component under test** + +```js + + + +``` + +**Test** + +```js +import YesNoComponent from '@/components/YesNoComponent' +import { mount } from 'vue-test-utils' +import sinon from 'sinon' + +describe('Click event', () => { + it('Click on yes button calls our method with argument "yes"', () => { + const spy = sinon.spy() + const wrapper = mount(YesNoComponent, { + propsData: { + callMe: spy + } + }) + wrapper.find('button.yes').trigger('click') + + spy.should.have.been.calledWith('yes') + }) +}) +``` + +## Keyboard Example + +**Component under test** + +This component allows to increment/decrement the quantity using various keys. + +```js + + + +``` + +**Test** + +```js +import QuantityComponent from '@/components/QuantityComponent' +import { mount } from 'vue-test-utils' + +describe('Key event tests', () => { + it('Quantity is zero by default', () => { + const wrapper = mount(QuantityComponent) + expect(wrapper.vm.quantity).to.equal(0) + }) + + it('Cursor up sets quantity to 1', () => { + const wrapper = mount(QuantityComponent) + wrapper.trigger('keydown.up') + expect(wrapper.vm.quantity).to.equal(1) + }) + + it('Cursor down reduce quantity by 1', () => { + const wrapper = mount(QuantityComponent) + wrapper.vm.quantity = 5 + wrapper.trigger('keydown.down') + expect(wrapper.vm.quantity).to.equal(4) + }) + + it('Escape sets quantity to 0', () => { + const wrapper = mount(QuantityComponent) + wrapper.vm.quantity = 5 + wrapper.trigger('keydown.esc') + expect(wrapper.vm.quantity).to.equal(0) + }) + + it('Magic character "a" sets quantity to 13', () => { + const wrapper = mount(QuantityComponent) + wrapper.trigger('keydown', { + which: 65 + }) + expect(wrapper.vm.quantity).to.equal(13) + }) +}) + +``` + +**Limitations** + +A key name after the dot `keydown.up` is translated to a `keyCode`. This is supported for the following names: + +* enter, tab, delete, esc, space, up, down, left, right + +## Important + +vue-test-utils triggers event synchronously. Consequently, `vue.nextTick` is not required. From a88d9bb2f0904ed088199e45be8f4df83915dbe6 Mon Sep 17 00:00:00 2001 From: Lachlan Date: Wed, 25 Oct 2017 01:32:09 +0900 Subject: [PATCH 0036/1136] feat(hasClass): allow `hasClass` to compare multiple classes (#123) * Support multiple classes in `hasClass` * Remove unneccessary temp varible and fix linting * Use every instead of reduce and map * Revert package.json changes * Add test for wrapper.element = undefined * Update to include test for wrapper.element is null * Restore original package.json and package-lock.json * Remove commented out line * Update package.json --- src/wrappers/wrapper.js | 6 +++++- .../unit/specs/mount/Wrapper/hasClass.spec.js | 20 ++++++++++++------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/wrappers/wrapper.js b/src/wrappers/wrapper.js index 96a4ac8d4..fb5e310a7 100644 --- a/src/wrappers/wrapper.js +++ b/src/wrappers/wrapper.js @@ -109,7 +109,11 @@ export default class Wrapper implements BaseWrapper { targetClass = this.vm.$style[targetClass] } - return !!(this.element && this.element.classList.contains(targetClass)) + const containsAllClasses = targetClass + .split(' ') + .every(target => this.element.classList.contains(target)) + + return !!(this.element && containsAllClasses) } /** diff --git a/test/unit/specs/mount/Wrapper/hasClass.spec.js b/test/unit/specs/mount/Wrapper/hasClass.spec.js index ee05d1c0c..20751e33e 100644 --- a/test/unit/specs/mount/Wrapper/hasClass.spec.js +++ b/test/unit/specs/mount/Wrapper/hasClass.spec.js @@ -21,13 +21,6 @@ describe('hasClass', () => { expect(wrapper.hasClass('class-name')).to.equal(false) }) - it('returns false if wrapper does not have an element', () => { - const compiled = compileToFunctions('
') - const wrapper = mount(compiled) - wrapper.element = null - expect(wrapper.hasClass('not-class-name')).to.equal(false) - }) - it('throws an error if selector is not a string', () => { const compiled = compileToFunctions('
') const wrapper = mount(compiled) @@ -46,4 +39,17 @@ describe('hasClass', () => { expect(wrapper.hasClass('color-red')).to.equal(true) }) + + it('returns false if wrapper does not contain element', () => { + const wrapper = mount({ render: (h) => h('div.a-class.b-class') }) + const div = wrapper.find('div') + div.element = null + expect(wrapper.hasClass('a-class b-class')).to.equal(false) + }) + + it('returns true when the element contains multiple classes', () => { + const compiled = compileToFunctions('
') + const wrapper = mount(compiled) + expect(wrapper.hasClass('a-class b-class')).to.equal(true) + }) }) From eb0bd5899cd1d432ce5b6c10ddc42535ee676288 Mon Sep 17 00:00:00 2001 From: Edd Yerburgh Date: Tue, 24 Oct 2017 20:30:29 +0100 Subject: [PATCH 0037/1136] docs: use vue-jest instead of jest-vue --- docs/en/guides/choosing-a-test-runner.md | 4 ++-- docs/en/guides/testing-SFCs-with-jest.md | 10 +++++----- docs/ja/guides/choosing-a-test-runner.md | 4 ++-- docs/ja/guides/testing-SFCs-with-jest.md | 10 +++++----- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/en/guides/choosing-a-test-runner.md b/docs/en/guides/choosing-a-test-runner.md index 692264d5d..8d69377b1 100644 --- a/docs/en/guides/choosing-a-test-runner.md +++ b/docs/en/guides/choosing-a-test-runner.md @@ -6,7 +6,7 @@ There are many popular JavaScript test runners, and `vue-test-utils` works with There are a few things to consider when choosing a test runner though: feature set, performance, and support for single-file component (SFC) pre-compilation. After carefully comparing existing libraries, here are two test runners that we recommend: -- [Jest](https://facebook.github.io/jest/docs/en/getting-started.html#content) is the most fully featured test runner. It requires the least configuration, sets up JSDOM by default, provides built-in assertions, and has a great command line user experience. However, you will need a preprocessor to be able to import SFC components in your tests. We have created the `jest-vue` preprocessor which can handle most common SFC features, but it currently does not have 100% feature parity with `vue-loader`. +- [Jest](https://facebook.github.io/jest/docs/en/getting-started.html#content) is the most fully featured test runner. It requires the least configuration, sets up JSDOM by default, provides built-in assertions, and has a great command line user experience. However, you will need a preprocessor to be able to import SFC components in your tests. We have created the `vue-jest` preprocessor which can handle most common SFC features, but it currently does not have 100% feature parity with `vue-loader`. - [mocha-webpack](https://github.com/zinserjan/mocha-webpack) is a wrapper around webpack + Mocha, but with a more streamlined interface and watch mode. The benefits of this setup is that we can get complete SFC support via webpack + `vue-loader`, but it requires more configuration upfront. @@ -29,7 +29,7 @@ require('jsdom-global')() Single-file Vue components (SFCs) require pre-compilation before they can be run in Node or in the browser. There are two recommended ways to perform the compilation: with a Jest preprocessor, or directly use webpack. -The `jest-vue` preprocessor supports basic SFC functionalities, but currently does not handle style blocks or custom blocks, which are only supported in `vue-loader`. If you rely on these features or other webpack-specific configurations, you will need to use a webpack + `vue-loader` based setup. +The `vue-jest` preprocessor supports basic SFC functionalities, but currently does not handle style blocks or custom blocks, which are only supported in `vue-loader`. If you rely on these features or other webpack-specific configurations, you will need to use a webpack + `vue-loader` based setup. Read the following guides for different setups: diff --git a/docs/en/guides/testing-SFCs-with-jest.md b/docs/en/guides/testing-SFCs-with-jest.md index 0bd70718d..8ca1e8f60 100644 --- a/docs/en/guides/testing-SFCs-with-jest.md +++ b/docs/en/guides/testing-SFCs-with-jest.md @@ -27,10 +27,10 @@ Next we need to define a unit script in our `package.json`. ## Processing SFCs in Jest -To teach Jest how to process `*.vue` files, we will need to install and configure the `jest-vue` preprocessor: +To teach Jest how to process `*.vue` files, we will need to install and configure the `vue-jest` preprocessor: ``` bash -npm install --save-dev jest-vue +npm install --save-dev vue-jest ``` Next, create a `jest` block in `package.json`: @@ -46,15 +46,15 @@ Next, create a `jest` block in `package.json`: "vue" ], "transform": { - // process *.vue files with jest-vue - ".*\\.(vue)$": "/node_modules/jest-vue" + // process *.vue files with vue-jest + ".*\\.(vue)$": "/node_modules/vue-jest" }, "mapCoverage": true } } ``` -> **Note:** `jest-vue` currently does not support all the features of `vue-loader`, for example custom block support and style loading. In addition, some webpack-specific features such as code-splitting are not supported either. To use them, read the guide on [testing SFCs with Mocha + webpack](./testing-SFCs-with-mocha-webpack.md). +> **Note:** `vue-jest` currently does not support all the features of `vue-loader`, for example custom block support and style loading. In addition, some webpack-specific features such as code-splitting are not supported either. To use them, read the guide on [testing SFCs with Mocha + webpack](./testing-SFCs-with-mocha-webpack.md). ## Handling webpack Aliases diff --git a/docs/ja/guides/choosing-a-test-runner.md b/docs/ja/guides/choosing-a-test-runner.md index 040881db9..86b26c3ce 100644 --- a/docs/ja/guides/choosing-a-test-runner.md +++ b/docs/ja/guides/choosing-a-test-runner.md @@ -6,7 +6,7 @@ ですが、テストランナを選択する際には、機能セット、パフォーマンス、および単一ファイルコンポーネント (SFC) の事前コンパイルのサポートなどを考慮すべきです。既存のライブラリを慎重に比較した上で、以下の2つのテストランナをお勧めします: -- [Jest](https://facebook.github.io/jest/docs/en/getting-started.html#content) は最も充実したテストランナです。最小の設定が必要で、デフォルトで JSDOM を設定し、組み込みの検証を提供し、コマンドラインのユーザーエクスペリエンスが優れています。ただし、テストで SFC コンポーネントをインポートできるようにするには、プリプロセッサが必要です。最も一般的な SFC 機能を処理できる `jest-vue` プリプロセッサを作成しましたが、現在 `vue-loader` と 100% 同じ機能を持っていません。 +- [Jest](https://facebook.github.io/jest/docs/en/getting-started.html#content) は最も充実したテストランナです。最小の設定が必要で、デフォルトで JSDOM を設定し、組み込みの検証を提供し、コマンドラインのユーザーエクスペリエンスが優れています。ただし、テストで SFC コンポーネントをインポートできるようにするには、プリプロセッサが必要です。最も一般的な SFC 機能を処理できる `vue-jest` プリプロセッサを作成しましたが、現在 `vue-loader` と 100% 同じ機能を持っていません。 - [mocha-webpack](https://github.com/zinserjan/mocha-webpack) は webpack + Mocha のラッパですが、より合理的なインタフェースと watch モードを備えています。この設定のメリットは、webpack + `vue-loader` を使用して完全な SFC サポートを得ることができるということですが、より多くの設定を行う必要があります。 @@ -29,7 +29,7 @@ require('jsdom-global')() 単一ファイルコンポーネントは、ノードまたはブラウザで実行する前に事前コンパイルが必要です。コンパイルを実行するには、Jest プリプロセッサを使用する方法と webpack を直接使用する方法が推奨されます。 -`jest-vue` プリプロセッサは基本的な SFC 機能をサポートしていますが、現在 `vue-loader` でのみサポートされているスタイルブロックやカスタムブロックは扱いません。これらの機能やその他の Webpack 固有の設定に依存する場合は、webpack + `vue-loader` ベースの設定を使用する必要があります。 +`vue-jest` プリプロセッサは基本的な SFC 機能をサポートしていますが、現在 `vue-loader` でのみサポートされているスタイルブロックやカスタムブロックは扱いません。これらの機能やその他の Webpack 固有の設定に依存する場合は、webpack + `vue-loader` ベースの設定を使用する必要があります。 さまざまな設定については、次のガイドをお読みください: - [Jest による単一ファイルコンポーネントのテスト](./testing-SFCs-with-jest.md) diff --git a/docs/ja/guides/testing-SFCs-with-jest.md b/docs/ja/guides/testing-SFCs-with-jest.md index bf544a2bd..746ae1bd2 100644 --- a/docs/ja/guides/testing-SFCs-with-jest.md +++ b/docs/ja/guides/testing-SFCs-with-jest.md @@ -27,10 +27,10 @@ $ npm install --save-dev jest vue-test-utils ## Jest における単一ファイルコンポーネントの処理 -Jest に `*.vue` ファイルの処理方法を教えるために、`jest-vue` プリプロセッサをインストールして設定する必要があります。: +Jest に `*.vue` ファイルの処理方法を教えるために、`vue-jest` プリプロセッサをインストールして設定する必要があります。: ``` bash -npm install --save-dev jest-vue +npm install --save-dev vue-jest ``` 次に、`package.json` に `jest` ブロックを作成します: @@ -46,15 +46,15 @@ npm install --save-dev jest-vue "vue" ], "transform": { - // jest-vue で *.vue ファイルを処理する - ".*\\.(vue)$": "/node_modules/jest-vue" + // vue-jest で *.vue ファイルを処理する + ".*\\.(vue)$": "/node_modules/vue-jest" }, "mapCoverage": true } } ``` -> **注意:** `jest-vue` は現在、カスタムブロックのサポートやスタイルのロードなど、`vue-loader` のすべての機能をサポートしていません。さらに、コード分割などのWebpack固有の機能はサポートされていません。それらを使用するには、[Mocha + webpackによる単一ファイルコンポーネントのテスト](./testing-SFCs-with-mocha-webpack.md)のガイドをお読みください。 +> **注意:** `vue-jest` は現在、カスタムブロックのサポートやスタイルのロードなど、`vue-loader` のすべての機能をサポートしていません。さらに、コード分割などのWebpack固有の機能はサポートされていません。それらを使用するには、[Mocha + webpackによる単一ファイルコンポーネントのテスト](./testing-SFCs-with-mocha-webpack.md)のガイドをお読みください。 ## Webpack エイリアスの処理 From d3846a8d7db0e54e8cc5bbbb24a7207ae906852b Mon Sep 17 00:00:00 2001 From: ross francis Date: Tue, 24 Oct 2017 20:37:48 +0100 Subject: [PATCH 0038/1136] feat(functional components): add context to all functional components (#126) * closes #117 * remove brackets * add test case --- src/lib/create-instance.js | 22 ++++++++++++------- test/unit/specs/mount/options/context.spec.js | 16 ++++++++++++++ 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/lib/create-instance.js b/src/lib/create-instance.js index 67328ecf8..a8f257a59 100644 --- a/src/lib/create-instance.js +++ b/src/lib/create-instance.js @@ -11,23 +11,29 @@ import { throwError } from './util' import cloneDeep from 'lodash/cloneDeep' import { compileTemplate } from './compile-template' -export default function createConstructor (component: Component, options: Options): Component { +export default function createConstructor ( + component: Component, + options: Options +): Component { const vue = options.localVue || Vue - if (options.context) { - if (!component.functional) { - throwError('mount.context can only be used when mounting a functional component') - } - - if (typeof options.context !== 'object') { + if (component.functional) { + if (options.context && typeof options.context !== 'object') { throwError('mount.context must be an object') } const clonedComponent = cloneDeep(component) component = { render (h) { - return h(clonedComponent, options.context) + return h( + clonedComponent, + options.context || component.FunctionalRenderContext + ) } } + } else if (options.context) { + throwError( + 'mount.context can only be used when mounting a functional component' + ) } if (options.provide) { diff --git a/test/unit/specs/mount/options/context.spec.js b/test/unit/specs/mount/options/context.spec.js index 098cc345c..cc76b89c3 100644 --- a/test/unit/specs/mount/options/context.spec.js +++ b/test/unit/specs/mount/options/context.spec.js @@ -49,4 +49,20 @@ describe('context', () => { const fn = () => mount(Component, { context }) expect(fn).to.throw().with.property('message', message) }) + + it('mounts functional component with a defined context when no context object passed in options', () => { + const defaultValue = '[vue-test-utils]: testProp default value' + const Component = { + functional: true, + props: { + testProp: { + type: String, + default: defaultValue + } + }, + render: (h, { props }) => h('div', props.testProp) + } + const wrapper = mount(Component) + expect(wrapper.element.textContent).to.equal(defaultValue) + }) }) From ad9d32ad05ff6bf9528ce9c649a73aa65f8afc8c Mon Sep 17 00:00:00 2001 From: vouill Date: Wed, 25 Oct 2017 22:57:23 +0200 Subject: [PATCH 0039/1136] feat(destroy): add destroy wrapper method (#129) * Added Destroy method to wrapper * Added Destroy method to wrapper-array * Changed update to destroy * Added destroy to type/index.d.ts * Added destroy array test * Added doc * updated doc * Updated wording * Added destroy to summary * Added destroy to wrapper doc --- docs/en/SUMMARY.md | 2 ++ docs/en/api/wrapper-array/destroy.md | 18 ++++++++++ docs/en/api/wrapper/destroy.md | 20 +++++++++++ flow/wrapper.flow.js | 3 +- src/wrappers/error-wrapper.js | 4 +++ src/wrappers/wrapper-array.js | 6 ++++ src/wrappers/wrapper.js | 15 ++++++++ test/unit/specs/mount/Wrapper/destroy.spec.js | 35 +++++++++++++++++++ .../unit/specs/wrappers/error-wrapper.spec.js | 7 ++++ .../unit/specs/wrappers/wrapper-array.spec.js | 7 ++++ types/index.d.ts | 1 + 11 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 docs/en/api/wrapper-array/destroy.md create mode 100644 docs/en/api/wrapper/destroy.md create mode 100644 test/unit/specs/mount/Wrapper/destroy.spec.js diff --git a/docs/en/SUMMARY.md b/docs/en/SUMMARY.md index 1143c9e95..149bc273a 100644 --- a/docs/en/SUMMARY.md +++ b/docs/en/SUMMARY.md @@ -43,6 +43,7 @@ * [text](api/wrapper/text.md) * [trigger](api/wrapper/trigger.md) * [update](api/wrapper/update.md) + * [destroy](api/wrapper/destroy.md) * [WrapperArray](api/wrapper-array/README.md) * [at](api/wrapper-array/at.md) * [contains](api/wrapper-array/contains.md) @@ -58,5 +59,6 @@ * [setProps](api/wrapper-array/setProps.md) * [trigger](api/wrapper-array/trigger.md) * [update](api/wrapper-array/update.md) + * [destroy](api/wrapper-array/destroy.md) * [Selectors](api/selectors.md) * [createLocalVue](api/createLocalVue.md) diff --git a/docs/en/api/wrapper-array/destroy.md b/docs/en/api/wrapper-array/destroy.md new file mode 100644 index 000000000..875936ecb --- /dev/null +++ b/docs/en/api/wrapper-array/destroy.md @@ -0,0 +1,18 @@ +# destroy() + +Destroys each Vue `Wrapper` in `WrapperArray`. + +- **Example:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +const divArray = wrapper.findAll('div') +expect(divArray.contains('p')).toBe(true) +divArray.destroy() +expect(divArray.contains('p')).toBe(false) + +``` diff --git a/docs/en/api/wrapper/destroy.md b/docs/en/api/wrapper/destroy.md new file mode 100644 index 000000000..015d5a5ea --- /dev/null +++ b/docs/en/api/wrapper/destroy.md @@ -0,0 +1,20 @@ +# destroy() + +Destroys a Vue component instance. + +- **Example:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import sinon from 'sinon' + +const spy = sinon.stub() +mount({ + render: null, + destroyed () { + spy() + } +}).destroy() +expect(spy.calledOnce).to.equal(true) +``` diff --git a/flow/wrapper.flow.js b/flow/wrapper.flow.js index 9cf25f48e..9cbf94fec 100644 --- a/flow/wrapper.flow.js +++ b/flow/wrapper.flow.js @@ -28,7 +28,8 @@ declare interface BaseWrapper { // eslint-disable-line no-undef setMethods(methods: Object): void, setProps(data: Object): void, trigger(type: string, options: Object): void, - update(): void + update(): void, + destroy(): void } declare type WrapperOptions = { // eslint-disable-line no-undef diff --git a/src/wrappers/error-wrapper.js b/src/wrappers/error-wrapper.js index 434ddd1f4..16fe1e8bc 100644 --- a/src/wrappers/error-wrapper.js +++ b/src/wrappers/error-wrapper.js @@ -99,4 +99,8 @@ export default class ErrorWrapper implements BaseWrapper { update (): void { throwError(`find did not return ${this.selector}, cannot call update() on empty Wrapper`) } + + destroy (): void { + throwError(`find did not return ${this.selector}, cannot call destroy() on empty Wrapper`) + } } diff --git a/src/wrappers/wrapper-array.js b/src/wrappers/wrapper-array.js index 113ff9f7b..8cbddbd0d 100644 --- a/src/wrappers/wrapper-array.js +++ b/src/wrappers/wrapper-array.js @@ -154,4 +154,10 @@ export default class WrapperArray implements BaseWrapper { this.wrappers.forEach(wrapper => wrapper.update()) } + + destroy (): void { + this.throwErrorIfWrappersIsEmpty('destroy') + + this.wrappers.forEach(wrapper => wrapper.destroy()) + } } diff --git a/src/wrappers/wrapper.js b/src/wrappers/wrapper.js index fb5e310a7..6c50e4d1f 100644 --- a/src/wrappers/wrapper.js +++ b/src/wrappers/wrapper.js @@ -384,6 +384,21 @@ export default class Wrapper implements BaseWrapper { return this.element.textContent } + /** + * Calls destroy on vm + */ + destroy () { + if (!this.isVueComponent) { + throwError('wrapper.destroy() can only be called on a Vue instance') + } + + if (this.vm.$el.parentNode) { + this.vm.$el.parentNode.removeChild(this.vm.$el) + } + + this.vm.$destroy() + } + /** * Dispatches a DOM event on wrapper */ diff --git a/test/unit/specs/mount/Wrapper/destroy.spec.js b/test/unit/specs/mount/Wrapper/destroy.spec.js new file mode 100644 index 000000000..83bee0cc4 --- /dev/null +++ b/test/unit/specs/mount/Wrapper/destroy.spec.js @@ -0,0 +1,35 @@ +import { compileToFunctions } from 'vue-template-compiler' +import mount from '~src/mount' +import sinon from 'sinon' + +describe('destroy', () => { + it('should trigger beforeDestroy ', () => { + const spy = sinon.stub() + mount({ + render: null, + beforeDestroy () { + spy() + } + }).destroy() + expect(spy.calledOnce).to.equal(true) + }) + + it('should trigger destroy ', () => { + const spy = sinon.stub() + mount({ + render: null, + destroyed () { + spy() + } + }).destroy() + expect(spy.calledOnce).to.equal(true) + }) + + it('should remove element from document.body', () => { + const compiled = compileToFunctions('
') + const wrapper = mount(compiled, { attachToDocument: true }) + expect(wrapper.vm.$el.parentNode).to.equal(document.body) + wrapper.destroy() + expect(wrapper.vm.$el.parentNode).to.be.null + }) +}) diff --git a/test/unit/specs/wrappers/error-wrapper.spec.js b/test/unit/specs/wrappers/error-wrapper.spec.js index 2e2e8ffc1..3b78eeac4 100644 --- a/test/unit/specs/wrappers/error-wrapper.spec.js +++ b/test/unit/specs/wrappers/error-wrapper.spec.js @@ -154,4 +154,11 @@ describe('ErrorWrapper', () => { const error = new ErrorWrapper(selector) expect(() => error.update()).to.throw().with.property('message', message) }) + + it('destroy throws error when called', () => { + const selector = 'div' + const message = `[vue-test-utils]: find did not return ${selector}, cannot call destroy() on empty Wrapper` + const error = new ErrorWrapper(selector) + expect(() => error.destroy()).to.throw().with.property('message', message) + }) }) diff --git a/test/unit/specs/wrappers/wrapper-array.spec.js b/test/unit/specs/wrappers/wrapper-array.spec.js index 7946f36e5..cba5407e4 100644 --- a/test/unit/specs/wrappers/wrapper-array.spec.js +++ b/test/unit/specs/wrappers/wrapper-array.spec.js @@ -258,4 +258,11 @@ describe('WrapperArray', () => { wrapperArray.update() expect(update.calledTwice).to.equal(true) }) + + it('destroy calls destroy on each wrapper', () => { + const destroy = sinon.stub() + const wrapperArray = new WrapperArray([{ destroy }, { destroy }]) + wrapperArray.destroy() + expect(destroy.calledTwice).to.equal(true) + }) }) diff --git a/types/index.d.ts b/types/index.d.ts index 030268318..83113d3e7 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -48,6 +48,7 @@ interface BaseWrapper { setMethods (data: object): void setProps (props: object): void trigger (eventName: string, options?: object): void + destroy (): void } interface Wrapper extends BaseWrapper { From 3c563d12735b8eab7529d32494f0955e0c51a294 Mon Sep 17 00:00:00 2001 From: Yasuharu Yanamura Date: Fri, 27 Oct 2017 14:28:03 +0900 Subject: [PATCH 0040/1136] fix: dom-events.md eslint errors (#131) --- docs/en/guides/dom-events.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/en/guides/dom-events.md b/docs/en/guides/dom-events.md index 83d9e8ad7..1ed117610 100644 --- a/docs/en/guides/dom-events.md +++ b/docs/en/guides/dom-events.md @@ -27,7 +27,7 @@ You can run preventDefault on the event by passing `preventDefault: true` in `op ```js const wrapper = mount(MyButton) -wrapper.trigger('click', {preventDefault: true}) +wrapper.trigger('click', { preventDefault: true }) ``` @@ -35,7 +35,7 @@ wrapper.trigger('click', {preventDefault: true}) **Component under test** -```js +```html diff --git a/test/resources/components/component-with-transition.vue b/test/resources/components/component-with-transition.vue index ea52f9381..3342e28f7 100644 --- a/test/resources/components/component-with-transition.vue +++ b/test/resources/components/component-with-transition.vue @@ -5,10 +5,10 @@ diff --git a/test/resources/components/component-with-v-for.vue b/test/resources/components/component-with-v-for.vue index 06db0fbf9..30a7b4a62 100644 --- a/test/resources/components/component-with-v-for.vue +++ b/test/resources/components/component-with-v-for.vue @@ -1,17 +1,17 @@ diff --git a/test/resources/components/component-with-v-if.vue b/test/resources/components/component-with-v-if.vue index 6cbad054e..80071efcc 100644 --- a/test/resources/components/component-with-v-if.vue +++ b/test/resources/components/component-with-v-if.vue @@ -1,19 +1,19 @@ diff --git a/test/resources/components/component-with-vuex.vue b/test/resources/components/component-with-vuex.vue index 20ef57331..24dfd1b0c 100644 --- a/test/resources/components/component-with-vuex.vue +++ b/test/resources/components/component-with-vuex.vue @@ -3,19 +3,19 @@ diff --git a/test/resources/components/component-with-watch.vue b/test/resources/components/component-with-watch.vue index 341b27c86..cfead83e4 100644 --- a/test/resources/components/component-with-watch.vue +++ b/test/resources/components/component-with-watch.vue @@ -1,23 +1,23 @@ diff --git a/test/resources/components/component-without-name.vue b/test/resources/components/component-without-name.vue index 6dd3e72df..b97e614c6 100644 --- a/test/resources/components/component-without-name.vue +++ b/test/resources/components/component-without-name.vue @@ -1,7 +1,7 @@ diff --git a/test/resources/components/component.vue b/test/resources/components/component.vue index 7c521befc..2b007be61 100644 --- a/test/resources/components/component.vue +++ b/test/resources/components/component.vue @@ -1,9 +1,9 @@ From 4fae8da3931e94a8a3df1fd59bebd06037231a21 Mon Sep 17 00:00:00 2001 From: eddyerburgh Date: Fri, 3 Nov 2017 21:22:40 +0000 Subject: [PATCH 0049/1136] fix: run watchers in setData Closes #149 --- src/wrappers/wrapper.js | 8 ++++++ .../components/component-with-watch.vue | 12 +++++++++ test/unit/specs/mount/Wrapper/setData.spec.js | 25 +++++++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/src/wrappers/wrapper.js b/src/wrappers/wrapper.js index 4a1b54f0d..a5d32e082 100644 --- a/src/wrappers/wrapper.js +++ b/src/wrappers/wrapper.js @@ -293,6 +293,14 @@ export default class Wrapper implements BaseWrapper { // $FlowIgnore : Problem with possibly null this.vm this.vm.$set(this.vm, [key], data[key]) }) + + Object.keys(data).forEach((key) => { + // $FlowIgnore : Problem with possibly null this.vm + this.vm._watchers.forEach((watcher) => { + if (watcher.expression === key) { watcher.run() } + }) + }) + this.update() } diff --git a/test/resources/components/component-with-watch.vue b/test/resources/components/component-with-watch.vue index cfead83e4..6a7d4b099 100644 --- a/test/resources/components/component-with-watch.vue +++ b/test/resources/components/component-with-watch.vue @@ -7,6 +7,12 @@ +``` + +然后创建一个名为 `test/Counter.spec.js` 的测试文件并写入如下代码: + +```js +import { shallow } from 'vue-test-utils' +import Counter from '../src/Counter.vue' + +describe('Counter.vue', () => { + it('计数器在点击按钮时自增', () => { + const wrapper = shallow(Counter) + wrapper.find('button').trigger('click') + expect(wrapper.find('div').text()).toMatch('1') + }) +}) +``` + +现在我们运行测试: + +``` +npm run unit +``` + +喔,我们的测试运行起来了! + +### 相关资料 + +- [该设置的示例工程](https://github.com/vuejs/vue-test-utils-mocha-webpack-example) +- [Mocha](https://mochajs.org/) +- [mocha-webpack](http://zinserjan.github.io/mocha-webpack/) +- [Chai](http://chaijs.com/) +- [Sinon](http://sinonjs.org/) +- [jest/expect](http://facebook.github.io/jest/docs/en/expect.html#content) diff --git a/docs/zh-cn/guides/using-with-vue-router.md b/docs/zh-cn/guides/using-with-vue-router.md new file mode 100644 index 000000000..4da034893 --- /dev/null +++ b/docs/zh-cn/guides/using-with-vue-router.md @@ -0,0 +1,71 @@ +# 配合 Vue Router 使用 + +## 在测试中安装 Vue Router + +在测试中,你应该杜绝在基本的 Vue 构造函数中安装 Vue Router。安装 Vue Router 之后 Vue 的原型上会增加 `$route` 和 `$router` 这两个只读属性。 + +为了避免这样的事情发生,我们创建了一个 `localVue` 并对其安装 Vue Router。 + +```js +import VueRouter from 'vue-router' +const localVue = createLocalVue() + +localVue.use(VueRouter) + +shallow(Component, { + localVue +}) +``` + +## 测试使用了 `router-link` 或 `router-view` 的组件 + +当你安装 Vue Router 的时候,`router-link` 和 `router-view` 组件就被注册了。这意味着我们无需再导入可以在应用的任意地方使用它们。 + +当我们运行测试的时候,需要令 vue-router 相关组件在我们挂载的组件中可用。有以下两种做法: + +### 使用存根 + +```js +shallow(Component, { + stubs: ['router-link', 'router-view'] +}) +``` + +### 为 localVue 安装 Vue Router + +```js +import VueRouter from 'vue-router' +const localVue = createLocalVue() + +localVue.use(VueRouter) + +shallow(Component, { + localVue +}) +``` + +## 伪造 `$route` 和 `$router` + +有的时候你想要测试一个组件在配合 `$route` 和 `$router` 对象的参数时的行为。这时候你可以传递自定义假数据给 Vue 实例。 + +```js +const $route = { + path: '/some/path' +} + +const wrapper = shallow(Component, { + mocks: { + $route + } +}) + +wrapper.vm.$router // /some/path +``` + +## 常识 + +安装 Vue Router 会在 Vue 的原型上添加 `$route` 和 `$router` 只读属性。 + +这意味着在未来的任何测试中,伪造 `$route` 或 `$router` 都会失效。 + +要想回避这个问题,就不要在运行测试的时候安装 Vue Router。 diff --git a/docs/zh-cn/guides/using-with-vuex.md b/docs/zh-cn/guides/using-with-vuex.md new file mode 100644 index 000000000..c97e0bd0c --- /dev/null +++ b/docs/zh-cn/guides/using-with-vuex.md @@ -0,0 +1,267 @@ +# 配合 Vuex 实用 + +在本教程中,我们将会看到如何用 `vue-test-utils` 测试组件中的 Vuex。 + +## 伪造 Action + +我们来看一些代码。 + +这是我们想要测试的组件。它会调用 Vuex action。 + +``` html + + + +``` + +站在测试的角度,我们不关心这个 action 做了什么或者这个 store 是什么样子的。我们只需要知道这些 action 将会在适当的时机触发,已经它们触发时的预期值。 + +为了完成这个测试,我们需要在浅渲染组件时给 Vue 传递一个伪造的 store。 + +我们可以把 store 传递给一个 [`localVue`](../api/options.md#localvue),而不是传递给基础的 Vue 构造函数。`localVue` 是一个独立作用域的 Vue 构造函数,我们可以对其进行改动而不会影响到全局的 Vue 构造函数。 + +我们来看看它的样子: + +``` js +import { shallow, createLocalVue } from 'vue-test-utils' +import Vuex from 'vuex' +import Actions from '../../../src/components/Actions' + +const localVue = createLocalVue() + +localVue.use(Vuex) + +describe('Actions.vue', () => { + let actions + let store + + beforeEach(() => { + actions = { + actionClick: jest.fn(), + actionInput: jest.fn() + } + store = new Vuex.Store({ + state: {}, + actions + }) + }) + + it('当输入框的值是“input”且一个“input”事件被触发时会调用“actionInput”的 action', () => { + const wrapper = shallow(Actions, { store, localVue }) + const input = wrapper.find('input') + input.element.value = 'input' + input.trigger('input') + expect(actions.actionInput).toHaveBeenCalled() + }) + + it('当输入框的值不是“input”但有“input”事件触发时不会掉用“actionInput”的 action', () => { + const wrapper = shallow(Actions, { store, localVue }) + const input = wrapper.find('input') + input.element.value = 'not input' + input.trigger('input') + expect(actions.actionInput).not.toHaveBeenCalled() + }) + + it('当按钮被点击时候调用“actionClick”的 action', () => { + const wrapper = shallow(Actions, { store, localVue }) + wrapper.find('button').trigger('click') + expect(actions.actionClick).toHaveBeenCalled() + }) +}) +``` + +这里发生了什么?首先我们用 `Vue.use` 方法告诉 Vue 使用 Vuex。这只是 `Vue.use` 的一个包裹器。 + +然后我们用 `new Vuex.store` 伪造了一个 store 并填入假数据。我们只把它传递给 action,因为我们只关心这个。 + +该 action 是 [Jest 伪造函数](https://facebook.github.io/jest/docs/en/mock-functions.html)。这些伪造函数让我们去断言该 action 是否被调用。 + +然后我们可以在我们的测试中断言 action 存根是否如预期般被调用。 + +现在我们定义 store 的方式在你看来可能有点特别。 + +我们使用 `beforeEach` 来确认我们在每项测试之前已经拥有一个干净的 store。`beforeEach` 是一个 mocha 的钩子,会在每项测试之前被调用。我们在测试中会重新为 store 的变量赋值。如果我们没有这样做,伪造函数就需要被自动重置。它还需要我们改变测试中的 state,而不会影响后面的其它测试。 + +该测试中最重要的注意事项是:**我们创建了一个伪造的 Vuex store 并将其传递给 `vue-test-utils`**。 + +好的,现在我们可以伪造 action 了,我们再来看看伪造 getter。 + +## 伪造 Getter + + +``` html + + + +``` + +这是一个非常简单的组件。它根据 getter `clicks` 和 `inputValue` 渲染结果。还是那句话,我们并不关注这些 getter 返回什么——只关注它们被正确的渲染。 + +让我们看看这个测试: + +``` js +import { shallow, createLocalVue } from 'vue-test-utils' +import Vuex from 'vuex' +import Actions from '../../../src/components/Getters' + +const localVue = createLocalVue() + +localVue.use(Vuex) + +describe('Getters.vue', () => { + let getters + let store + + beforeEach(() => { + getters = { + clicks: () => 2, + inputValue: () => 'input' + } + + store = new Vuex.Store({ + getters + }) + }) + + it('在第一个 p 标签中渲染“state.inputValue”', () => { + const wrapper = shallow(Actions, { store, localVue }) + const p = wrapper.find('p') + expect(p.text()).toBe(getters.inputValue()) + }) + + it('在第二个 p 标签中渲染“state.clicks”', () => { + const wrapper = shallow(Actions, { store, localVue }) + const p = wrapper.findAll('p').at(1) + expect(p.text()).toBe(getters.clicks().toString()) + }) +}) +``` + +这个测试和我们的 action 测试很相似。我们在每个测试运行之前创建了一个伪造的 store,在我们调用 `shallow` 的时候将其以一个选项传递进去,并断言我们伪造的 getter 的返回值被渲染。 + +这非常好,但是如果我们想要检查我们的 getter 是否返回了正确的 state 的部分该怎么办呢? + +## 伪造 Module + +[Module](https://vuex.vuejs.org/zh-cn/modules.html) 对于将我们的 store 分隔成多个可管理的块来说非常有用。它们也暴露 getter。我们可以在测试中使用它们。 + +看看这个组件: + +``` html + + + +``` + +简单的包含一个 action 和一个 getter 的组件。 + +其测试: + +``` js +import { shallow, createLocalVue } from 'vue-test-utils' +import Vuex from 'vuex' +import Modules from '../../../src/components/Modules' +import module from '../../../src/store/module' + +const localVue = createLocalVue() + +localVue.use(Vuex) + +describe('Modules.vue', () => { + let actions + let state + let store + + beforeEach(() => { + state = { + module: { + clicks: 2 + } + } + + actions = { + moduleActionClick: jest.fn() + } + + store = new Vuex.Store({ + state, + actions, + getters: module.getters + }) + }) + + it('在点击按钮时调用 action“moduleActionClick”', () => { + const wrapper = shallow(Modules, { store, localVue }) + const button = wrapper.find('button') + button.trigger('click') + expect(actions.moduleActionClick).toHaveBeenCalled() + }) + + it('在第一个 p 标签内渲染“state.inputValue”', () => { + const wrapper = shallow(Modules, { store, localVue }) + const p = wrapper.find('p') + expect(p.text()).toBe(state.module.clicks.toString()) + }) +}) +``` + +### 相关资料 + +- [该设置的示例工程](https://github.com/eddyerburgh/vue-test-utils-vuex-example) +- [`localVue`](../api/options.md#localvue) +- [`createLocalVue`](../api/createLocalVue.md) + From 4275dd3a47fdb6986a4397d81a05813555ccab75 Mon Sep 17 00:00:00 2001 From: eddyerburgh Date: Sun, 5 Nov 2017 14:14:22 +0000 Subject: [PATCH 0051/1136] docs: add zn-cn to LANGS.md --- docs/LANGS.md | 1 + package-lock.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/LANGS.md b/docs/LANGS.md index 58d8021f6..734a128c2 100644 --- a/docs/LANGS.md +++ b/docs/LANGS.md @@ -1,2 +1,3 @@ * [English](en/) * [日本語](ja/) +* [简体中文](zh-cn/) diff --git a/package-lock.json b/package-lock.json index 938119af3..13c90f371 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "vue-test-utils", - "version": "1.0.0-beta.3", + "version": "1.0.0-beta.4", "lockfileVersion": 1, "requires": true, "dependencies": { From a5ad66ae5c10d217f0766cdebfa57d00eb938341 Mon Sep 17 00:00:00 2001 From: Austin Date: Sun, 5 Nov 2017 14:29:39 -0600 Subject: [PATCH 0052/1136] refactor: remove debugger from setComputed spec (#153) --- test/unit/specs/mount/Wrapper/setComputed.spec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/unit/specs/mount/Wrapper/setComputed.spec.js b/test/unit/specs/mount/Wrapper/setComputed.spec.js index 9c6bcd5cf..729aba584 100644 --- a/test/unit/specs/mount/Wrapper/setComputed.spec.js +++ b/test/unit/specs/mount/Wrapper/setComputed.spec.js @@ -6,7 +6,6 @@ describe('setComputed', () => { it('sets component computed props and updates when called on Vue instance', () => { const wrapper = mount(ComponentWithComputed) expect(wrapper.text()).to.contain('message') - debugger wrapper.setComputed({ reversedMessage: 'custom' }) expect(wrapper.text()).to.contain('custom') }) From e484c7ececca9650ba2b93c8861341f7b3739641 Mon Sep 17 00:00:00 2001 From: Kouki Narumi Date: Tue, 7 Nov 2017 17:10:02 +0900 Subject: [PATCH 0053/1136] docs: fix typo and update (#160) * fix typo and update * natural translate --- docs/ja/api/wrapper-array/hasStyle.md | 4 ++-- docs/ja/api/wrapper/hasStyle.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/ja/api/wrapper-array/hasStyle.md b/docs/ja/api/wrapper-array/hasStyle.md index 822580101..1eeac3b84 100644 --- a/docs/ja/api/wrapper-array/hasStyle.md +++ b/docs/ja/api/wrapper-array/hasStyle.md @@ -1,8 +1,8 @@ # hasStyle(style, value) -`WrapperArray` の全ての `Wrapper` の DOM ノードに style 属性とマッチする値を持っているか検証します。 +`WrapperArray` の全ての `Wrapper` の DOM ノードが style 属性とマッチする値を持っているか検証します。 -`Wrapper` DOM ノードに `string` にマッチする `style` 値がある場合 `true` を返します。 +`Wrapper` DOM ノードが `value` にマッチする `style` 値がある場合 `true` を返します。 **`jsdom`で実行しているときにのみインラインスタイルを検出しますので注意してください。** - **引数:** diff --git a/docs/ja/api/wrapper/hasStyle.md b/docs/ja/api/wrapper/hasStyle.md index 1a7bd7126..9f73ef8cf 100644 --- a/docs/ja/api/wrapper/hasStyle.md +++ b/docs/ja/api/wrapper/hasStyle.md @@ -2,7 +2,7 @@ `Wrapper` DOM ノードが値に一致するにスタイルを持つか検証します。 -`Wrapper` DOM ノードが `string` に一致する `stuyle` を持つ場合は、`true` を返します。 +`Wrapper` DOM ノードが `value` に一致する `style` を持つ場合は、`true` を返します。 **`jsdom` で実行しているときのみ、インラインスタイルを検出します。** From 3f3c64a371bceb9b9907bb582cef5ecd0b80e76e Mon Sep 17 00:00:00 2001 From: Austin Date: Thu, 9 Nov 2017 00:53:19 -0600 Subject: [PATCH 0054/1136] feat(errors): propogate thrown errors by configuring error handler (#154) * Propogate thrown errors by configuring error handler and attaching in mount function resolves #147 * set errorHandler to be a default export and set Vue.config.errorHandler = errorHandler in mount * Add test for create-local-vue error handler being defined and configure that in create local vue --- src/create-local-vue.js | 3 +++ src/lib/error-handler.js | 19 ++++++++++++++ src/mount.js | 2 ++ test/unit/specs/create-local-vue.spec.js | 6 +++++ test/unit/specs/lib/error-handler.spec.js | 31 +++++++++++++++++++++++ test/unit/specs/mount.spec.js | 9 +++++++ test/unit/specs/shallow.spec.js | 9 +++++++ 7 files changed, 79 insertions(+) create mode 100644 src/lib/error-handler.js create mode 100644 test/unit/specs/lib/error-handler.spec.js diff --git a/src/create-local-vue.js b/src/create-local-vue.js index cc33d6e3a..2eb2ba730 100644 --- a/src/create-local-vue.js +++ b/src/create-local-vue.js @@ -2,6 +2,7 @@ import Vue from 'vue' import cloneDeep from 'lodash/cloneDeep' +import errorHandler from './lib/error-handler' function createLocalVue (): Component { const instance = Vue.extend() @@ -19,6 +20,8 @@ function createLocalVue (): Component { // config is not enumerable instance.config = cloneDeep(Vue.config) + instance.config.errorHandler = errorHandler + // option merge strategies need to be exposed by reference // so that merge strats registered by plguins can work properly instance.config.optionMergeStrategies = Vue.config.optionMergeStrategies diff --git a/src/lib/error-handler.js b/src/lib/error-handler.js new file mode 100644 index 000000000..3f721826b --- /dev/null +++ b/src/lib/error-handler.js @@ -0,0 +1,19 @@ +function errorMessage (msg, info) { + if (info) { + return `${msg} : additional info ${info}` + } + + return msg +} + +export default function errorHandler (err, _vm, info) { + if ((typeof err === 'object') && err.message) { + if (info) { + err.message = errorMessage(err.message, info) + } + + throw err + } + + throw new Error(errorMessage(err, info)) +} diff --git a/src/mount.js b/src/mount.js index c33163b80..9a461d2b9 100644 --- a/src/mount.js +++ b/src/mount.js @@ -7,8 +7,10 @@ import createInstance from './lib/create-instance' import cloneDeep from 'lodash/cloneDeep' import createElement from './lib/create-element' import './lib/matches-polyfill' +import errorHandler from './lib/error-handler' Vue.config.productionTip = false +Vue.config.errorHandler = errorHandler export default function mount (component: Component, options: Options = {}): VueWrapper { const componentToMount = options.clone === false ? component : cloneDeep(component.extend ? component.options : component) diff --git a/test/unit/specs/create-local-vue.spec.js b/test/unit/specs/create-local-vue.spec.js index ff14bb22f..fdac20c9b 100644 --- a/test/unit/specs/create-local-vue.spec.js +++ b/test/unit/specs/create-local-vue.spec.js @@ -112,4 +112,10 @@ describe('createLocalVue', () => { const localVue = createLocalVue() localVue.use(Vuetify) }) + + it('has an errorHandler', () => { + const localVue = createLocalVue() + + expect(localVue.config.errorHandler).to.be.an('function') + }) }) diff --git a/test/unit/specs/lib/error-handler.spec.js b/test/unit/specs/lib/error-handler.spec.js new file mode 100644 index 000000000..9e6dbdbb0 --- /dev/null +++ b/test/unit/specs/lib/error-handler.spec.js @@ -0,0 +1,31 @@ +import errorHandler from '../../../../src/lib/error-handler' + +describe('errorHandler', () => { + const errorString = 'errorString' + const info = 'additional info provided by vue' + const errorObject = new Error(errorString) + + it('when error object: rethrows error', () => { + expect(() => errorHandler(errorObject)).to.throw().with.property('message', errorString) + }) + + it('when error object: rethrown error contains vue info when provided', () => { + expect(() => errorHandler(errorObject, {}, info)).to.throw().that.satisfies(function (err) { + const errorMessage = err.message + + return errorMessage.includes(errorString) && errorMessage.includes(info) + }) + }) + + it('when error string: throws error with string', () => { + expect(() => errorHandler(errorString)).to.throw().with.property('message', errorString) + }) + + it('throws error with string and appends info when provided', () => { + expect(() => errorHandler(errorString, {}, info)).to.throw().that.satisfies(function (err) { + const errorMessage = err.message + + return errorMessage.includes(errorString) && errorMessage.includes(info) + }) + }) +}) diff --git a/test/unit/specs/mount.spec.js b/test/unit/specs/mount.spec.js index 0082d2d7b..29c086729 100644 --- a/test/unit/specs/mount.spec.js +++ b/test/unit/specs/mount.spec.js @@ -85,4 +85,13 @@ describe('mount', () => { expect(wrapper.vm).to.be.an('object') expect(wrapper.html()).to.equal(`
foo
`) }) + + it('throws an error when the component fails to mount', () => { + expect(() => mount({ + template: '
', + mounted: function () { + throw (new Error('Error')) + } + })).to.throw() + }) }) diff --git a/test/unit/specs/shallow.spec.js b/test/unit/specs/shallow.spec.js index 2204b8dd5..066a44868 100644 --- a/test/unit/specs/shallow.spec.js +++ b/test/unit/specs/shallow.spec.js @@ -74,4 +74,13 @@ describe('shallow', () => { shallow(ComponentWithNestedChildren) expect(info.called).to.equal(false) }) + + it('throws an error when the component fails to mount', () => { + expect(() => shallow({ + template: '
', + mounted: function () { + throw (new Error('Error')) + } + })).to.throw() + }) }) From b3c12c44ff1bb9274db4e60de776b6e94d5239b9 Mon Sep 17 00:00:00 2001 From: Tho Date: Thu, 9 Nov 2017 07:53:41 +0100 Subject: [PATCH 0055/1136] feat(types): add missing wrappers type def in ArrayWrapper (#164) Offer missing field `wrappers` in TS type definition to access wrappers directly from a test. --- types/index.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/types/index.d.ts b/types/index.d.ts index 83113d3e7..cf71d414a 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -76,6 +76,7 @@ interface Wrapper extends BaseWrapper { interface WrapperArray extends BaseWrapper { readonly length: number + readonly wrappers: Array> at (index: number): Wrapper } From a9740427dd0a55a15893bd775f701e5874805b5d Mon Sep 17 00:00:00 2001 From: eddyerburgh Date: Fri, 10 Nov 2017 18:36:21 +0000 Subject: [PATCH 0056/1136] feat(text): trim text by default closes 152 --- src/wrappers/wrapper.js | 2 +- test/unit/specs/mount/Wrapper/text.spec.js | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/wrappers/wrapper.js b/src/wrappers/wrapper.js index a5d32e082..555b2f305 100644 --- a/src/wrappers/wrapper.js +++ b/src/wrappers/wrapper.js @@ -389,7 +389,7 @@ export default class Wrapper implements BaseWrapper { throwError('cannot call wrapper.text() on a wrapper without an element') } - return this.element.textContent + return this.element.textContent.trim() } /** diff --git a/test/unit/specs/mount/Wrapper/text.spec.js b/test/unit/specs/mount/Wrapper/text.spec.js index 15f0a26db..22871af4a 100644 --- a/test/unit/specs/mount/Wrapper/text.spec.js +++ b/test/unit/specs/mount/Wrapper/text.spec.js @@ -10,6 +10,17 @@ describe('text', () => { expect(wrapper.text()).to.equal(text) }) + it('returns trimmed text content of wrapper node', () => { + const text = 'test text prop' + const compiled = compileToFunctions(` +
+ ${text} +
`) + const wrapper = mount(compiled) + + expect(wrapper.text()).to.equal(text) + }) + 152 it('throws error if wrapper does not contain element', () => { const wrapper = mount({ render: (h) => h('div') }) const div = wrapper.find('div') From 24bfb95122fbef67b1f98e364eb0412d1ce8b0cd Mon Sep 17 00:00:00 2001 From: Matt O'Connell Date: Fri, 10 Nov 2017 16:46:40 -0500 Subject: [PATCH 0057/1136] feat(selector): add refs option (#68) --- flow/wrapper.flow.js | 2 +- src/lib/find-matching-vnodes.js | 38 ---------- src/lib/find-vnodes-by-ref.js | 15 ++++ src/lib/find-vnodes-by-selector.js | 13 ++++ src/lib/get-selector-type.js | 32 ++++++++ src/lib/validators.js | 29 ++++++- src/lib/vnode-utils.js | 28 +++++++ src/wrappers/wrapper.js | 75 ++++++++++++------- .../components/component-with-child.vue | 2 +- .../components/component-with-v-for.vue | 2 +- .../unit/specs/mount/Wrapper/contains.spec.js | 25 ++++++- test/unit/specs/mount/Wrapper/find.spec.js | 41 +++++++++- test/unit/specs/mount/Wrapper/findAll.spec.js | 46 +++++++++++- test/unit/specs/mount/Wrapper/is.spec.js | 13 +++- .../specs/mount/WrapperArray/contains.spec.js | 2 +- test/unit/specs/mount/WrapperArray/is.spec.js | 2 +- types/index.d.ts | 9 +++ 17 files changed, 294 insertions(+), 80 deletions(-) delete mode 100644 src/lib/find-matching-vnodes.js create mode 100644 src/lib/find-vnodes-by-ref.js create mode 100644 src/lib/find-vnodes-by-selector.js create mode 100644 src/lib/get-selector-type.js create mode 100644 src/lib/vnode-utils.js diff --git a/flow/wrapper.flow.js b/flow/wrapper.flow.js index 9cbf94fec..3062f4867 100644 --- a/flow/wrapper.flow.js +++ b/flow/wrapper.flow.js @@ -3,7 +3,7 @@ import type Wrapper from '~src/Wrapper' import type WrapperArray from '~src/WrapperArray' -declare type Selector = string | Component +declare type Selector = any declare interface BaseWrapper { // eslint-disable-line no-undef at(index: number): Wrapper | void, diff --git a/src/lib/find-matching-vnodes.js b/src/lib/find-matching-vnodes.js deleted file mode 100644 index 59f359a10..000000000 --- a/src/lib/find-matching-vnodes.js +++ /dev/null @@ -1,38 +0,0 @@ -// @flow - -function findAllVNodes (vnode: VNode, nodes: Array = []): Array { - nodes.push(vnode) - - if (Array.isArray(vnode.children)) { - vnode.children.forEach((childVNode) => { - findAllVNodes(childVNode, nodes) - }) - } - - if (vnode.child) { - findAllVNodes(vnode.child._vnode, nodes) - } - - return nodes -} - -function nodeMatchesSelector (node: VNode, selector: string): boolean { - return node.elm && node.elm.getAttribute && node.elm.matches(selector) -} - -function removeDuplicateNodes (vNodes: Array): Array { - const uniqueNodes = [] - vNodes.forEach((vNode) => { - const exists = uniqueNodes.some(node => vNode.elm === node.elm) - if (!exists) { - uniqueNodes.push(vNode) - } - }) - return uniqueNodes -} - -export default function findMatchingVNodes (vNode: VNode, selector: string): Array { - const nodes = findAllVNodes(vNode) - const filteredNodes = nodes.filter(node => nodeMatchesSelector(node, selector)) - return removeDuplicateNodes(filteredNodes) -} diff --git a/src/lib/find-vnodes-by-ref.js b/src/lib/find-vnodes-by-ref.js new file mode 100644 index 000000000..3c8b871eb --- /dev/null +++ b/src/lib/find-vnodes-by-ref.js @@ -0,0 +1,15 @@ +// @flow + +import { removeDuplicateNodes, findAllVNodes } from './vnode-utils' + +function nodeMatchesRef (node: VNode, refName: string): boolean { + return node.data && node.data.ref === refName +} + +export default function findVNodesByRef (vNode: VNode, refName: string): Array { + const nodes = findAllVNodes(vNode) + const refFilteredNodes = nodes.filter(node => nodeMatchesRef(node, refName)) + // Only return refs defined on top-level VNode to provide the same behavior as selecting via vm.$ref.{someRefName} + const mainVNodeFilteredNodes = refFilteredNodes.filter(node => !!vNode.context.$refs[node.data.ref]) + return removeDuplicateNodes(mainVNodeFilteredNodes) +} diff --git a/src/lib/find-vnodes-by-selector.js b/src/lib/find-vnodes-by-selector.js new file mode 100644 index 000000000..bf8dab9cf --- /dev/null +++ b/src/lib/find-vnodes-by-selector.js @@ -0,0 +1,13 @@ +// @flow + +import { removeDuplicateNodes, findAllVNodes } from './vnode-utils' + +function nodeMatchesSelector (node: VNode, selector: string): boolean { + return node.elm && node.elm.getAttribute && node.elm.matches(selector) +} + +export default function findVNodesBySelector (vNode: VNode, selector: string): Array { + const nodes = findAllVNodes(vNode) + const filteredNodes = nodes.filter(node => nodeMatchesSelector(node, selector)) + return removeDuplicateNodes(filteredNodes) +} diff --git a/src/lib/get-selector-type.js b/src/lib/get-selector-type.js new file mode 100644 index 000000000..955898708 --- /dev/null +++ b/src/lib/get-selector-type.js @@ -0,0 +1,32 @@ +// @flow + +import { isDomSelector, isVueComponent, isRefSelector } from './validators.js' +import { throwError } from '../lib/util' + +export const selectorTypes = { + DOM_SELECTOR: 'DOM_SELECTOR', + VUE_COMPONENT: 'VUE_COMPONENT', + OPTIONS_OBJECT: 'OPTIONS_OBJECT' +} + +function getSelectorType (selector: Selector): string | void { + if (isDomSelector(selector)) { + return selectorTypes.DOM_SELECTOR + } + + if (isVueComponent(selector)) { + return selectorTypes.VUE_COMPONENT + } + + if (isRefSelector(selector)) { + return selectorTypes.OPTIONS_OBJECT + } +} + +export default function getSelectorTypeOrThrow (selector: Selector, methodName: string): string | void { + const selectorType = getSelectorType(selector) + if (!selectorType) { + throwError(`wrapper.${methodName}() must be passed a valid CSS selector, Vue constructor, or valid find option object`) + } + return selectorType +} diff --git a/src/lib/validators.js b/src/lib/validators.js index d79a1b202..3677b7a8d 100644 --- a/src/lib/validators.js +++ b/src/lib/validators.js @@ -43,5 +43,32 @@ export function isValidSelector (selector: any): boolean { return true } - return isVueComponent(selector) + if (isVueComponent(selector)) { + return true + } + + return isRefSelector(selector) +} + +export function isRefSelector (refOptionsObject: any) { + if (typeof refOptionsObject !== 'object') { + return false + } + + if (refOptionsObject === null) { + return false + } + + const validFindKeys = ['ref'] + const entries = Object.entries(refOptionsObject) + + if (!entries.length) { + return false + } + + const isValid = entries.every(([key, value]) => { + return validFindKeys.includes(key) && typeof value === 'string' + }) + + return isValid } diff --git a/src/lib/vnode-utils.js b/src/lib/vnode-utils.js new file mode 100644 index 000000000..8d9692e7c --- /dev/null +++ b/src/lib/vnode-utils.js @@ -0,0 +1,28 @@ +// @flow + +export function findAllVNodes (vnode: VNode, nodes: Array = []): Array { + nodes.push(vnode) + + if (Array.isArray(vnode.children)) { + vnode.children.forEach((childVNode) => { + findAllVNodes(childVNode, nodes) + }) + } + + if (vnode.child) { + findAllVNodes(vnode.child._vnode, nodes) + } + + return nodes +} + +export function removeDuplicateNodes (vNodes: Array): Array { + const uniqueNodes = [] + vNodes.forEach((vNode) => { + const exists = uniqueNodes.some(node => vNode.elm === node.elm) + if (!exists) { + uniqueNodes.push(vNode) + } + }) + return uniqueNodes +} diff --git a/src/wrappers/wrapper.js b/src/wrappers/wrapper.js index 555b2f305..56a4e5a48 100644 --- a/src/wrappers/wrapper.js +++ b/src/wrappers/wrapper.js @@ -1,9 +1,10 @@ // @flow import Vue from 'vue' -import { isValidSelector } from '../lib/validators' +import getSelectorTypeOrThrow, { selectorTypes } from '../lib/get-selector-type' import findVueComponents, { vmCtorMatchesName } from '../lib/find-vue-components' -import findMatchingVNodes from '../lib/find-matching-vnodes' +import findVNodesBySelector from '../lib/find-vnodes-by-selector' +import findVNodesByRef from '../lib/find-vnodes-by-ref' import VueWrapper from './vue-wrapper' import WrapperArray from './wrapper-array' import ErrorWrapper from './error-wrapper' @@ -36,16 +37,22 @@ export default class Wrapper implements BaseWrapper { * Checks if wrapper contains provided selector. */ contains (selector: Selector) { - if (!isValidSelector(selector)) { - throwError('wrapper.contains() must be passed a valid CSS selector or a Vue constructor') - } + const selectorType = getSelectorTypeOrThrow(selector, 'contains') - if (typeof selector === 'object') { + if (selectorType === selectorTypes.VUE_COMPONENT) { const vm = this.vm || this.vnode.context.$root return findVueComponents(vm, selector.name).length > 0 } - if (typeof selector === 'string' && this.element instanceof HTMLElement) { + if (selectorType === selectorTypes.OPTIONS_OBJECT) { + if (!this.isVueComponent) { + throwError('$ref selectors can only be used on Vue component wrappers') + } + const nodes = findVNodesByRef(this.vnode, selector.ref) + return nodes.length > 0 + } + + if (selectorType === selectorTypes.DOM_SELECTOR && this.element instanceof HTMLElement) { return this.element.querySelectorAll(selector).length > 0 } @@ -174,12 +181,10 @@ export default class Wrapper implements BaseWrapper { /** * Finds first node in tree of the current wrapper that matches the provided selector. */ - find (selector: string): Wrapper | ErrorWrapper | VueWrapper { - if (!isValidSelector(selector)) { - throwError('wrapper.find() must be passed a valid CSS selector or a Vue constructor') - } + find (selector: Selector): Wrapper | ErrorWrapper | VueWrapper { + const selectorType = getSelectorTypeOrThrow(selector, 'find') - if (typeof selector === 'object') { + if (selectorType === selectorTypes.VUE_COMPONENT) { if (!selector.name) { throwError('.find() requires component to have a name property') } @@ -191,7 +196,18 @@ export default class Wrapper implements BaseWrapper { return new VueWrapper(components[0], this.options) } - const nodes = findMatchingVNodes(this.vnode, selector) + if (selectorType === selectorTypes.OPTIONS_OBJECT) { + if (!this.isVueComponent) { + throwError('$ref selectors can only be used on Vue component wrappers') + } + const nodes = findVNodesByRef(this.vnode, selector.ref) + if (nodes.length === 0) { + return new ErrorWrapper(`ref="${selector.ref}"`) + } + return new Wrapper(nodes[0], this.update, this.options) + } + + const nodes = findVNodesBySelector(this.vnode, selector) if (nodes.length === 0) { return new ErrorWrapper(selector) @@ -203,11 +219,9 @@ export default class Wrapper implements BaseWrapper { * Finds node in tree of the current wrapper that matches the provided selector. */ findAll (selector: Selector): WrapperArray { - if (!isValidSelector(selector)) { - throwError('wrapper.findAll() must be passed a valid CSS selector or a Vue constructor') - } + const selectorType = getSelectorTypeOrThrow(selector, 'findAll') - if (typeof selector === 'object') { + if (selectorType === selectorTypes.VUE_COMPONENT) { if (!selector.name) { throwError('.findAll() requires component to have a name property') } @@ -216,11 +230,19 @@ export default class Wrapper implements BaseWrapper { return new WrapperArray(components.map(component => new VueWrapper(component, this.options))) } + if (selectorType === selectorTypes.OPTIONS_OBJECT) { + if (!this.isVueComponent) { + throwError('$ref selectors can only be used on Vue component wrappers') + } + const nodes = findVNodesByRef(this.vnode, selector.ref) + return new WrapperArray(nodes.map(node => new Wrapper(node, this.update, this.options))) + } + function nodeMatchesSelector (node, selector) { return node.elm && node.elm.getAttribute && node.elm.matches(selector) } - const nodes = findMatchingVNodes(this.vnode, selector) + const nodes = findVNodesBySelector(this.vnode, selector) const matchingNodes = nodes.filter(node => nodeMatchesSelector(node, selector)) return new WrapperArray(matchingNodes.map(node => new Wrapper(node, this.update, this.options))) @@ -237,20 +259,23 @@ export default class Wrapper implements BaseWrapper { * Checks if node matches selector */ is (selector: Selector): boolean { - if (!isValidSelector(selector)) { - throwError('wrapper.is() must be passed a valid CSS selector or a Vue constructor') - } + const selectorType = getSelectorTypeOrThrow(selector, 'is') - if (typeof selector === 'object') { - if (!this.isVueComponent) { - return false - } + if (selectorType === selectorTypes.VUE_COMPONENT && this.isVueComponent) { if (typeof selector.name !== 'string') { throwError('a Component used as a selector must have a name property') } return vmCtorMatchesName(this.vm, selector.name) } + if (selectorType === selectorTypes.OPTIONS_OBJECT) { + throwError('$ref selectors can not be used with wrapper.is()') + } + + if (typeof selector === 'object') { + return false + } + return !!(this.element && this.element.getAttribute && this.element.matches(selector)) diff --git a/test/resources/components/component-with-child.vue b/test/resources/components/component-with-child.vue index c34fa0182..b6a60d0aa 100644 --- a/test/resources/components/component-with-child.vue +++ b/test/resources/components/component-with-child.vue @@ -1,7 +1,7 @@ diff --git a/test/resources/components/component-with-v-for.vue b/test/resources/components/component-with-v-for.vue index 30a7b4a62..4c3013762 100644 --- a/test/resources/components/component-with-v-for.vue +++ b/test/resources/components/component-with-v-for.vue @@ -1,6 +1,6 @@ diff --git a/test/unit/specs/mount/Wrapper/contains.spec.js b/test/unit/specs/mount/Wrapper/contains.spec.js index 0b8fdaed3..5572dd1fa 100644 --- a/test/unit/specs/mount/Wrapper/contains.spec.js +++ b/test/unit/specs/mount/Wrapper/contains.spec.js @@ -15,19 +15,40 @@ describe('contains', () => { expect(wrapper.contains(Component)).to.equal(true) }) + it('returns true if wrapper contains element specified by ref selector', () => { + const compiled = compileToFunctions('
') + const wrapper = mount(compiled) + expect(wrapper.contains({ ref: 'foo' })).to.equal(true) + }) + + it('throws an error when ref selector is called on a wrapper that is not a Vue component', () => { + const compiled = compileToFunctions('
') + const wrapper = mount(compiled) + const a = wrapper.find('a') + const message = '[vue-test-utils]: $ref selectors can only be used on Vue component wrappers' + const fn = () => a.contains({ ref: 'foo' }) + expect(fn).to.throw().with.property('message', message) + }) + it('returns false if wrapper does not contain element', () => { const compiled = compileToFunctions('
') const wrapper = mount(compiled) expect(wrapper.contains('doesntexist')).to.equal(false) }) + it('returns false if wrapper does not contain element specified by ref selector', () => { + const compiled = compileToFunctions('
') + const wrapper = mount(compiled) + expect(wrapper.contains({ ref: 'foo' })).to.equal(false) + }) + it('throws an error if selector is not a valid selector', () => { const wrapper = mount(Component) const invalidSelectors = [ - undefined, null, NaN, 0, 2, true, false, () => {}, {}, { name: undefined }, [] + undefined, null, NaN, 0, 2, true, false, () => {}, {}, { name: undefined }, { ref: 'foo', nope: true }, [] ] invalidSelectors.forEach((invalidSelector) => { - const message = '[vue-test-utils]: wrapper.contains() must be passed a valid CSS selector or a Vue constructor' + const message = '[vue-test-utils]: wrapper.contains() must be passed a valid CSS selector, Vue constructor, or valid find option object' const fn = () => wrapper.contains(invalidSelector) expect(fn).to.throw().with.property('message', message) }) diff --git a/test/unit/specs/mount/Wrapper/find.spec.js b/test/unit/specs/mount/Wrapper/find.spec.js index 38c99f848..df2ae9981 100644 --- a/test/unit/specs/mount/Wrapper/find.spec.js +++ b/test/unit/specs/mount/Wrapper/find.spec.js @@ -75,7 +75,7 @@ describe('find', () => { it('throws an error when passed an invalid DOM selector', () => { const compiled = compileToFunctions('
') const wrapper = mount(compiled) - const message = '[vue-test-utils]: wrapper.find() must be passed a valid CSS selector or a Vue constructor' + const message = '[vue-test-utils]: wrapper.find() must be passed a valid CSS selector, Vue constructor, or valid find option object' const fn = () => wrapper.find('[href=&6"/"]') expect(fn).to.throw().with.property('message', message) }) @@ -103,7 +103,7 @@ describe('find', () => { expect(wrapper.find(Component)).to.be.instanceOf(Wrapper) }) - it('returns correct number of Vue Wrapper when component has a v-for', () => { + it('returns correct number of Vue Wrappers when component has a v-for', () => { const items = [{ id: 1 }, { id: 2 }, { id: 3 }] const wrapper = mount(ComponentWithVFor, { propsData: { items }}) expect(wrapper.find(Component)).to.be.instanceOf(Wrapper) @@ -144,13 +144,46 @@ describe('find', () => { expect(error.selector).to.equal('Component') }) + it('returns Wrapper of elements matching the ref in options object', () => { + const compiled = compileToFunctions('

') + const wrapper = mount(compiled) + expect(wrapper.find({ ref: 'foo' })).to.be.instanceOf(Wrapper) + }) + + it('returns Wrapper of Vue Components matching the ref in options object', () => { + const wrapper = mount(ComponentWithChild) + expect(wrapper.find({ ref: 'child' })).to.be.instanceOf(Wrapper) + }) + + it('throws an error when ref selector is called on a wrapper that is not a Vue component', () => { + const compiled = compileToFunctions('
') + const wrapper = mount(compiled) + const a = wrapper.find('a') + const message = '[vue-test-utils]: $ref selectors can only be used on Vue component wrappers' + const fn = () => a.find({ ref: 'foo' }) + expect(fn).to.throw().with.property('message', message) + }) + + it('returns Wrapper matching ref selector in options object passed if nested in a transition', () => { + const compiled = compileToFunctions('
') + const wrapper = mount(compiled) + expect(wrapper.find({ ref: 'foo' })).to.be.instanceOf(Wrapper) + }) + + it('returns empty Wrapper with error if no nodes are found via ref in options object', () => { + const wrapper = mount(Component) + const error = wrapper.find({ ref: 'foo' }) + expect(error).to.be.instanceOf(ErrorWrapper) + expect(error.selector).to.equal('ref="foo"') + }) + it('throws an error if selector is not a valid selector', () => { const wrapper = mount(Component) const invalidSelectors = [ - undefined, null, NaN, 0, 2, true, false, () => {}, {}, { name: undefined }, [] + undefined, null, NaN, 0, 2, true, false, () => {}, {}, { name: undefined }, { ref: 'foo', nope: true }, [] ] invalidSelectors.forEach((invalidSelector) => { - const message = '[vue-test-utils]: wrapper.find() must be passed a valid CSS selector or a Vue constructor' + const message = '[vue-test-utils]: wrapper.find() must be passed a valid CSS selector, Vue constructor, or valid find option object' const fn = () => wrapper.find(invalidSelector) expect(fn).to.throw().with.property('message', message) }) diff --git a/test/unit/specs/mount/Wrapper/findAll.spec.js b/test/unit/specs/mount/Wrapper/findAll.spec.js index ecafebc9a..3cee023ee 100644 --- a/test/unit/specs/mount/Wrapper/findAll.spec.js +++ b/test/unit/specs/mount/Wrapper/findAll.spec.js @@ -82,7 +82,7 @@ describe('findAll', () => { it('throws an error when passed an invalid DOM selector', () => { const compiled = compileToFunctions('
') const wrapper = mount(compiled) - const message = '[vue-test-utils]: wrapper.findAll() must be passed a valid CSS selector or a Vue constructor' + const message = '[vue-test-utils]: wrapper.findAll() must be passed a valid CSS selector, Vue constructor, or valid find option object' const fn = () => wrapper.findAll('[href=&6"/"]') expect(fn).to.throw().with.property('message', message) }) @@ -161,13 +161,53 @@ describe('findAll', () => { expect(preArray.wrappers).to.deep.equal([]) }) + it('returns an array of Wrapper of elements matching the ref in options object', () => { + const compiled = compileToFunctions('
') + const wrapper = mount(compiled) + const fooArr = wrapper.findAll({ ref: 'foo' }) + expect(fooArr).to.be.instanceOf(WrapperArray) + expect(fooArr.length).to.equal(1) + }) + + it('throws an error when ref selector is called on a wrapper that is not a Vue component', () => { + const compiled = compileToFunctions('
') + const wrapper = mount(compiled) + const a = wrapper.find('a') + const message = '[vue-test-utils]: $ref selectors can only be used on Vue component wrappers' + const fn = () => a.findAll({ ref: 'foo' }) + expect(fn).to.throw().with.property('message', message) + }) + + it('returns an array of Wrapper of elements matching the ref in options object if they are nested in a transition', () => { + const compiled = compileToFunctions('
') + const wrapper = mount(compiled) + const divArr = wrapper.findAll({ ref: 'foo' }) + expect(divArr).to.be.instanceOf(WrapperArray) + expect(divArr.length).to.equal(1) + }) + + it('returns correct number of Vue Wrapper when component has a v-for and matches the ref in options object', () => { + const items = [{ id: 1 }, { id: 2 }, { id: 3 }] + const wrapper = mount(ComponentWithVFor, { propsData: { items }}) + const componentArray = wrapper.findAll({ ref: 'item' }) + expect(componentArray).to.be.instanceOf(WrapperArray) + expect(componentArray.length).to.equal(items.length) + }) + + it('returns VueWrapper with length 0 if no nodes matching the ref in options object are found', () => { + const wrapper = mount(Component) + const preArray = wrapper.findAll({ ref: 'foo' }) + expect(preArray.length).to.equal(0) + expect(preArray.wrappers).to.deep.equal([]) + }) + it('throws an error if selector is not a valid selector', () => { const wrapper = mount(Component) const invalidSelectors = [ - undefined, null, NaN, 0, 2, true, false, () => {}, {}, { name: undefined }, [] + undefined, null, NaN, 0, 2, true, false, () => {}, {}, { name: undefined }, { ref: 'foo', nope: true }, [] ] invalidSelectors.forEach((invalidSelector) => { - const message = '[vue-test-utils]: wrapper.findAll() must be passed a valid CSS selector or a Vue constructor' + const message = '[vue-test-utils]: wrapper.findAll() must be passed a valid CSS selector, Vue constructor, or valid find option object' const fn = () => wrapper.findAll(invalidSelector) expect(fn).to.throw().with.property('message', message) }) diff --git a/test/unit/specs/mount/Wrapper/is.spec.js b/test/unit/specs/mount/Wrapper/is.spec.js index a10c043de..80b879298 100644 --- a/test/unit/specs/mount/Wrapper/is.spec.js +++ b/test/unit/specs/mount/Wrapper/is.spec.js @@ -63,6 +63,15 @@ describe('is', () => { expect(wrapper.is('#p')).to.equal(false) }) + it('throws error if ref options object is passed', () => { + const compiled = compileToFunctions('
') + const wrapper = mount(compiled) + + const message = '[vue-test-utils]: $ref selectors can not be used with wrapper.is()' + const fn = () => wrapper.is({ ref: 'foo' }) + expect(fn).to.throw().with.property('message', message) + }) + it('throws error if component passed to use as identifier does not have a name', () => { const compiled = compileToFunctions('
') const wrapper = mount(compiled) @@ -76,10 +85,10 @@ describe('is', () => { const compiled = compileToFunctions('
') const wrapper = mount(compiled) const invalidSelectors = [ - undefined, null, NaN, 0, 2, true, false, () => {}, {}, { name: undefined }, [] + undefined, null, NaN, 0, 2, true, false, () => {}, {}, { name: undefined }, { ref: 'foo', nope: true }, [] ] invalidSelectors.forEach((invalidSelector) => { - const message = '[vue-test-utils]: wrapper.is() must be passed a valid CSS selector or a Vue constructor' + const message = '[vue-test-utils]: wrapper.is() must be passed a valid CSS selector, Vue constructor, or valid find option object' const fn = () => wrapper.is(invalidSelector) expect(fn).to.throw().with.property('message', message) }) diff --git a/test/unit/specs/mount/WrapperArray/contains.spec.js b/test/unit/specs/mount/WrapperArray/contains.spec.js index aed9742d3..9404ed030 100644 --- a/test/unit/specs/mount/WrapperArray/contains.spec.js +++ b/test/unit/specs/mount/WrapperArray/contains.spec.js @@ -30,7 +30,7 @@ describe('contains', () => { undefined, null, NaN, 0, 2, true, false, () => {}, {}, { name: undefined }, [] ] invalidSelectors.forEach((invalidSelector) => { - const message = '[vue-test-utils]: wrapper.contains() must be passed a valid CSS selector or a Vue constructor' + const message = '[vue-test-utils]: wrapper.contains() must be passed a valid CSS selector, Vue constructor, or valid find option object' expect(() => pArr.contains(invalidSelector)).to.throw().with.property('message', message) }) }) diff --git a/test/unit/specs/mount/WrapperArray/is.spec.js b/test/unit/specs/mount/WrapperArray/is.spec.js index 48c000a0f..b0bc590fc 100644 --- a/test/unit/specs/mount/WrapperArray/is.spec.js +++ b/test/unit/specs/mount/WrapperArray/is.spec.js @@ -42,7 +42,7 @@ describe('is', () => { undefined, null, NaN, 0, 2, true, false, () => {}, {}, { name: undefined }, [] ] invalidSelectors.forEach((invalidSelector) => { - const message = '[vue-test-utils]: wrapper.is() must be passed a valid CSS selector or a Vue constructor' + const message = '[vue-test-utils]: wrapper.is() must be passed a valid CSS selector, Vue constructor, or valid find option object' const fn = () => wrapper.findAll('div').is(invalidSelector) expect(fn).to.throw().with.property('message', message) }) diff --git a/types/index.d.ts b/types/index.d.ts index cf71d414a..914970fa9 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -25,6 +25,13 @@ type Stubs = { [key: string]: Component | string | true } | string[] +/** + * Utility type for ref options object that can be used as a Selector + */ +type RefSelector = { + ref: string +} + /** * Base class of Wrapper and WrapperArray * It has common methods on both Wrapper and WrapperArray @@ -60,11 +67,13 @@ interface Wrapper extends BaseWrapper { find (selector: ComponentOptions): Wrapper find (selector: FunctionalComponentOptions): Wrapper find (selector: string): Wrapper + find (selector: RefSelector): Wrapper findAll = VueClass> (selector: Ctor): WrapperArray findAll (selector: ComponentOptions): WrapperArray findAll (selector: FunctionalComponentOptions): WrapperArray findAll (selector: string): WrapperArray + findAll (selector: RefSelector): WrapperArray html (): string text (): string From a7641b4b1b7ceeb4daae1a0e2ebfae9679123ec1 Mon Sep 17 00:00:00 2001 From: eddyerburgh Date: Sat, 11 Nov 2017 07:07:06 +0000 Subject: [PATCH 0058/1136] feat(config): add config --- src/config.js | 9 ++++ src/index.js | 2 + src/lib/create-instance.js | 12 ++++- src/lib/stub-components.js | 9 +++- test/unit/specs/config.spec.js | 55 +++++++++++++++++++++ test/unit/specs/mount.spec.js | 10 ++-- test/unit/specs/mount/options/stubs.spec.js | 14 +++++- test/unit/specs/shallow.spec.js | 2 +- 8 files changed, 104 insertions(+), 9 deletions(-) create mode 100644 src/config.js create mode 100644 test/unit/specs/config.spec.js diff --git a/src/config.js b/src/config.js new file mode 100644 index 000000000..d23364c40 --- /dev/null +++ b/src/config.js @@ -0,0 +1,9 @@ +import TransitionStub from './components/TransitionStub' +import TransitionGroupStub from './components/TransitionGroupStub' + +export default { + stubs: { + transition: TransitionStub, + 'transition-group': TransitionGroupStub + } +} diff --git a/src/index.js b/src/index.js index f1ed854b1..40c25fa65 100644 --- a/src/index.js +++ b/src/index.js @@ -3,9 +3,11 @@ import mount from './mount' import createLocalVue from './create-local-vue' import TransitionStub from './components/TransitionStub' import TransitionGroupStub from './components/TransitionGroupStub' +import config from './config' export default { createLocalVue, + config, mount, shallow, TransitionStub, diff --git a/src/lib/create-instance.js b/src/lib/create-instance.js index bf9220120..7f98e9b3f 100644 --- a/src/lib/create-instance.js +++ b/src/lib/create-instance.js @@ -10,6 +10,7 @@ import { throwError } from './util' import cloneDeep from 'lodash/cloneDeep' import { compileTemplate } from './compile-template' import createLocalVue from '../create-local-vue' +import config from '../config' export default function createConstructor ( component: Component, @@ -44,8 +45,15 @@ export default function createConstructor ( addProvide(component, options) } - if (options.stubs) { - stubComponents(component, options.stubs) + if (options.stubs || Object.keys(config.stubs).length > 0) { + if (Array.isArray(options.stubs)) { + stubComponents(component, options.stubs) + } else { + stubComponents(component, { + ...config.stubs, + ...options.stubs + }) + } } if (!component.render && component.template && !component.functional) { diff --git a/src/lib/stub-components.js b/src/lib/stub-components.js index d531243e2..93d309e2a 100644 --- a/src/lib/stub-components.js +++ b/src/lib/stub-components.js @@ -76,13 +76,20 @@ export function stubComponents (component: Component, stubs: Object): void { if (Array.isArray(stubs)) { stubs.forEach(stub => { + if (stub === false) { + return + } + if (typeof stub !== 'string') { - throwError('each item in options.stub must be a string') + throwError('each item in an options.stubs array must be a string') } component.components[stub] = createBlankStub({}) }) } else { Object.keys(stubs).forEach(stub => { + if (stubs[stub] === false) { + return + } if (!isValidStub(stubs[stub])) { throwError('options.stub values must be passed a string or component') } diff --git a/test/unit/specs/config.spec.js b/test/unit/specs/config.spec.js new file mode 100644 index 000000000..6372a46f0 --- /dev/null +++ b/test/unit/specs/config.spec.js @@ -0,0 +1,55 @@ +import mount from '~src/mount' +import TransitionStub from '~src/components/TransitionStub' +import TransitionGroupStub from '~src/components/TransitionGroupStub' +import config from '~src/config' + +describe('config', () => { + beforeEach(() => { + TransitionGroupStub.name = 'another-temp-name' + TransitionStub.name = 'a-temp-name' + }) + + afterEach(() => { + TransitionGroupStub.name = 'transition-group' + TransitionStub.name = 'transition' + }) + it('stubs transition and transition-group by default', () => { + const testComponent = { + template: ` +
+

+

+

+ ` + } + const wrapper = mount(testComponent) + expect(wrapper.contains(TransitionStub)).to.equal(true) + expect(wrapper.contains(TransitionGroupStub)).to.equal(true) + }) + + it('doesn\'t stub transition when config.stubs.transition is set to false', () => { + const testComponent = { + template: ` +
+

+

+ ` + } + config.stubs.transition = false + const wrapper = mount(testComponent) + expect(wrapper.contains(TransitionStub)).to.equal(false) + }) + + it('doesn\'t stub transition when config.stubs.transition is set to false', () => { + const testComponent = { + template: ` +
+

+

+ ` + } + config.stubs['transition-group'] = false + const wrapper = mount(testComponent) + expect(wrapper.contains(TransitionGroupStub)).to.equal(false) + }) +}) diff --git a/test/unit/specs/mount.spec.js b/test/unit/specs/mount.spec.js index 29c086729..dcb8d4dcd 100644 --- a/test/unit/specs/mount.spec.js +++ b/test/unit/specs/mount.spec.js @@ -86,12 +86,14 @@ describe('mount', () => { expect(wrapper.html()).to.equal(`
foo
`) }) - it('throws an error when the component fails to mount', () => { - expect(() => mount({ + it.skip('throws an error when the component fails to mount', () => { + const TestComponent = { template: '
', mounted: function () { - throw (new Error('Error')) + throw new Error('Error in mounted') } - })).to.throw() + } + const fn = () => mount(TestComponent) + expect(fn).to.throw() }) }) diff --git a/test/unit/specs/mount/options/stubs.spec.js b/test/unit/specs/mount/options/stubs.spec.js index 269ef61a8..db415724b 100644 --- a/test/unit/specs/mount/options/stubs.spec.js +++ b/test/unit/specs/mount/options/stubs.spec.js @@ -2,19 +2,24 @@ import mount from '~src/mount' import ComponentWithChild from '~resources/components/component-with-child.vue' import ComponentWithNestedChildren from '~resources/components/component-with-nested-children.vue' import Component from '~resources/components/component.vue' +import config from '~src/config' describe('mount.stub', () => { let info let warn + let configStubsSave beforeEach(() => { info = sinon.stub(console, 'info') warn = sinon.stub(console, 'error') + configStubsSave = config.stubs + config.stubs = {} }) afterEach(() => { info.restore() warn.restore() + config.stubs = configStubsSave }) it('replaces component with template string ', () => { @@ -101,7 +106,7 @@ describe('mount.stub', () => { render: h => h('registered-component') } const invalidValues = [{}, [], 3] - const error = '[vue-test-utils]: each item in options.stub must be a string' + const error = '[vue-test-utils]: each item in an options.stubs array must be a string' invalidValues.forEach(invalidValue => { const fn = () => mount(ComponentWithGlobalComponent, { stubs: [invalidValue] @@ -129,6 +134,13 @@ describe('mount.stub', () => { } require.cache[require.resolve('vue-template-compiler')].exports.compileToFunctions = compilerSave }) + it('does not stub component when set to false', () => { + const wrapper = mount(ComponentWithChild, { + stubs: { + ChildComponent: false + }}) + expect(wrapper.find('span').contains('div')).to.equal(true) + }) it('throws an error when passed an invalid value as stub', () => { const error = '[vue-test-utils]: options.stub values must be passed a string or component' diff --git a/test/unit/specs/shallow.spec.js b/test/unit/specs/shallow.spec.js index 066a44868..c969ffe7e 100644 --- a/test/unit/specs/shallow.spec.js +++ b/test/unit/specs/shallow.spec.js @@ -75,7 +75,7 @@ describe('shallow', () => { expect(info.called).to.equal(false) }) - it('throws an error when the component fails to mount', () => { + it.skip('throws an error when the component fails to mount', () => { expect(() => shallow({ template: '
', mounted: function () { From a2b1384dc3b885d4f8c57e678361aec515cfc023 Mon Sep 17 00:00:00 2001 From: eddyerburgh Date: Sat, 11 Nov 2017 08:20:30 +0000 Subject: [PATCH 0059/1136] refactor: move extractOptions to seperate file --- flow/options.flow.js | 1 + src/lib/add-provide.js | 10 ++++----- src/lib/create-instance.js | 39 +++++++++++++++------------------- src/options/extract-options.js | 30 ++++++++++++++++++++++++++ test/unit/specs/config.spec.js | 33 ++++++++++++++++++++++++++++ 5 files changed, 85 insertions(+), 28 deletions(-) create mode 100644 src/options/extract-options.js diff --git a/flow/options.flow.js b/flow/options.flow.js index 8345524d6..8d3cf00f7 100644 --- a/flow/options.flow.js +++ b/flow/options.flow.js @@ -3,6 +3,7 @@ declare type Options = { // eslint-disable-line no-undef mocks?: Object, slots?: Object, localVue?: Component, + provide?: Object, stubs?: Object, context?: Object, clone?: boolean, diff --git a/src/lib/add-provide.js b/src/lib/add-provide.js index 5c60a5ac5..f36c0a4f4 100644 --- a/src/lib/add-provide.js +++ b/src/lib/add-provide.js @@ -1,9 +1,7 @@ -function addProvide (component, options) { - const provide = typeof options.provide === 'function' - ? options.provide - : Object.assign({}, options.provide) - - delete options.provide +function addProvide (component, optionProvide, options) { + const provide = typeof optionProvide === 'function' + ? optionProvide + : Object.assign({}, optionProvide) options.beforeCreate = function vueTestUtilBeforeCreate () { this._provided = typeof provide === 'function' diff --git a/src/lib/create-instance.js b/src/lib/create-instance.js index 7f98e9b3f..7b68bd656 100644 --- a/src/lib/create-instance.js +++ b/src/lib/create-instance.js @@ -10,20 +10,22 @@ import { throwError } from './util' import cloneDeep from 'lodash/cloneDeep' import { compileTemplate } from './compile-template' import createLocalVue from '../create-local-vue' -import config from '../config' +import extractOptions from '../options/extract-options' export default function createConstructor ( component: Component, options: Options ): Component { - const vue = options.localVue || createLocalVue() + const mountingOptions = extractOptions(options) - if (options.mocks) { - addMocks(options.mocks, vue) + const vue = mountingOptions.localVue || createLocalVue() + + if (mountingOptions.mocks) { + addMocks(mountingOptions.mocks, vue) } if (component.functional) { - if (options.context && typeof options.context !== 'object') { + if (mountingOptions.context && typeof mountingOptions.context !== 'object') { throwError('mount.context must be an object') } const clonedComponent = cloneDeep(component) @@ -31,29 +33,22 @@ export default function createConstructor ( render (h) { return h( clonedComponent, - options.context || component.FunctionalRenderContext + mountingOptions.context || component.FunctionalRenderContext ) } } - } else if (options.context) { + } else if (mountingOptions.context) { throwError( 'mount.context can only be used when mounting a functional component' ) } - if (options.provide) { - addProvide(component, options) + if (mountingOptions.provide) { + addProvide(component, mountingOptions.provide, options) } - if (options.stubs || Object.keys(config.stubs).length > 0) { - if (Array.isArray(options.stubs)) { - stubComponents(component, options.stubs) - } else { - stubComponents(component, { - ...config.stubs, - ...options.stubs - }) - } + if (mountingOptions.stubs) { + stubComponents(component, mountingOptions.stubs) } if (!component.render && component.template && !component.functional) { @@ -64,11 +59,11 @@ export default function createConstructor ( const vm = new Constructor(options) - addAttrs(vm, options.attrs) - addListeners(vm, options.listeners) + addAttrs(vm, mountingOptions.attrs) + addListeners(vm, mountingOptions.listeners) - if (options.slots) { - addSlots(vm, options.slots) + if (mountingOptions.slots) { + addSlots(vm, mountingOptions.slots) } return vm diff --git a/src/options/extract-options.js b/src/options/extract-options.js new file mode 100644 index 000000000..fd4e9a676 --- /dev/null +++ b/src/options/extract-options.js @@ -0,0 +1,30 @@ +// @flow +import config from '../config' + +function getStubs (optionStubs) { + if (optionStubs || Object.keys(config.stubs).length > 0) { + if (Array.isArray(optionStubs)) { + return optionStubs + } else { + return { + ...config.stubs, + ...optionStubs + } + } + } +} + +export default function extractOptions ( + options: Options +): Options { + return { + mocks: options.mocks, + context: options.context, + provide: options.provide, + stubs: getStubs(options.stubs), + attrs: options.attrs, + listeners: options.listeners, + slots: options.slots, + localVue: options.localVue + } +} diff --git a/test/unit/specs/config.spec.js b/test/unit/specs/config.spec.js index 6372a46f0..8267c75cf 100644 --- a/test/unit/specs/config.spec.js +++ b/test/unit/specs/config.spec.js @@ -13,6 +13,7 @@ describe('config', () => { TransitionGroupStub.name = 'transition-group' TransitionStub.name = 'transition' }) + it('stubs transition and transition-group by default', () => { const testComponent = { template: ` @@ -52,4 +53,36 @@ describe('config', () => { const wrapper = mount(testComponent) expect(wrapper.contains(TransitionGroupStub)).to.equal(false) }) + + it('doesn\'t stub transition when config.stubs is set to false', () => { + const configStubsSave = config.stubs + config.stubs = false + const testComponent = { + template: ` +
+

+

+ ` + } + const wrapper = mount(testComponent) + expect(wrapper.contains(TransitionGroupStub)).to.equal(false) + expect(wrapper.contains(TransitionStub)).to.equal(false) + config.stubs = configStubsSave + }) + + it('doesn\'t stub transition when config.stubs is set to a string', () => { + const configStubsSave = config.stubs + config.stubs = 'a string' + const testComponent = { + template: ` +
+

+

+ ` + } + const wrapper = mount(testComponent) + expect(wrapper.contains(TransitionGroupStub)).to.equal(false) + expect(wrapper.contains(TransitionStub)).to.equal(false) + config.stubs = configStubsSave + }) }) From d64503732234cd2bd7481a61860b2ba44fc53a4c Mon Sep 17 00:00:00 2001 From: eddyerburgh Date: Sat, 11 Nov 2017 09:37:03 +0000 Subject: [PATCH 0060/1136] fix: delete mounting options before extending component --- src/lib/create-instance.js | 6 +++- src/options/delete-mounting-options.js | 12 +++++++ test/unit/specs/mount.spec.js | 43 ++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 src/options/delete-mounting-options.js diff --git a/src/lib/create-instance.js b/src/lib/create-instance.js index 7b68bd656..c5c575403 100644 --- a/src/lib/create-instance.js +++ b/src/lib/create-instance.js @@ -11,6 +11,7 @@ import cloneDeep from 'lodash/cloneDeep' import { compileTemplate } from './compile-template' import createLocalVue from '../create-local-vue' import extractOptions from '../options/extract-options' +import deleteMountingOptions from '../options/delete-mounting-options' export default function createConstructor ( component: Component, @@ -57,7 +58,10 @@ export default function createConstructor ( const Constructor = vue.extend(component) - const vm = new Constructor(options) + const instanceOptions = { ...options } + deleteMountingOptions(instanceOptions) + + const vm = new Constructor(instanceOptions) addAttrs(vm, mountingOptions.attrs) addListeners(vm, mountingOptions.listeners) diff --git a/src/options/delete-mounting-options.js b/src/options/delete-mounting-options.js new file mode 100644 index 000000000..41817399b --- /dev/null +++ b/src/options/delete-mounting-options.js @@ -0,0 +1,12 @@ +export default function deleteMountingOptions (options) { + delete options.custom + delete options.attachToDocument + delete options.mocks + delete options.slots + delete options.localVue + delete options.stubs + delete options.context + delete options.clone + delete options.attrs + delete options.listeners +} diff --git a/test/unit/specs/mount.spec.js b/test/unit/specs/mount.spec.js index dcb8d4dcd..fef593939 100644 --- a/test/unit/specs/mount.spec.js +++ b/test/unit/specs/mount.spec.js @@ -3,6 +3,7 @@ import { compileToFunctions } from 'vue-template-compiler' import mount from '~src/mount' import ComponentWithProps from '~resources/components/component-with-props.vue' import ComponentWithMixin from '~resources/components/component-with-mixin.vue' +import createLocalVue from '~src/create-local-vue' describe('mount', () => { it('returns new VueWrapper with mounted Vue instance if no options are passed', () => { @@ -86,6 +87,48 @@ describe('mount', () => { expect(wrapper.html()).to.equal(`
foo
`) }) + it('deletes mounting options before passing options to component', () => { + const wrapper = mount({ + template: `
foo
` + }, { + provide: { + 'prop': 'val' + }, + custom: 'custom', + attachToDocument: 'attachToDocument', + mocks: { + 'prop': 'val' + }, + slots: { + 'prop': 'val' + }, + localVue: createLocalVue(), + stubs: { + 'prop': 'val' + }, + clone: 'clone', + attrs: { + 'prop': 'val' + }, + listeners: { + 'prop': 'val' + } + }) + debugger + // provide is always a function on an the $options object + expect(typeof wrapper.vm.$options.provide).to.equal('function') + expect(wrapper.vm.$options.custom).to.equal(undefined) + expect(wrapper.vm.$options.attachToDocument).to.equal(undefined) + expect(wrapper.vm.$options.mocks).to.equal(undefined) + expect(wrapper.vm.$options.slots).to.equal(undefined) + expect(wrapper.vm.$options.localVue).to.equal(undefined) + expect(wrapper.vm.$options.stubs).to.equal(undefined) + expect(wrapper.vm.$options.context).to.equal(undefined) + expect(wrapper.vm.$options.clone).to.equal(undefined) + expect(wrapper.vm.$options.attrs).to.equal(undefined) + expect(wrapper.vm.$options.listeners).to.equal(undefined) + }) + it.skip('throws an error when the component fails to mount', () => { const TestComponent = { template: '
', From 0a46a2f5ab3fcc39f1317a566127616d47a12887 Mon Sep 17 00:00:00 2001 From: eddyerburgh Date: Sat, 11 Nov 2017 10:14:17 +0000 Subject: [PATCH 0061/1136] git st --- test/unit/specs/mount.spec.js | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/test/unit/specs/mount.spec.js b/test/unit/specs/mount.spec.js index fef593939..a04ec004e 100644 --- a/test/unit/specs/mount.spec.js +++ b/test/unit/specs/mount.spec.js @@ -5,6 +5,13 @@ import ComponentWithProps from '~resources/components/component-with-props.vue' import ComponentWithMixin from '~resources/components/component-with-mixin.vue' import createLocalVue from '~src/create-local-vue' +function injectSupported () { + console.log(Vue.version) + const version = Number(`${Vue.version.split('.')[0]}.${Vue.version.split('.')[1]}`) + console.log(version) + return version > 2.2 +} + describe('mount', () => { it('returns new VueWrapper with mounted Vue instance if no options are passed', () => { const compiled = compileToFunctions('
') @@ -89,7 +96,7 @@ describe('mount', () => { it('deletes mounting options before passing options to component', () => { const wrapper = mount({ - template: `
foo
` + render: h => h('div') }, { provide: { 'prop': 'val' @@ -114,9 +121,16 @@ describe('mount', () => { 'prop': 'val' } }) - debugger - // provide is always a function on an the $options object - expect(typeof wrapper.vm.$options.provide).to.equal('function') + const version = Number(`${Vue.version.split('.')[0]}.${Vue.version.split('.')[1]}`) + if (injectSupported()) { + // provide is added by Vue, it's a function in Vue > 2.3 + if (version > 2.3) { + expect(typeof wrapper.vm.$options.provide).to.equal('function') + } else { + expect(typeof wrapper.vm.$options.provide).to.equal('object') + } + } + expect(wrapper.vm.$options.custom).to.equal(undefined) expect(wrapper.vm.$options.attachToDocument).to.equal(undefined) expect(wrapper.vm.$options.mocks).to.equal(undefined) From ade28d5d1d8436c0125954154bab5bf0e36acfea Mon Sep 17 00:00:00 2001 From: Austin Date: Sat, 11 Nov 2017 14:12:38 -0600 Subject: [PATCH 0062/1136] fix: set errorr on vm before we rethrow it in error handler --- src/lib/error-handler.js | 21 ++++++++++----------- src/mount.js | 4 ++++ test/unit/specs/lib/error-handler.spec.js | 20 +++++++++++++++++--- test/unit/specs/mount.spec.js | 3 ++- test/unit/specs/shallow.spec.js | 2 +- 5 files changed, 34 insertions(+), 16 deletions(-) diff --git a/src/lib/error-handler.js b/src/lib/error-handler.js index 3f721826b..f633f995a 100644 --- a/src/lib/error-handler.js +++ b/src/lib/error-handler.js @@ -1,19 +1,18 @@ -function errorMessage (msg, info) { +function errorMessage (message, info) { if (info) { - return `${msg} : additional info ${info}` + return `${message} : additional info ${info}` } - return msg + return message } -export default function errorHandler (err, _vm, info) { - if ((typeof err === 'object') && err.message) { - if (info) { - err.message = errorMessage(err.message, info) - } +export default function errorHandler (errorOrString, vm, info) { + const error = (typeof errorOrString === 'object') + ? errorOrString + : new Error(errorOrString) - throw err - } + error.message = errorMessage(error.message, info) + vm._error = error - throw new Error(errorMessage(err, info)) + throw error } diff --git a/src/mount.js b/src/mount.js index 9a461d2b9..603fca128 100644 --- a/src/mount.js +++ b/src/mount.js @@ -25,5 +25,9 @@ export default function mount (component: Component, options: Options = {}): Vue vm.$mount() } + if (vm._error) { + throw (vm._error) + } + return new VueWrapper(vm, { attachedToDocument: !!options.attachToDocument }) } diff --git a/test/unit/specs/lib/error-handler.spec.js b/test/unit/specs/lib/error-handler.spec.js index 9e6dbdbb0..84c3c2b95 100644 --- a/test/unit/specs/lib/error-handler.spec.js +++ b/test/unit/specs/lib/error-handler.spec.js @@ -4,9 +4,8 @@ describe('errorHandler', () => { const errorString = 'errorString' const info = 'additional info provided by vue' const errorObject = new Error(errorString) - it('when error object: rethrows error', () => { - expect(() => errorHandler(errorObject)).to.throw().with.property('message', errorString) + expect(() => errorHandler(errorObject, {})).to.throw().with.property('message', errorString) }) it('when error object: rethrown error contains vue info when provided', () => { @@ -17,8 +16,15 @@ describe('errorHandler', () => { }) }) + it('when error object: sets vm_error to the error that is thrown', () => { + const vm = {} + expect(() => errorHandler(errorObject, vm, info)).to.throw().that.satisfies(function (err) { + return err === vm._error + }) + }) + it('when error string: throws error with string', () => { - expect(() => errorHandler(errorString)).to.throw().with.property('message', errorString) + expect(() => errorHandler(errorString, {})).to.throw().with.property('message', errorString) }) it('throws error with string and appends info when provided', () => { @@ -28,4 +34,12 @@ describe('errorHandler', () => { return errorMessage.includes(errorString) && errorMessage.includes(info) }) }) + + it('when error string: sets vm_error to the error that is thrown', () => { + const vm = {} + + expect(() => errorHandler(errorObject, vm, info)).to.throw().that.satisfies(function (err) { + return err === vm._error + }) + }) }) diff --git a/test/unit/specs/mount.spec.js b/test/unit/specs/mount.spec.js index a04ec004e..f6cc506cc 100644 --- a/test/unit/specs/mount.spec.js +++ b/test/unit/specs/mount.spec.js @@ -143,13 +143,14 @@ describe('mount', () => { expect(wrapper.vm.$options.listeners).to.equal(undefined) }) - it.skip('throws an error when the component fails to mount', () => { + it('propagates errors when they are thrown', () => { const TestComponent = { template: '
', mounted: function () { throw new Error('Error in mounted') } } + const fn = () => mount(TestComponent) expect(fn).to.throw() }) diff --git a/test/unit/specs/shallow.spec.js b/test/unit/specs/shallow.spec.js index c969ffe7e..066a44868 100644 --- a/test/unit/specs/shallow.spec.js +++ b/test/unit/specs/shallow.spec.js @@ -75,7 +75,7 @@ describe('shallow', () => { expect(info.called).to.equal(false) }) - it.skip('throws an error when the component fails to mount', () => { + it('throws an error when the component fails to mount', () => { expect(() => shallow({ template: '
', mounted: function () { From 57aca747479af13e9111f4c84d5c4b632f1d7f60 Mon Sep 17 00:00:00 2001 From: Austin Date: Sat, 11 Nov 2017 14:22:30 -0600 Subject: [PATCH 0063/1136] docs: add documentation for setComputed (#171) --- docs/en/README.md | 2 ++ docs/en/SUMMARY.md | 2 ++ docs/en/api/wrapper-array/setComputed.md | 26 ++++++++++++++ docs/en/api/wrapper/setComputed.md | 43 ++++++++++++++++++++++++ 4 files changed, 73 insertions(+) create mode 100644 docs/en/api/wrapper-array/setComputed.md create mode 100644 docs/en/api/wrapper/setComputed.md diff --git a/docs/en/README.md b/docs/en/README.md index 7c4bdf55f..2292502a0 100644 --- a/docs/en/README.md +++ b/docs/en/README.md @@ -39,6 +39,7 @@ * [isEmpty](api/wrapper/isEmpty.md) * [isVueInstance](api/wrapper/isVueInstance.md) * [name](api/wrapper/name.md) + * [setComputed](api/wrapper/setComputed.md) * [setData](api/wrapper/setData.md) * [setMethods](api/wrapper/setMethods.md) * [setProps](api/wrapper/setProps.md) @@ -55,6 +56,7 @@ * [is](api/wrapper-array/is.md) * [isEmpty](api/wrapper-array/isEmpty.md) * [isVueInstance](api/wrapper-array/isVueInstance.md) + * [setComputed](api/wrapper-array/setComputed.md) * [setData](api/wrapper-array/setData.md) * [setMethods](api/wrapper-array/setMethods.md) * [setProps](api/wrapper-array/setProps.md) diff --git a/docs/en/SUMMARY.md b/docs/en/SUMMARY.md index 149bc273a..c162dc533 100644 --- a/docs/en/SUMMARY.md +++ b/docs/en/SUMMARY.md @@ -37,6 +37,7 @@ * [isEmpty](api/wrapper/isEmpty.md) * [isVueInstance](api/wrapper/isVueInstance.md) * [name](api/wrapper/name.md) + * [setComputed](api/wrapper/setComputed.md) * [setData](api/wrapper/setData.md) * [setMethods](api/wrapper/setMethods.md) * [setProps](api/wrapper/setProps.md) @@ -54,6 +55,7 @@ * [is](api/wrapper-array/is.md) * [isEmpty](api/wrapper-array/isEmpty.md) * [isVueInstance](api/wrapper-array/isVueInstance.md) + * [setComputed](api/wrapper-array/setComputed.md) * [setData](api/wrapper-array/setData.md) * [setMethods](api/wrapper-array/setMethods.md) * [setProps](api/wrapper-array/setProps.md) diff --git a/docs/en/api/wrapper-array/setComputed.md b/docs/en/api/wrapper-array/setComputed.md new file mode 100644 index 000000000..1d9466ee8 --- /dev/null +++ b/docs/en/api/wrapper-array/setComputed.md @@ -0,0 +1,26 @@ + +# setComputed(computedObjects) + +Sets `Wrapper` `vm` computed and forces update on each `Wrapper` in `WrapperArray`. + +**Note every `Wrapper` must contain a Vue instance.** +**Note every Vue instance must already have the computed properties passed to setComputed.** + +- **Arguments:** + - `{Object} computed properties` + +- **Example:** + +```js +import { mount } from 'vue-test-utils' +import Foo from './Foo.vue' +import Bar from './Bar.vue' + +const wrapper = mount(Foo) +const barArray = wrapper.findAll(Bar) + +barArray.setComputed({ + computed1: 'new-computed1', + computed2: 'new-computed2' +}) +``` diff --git a/docs/en/api/wrapper/setComputed.md b/docs/en/api/wrapper/setComputed.md new file mode 100644 index 000000000..74c48565e --- /dev/null +++ b/docs/en/api/wrapper/setComputed.md @@ -0,0 +1,43 @@ +# setComputed(computedProperties) + +Sets `Wrapper` `vm` computed property and forces update. + +**Note the Wrapper must contain a Vue instance.** +**Note every Vue instance must already have the computed properties passed to setComputed.** + + +- **Arguments:** + - `{Object} computed properties` + +- **Example:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' + +const wrapper = mount({ + template: '
{{ computed1 }} {{ computed2 }}
', + data() { + return { + initial: 'initial', + }; + }, + computed: { + computed1() { + return this.initial + }, + computed2() { + return this.initial + }, + } +}) + +expect(wrapper.html()).toBe('
initial initial
') + +wrapper.setComputed({ + computed1: 'new-computed1', + computed2: 'new-computed2' +}) + +expect(wrapper.html()).toBe('
new-computed1 new-computed2
') +``` From 5fc1c57bf5d14d0425b748f9a3f42d6eb59791e2 Mon Sep 17 00:00:00 2001 From: eddyerburgh Date: Sun, 12 Nov 2017 09:45:24 +0000 Subject: [PATCH 0064/1136] fix: pass children to functional component --- src/lib/create-instance.js | 3 ++- test/unit/specs/mount/options/context.spec.js | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/lib/create-instance.js b/src/lib/create-instance.js index c5c575403..9beb38ee6 100644 --- a/src/lib/create-instance.js +++ b/src/lib/create-instance.js @@ -34,7 +34,8 @@ export default function createConstructor ( render (h) { return h( clonedComponent, - mountingOptions.context || component.FunctionalRenderContext + mountingOptions.context || component.FunctionalRenderContext, + mountingOptions.context && mountingOptions.context.children ) } } diff --git a/test/unit/specs/mount/options/context.spec.js b/test/unit/specs/mount/options/context.spec.js index cc76b89c3..6b5fbb346 100644 --- a/test/unit/specs/mount/options/context.spec.js +++ b/test/unit/specs/mount/options/context.spec.js @@ -65,4 +65,19 @@ describe('context', () => { const wrapper = mount(Component) expect(wrapper.element.textContent).to.equal(defaultValue) }) + + it('mounts functional component with a defined context.children', () => { + const Component = { + functional: true, + render: (h, {children}) => { + return h('div', children) + } + } + const wrapper = mount(Component, { + context: { + children: ['hello'] + } + }) + expect(wrapper.text()).to.equal('hello') +}) }) From 7962b88f7535ce8f278b35b0ead1dcb6c2c0981c Mon Sep 17 00:00:00 2001 From: eddyerburgh Date: Sun, 12 Nov 2017 12:06:28 +0000 Subject: [PATCH 0065/1136] fix: add config stubs to array when stubs is an array --- src/options/extract-options.js | 2 +- test/unit/specs/mount/options/context.spec.js | 22 +++--- test/unit/specs/mount/options/stubs.spec.js | 72 +++++++++++++++++++ 3 files changed, 84 insertions(+), 12 deletions(-) diff --git a/src/options/extract-options.js b/src/options/extract-options.js index fd4e9a676..372bd637d 100644 --- a/src/options/extract-options.js +++ b/src/options/extract-options.js @@ -4,7 +4,7 @@ import config from '../config' function getStubs (optionStubs) { if (optionStubs || Object.keys(config.stubs).length > 0) { if (Array.isArray(optionStubs)) { - return optionStubs + return [...optionStubs, ...Object.keys(config.stubs)] } else { return { ...config.stubs, diff --git a/test/unit/specs/mount/options/context.spec.js b/test/unit/specs/mount/options/context.spec.js index 6b5fbb346..a14425adb 100644 --- a/test/unit/specs/mount/options/context.spec.js +++ b/test/unit/specs/mount/options/context.spec.js @@ -67,17 +67,17 @@ describe('context', () => { }) it('mounts functional component with a defined context.children', () => { - const Component = { - functional: true, - render: (h, {children}) => { - return h('div', children) - } - } - const wrapper = mount(Component, { - context: { - children: ['hello'] + const Component = { + functional: true, + render: (h, { children }) => { + return h('div', children) + } } + const wrapper = mount(Component, { + context: { + children: ['hello'] + } + }) + expect(wrapper.text()).to.equal('hello') }) - expect(wrapper.text()).to.equal('hello') -}) }) diff --git a/test/unit/specs/mount/options/stubs.spec.js b/test/unit/specs/mount/options/stubs.spec.js index db415724b..9ccbaddef 100644 --- a/test/unit/specs/mount/options/stubs.spec.js +++ b/test/unit/specs/mount/options/stubs.spec.js @@ -3,6 +3,7 @@ import ComponentWithChild from '~resources/components/component-with-child.vue' import ComponentWithNestedChildren from '~resources/components/component-with-nested-children.vue' import Component from '~resources/components/component.vue' import config from '~src/config' +import createLocalVue from '~src/create-local-vue' describe('mount.stub', () => { let info @@ -134,6 +135,7 @@ describe('mount.stub', () => { } require.cache[require.resolve('vue-template-compiler')].exports.compileToFunctions = compilerSave }) + it('does not stub component when set to false', () => { const wrapper = mount(ComponentWithChild, { stubs: { @@ -142,6 +144,76 @@ describe('mount.stub', () => { expect(wrapper.find('span').contains('div')).to.equal(true) }) + it('combines with stubs from config', () => { + const localVue = createLocalVue() + config.stubs['time-component'] = '

' + const SpanComponent = { + render: h => h('span') + } + const TimeComponent = { + render: h => h('time') + } + localVue.component('span-component', SpanComponent) + localVue.component('time-component', TimeComponent) + const TestComponent = { + render: h => h('div', [ + h('span-component'), + h('time-component') + ]) + } + + const wrapper = mount(TestComponent, { + stubs: { + 'span-component': '

' + }, + localVue + }) + expect(wrapper.findAll('p').length).to.equal(2) + }) + + it('prioritize mounting options over config', () => { + const localVue = createLocalVue() + config.stubs['time-component'] = '

' + const TimeComponent = { + render: h => h('time') + } + localVue.component('time-component', TimeComponent) + const TestComponent = { + render: h => h('div', [ + h('time-component') + ]) + } + + const wrapper = mount(TestComponent, { + stubs: { + 'time-component': '' + }, + localVue + }) + expect(wrapper.contains('span')).to.equal(true) + }) + + it('converts config to array if stubs is an array', () => { + const localVue = createLocalVue() + config.stubs['time-component'] = '

' + const TimeComponent = { + render: h => h('time') + } + localVue.component('time-component', TimeComponent) + const TestComponent = { + render: h => h('div', [ + h('time-component') + ]) + } + + const wrapper = mount(TestComponent, { + stubs: ['a-component'], + localVue + }) + expect(wrapper.contains('time')).to.equal(false) + expect(wrapper.contains('p')).to.equal(false) + }) + it('throws an error when passed an invalid value as stub', () => { const error = '[vue-test-utils]: options.stub values must be passed a string or component' const invalidValues = [1, null, [], {}, NaN] From 7f537efca6994aff1c5750ceea0c313659ad2990 Mon Sep 17 00:00:00 2001 From: eddyerburgh Date: Sun, 12 Nov 2017 12:37:51 +0000 Subject: [PATCH 0066/1136] docs: add section on TransitionStub and TransitionGroupStub --- docs/en/README.md | 9 +++++- docs/en/SUMMARY.md | 3 ++ docs/en/api/components/README.md | 5 ++++ docs/en/api/components/TransitionGroupStub.md | 30 +++++++++++++++++++ docs/en/api/components/TransitionStub.md | 30 +++++++++++++++++++ 5 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 docs/en/api/components/README.md create mode 100644 docs/en/api/components/TransitionGroupStub.md create mode 100644 docs/en/api/components/TransitionStub.md diff --git a/docs/en/README.md b/docs/en/README.md index 2292502a0..05db1297f 100644 --- a/docs/en/README.md +++ b/docs/en/README.md @@ -2,10 +2,12 @@ `vue-test-utils` is the official unit testing utility library for Vue.js. +## Table of Contents + * [Guides](guides/README.md) * [Getting Started](guides/getting-started.md) * [Common Tips](guides/common-tips.md) - * [Mouse, Key and other DOM Events](guides/dom-events.md) + * [Mouse, Key and other DOM Events](guides/dom-events.md) * [Choosing a test runner](guides/choosing-a-test-runner.md) * [Testing SFCs with Jest](guides/testing-SFCs-with-jest.md) * [Testing SFCs with Mocha + webpack](guides/testing-SFCs-with-mocha-webpack.md) @@ -46,6 +48,7 @@ * [text](api/wrapper/text.md) * [trigger](api/wrapper/trigger.md) * [update](api/wrapper/update.md) + * [destroy](api/wrapper/destroy.md) * [WrapperArray](api/wrapper-array/README.md) * [at](api/wrapper-array/at.md) * [contains](api/wrapper-array/contains.md) @@ -62,5 +65,9 @@ * [setProps](api/wrapper-array/setProps.md) * [trigger](api/wrapper-array/trigger.md) * [update](api/wrapper-array/update.md) + * [destroy](api/wrapper-array/destroy.md) + * [components](api/components/README.md) + * [TransitionStub](api/components/TransitionStub.md) + * [TransitionGroupStub](api/components/TransitionGroupStub.md) * [Selectors](api/selectors.md) * [createLocalVue](api/createLocalVue.md) diff --git a/docs/en/SUMMARY.md b/docs/en/SUMMARY.md index c162dc533..ed5724d19 100644 --- a/docs/en/SUMMARY.md +++ b/docs/en/SUMMARY.md @@ -62,5 +62,8 @@ * [trigger](api/wrapper-array/trigger.md) * [update](api/wrapper-array/update.md) * [destroy](api/wrapper-array/destroy.md) + * [components](api/components/README.md) + * [TransitionStub](api/components/TransitionStub.md) + * [TransitionGroupStub](api/components/TransitionGroupStub.md) * [Selectors](api/selectors.md) * [createLocalVue](api/createLocalVue.md) diff --git a/docs/en/api/components/README.md b/docs/en/api/components/README.md new file mode 100644 index 000000000..b6db0b05b --- /dev/null +++ b/docs/en/api/components/README.md @@ -0,0 +1,5 @@ +# Components + +vue-test-utils includes utility components you can use to stub components. + +[TransitionStub](./TransitionStub.md) and [TransitionGroupStub](./TransitionGroupStub.md) are used to stub `transition` and `transition-group` components by default. You can edit the stubs by editing the config. diff --git a/docs/en/api/components/TransitionGroupStub.md b/docs/en/api/components/TransitionGroupStub.md new file mode 100644 index 000000000..f7061f5c3 --- /dev/null +++ b/docs/en/api/components/TransitionGroupStub.md @@ -0,0 +1,30 @@ +# TransitionGroupStub + +A component to stub the `transition-group` wrapper component. Instead of performing transitions asynchronously, it returns the child components synchronously. + +This is set to stub all `transition-group` components by default in the vue-test-utils config. To use the built-in `transition-group` wrapper component set `config.stubs[transition-group]` to false: + +```js +import VueTestUtils from 'vue-test-utils' + +VueTestUtils.config.stubs.transition = false +``` + +To reset it to stub transition components: +```js +import VueTestUtils, { TransitionGroupStub } from 'vue-test-utils' + +VueTestUtils.config.stubs['transition-group'] = TransitionGroupStub +``` + +To set it as a stub in mounting options: + +```js +import { mount, TransitionGroupStub } from 'vue-test-utils' + +mount(Component, { + stubs: { + 'transition-group': TransitionGroupStub + } +}) +``` diff --git a/docs/en/api/components/TransitionStub.md b/docs/en/api/components/TransitionStub.md new file mode 100644 index 000000000..8a11d34eb --- /dev/null +++ b/docs/en/api/components/TransitionStub.md @@ -0,0 +1,30 @@ +# TransitionStub + +A component to stub the `transition` wrapper component. Instead of performing transitions asynchronously, it returns the child component synchronously. + +This is set to stub all `transition` components by default in the vue-test-utils config. To use the built-in `transition` wrapper component set `config.stubs.transition` to false: + +```js +import VueTestUtils from 'vue-test-utils' + +VueTestUtils.config.stubs.transition = false +``` + +To reset it to stub transition components: +```js +import VueTestUtils, { TransitionStub } from 'vue-test-utils' + +VueTestUtils.config.stubs.transition = TransitionStub +``` + +To set it as a stub in mounting options: + +```js +import { mount, TransitionStub } from 'vue-test-utils' + +mount(Component, { + stubs: { + transition: TransitionStub + } +}) +``` From e7034b28f400c60812d655121fd881c1d873f970 Mon Sep 17 00:00:00 2001 From: eddyerburgh Date: Sun, 12 Nov 2017 12:39:39 +0000 Subject: [PATCH 0067/1136] docs: fix linting errors --- docs/en/api/wrapper-array/setComputed.md | 4 ++-- docs/en/api/wrapper/setComputed.md | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/en/api/wrapper-array/setComputed.md b/docs/en/api/wrapper-array/setComputed.md index 1d9466ee8..7531132cf 100644 --- a/docs/en/api/wrapper-array/setComputed.md +++ b/docs/en/api/wrapper-array/setComputed.md @@ -19,8 +19,8 @@ import Bar from './Bar.vue' const wrapper = mount(Foo) const barArray = wrapper.findAll(Bar) -barArray.setComputed({ - computed1: 'new-computed1', +barArray.setComputed({ + computed1: 'new-computed1', computed2: 'new-computed2' }) ``` diff --git a/docs/en/api/wrapper/setComputed.md b/docs/en/api/wrapper/setComputed.md index 74c48565e..b5db5549e 100644 --- a/docs/en/api/wrapper/setComputed.md +++ b/docs/en/api/wrapper/setComputed.md @@ -17,24 +17,24 @@ import { expect } from 'chai' const wrapper = mount({ template: '

{{ computed1 }} {{ computed2 }}
', - data() { + data () { return { - initial: 'initial', - }; + initial: 'initial' + } }, computed: { - computed1() { - return this.initial - }, - computed2() { - return this.initial + computed1 () { + return this.initial }, + computed2 () { + return this.initial + } } }) expect(wrapper.html()).toBe('
initial initial
') -wrapper.setComputed({ +wrapper.setComputed({ computed1: 'new-computed1', computed2: 'new-computed2' }) From b020d4b7453c65be3c4fd60eb0e319f427aff1fa Mon Sep 17 00:00:00 2001 From: eddyerburgh Date: Sun, 12 Nov 2017 12:48:31 +0000 Subject: [PATCH 0068/1136] docs: add config to docs --- docs/en/SUMMARY.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/SUMMARY.md b/docs/en/SUMMARY.md index ed5724d19..9017c2c8c 100644 --- a/docs/en/SUMMARY.md +++ b/docs/en/SUMMARY.md @@ -67,3 +67,4 @@ * [TransitionGroupStub](api/components/TransitionGroupStub.md) * [Selectors](api/selectors.md) * [createLocalVue](api/createLocalVue.md) + * [config](api/config.md) From 93a11e0c81d4d30f66cc01ebf1623bbc1e41a8b3 Mon Sep 17 00:00:00 2001 From: eddyerburgh Date: Sun, 12 Nov 2017 12:51:56 +0000 Subject: [PATCH 0069/1136] docs: add config to docs README --- docs/en/README.md | 1 + docs/en/api/config.md | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 docs/en/api/config.md diff --git a/docs/en/README.md b/docs/en/README.md index 05db1297f..edaae770a 100644 --- a/docs/en/README.md +++ b/docs/en/README.md @@ -71,3 +71,4 @@ * [TransitionGroupStub](api/components/TransitionGroupStub.md) * [Selectors](api/selectors.md) * [createLocalVue](api/createLocalVue.md) + * [config](api/config.md) diff --git a/docs/en/api/config.md b/docs/en/api/config.md new file mode 100644 index 000000000..6f44dd639 --- /dev/null +++ b/docs/en/api/config.md @@ -0,0 +1,25 @@ +# Config + +vue-test-utils includes a config object to defined options used by vue-test-utils. + +## `vue-test-utils` Config Options + +### `stubs` + +- type: `Object` +- default: `{ + transition: TransitionStub, + 'transition-group': TransitionGroupStub +}` + +Stubs to use in components. These are overwritten by `stubs` passed in the mounting options. + +When passing `stubs` as an array in the mounting options, `config.stubs` are converted to an array, and will stub components with a basic component that returns a div. + +Example: + +```js +import VueTestUtils from 'vue-test-utils' + +VueTestUtils.config.stubs['my-compomnent'] = '
' +``` From ff97e28edd22f5c73dcb9779fe88e21c630146b0 Mon Sep 17 00:00:00 2001 From: eddyerburgh Date: Sun, 12 Nov 2017 20:31:37 +0000 Subject: [PATCH 0070/1136] fix: handle default slots in functional components;2C --- src/lib/create-instance.js | 14 ++++++++- .../functional-component-with-slots.vue | 9 ++++++ test/unit/specs/mount/options/slots.spec.js | 31 ++++++++++++++----- 3 files changed, 46 insertions(+), 8 deletions(-) create mode 100644 test/resources/components/functional-component-with-slots.vue diff --git a/src/lib/create-instance.js b/src/lib/create-instance.js index 9beb38ee6..9a5a0ada7 100644 --- a/src/lib/create-instance.js +++ b/src/lib/create-instance.js @@ -12,6 +12,17 @@ import { compileTemplate } from './compile-template' import createLocalVue from '../create-local-vue' import extractOptions from '../options/extract-options' import deleteMountingOptions from '../options/delete-mounting-options' +import { compileToFunctions } from 'vue-template-compiler' + +function createFunctionalSlots (slots = {}, h) { + if (Array.isArray(slots.default)) { + return slots.default.map(h) + } + + if (typeof slots.default === 'string') { + return [h(compileToFunctions(slots.default))] + } +} export default function createConstructor ( component: Component, @@ -29,13 +40,14 @@ export default function createConstructor ( if (mountingOptions.context && typeof mountingOptions.context !== 'object') { throwError('mount.context must be an object') } + const clonedComponent = cloneDeep(component) component = { render (h) { return h( clonedComponent, mountingOptions.context || component.FunctionalRenderContext, - mountingOptions.context && mountingOptions.context.children + (mountingOptions.context && mountingOptions.context.children) || createFunctionalSlots(mountingOptions.slots, h) ) } } diff --git a/test/resources/components/functional-component-with-slots.vue b/test/resources/components/functional-component-with-slots.vue new file mode 100644 index 000000000..da7dfbdff --- /dev/null +++ b/test/resources/components/functional-component-with-slots.vue @@ -0,0 +1,9 @@ + diff --git a/test/unit/specs/mount/options/slots.spec.js b/test/unit/specs/mount/options/slots.spec.js index c8f71c638..c97ed3075 100644 --- a/test/unit/specs/mount/options/slots.spec.js +++ b/test/unit/specs/mount/options/slots.spec.js @@ -2,19 +2,15 @@ import { compileToFunctions } from 'vue-template-compiler' import mount from '~src/mount' import Component from '~resources/components/component.vue' import ComponentWithSlots from '~resources/components/component-with-slots.vue' +import FunctionalComponentWithSlots from '~resources/components/functional-component-with-slots.vue' describe('mount.slots', () => { it('mounts component with default slot if passed component in slot object', () => { - const wrapper = mount(ComponentWithSlots, { slots: { default: [Component] }}) - expect(wrapper.contains(Component)).to.equal(true) - }) - - it('mounts component with default slot if passed object with template prop in slot object', () => { - const wrapper = mount(ComponentWithSlots, { slots: { default: [Component] }}) + const wrapper = mount(ComponentWithSlots, { slots: { default: Component }}) expect(wrapper.contains(Component)).to.equal(true) }) - it('mounts component with default slot if passed component in slot object', () => { + it('mounts component with default slot if passed component in array in slot object', () => { const wrapper = mount(ComponentWithSlots, { slots: { default: [Component] }}) expect(wrapper.contains(Component)).to.equal(true) }) @@ -86,4 +82,25 @@ describe('mount.slots', () => { expect(wrapper.findAll(Component).length).to.equal(1) expect(Array.isArray(wrapper.vm.$slots.header)).to.equal(true) }) + + it.only('mounts functional component with default slot if passed component in slot object', () => { + const wrapper = mount(FunctionalComponentWithSlots, { slots: { default: [Component] }}) + expect(wrapper.contains(Component)).to.equal(true) + }) + + it.only('mounts component with default slot if passed component in slot object', () => { + const wrapper = mount(FunctionalComponentWithSlots, { slots: { default: [Component] }}) + expect(wrapper.contains(Component)).to.equal(true) + }) + + it.only('mounts component with default slot if passed object with template prop in slot object', () => { + const compiled = compileToFunctions('
') + const wrapper = mount(FunctionalComponentWithSlots, { slots: { default: [compiled] }}) + expect(wrapper.contains('#div')).to.equal(true) + }) + + it.only('mounts component with default slot if passed string in slot object', () => { + const wrapper = mount(FunctionalComponentWithSlots, { slots: { default: '' }}) + expect(wrapper.contains('span')).to.equal(true) + }) }) From d9e368bc3cfaff510ecde3991e0bc441b621c17f Mon Sep 17 00:00:00 2001 From: eddyerburgh Date: Sun, 12 Nov 2017 21:41:38 +0000 Subject: [PATCH 0071/1136] fix: handle named functional slots --- src/lib/create-instance.js | 17 +++++++ test/unit/specs/mount/options/slots.spec.js | 54 +++++++++++++++++++-- 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/src/lib/create-instance.js b/src/lib/create-instance.js index 9a5a0ada7..385e1a3d3 100644 --- a/src/lib/create-instance.js +++ b/src/lib/create-instance.js @@ -22,6 +22,23 @@ function createFunctionalSlots (slots = {}, h) { if (typeof slots.default === 'string') { return [h(compileToFunctions(slots.default))] } + const children = [] + Object.keys(slots).forEach(slotType => { + if (Array.isArray(slots[slotType])) { + slots[slotType].forEach(slot => { + const component = typeof slot === 'string' ? compileToFunctions(slot) : slot + const newSlot = h(component) + newSlot.data.slot = slotType + children.push(newSlot) + }) + } else { + const component = typeof slots[slotType] === 'string' ? compileToFunctions(slots[slotType]) : slots[slotType] + const slot = h(component) + slot.data.slot = slotType + children.push(slot) + } + }) + return children } export default function createConstructor ( diff --git a/test/unit/specs/mount/options/slots.spec.js b/test/unit/specs/mount/options/slots.spec.js index c97ed3075..dcf0fd386 100644 --- a/test/unit/specs/mount/options/slots.spec.js +++ b/test/unit/specs/mount/options/slots.spec.js @@ -83,24 +83,68 @@ describe('mount.slots', () => { expect(Array.isArray(wrapper.vm.$slots.header)).to.equal(true) }) - it.only('mounts functional component with default slot if passed component in slot object', () => { - const wrapper = mount(FunctionalComponentWithSlots, { slots: { default: [Component] }}) + it('mounts functional component with default slot if passed component in slot object', () => { + const wrapper = mount(FunctionalComponentWithSlots, { slots: { default: Component }}) expect(wrapper.contains(Component)).to.equal(true) }) - it.only('mounts component with default slot if passed component in slot object', () => { + it('mounts component with default slot if passed component in slot object', () => { const wrapper = mount(FunctionalComponentWithSlots, { slots: { default: [Component] }}) expect(wrapper.contains(Component)).to.equal(true) }) - it.only('mounts component with default slot if passed object with template prop in slot object', () => { + it('mounts component with default slot if passed object with template prop in slot object', () => { const compiled = compileToFunctions('
') const wrapper = mount(FunctionalComponentWithSlots, { slots: { default: [compiled] }}) expect(wrapper.contains('#div')).to.equal(true) }) - it.only('mounts component with default slot if passed string in slot object', () => { + it('mounts component with default slot if passed string in slot object', () => { const wrapper = mount(FunctionalComponentWithSlots, { slots: { default: '' }}) expect(wrapper.contains('span')).to.equal(true) }) + + it('mounts component with named slot if passed string in slot object', () => { + const TestComponent = { + functional: true, + render (h, ctx) { + return h('div', {}, ctx.slots().named) + } + } + const wrapper = mount(TestComponent, { slots: { named: Component }}) + expect(wrapper.contains(Component)).to.equal(true) + }) + + it('mounts component with named slot if passed string in slot object in array', () => { + const TestComponent = { + functional: true, + render (h, ctx) { + return h('div', {}, ctx.slots().named) + } + } + const wrapper = mount(TestComponent, { slots: { named: [Component] }}) + expect(wrapper.contains(Component)).to.equal(true) + }) + + it('mounts component with named slot if passed string in slot object in array', () => { + const TestComponent = { + functional: true, + render (h, ctx) { + return h('div', {}, ctx.slots().named) + } + } + const wrapper = mount(TestComponent, { slots: { named: '' }}) + expect(wrapper.contains('span')).to.equal(true) + }) + + it('mounts component with named slot if passed string in slot object in array', () => { + const TestComponent = { + functional: true, + render (h, ctx) { + return h('div', {}, ctx.slots().named) + } + } + const wrapper = mount(TestComponent, { slots: { named: [''] }}) + expect(wrapper.contains('span')).to.equal(true) + }) }) From d27becb904f8a0a5f3803b779112561114d602e7 Mon Sep 17 00:00:00 2001 From: eddyerburgh Date: Mon, 13 Nov 2017 07:03:35 +0000 Subject: [PATCH 0072/1136] fix: support named functional slots --- src/lib/create-instance.js | 10 ++ src/lib/error-handler.js | 11 +- .../functional-component-with-slots.vue | 9 -- test/unit/specs/lib/error-handler.spec.js | 34 ++---- test/unit/specs/mount/options/slots.spec.js | 108 +++++++++++++++--- 5 files changed, 114 insertions(+), 58 deletions(-) delete mode 100644 test/resources/components/functional-component-with-slots.vue diff --git a/src/lib/create-instance.js b/src/lib/create-instance.js index 385e1a3d3..727a8c56c 100644 --- a/src/lib/create-instance.js +++ b/src/lib/create-instance.js @@ -14,6 +14,10 @@ import extractOptions from '../options/extract-options' import deleteMountingOptions from '../options/delete-mounting-options' import { compileToFunctions } from 'vue-template-compiler' +function isValidSlot (slot: any): boolean { + return Array.isArray(slot) || (slot !== null && typeof slot === 'object') || typeof slot === 'string' +} + function createFunctionalSlots (slots = {}, h) { if (Array.isArray(slots.default)) { return slots.default.map(h) @@ -26,12 +30,18 @@ function createFunctionalSlots (slots = {}, h) { Object.keys(slots).forEach(slotType => { if (Array.isArray(slots[slotType])) { slots[slotType].forEach(slot => { + if (!isValidSlot(slot)) { + throwError('slots[key] must be a Component, string or an array of Components') + } const component = typeof slot === 'string' ? compileToFunctions(slot) : slot const newSlot = h(component) newSlot.data.slot = slotType children.push(newSlot) }) } else { + if (!isValidSlot(slots[slotType])) { + throwError('slots[key] must be a Component, string or an array of Components') + } const component = typeof slots[slotType] === 'string' ? compileToFunctions(slots[slotType]) : slots[slotType] const slot = h(component) slot.data.slot = slotType diff --git a/src/lib/error-handler.js b/src/lib/error-handler.js index f633f995a..c87d1916e 100644 --- a/src/lib/error-handler.js +++ b/src/lib/error-handler.js @@ -1,17 +1,8 @@ -function errorMessage (message, info) { - if (info) { - return `${message} : additional info ${info}` - } - - return message -} - -export default function errorHandler (errorOrString, vm, info) { +export default function errorHandler (errorOrString, vm) { const error = (typeof errorOrString === 'object') ? errorOrString : new Error(errorOrString) - error.message = errorMessage(error.message, info) vm._error = error throw error diff --git a/test/resources/components/functional-component-with-slots.vue b/test/resources/components/functional-component-with-slots.vue deleted file mode 100644 index da7dfbdff..000000000 --- a/test/resources/components/functional-component-with-slots.vue +++ /dev/null @@ -1,9 +0,0 @@ - diff --git a/test/unit/specs/lib/error-handler.spec.js b/test/unit/specs/lib/error-handler.spec.js index 84c3c2b95..161b3e50b 100644 --- a/test/unit/specs/lib/error-handler.spec.js +++ b/test/unit/specs/lib/error-handler.spec.js @@ -1,44 +1,34 @@ import errorHandler from '../../../../src/lib/error-handler' +const errorString = 'errorString' +const errorObject = new Error(errorString) + describe('errorHandler', () => { - const errorString = 'errorString' - const info = 'additional info provided by vue' - const errorObject = new Error(errorString) - it('when error object: rethrows error', () => { + it('throws error', () => { expect(() => errorHandler(errorObject, {})).to.throw().with.property('message', errorString) }) - it('when error object: rethrown error contains vue info when provided', () => { - expect(() => errorHandler(errorObject, {}, info)).to.throw().that.satisfies(function (err) { - const errorMessage = err.message - - return errorMessage.includes(errorString) && errorMessage.includes(info) + it('throws error with vue info when provided', () => { + expect(() => errorHandler(errorObject, {})).to.throw().that.satisfies(function (err) { + return err.message.includes(errorString) }) }) - it('when error object: sets vm_error to the error that is thrown', () => { + it('sets vm_error to the error that is thrown', () => { const vm = {} - expect(() => errorHandler(errorObject, vm, info)).to.throw().that.satisfies(function (err) { + expect(() => errorHandler(errorObject, vm)).to.throw().that.satisfies(function (err) { return err === vm._error }) }) - it('when error string: throws error with string', () => { + it('throws error with string', () => { expect(() => errorHandler(errorString, {})).to.throw().with.property('message', errorString) }) - it('throws error with string and appends info when provided', () => { - expect(() => errorHandler(errorString, {}, info)).to.throw().that.satisfies(function (err) { - const errorMessage = err.message - - return errorMessage.includes(errorString) && errorMessage.includes(info) - }) - }) - - it('when error string: sets vm_error to the error that is thrown', () => { + it('sets vm_error to the error that is thrown', () => { const vm = {} - expect(() => errorHandler(errorObject, vm, info)).to.throw().that.satisfies(function (err) { + expect(() => errorHandler(errorObject, vm)).to.throw().that.satisfies(function (err) { return err === vm._error }) }) diff --git a/test/unit/specs/mount/options/slots.spec.js b/test/unit/specs/mount/options/slots.spec.js index dcf0fd386..6458c665a 100644 --- a/test/unit/specs/mount/options/slots.spec.js +++ b/test/unit/specs/mount/options/slots.spec.js @@ -2,7 +2,6 @@ import { compileToFunctions } from 'vue-template-compiler' import mount from '~src/mount' import Component from '~resources/components/component.vue' import ComponentWithSlots from '~resources/components/component-with-slots.vue' -import FunctionalComponentWithSlots from '~resources/components/functional-component-with-slots.vue' describe('mount.slots', () => { it('mounts component with default slot if passed component in slot object', () => { @@ -84,32 +83,50 @@ describe('mount.slots', () => { }) it('mounts functional component with default slot if passed component in slot object', () => { - const wrapper = mount(FunctionalComponentWithSlots, { slots: { default: Component }}) + const TestComponent = { + name: 'component-with-slots', + functional: true, + render: (h, ctx) => h('div', ctx.data, ctx.slots().default) + } + const wrapper = mount(TestComponent, { slots: { default: Component }}) expect(wrapper.contains(Component)).to.equal(true) }) it('mounts component with default slot if passed component in slot object', () => { - const wrapper = mount(FunctionalComponentWithSlots, { slots: { default: [Component] }}) + const TestComponent = { + name: 'component-with-slots', + functional: true, + render: (h, ctx) => h('div', ctx.data, ctx.slots().default) + } + const wrapper = mount(TestComponent, { slots: { default: [Component] }}) expect(wrapper.contains(Component)).to.equal(true) }) it('mounts component with default slot if passed object with template prop in slot object', () => { + const TestComponent = { + name: 'component-with-slots', + functional: true, + render: (h, ctx) => h('div', ctx.data, ctx.slots().default) + } const compiled = compileToFunctions('
') - const wrapper = mount(FunctionalComponentWithSlots, { slots: { default: [compiled] }}) + const wrapper = mount(TestComponent, { slots: { default: [compiled] }}) expect(wrapper.contains('#div')).to.equal(true) }) it('mounts component with default slot if passed string in slot object', () => { - const wrapper = mount(FunctionalComponentWithSlots, { slots: { default: '' }}) + const TestComponent = { + name: 'component-with-slots', + functional: true, + render: (h, ctx) => h('div', ctx.data, ctx.slots().default) + } + const wrapper = mount(TestComponent, { slots: { default: '' }}) expect(wrapper.contains('span')).to.equal(true) }) it('mounts component with named slot if passed string in slot object', () => { const TestComponent = { functional: true, - render (h, ctx) { - return h('div', {}, ctx.slots().named) - } + render: (h, ctx) => h('div', {}, ctx.slots().named) } const wrapper = mount(TestComponent, { slots: { named: Component }}) expect(wrapper.contains(Component)).to.equal(true) @@ -118,9 +135,7 @@ describe('mount.slots', () => { it('mounts component with named slot if passed string in slot object in array', () => { const TestComponent = { functional: true, - render (h, ctx) { - return h('div', {}, ctx.slots().named) - } + render: (h, ctx) => h('div', {}, ctx.slots().named) } const wrapper = mount(TestComponent, { slots: { named: [Component] }}) expect(wrapper.contains(Component)).to.equal(true) @@ -129,9 +144,7 @@ describe('mount.slots', () => { it('mounts component with named slot if passed string in slot object in array', () => { const TestComponent = { functional: true, - render (h, ctx) { - return h('div', {}, ctx.slots().named) - } + render: (h, ctx) => h('div', {}, ctx.slots().named) } const wrapper = mount(TestComponent, { slots: { named: '' }}) expect(wrapper.contains('span')).to.equal(true) @@ -140,11 +153,72 @@ describe('mount.slots', () => { it('mounts component with named slot if passed string in slot object in array', () => { const TestComponent = { functional: true, - render (h, ctx) { - return h('div', {}, ctx.slots().named) - } + render: (h, ctx) => h('div', {}, ctx.slots().named) } const wrapper = mount(TestComponent, { slots: { named: [''] }}) expect(wrapper.contains('span')).to.equal(true) }) + + it('throws error if passed false for named slots', () => { + const TestComponent = { + name: 'component-with-slots', + functional: true, + render: (h, ctx) => h('div', ctx.data, ctx.slots().default) + } + const fn = () => mount(TestComponent, { slots: { named: [false] }}) + const message = '[vue-test-utils]: slots[key] must be a Component, string or an array of Components' + expect(fn).to.throw().with.property('message', message) + }) + + it('throws error if passed a number for named slots', () => { + const TestComponent = { + name: 'component-with-slots', + functional: true, + render: (h, ctx) => h('div', ctx.data, ctx.slots().default) + } + const fn = () => mount(TestComponent, { slots: { named: [1] }}) + const message = '[vue-test-utils]: slots[key] must be a Component, string or an array of Components' + expect(fn).to.throw().with.property('message', message) + }) + it('throws error if passed false for named slots', () => { + const TestComponent = { + name: 'component-with-slots', + functional: true, + render: (h, ctx) => h('div', ctx.data, ctx.slots().default) + } + const fn = () => mount(TestComponent, { slots: { named: false }}) + const message = '[vue-test-utils]: slots[key] must be a Component, string or an array of Components' + expect(fn).to.throw().with.property('message', message) + }) + + it('throws error if passed a number for named slots', () => { + const TestComponent = { + name: 'component-with-slots', + functional: true, + render: (h, ctx) => h('div', ctx.data, ctx.slots().default) + } + const fn = () => mount(TestComponent, { slots: { named: 1 }}) + const message = '[vue-test-utils]: slots[key] must be a Component, string or an array of Components' + expect(fn).to.throw().with.property('message', message) + }) + it('throws error if passed string in default slot array when vue-template-compiler is undefined', () => { + const TestComponent = { + name: 'component-with-slots', + functional: true, + render: (h, ctx) => h('div', ctx.data, ctx.slots().default) + } + const compilerSave = require.cache[require.resolve('vue-template-compiler')].exports.compileToFunctions + require.cache[require.resolve('vue-template-compiler')].exports.compileToFunctions = undefined + delete require.cache[require.resolve('../../../../../src/mount')] + const mountFresh = require('../../../../../src/mount').default + const message = '[vue-test-utils]: vueTemplateCompiler is undefined, you must pass components explicitly if vue-template-compiler is undefined' + const fn = () => mountFresh(TestComponent, { slots: { default: [''] }}) + try { + expect(fn).to.throw().with.property('message', message) + } catch (err) { + require.cache[require.resolve('vue-template-compiler')].exports.compileToFunctions = compilerSave + throw err + } + require.cache[require.resolve('vue-template-compiler')].exports.compileToFunctions = compilerSave + }) }) From e81095c9785b3c39d4e006fe05db9a7a38fdbcef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8B=BE=E4=B8=89=E8=82=A1=E5=9B=9B?= Date: Tue, 14 Nov 2017 01:03:27 +0800 Subject: [PATCH 0073/1136] docs: [zh-cn] synced from #de89293 to #a5ad66a (#168) * [zh-cn] synced from #de89293 to #a5ad66a * [zh-cn] translated guides/dom-events --- docs/zh-cn/README.md | 3 + docs/zh-cn/SUMMARY.md | 3 + docs/zh-cn/api/README.md | 2 + docs/zh-cn/api/wrapper-array/destroy.md | 17 ++ docs/zh-cn/api/wrapper-array/hasStyle.md | 2 +- docs/zh-cn/api/wrapper/destroy.md | 20 ++ docs/zh-cn/guides/README.md | 1 + docs/zh-cn/guides/choosing-a-test-runner.md | 4 +- docs/zh-cn/guides/dom-events.md | 196 ++++++++++++++++++++ docs/zh-cn/guides/testing-SFCs-with-jest.md | 10 +- 10 files changed, 250 insertions(+), 8 deletions(-) create mode 100644 docs/zh-cn/api/wrapper-array/destroy.md create mode 100644 docs/zh-cn/api/wrapper/destroy.md create mode 100644 docs/zh-cn/guides/dom-events.md diff --git a/docs/zh-cn/README.md b/docs/zh-cn/README.md index ec8e91841..dc1697285 100644 --- a/docs/zh-cn/README.md +++ b/docs/zh-cn/README.md @@ -5,6 +5,7 @@ * [教程](guides/README.md) * [起步](guides/getting-started.md) * [常用技巧](guides/common-tips.md) + * [鼠标、键盘以及其它 DOM 事件](guides/dom-events.md) * [选择一个测试运行器](guides/choosing-a-test-runner.md) * [用 Jest 测试单文件组件](guides/testing-SFCs-with-jest.md) * [用 Mocha 和 webpack 测试单文件组件](guides/testing-SFCs-with-mocha-webpack.md) @@ -44,6 +45,7 @@ * [text](api/wrapper/text.md) * [trigger](api/wrapper/trigger.md) * [update](api/wrapper/update.md) + * [destroy](api/wrapper/destroy.md) * [WrapperArray](api/wrapper-array/README.md) * [at](api/wrapper-array/at.md) * [contains](api/wrapper-array/contains.md) @@ -59,5 +61,6 @@ * [setProps](api/wrapper-array/setProps.md) * [trigger](api/wrapper-array/trigger.md) * [update](api/wrapper-array/update.md) + * [destroy](api/wrapper-array/destroy.md) * [选择器](api/selectors.md) * [createLocalVue](api/createLocalVue.md) diff --git a/docs/zh-cn/SUMMARY.md b/docs/zh-cn/SUMMARY.md index 4eed1a7b9..486368538 100644 --- a/docs/zh-cn/SUMMARY.md +++ b/docs/zh-cn/SUMMARY.md @@ -3,6 +3,7 @@ * [教程](guides/README.md) * [起步](guides/getting-started.md) * [常用技巧](guides/common-tips.md) + * [鼠标、键盘以及其它 DOM 事件](guides/dom-events.md) * [选择一个测试运行器](guides/choosing-a-test-runner.md) * [用 Jest 测试单文件组件](guides/testing-SFCs-with-jest.md) * [用 Mocha 和 webpack 测试单文件组件](guides/testing-SFCs-with-mocha-webpack.md) @@ -42,6 +43,7 @@ * [text](api/wrapper/text.md) * [trigger](api/wrapper/trigger.md) * [update](api/wrapper/update.md) + * [destroy](api/wrapper/destroy.md) * [WrapperArray](api/wrapper-array/README.md) * [at](api/wrapper-array/at.md) * [contains](api/wrapper-array/contains.md) @@ -57,5 +59,6 @@ * [setProps](api/wrapper-array/setProps.md) * [trigger](api/wrapper-array/trigger.md) * [update](api/wrapper-array/update.md) + * [destroy](api/wrapper-array/destroy.md) * [选择器](api/selectors.md) * [createLocalVue](api/createLocalVue.md) diff --git a/docs/zh-cn/api/README.md b/docs/zh-cn/api/README.md index 158753d03..ac7e14b5f 100644 --- a/docs/zh-cn/api/README.md +++ b/docs/zh-cn/api/README.md @@ -33,6 +33,7 @@ * [text](./wrapper/text.md) * [trigger](./wrapper/trigger.md) * [update](./wrapper/update.md) + * [destroy](./wrapper/destroy.md) * [WrapperArray](./wrapper-array/README.md) * [at](./wrapper-array/at.md) * [contains](./wrapper-array/contains.md) @@ -48,5 +49,6 @@ * [setProps](./wrapper-array/setProps.md) * [trigger](./wrapper-array/trigger.md) * [update](./wrapper-array/update.md) + * [destroy](./wrapper-array/destroy.md) * [选择器](./selectors.md) * [createLocalVue](./createLocalVue.md) diff --git a/docs/zh-cn/api/wrapper-array/destroy.md b/docs/zh-cn/api/wrapper-array/destroy.md new file mode 100644 index 000000000..2958a9aae --- /dev/null +++ b/docs/zh-cn/api/wrapper-array/destroy.md @@ -0,0 +1,17 @@ +# `destroy()` + +销毁 `WrapperArray` 中的每个 Vue `Wrapper`。 + +- **示例:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +const divArray = wrapper.findAll('div') +expect(divArray.contains('p')).toBe(true) +divArray.destroy() +expect(divArray.contains('p')).toBe(false) +``` diff --git a/docs/zh-cn/api/wrapper-array/hasStyle.md b/docs/zh-cn/api/wrapper-array/hasStyle.md index c49e26cad..6907fd862 100644 --- a/docs/zh-cn/api/wrapper-array/hasStyle.md +++ b/docs/zh-cn/api/wrapper-array/hasStyle.md @@ -2,7 +2,7 @@ 断言 `WrapperArray` 中的每一个 `Wrapper` 的 DOM 节点都有样式的匹配值。 -如果 `Wrapper` 的 DOM 节点有 `style` 样式值匹配 `string` 则返回 `true`。 +如果 `Wrapper` 的 DOM 节点有 `style` 样式值匹配 `value` 则返回 `true`。 **注意:当运行在 `jsdom` 中的时候只会匹配内联样式。** diff --git a/docs/zh-cn/api/wrapper/destroy.md b/docs/zh-cn/api/wrapper/destroy.md new file mode 100644 index 000000000..7805d52fa --- /dev/null +++ b/docs/zh-cn/api/wrapper/destroy.md @@ -0,0 +1,20 @@ +# `destroy()` + +销毁一个 Vue 组件实例。 + +- **示例:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import sinon from 'sinon' + +const spy = sinon.stub() +mount({ + render: null, + destroyed () { + spy() + } +}).destroy() +expect(spy.calledOnce).to.equal(true) +``` diff --git a/docs/zh-cn/guides/README.md b/docs/zh-cn/guides/README.md index 1a04898a1..830908fb3 100644 --- a/docs/zh-cn/guides/README.md +++ b/docs/zh-cn/guides/README.md @@ -2,6 +2,7 @@ * [起步](./getting-started.md) * [常用技巧](./common-tips.md) +* [鼠标、键盘以及其它 DOM 事件](./dom-events.md) * [选择一个测试运行器](./choosing-a-test-runner.md) * [用 Jest 测试单文件组件](./testing-SFCs-with-jest.md) * [用 Mocha 和 webpack 测试单文件组件](./testing-SFCs-with-mocha-webpack.md) diff --git a/docs/zh-cn/guides/choosing-a-test-runner.md b/docs/zh-cn/guides/choosing-a-test-runner.md index a504e7598..77cb1ea71 100644 --- a/docs/zh-cn/guides/choosing-a-test-runner.md +++ b/docs/zh-cn/guides/choosing-a-test-runner.md @@ -6,7 +6,7 @@ 当然在我们选用测试运行器的时候也需要考虑一些事项:功能集合、性能和对单文件组件预编译的支持等。在仔细比对现有的库之后,我们推荐其中的两个测试运行器: -- [Jest](https://facebook.github.io/jest/docs/en/getting-started.html#content) 是功能最全的测试运行器。它所需的配置是最少的,默认安装了 JSDOM,内置断言且命令行的用户体验非常好。不过你需要一个能够将单文件组件导入到测试中的预处理器。我们已经创建了 `jest-vue` 预处理器来处理最常见的单文件组件特性,但仍不是 `vue-loader` 100% 的功能。 +- [Jest](https://facebook.github.io/jest/docs/en/getting-started.html#content) 是功能最全的测试运行器。它所需的配置是最少的,默认安装了 JSDOM,内置断言且命令行的用户体验非常好。不过你需要一个能够将单文件组件导入到测试中的预处理器。我们已经创建了 `vue-jest` 预处理器来处理最常见的单文件组件特性,但仍不是 `vue-loader` 100% 的功能。 - [mocha-webpack](https://github.com/zinserjan/mocha-webpack) 是一个 webpack + Mocha 的包裹器,同时包含了更顺畅的接口和侦听模式。这些设置的好处在于我们能够通过 webpack + `vue-loader` 得到完整的单文件组件支持,但这本身是需要很多配置的。 @@ -29,7 +29,7 @@ require('jsdom-global')() Vue 的单文件组件在它们运行于 Node 或浏览器之前是需要预编译的。我们推荐两种方式完成编译:通过一个 Jest 预编译器,或直接使用 webpack。 -`jest-vue` 预处理器支持基本的单文件组件功能,但是目前还不能处理样式块和自定义块,这些都只在 `vue-loader` 中支持。如果你依赖这些功能或其它 webpack 特有的配置项,那么你需要基于 webpack + `vue-loader` 进行设置。 +`vue-jest` 预处理器支持基本的单文件组件功能,但是目前还不能处理样式块和自定义块,这些都只在 `vue-loader` 中支持。如果你依赖这些功能或其它 webpack 特有的配置项,那么你需要基于 webpack + `vue-loader` 进行设置。 对于不同的设置方式请移步下面的教程: diff --git a/docs/zh-cn/guides/dom-events.md b/docs/zh-cn/guides/dom-events.md new file mode 100644 index 000000000..b7fe7ffc5 --- /dev/null +++ b/docs/zh-cn/guides/dom-events.md @@ -0,0 +1,196 @@ +# 测试键盘、鼠标等其它 DOM 事件 + +## 触发事件 + +`Wrapper` 暴露了一个 `trigger` 方法。它可以用来触发 DOM 事件。 + +```js +const wrapper = mount(MyButton) + +wrapper.trigger('click') +``` + +你应该注意到了,`find` 也会返回一个包裹器。假设 `MyComponent` 包含一个按钮,下面的代码会点击这个按钮。 + +```js +const wrapper = mount(MyComponent) + +wrapper.find('button').trigger('click') +``` + +## 选项 + +该触发方法接受一个可选的 `options` 对象。这个 `options` 对象里的属性会被添加到事件中。 + +你可以通过在 `options` 里传入 `preventDefault: true` 来运行事件上的 `preventDefault`。 + +```js +const wrapper = mount(MyButton) + +wrapper.trigger('click', { preventDefault: true }) +``` + + +## 鼠标点击示例 + +**待测试的组件** + +```html + + + +``` + +**测试** + +```js +import YesNoComponent from '@/components/YesNoComponent' +import { mount } from 'vue-test-utils' +import sinon from 'sinon' + +describe('点击事件', () => { + it('在 yes 按钮上点击会调用我们的方法并附带参数 "yes"', () => { + const spy = sinon.spy() + const wrapper = mount(YesNoComponent, { + propsData: { + callMe: spy + } + }) + wrapper.find('button.yes').trigger('click') + + spy.should.have.been.calledWith('yes') + }) +}) +``` + +## 键盘示例 + +**待测试的组件** + +这个组件允许使用不同的按键将数量递增/递减。 + +```html + + + +``` + +**Test** + +```js +import QuantityComponent from '@/components/QuantityComponent' +import { mount } from 'vue-test-utils' + +describe('键盘事件测试', () => { + it('默认的数量是零', () => { + const wrapper = mount(QuantityComponent) + expect(wrapper.vm.quantity).to.equal(0) + }) + + it('上按键将数量设置为 1', () => { + const wrapper = mount(QuantityComponent) + wrapper.trigger('keydown.up') + expect(wrapper.vm.quantity).to.equal(1) + }) + + it('下按键将数量减 1', () => { + const wrapper = mount(QuantityComponent) + wrapper.vm.quantity = 5 + wrapper.trigger('keydown.down') + expect(wrapper.vm.quantity).to.equal(4) + }) + + it('ESC 键将数量设置为 0', () => { + const wrapper = mount(QuantityComponent) + wrapper.vm.quantity = 5 + wrapper.trigger('keydown.esc') + expect(wrapper.vm.quantity).to.equal(0) + }) + + it('魔术字符 "a" 键将数量设置为 13', () => { + const wrapper = mount(QuantityComponent) + wrapper.trigger('keydown', { + which: 65 + }) + expect(wrapper.vm.quantity).to.equal(13) + }) +}) + +``` + +**限制** + +点后面的按键名 `keydown.up` 会被翻译成一个 `keyCode`。这些被支持的按键名有: + +* `enter`, `tab`, `delete`, `esc`, `space`, `up`, `down`, `left`, `right` + +## 重要事项 + +`vue-test-utils` 是同步触发事件。因此 `vue.nextTick` 不是必须的。 diff --git a/docs/zh-cn/guides/testing-SFCs-with-jest.md b/docs/zh-cn/guides/testing-SFCs-with-jest.md index f4c225c46..f8b1a1786 100644 --- a/docs/zh-cn/guides/testing-SFCs-with-jest.md +++ b/docs/zh-cn/guides/testing-SFCs-with-jest.md @@ -27,10 +27,10 @@ $ npm install --save-dev jest vue-test-utils ## 在 Jest 中处理单文件组件 -为了告诉 Jest 如何处理 `*.vue` 文件,我们需要安装和配置 `jest-vue` 预处理器: +为了告诉 Jest 如何处理 `*.vue` 文件,我们需要安装和配置 `vue-jest` 预处理器: ``` bash -npm install --save-dev jest-vue +npm install --save-dev vue-jest ``` 接下来在 `package.json` 中创建一个 `jest` 块: @@ -46,15 +46,15 @@ npm install --save-dev jest-vue "vue" ], "transform": { - // 用 `jest-vue` 处理 `*.vue` 文件 - ".*\\.(vue)$": "/node_modules/jest-vue" + // 用 `vue-jest` 处理 `*.vue` 文件 + ".*\\.(vue)$": "/node_modules/vue-jest" }, "mapCoverage": true } } ``` -> **注意:**`jest-vue` 目前并不支持 `vue-loader` 所有的功能,比如自定义块和样式加载。额外的,诸如代码分隔等 webpack 特有的功能也是不支持的。如果要使用它们,请阅读教程里的[用 Mocha + webpack 测试单文件组件](./testing-SFCs-with-mocha-webpack.md)。 +> **注意:**`vue-jest` 目前并不支持 `vue-loader` 所有的功能,比如自定义块和样式加载。额外的,诸如代码分隔等 webpack 特有的功能也是不支持的。如果要使用它们,请阅读教程里的[用 Mocha + webpack 测试单文件组件](./testing-SFCs-with-mocha-webpack.md)。 ## 处理 webpack 别名 From abecaa719e39ce9812d1e434bb587f2be0cefe89 Mon Sep 17 00:00:00 2001 From: eddyerburgh Date: Mon, 13 Nov 2017 17:57:31 +0000 Subject: [PATCH 0074/1136] build: 1.0.0-beta.5 --- dist/vue-test-utils.amd.js | 542 +++++++++++++++++++++++++----------- dist/vue-test-utils.iife.js | 542 +++++++++++++++++++++++++----------- dist/vue-test-utils.js | 542 +++++++++++++++++++++++++----------- dist/vue-test-utils.umd.js | 542 +++++++++++++++++++++++++----------- 4 files changed, 1512 insertions(+), 656 deletions(-) diff --git a/dist/vue-test-utils.amd.js b/dist/vue-test-utils.amd.js index c55f8a5f9..d7df40ce0 100644 --- a/dist/vue-test-utils.amd.js +++ b/dist/vue-test-utils.amd.js @@ -2623,13 +2623,20 @@ function stubComponents (component, stubs) { if (Array.isArray(stubs)) { stubs.forEach(function (stub) { + if (stub === false) { + return + } + if (typeof stub !== 'string') { - throwError('each item in options.stub must be a string'); + throwError('each item in an options.stubs array must be a string'); } component.components[stub] = createBlankStub({}); }); } else { Object.keys(stubs).forEach(function (stub) { + if (stubs[stub] === false) { + return + } if (!isValidStub(stubs[stub])) { throwError('options.stub values must be passed a string or component'); } @@ -2735,12 +2742,62 @@ function isVueComponent (component) { return typeof component.render === 'function' } -function isValidSelector (selector) { + + +function isRefSelector (refOptionsObject) { + if (typeof refOptionsObject !== 'object') { + return false + } + + if (refOptionsObject === null) { + return false + } + + var validFindKeys = ['ref']; + var entries = Object.entries(refOptionsObject); + + if (!entries.length) { + return false + } + + var isValid = entries.every(function (ref) { + var key = ref[0]; + var value = ref[1]; + + return validFindKeys.includes(key) && typeof value === 'string' + }); + + return isValid +} + +// + +var selectorTypes = { + DOM_SELECTOR: 'DOM_SELECTOR', + VUE_COMPONENT: 'VUE_COMPONENT', + OPTIONS_OBJECT: 'OPTIONS_OBJECT' +}; + +function getSelectorType (selector) { if (isDomSelector(selector)) { - return true + return selectorTypes.DOM_SELECTOR + } + + if (isVueComponent(selector)) { + return selectorTypes.VUE_COMPONENT } - return isVueComponent(selector) + if (isRefSelector(selector)) { + return selectorTypes.OPTIONS_OBJECT + } +} + +function getSelectorTypeOrThrow (selector, methodName) { + var selectorType = getSelectorType(selector); + if (!selectorType) { + throwError(("wrapper." + methodName + "() must be passed a valid CSS selector, Vue constructor, or valid find option object")); + } + return selectorType } // @@ -2793,10 +2850,6 @@ function findAllVNodes (vnode, nodes) { return nodes } -function nodeMatchesSelector (node, selector) { - return node.elm && node.elm.getAttribute && node.elm.matches(selector) -} - function removeDuplicateNodes (vNodes) { var uniqueNodes = []; vNodes.forEach(function (vNode) { @@ -2808,7 +2861,13 @@ function removeDuplicateNodes (vNodes) { return uniqueNodes } -function findMatchingVNodes (vNode, selector) { +// + +function nodeMatchesSelector (node, selector) { + return node.elm && node.elm.getAttribute && node.elm.matches(selector) +} + +function findVNodesBySelector (vNode, selector) { var nodes = findAllVNodes(vNode); var filteredNodes = nodes.filter(function (node) { return nodeMatchesSelector(node, selector); }); return removeDuplicateNodes(filteredNodes) @@ -2816,6 +2875,20 @@ function findMatchingVNodes (vNode, selector) { // +function nodeMatchesRef (node, refName) { + return node.data && node.data.ref === refName +} + +function findVNodesByRef (vNode, refName) { + var nodes = findAllVNodes(vNode); + var refFilteredNodes = nodes.filter(function (node) { return nodeMatchesRef(node, refName); }); + // Only return refs defined on top-level VNode to provide the same behavior as selecting via vm.$ref.{someRefName} + var mainVNodeFilteredNodes = refFilteredNodes.filter(function (node) { return !!vNode.context.$refs[node.data.ref]; }); + return removeDuplicateNodes(mainVNodeFilteredNodes) +} + +// + var WrapperArray = function WrapperArray (wrappers) { @@ -3090,16 +3163,22 @@ Wrapper.prototype.at = function at () { * Checks if wrapper contains provided selector. */ Wrapper.prototype.contains = function contains (selector) { - if (!isValidSelector(selector)) { - throwError('wrapper.contains() must be passed a valid CSS selector or a Vue constructor'); - } + var selectorType = getSelectorTypeOrThrow(selector, 'contains'); - if (typeof selector === 'object') { + if (selectorType === selectorTypes.VUE_COMPONENT) { var vm = this.vm || this.vnode.context.$root; return findVueComponents(vm, selector.name).length > 0 } - if (typeof selector === 'string' && this.element instanceof HTMLElement) { + if (selectorType === selectorTypes.OPTIONS_OBJECT) { + if (!this.isVueComponent) { + throwError('$ref selectors can only be used on Vue component wrappers'); + } + var nodes = findVNodesByRef(this.vnode, selector.ref); + return nodes.length > 0 + } + + if (selectorType === selectorTypes.DOM_SELECTOR && this.element instanceof HTMLElement) { return this.element.querySelectorAll(selector).length > 0 } @@ -3231,11 +3310,9 @@ Wrapper.prototype.hasStyle = function hasStyle (style, value) { * Finds first node in tree of the current wrapper that matches the provided selector. */ Wrapper.prototype.find = function find (selector) { - if (!isValidSelector(selector)) { - throwError('wrapper.find() must be passed a valid CSS selector or a Vue constructor'); - } + var selectorType = getSelectorTypeOrThrow(selector, 'find'); - if (typeof selector === 'object') { + if (selectorType === selectorTypes.VUE_COMPONENT) { if (!selector.name) { throwError('.find() requires component to have a name property'); } @@ -3247,7 +3324,18 @@ Wrapper.prototype.find = function find (selector) { return new VueWrapper(components[0], this.options) } - var nodes = findMatchingVNodes(this.vnode, selector); + if (selectorType === selectorTypes.OPTIONS_OBJECT) { + if (!this.isVueComponent) { + throwError('$ref selectors can only be used on Vue component wrappers'); + } + var nodes$1 = findVNodesByRef(this.vnode, selector.ref); + if (nodes$1.length === 0) { + return new ErrorWrapper(("ref=\"" + (selector.ref) + "\"")) + } + return new Wrapper(nodes$1[0], this.update, this.options) + } + + var nodes = findVNodesBySelector(this.vnode, selector); if (nodes.length === 0) { return new ErrorWrapper(selector) @@ -3261,11 +3349,9 @@ Wrapper.prototype.find = function find (selector) { Wrapper.prototype.findAll = function findAll (selector) { var this$1 = this; - if (!isValidSelector(selector)) { - throwError('wrapper.findAll() must be passed a valid CSS selector or a Vue constructor'); - } + var selectorType = getSelectorTypeOrThrow(selector, 'findAll'); - if (typeof selector === 'object') { + if (selectorType === selectorTypes.VUE_COMPONENT) { if (!selector.name) { throwError('.findAll() requires component to have a name property'); } @@ -3274,11 +3360,19 @@ Wrapper.prototype.findAll = function findAll (selector) { return new WrapperArray(components.map(function (component) { return new VueWrapper(component, this$1.options); })) } + if (selectorType === selectorTypes.OPTIONS_OBJECT) { + if (!this.isVueComponent) { + throwError('$ref selectors can only be used on Vue component wrappers'); + } + var nodes$1 = findVNodesByRef(this.vnode, selector.ref); + return new WrapperArray(nodes$1.map(function (node) { return new Wrapper(node, this$1.update, this$1.options); })) + } + function nodeMatchesSelector (node, selector) { return node.elm && node.elm.getAttribute && node.elm.matches(selector) } - var nodes = findMatchingVNodes(this.vnode, selector); + var nodes = findVNodesBySelector(this.vnode, selector); var matchingNodes = nodes.filter(function (node) { return nodeMatchesSelector(node, selector); }); return new WrapperArray(matchingNodes.map(function (node) { return new Wrapper(node, this$1.update, this$1.options); })) @@ -3295,20 +3389,23 @@ Wrapper.prototype.html = function html () { * Checks if node matches selector */ Wrapper.prototype.is = function is (selector) { - if (!isValidSelector(selector)) { - throwError('wrapper.is() must be passed a valid CSS selector or a Vue constructor'); - } + var selectorType = getSelectorTypeOrThrow(selector, 'is'); - if (typeof selector === 'object') { - if (!this.isVueComponent) { - return false - } + if (selectorType === selectorTypes.VUE_COMPONENT && this.isVueComponent) { if (typeof selector.name !== 'string') { throwError('a Component used as a selector must have a name property'); } return vmCtorMatchesName(this.vm, selector.name) } + if (selectorType === selectorTypes.OPTIONS_OBJECT) { + throwError('$ref selectors can not be used with wrapper.is()'); + } + + if (typeof selector === 'object') { + return false + } + return !!(this.element && this.element.getAttribute && this.element.matches(selector)) @@ -3353,6 +3450,14 @@ Wrapper.prototype.setData = function setData (data) { // $FlowIgnore : Problem with possibly null this.vm this$1.vm.$set(this$1.vm, [key], data[key]); }); + + Object.keys(data).forEach(function (key) { + // $FlowIgnore : Problem with possibly null this.vm + this$1.vm._watchers.forEach(function (watcher) { + if (watcher.expression === key) { watcher.run(); } + }); + }); + this.update(); }; @@ -3447,7 +3552,7 @@ Wrapper.prototype.text = function text () { throwError('cannot call wrapper.text() on a wrapper without an element'); } - return this.element.textContent + return this.element.textContent.trim() }; /** @@ -3566,7 +3671,7 @@ var VueWrapper = (function (Wrapper$$1) { // -function isValidSlot (slot) { +function isValidSlot$1 (slot) { return Array.isArray(slot) || (slot !== null && typeof slot === 'object') || typeof slot === 'string' } @@ -3594,7 +3699,7 @@ function addSlotToVm (vm, slotName, slotValue) { function addSlots (vm, slots) { Object.keys(slots).forEach(function (key) { - if (!isValidSlot(slots[key])) { + if (!isValidSlot$1(slots[key])) { throwError('slots[key] must be a Component, string or an array of Components'); } @@ -3638,12 +3743,10 @@ function addListeners (vm, listeners) { Vue.config.silent = originalVueConfig.silent; } -function addProvide (component, options) { - var provide = typeof options.provide === 'function' - ? options.provide - : Object.assign({}, options.provide); - - delete options.provide; +function addProvide (component, optionProvide, options) { + var provide = typeof optionProvide === 'function' + ? optionProvide + : Object.assign({}, optionProvide); options.beforeCreate = function vueTestUtilBeforeCreate () { this._provided = typeof provide === 'function' @@ -3658,6 +3761,16 @@ function compileTemplate (component) { Object.assign(component, vueTemplateCompiler.compileToFunctions(component.template)); } +function errorHandler (errorOrString, vm) { + var error = (typeof errorOrString === 'object') + ? errorOrString + : new Error(errorOrString); + + vm._error = error; + + throw error +} + // function createLocalVue () { @@ -3676,6 +3789,8 @@ function createLocalVue () { // config is not enumerable instance.config = cloneDeep_1(Vue.config); + instance.config.errorHandler = errorHandler; + // option merge strategies need to be exposed by reference // so that merge strats registered by plguins can work properly instance.config.optionMergeStrategies = Vue.config.optionMergeStrategies; @@ -3704,130 +3819,6 @@ function createLocalVue () { // -function createConstructor ( - component, - options -) { - var vue = options.localVue || createLocalVue(); - - if (options.mocks) { - addMocks(options.mocks, vue); - } - - if (component.functional) { - if (options.context && typeof options.context !== 'object') { - throwError('mount.context must be an object'); - } - var clonedComponent = cloneDeep_1(component); - component = { - render: function render (h) { - return h( - clonedComponent, - options.context || component.FunctionalRenderContext - ) - } - }; - } else if (options.context) { - throwError( - 'mount.context can only be used when mounting a functional component' - ); - } - - if (options.provide) { - addProvide(component, options); - } - - if (options.stubs) { - stubComponents(component, options.stubs); - } - - if (!component.render && component.template && !component.functional) { - compileTemplate(component); - } - - var Constructor = vue.extend(component); - - var vm = new Constructor(options); - - addAttrs(vm, options.attrs); - addListeners(vm, options.listeners); - - if (options.slots) { - addSlots(vm, options.slots); - } - - return vm -} - -// - -function createElement () { - if (document) { - var elem = document.createElement('div'); - - if (document.body) { - document.body.appendChild(elem); - } - return elem - } -} - -if (!Element.prototype.matches) { - Element.prototype.matches = - Element.prototype.matchesSelector || - Element.prototype.mozMatchesSelector || - Element.prototype.msMatchesSelector || - Element.prototype.oMatchesSelector || - Element.prototype.webkitMatchesSelector || - function (s) { - var matches = (this.document || this.ownerDocument).querySelectorAll(s); - var i = matches.length; - while (--i >= 0 && matches.item(i) !== this) {} - return i > -1 - }; -} - -// - -Vue.config.productionTip = false; - -function mount (component, options) { - if ( options === void 0 ) options = {}; - - var componentToMount = options.clone === false ? component : cloneDeep_1(component.extend ? component.options : component); - // Remove cached constructor - delete componentToMount._Ctor; - - var vm = createConstructor(componentToMount, options); - - if (options.attachToDocument) { - vm.$mount(createElement()); - } else { - vm.$mount(); - } - - return new VueWrapper(vm, { attachedToDocument: !!options.attachToDocument }) -} - -// - -function shallow (component, options) { - if ( options === void 0 ) options = {}; - - var vue = options.localVue || Vue; - var clonedComponent = cloneDeep_1(component.extend ? component.options : component); - - if (clonedComponent.components) { - stubAllComponents(clonedComponent); - } - - stubGlobalComponents(clonedComponent, vue); - - return mount(clonedComponent, options) -} - -// - function getRealChild (vnode) { var compOptions = vnode && vnode.componentOptions; if (compOptions && compOptions.Ctor.options.abstract) { @@ -3951,8 +3942,231 @@ var TransitionGroupStub = { } }; +var config = { + stubs: { + transition: TransitionStub, + 'transition-group': TransitionGroupStub + } +}; + +// +function getStubs (optionStubs) { + if (optionStubs || Object.keys(config.stubs).length > 0) { + if (Array.isArray(optionStubs)) { + return optionStubs.concat( Object.keys(config.stubs)) + } else { + return Object.assign({}, config.stubs, + optionStubs) + } + } +} + +function extractOptions ( + options +) { + return { + mocks: options.mocks, + context: options.context, + provide: options.provide, + stubs: getStubs(options.stubs), + attrs: options.attrs, + listeners: options.listeners, + slots: options.slots, + localVue: options.localVue + } +} + +function deleteMountingOptions (options) { + delete options.custom; + delete options.attachToDocument; + delete options.mocks; + delete options.slots; + delete options.localVue; + delete options.stubs; + delete options.context; + delete options.clone; + delete options.attrs; + delete options.listeners; +} + +// + +function isValidSlot (slot) { + return Array.isArray(slot) || (slot !== null && typeof slot === 'object') || typeof slot === 'string' +} + +function createFunctionalSlots (slots, h) { + if ( slots === void 0 ) slots = {}; + + if (Array.isArray(slots.default)) { + return slots.default.map(h) + } + + if (typeof slots.default === 'string') { + return [h(vueTemplateCompiler.compileToFunctions(slots.default))] + } + var children = []; + Object.keys(slots).forEach(function (slotType) { + if (Array.isArray(slots[slotType])) { + slots[slotType].forEach(function (slot) { + if (!isValidSlot(slot)) { + throwError('slots[key] must be a Component, string or an array of Components'); + } + var component = typeof slot === 'string' ? vueTemplateCompiler.compileToFunctions(slot) : slot; + var newSlot = h(component); + newSlot.data.slot = slotType; + children.push(newSlot); + }); + } else { + if (!isValidSlot(slots[slotType])) { + throwError('slots[key] must be a Component, string or an array of Components'); + } + var component = typeof slots[slotType] === 'string' ? vueTemplateCompiler.compileToFunctions(slots[slotType]) : slots[slotType]; + var slot = h(component); + slot.data.slot = slotType; + children.push(slot); + } + }); + return children +} + +function createConstructor ( + component, + options +) { + var mountingOptions = extractOptions(options); + + var vue = mountingOptions.localVue || createLocalVue(); + + if (mountingOptions.mocks) { + addMocks(mountingOptions.mocks, vue); + } + + if (component.functional) { + if (mountingOptions.context && typeof mountingOptions.context !== 'object') { + throwError('mount.context must be an object'); + } + + var clonedComponent = cloneDeep_1(component); + component = { + render: function render (h) { + return h( + clonedComponent, + mountingOptions.context || component.FunctionalRenderContext, + (mountingOptions.context && mountingOptions.context.children) || createFunctionalSlots(mountingOptions.slots, h) + ) + } + }; + } else if (mountingOptions.context) { + throwError( + 'mount.context can only be used when mounting a functional component' + ); + } + + if (mountingOptions.provide) { + addProvide(component, mountingOptions.provide, options); + } + + if (mountingOptions.stubs) { + stubComponents(component, mountingOptions.stubs); + } + + if (!component.render && component.template && !component.functional) { + compileTemplate(component); + } + + var Constructor = vue.extend(component); + + var instanceOptions = Object.assign({}, options); + deleteMountingOptions(instanceOptions); + + var vm = new Constructor(instanceOptions); + + addAttrs(vm, mountingOptions.attrs); + addListeners(vm, mountingOptions.listeners); + + if (mountingOptions.slots) { + addSlots(vm, mountingOptions.slots); + } + + return vm +} + +// + +function createElement () { + if (document) { + var elem = document.createElement('div'); + + if (document.body) { + document.body.appendChild(elem); + } + return elem + } +} + +if (!Element.prototype.matches) { + Element.prototype.matches = + Element.prototype.matchesSelector || + Element.prototype.mozMatchesSelector || + Element.prototype.msMatchesSelector || + Element.prototype.oMatchesSelector || + Element.prototype.webkitMatchesSelector || + function (s) { + var matches = (this.document || this.ownerDocument).querySelectorAll(s); + var i = matches.length; + while (--i >= 0 && matches.item(i) !== this) {} + return i > -1 + }; +} + +// + +Vue.config.productionTip = false; +Vue.config.errorHandler = errorHandler; + +function mount (component, options) { + if ( options === void 0 ) options = {}; + + var componentToMount = options.clone === false ? component : cloneDeep_1(component.extend ? component.options : component); + // Remove cached constructor + delete componentToMount._Ctor; + + var vm = createConstructor(componentToMount, options); + + if (options.attachToDocument) { + vm.$mount(createElement()); + } else { + vm.$mount(); + } + + if (vm._error) { + throw (vm._error) + } + + return new VueWrapper(vm, { attachedToDocument: !!options.attachToDocument }) +} + +// + +function shallow (component, options) { + if ( options === void 0 ) options = {}; + + var vue = options.localVue || Vue; + var clonedComponent = cloneDeep_1(component.extend ? component.options : component); + + if (clonedComponent.components) { + stubAllComponents(clonedComponent); + } + + stubGlobalComponents(clonedComponent, vue); + + return mount(clonedComponent, options) +} + var index = { createLocalVue: createLocalVue, + config: config, mount: mount, shallow: shallow, TransitionStub: TransitionStub, diff --git a/dist/vue-test-utils.iife.js b/dist/vue-test-utils.iife.js index e75053da9..03f77d733 100644 --- a/dist/vue-test-utils.iife.js +++ b/dist/vue-test-utils.iife.js @@ -2624,13 +2624,20 @@ function stubComponents (component, stubs) { if (Array.isArray(stubs)) { stubs.forEach(function (stub) { + if (stub === false) { + return + } + if (typeof stub !== 'string') { - throwError('each item in options.stub must be a string'); + throwError('each item in an options.stubs array must be a string'); } component.components[stub] = createBlankStub({}); }); } else { Object.keys(stubs).forEach(function (stub) { + if (stubs[stub] === false) { + return + } if (!isValidStub(stubs[stub])) { throwError('options.stub values must be passed a string or component'); } @@ -2736,12 +2743,62 @@ function isVueComponent (component) { return typeof component.render === 'function' } -function isValidSelector (selector) { + + +function isRefSelector (refOptionsObject) { + if (typeof refOptionsObject !== 'object') { + return false + } + + if (refOptionsObject === null) { + return false + } + + var validFindKeys = ['ref']; + var entries = Object.entries(refOptionsObject); + + if (!entries.length) { + return false + } + + var isValid = entries.every(function (ref) { + var key = ref[0]; + var value = ref[1]; + + return validFindKeys.includes(key) && typeof value === 'string' + }); + + return isValid +} + +// + +var selectorTypes = { + DOM_SELECTOR: 'DOM_SELECTOR', + VUE_COMPONENT: 'VUE_COMPONENT', + OPTIONS_OBJECT: 'OPTIONS_OBJECT' +}; + +function getSelectorType (selector) { if (isDomSelector(selector)) { - return true + return selectorTypes.DOM_SELECTOR + } + + if (isVueComponent(selector)) { + return selectorTypes.VUE_COMPONENT } - return isVueComponent(selector) + if (isRefSelector(selector)) { + return selectorTypes.OPTIONS_OBJECT + } +} + +function getSelectorTypeOrThrow (selector, methodName) { + var selectorType = getSelectorType(selector); + if (!selectorType) { + throwError(("wrapper." + methodName + "() must be passed a valid CSS selector, Vue constructor, or valid find option object")); + } + return selectorType } // @@ -2794,10 +2851,6 @@ function findAllVNodes (vnode, nodes) { return nodes } -function nodeMatchesSelector (node, selector) { - return node.elm && node.elm.getAttribute && node.elm.matches(selector) -} - function removeDuplicateNodes (vNodes) { var uniqueNodes = []; vNodes.forEach(function (vNode) { @@ -2809,7 +2862,13 @@ function removeDuplicateNodes (vNodes) { return uniqueNodes } -function findMatchingVNodes (vNode, selector) { +// + +function nodeMatchesSelector (node, selector) { + return node.elm && node.elm.getAttribute && node.elm.matches(selector) +} + +function findVNodesBySelector (vNode, selector) { var nodes = findAllVNodes(vNode); var filteredNodes = nodes.filter(function (node) { return nodeMatchesSelector(node, selector); }); return removeDuplicateNodes(filteredNodes) @@ -2817,6 +2876,20 @@ function findMatchingVNodes (vNode, selector) { // +function nodeMatchesRef (node, refName) { + return node.data && node.data.ref === refName +} + +function findVNodesByRef (vNode, refName) { + var nodes = findAllVNodes(vNode); + var refFilteredNodes = nodes.filter(function (node) { return nodeMatchesRef(node, refName); }); + // Only return refs defined on top-level VNode to provide the same behavior as selecting via vm.$ref.{someRefName} + var mainVNodeFilteredNodes = refFilteredNodes.filter(function (node) { return !!vNode.context.$refs[node.data.ref]; }); + return removeDuplicateNodes(mainVNodeFilteredNodes) +} + +// + var WrapperArray = function WrapperArray (wrappers) { @@ -3091,16 +3164,22 @@ Wrapper.prototype.at = function at () { * Checks if wrapper contains provided selector. */ Wrapper.prototype.contains = function contains (selector) { - if (!isValidSelector(selector)) { - throwError('wrapper.contains() must be passed a valid CSS selector or a Vue constructor'); - } + var selectorType = getSelectorTypeOrThrow(selector, 'contains'); - if (typeof selector === 'object') { + if (selectorType === selectorTypes.VUE_COMPONENT) { var vm = this.vm || this.vnode.context.$root; return findVueComponents(vm, selector.name).length > 0 } - if (typeof selector === 'string' && this.element instanceof HTMLElement) { + if (selectorType === selectorTypes.OPTIONS_OBJECT) { + if (!this.isVueComponent) { + throwError('$ref selectors can only be used on Vue component wrappers'); + } + var nodes = findVNodesByRef(this.vnode, selector.ref); + return nodes.length > 0 + } + + if (selectorType === selectorTypes.DOM_SELECTOR && this.element instanceof HTMLElement) { return this.element.querySelectorAll(selector).length > 0 } @@ -3232,11 +3311,9 @@ Wrapper.prototype.hasStyle = function hasStyle (style, value) { * Finds first node in tree of the current wrapper that matches the provided selector. */ Wrapper.prototype.find = function find (selector) { - if (!isValidSelector(selector)) { - throwError('wrapper.find() must be passed a valid CSS selector or a Vue constructor'); - } + var selectorType = getSelectorTypeOrThrow(selector, 'find'); - if (typeof selector === 'object') { + if (selectorType === selectorTypes.VUE_COMPONENT) { if (!selector.name) { throwError('.find() requires component to have a name property'); } @@ -3248,7 +3325,18 @@ Wrapper.prototype.find = function find (selector) { return new VueWrapper(components[0], this.options) } - var nodes = findMatchingVNodes(this.vnode, selector); + if (selectorType === selectorTypes.OPTIONS_OBJECT) { + if (!this.isVueComponent) { + throwError('$ref selectors can only be used on Vue component wrappers'); + } + var nodes$1 = findVNodesByRef(this.vnode, selector.ref); + if (nodes$1.length === 0) { + return new ErrorWrapper(("ref=\"" + (selector.ref) + "\"")) + } + return new Wrapper(nodes$1[0], this.update, this.options) + } + + var nodes = findVNodesBySelector(this.vnode, selector); if (nodes.length === 0) { return new ErrorWrapper(selector) @@ -3262,11 +3350,9 @@ Wrapper.prototype.find = function find (selector) { Wrapper.prototype.findAll = function findAll (selector) { var this$1 = this; - if (!isValidSelector(selector)) { - throwError('wrapper.findAll() must be passed a valid CSS selector or a Vue constructor'); - } + var selectorType = getSelectorTypeOrThrow(selector, 'findAll'); - if (typeof selector === 'object') { + if (selectorType === selectorTypes.VUE_COMPONENT) { if (!selector.name) { throwError('.findAll() requires component to have a name property'); } @@ -3275,11 +3361,19 @@ Wrapper.prototype.findAll = function findAll (selector) { return new WrapperArray(components.map(function (component) { return new VueWrapper(component, this$1.options); })) } + if (selectorType === selectorTypes.OPTIONS_OBJECT) { + if (!this.isVueComponent) { + throwError('$ref selectors can only be used on Vue component wrappers'); + } + var nodes$1 = findVNodesByRef(this.vnode, selector.ref); + return new WrapperArray(nodes$1.map(function (node) { return new Wrapper(node, this$1.update, this$1.options); })) + } + function nodeMatchesSelector (node, selector) { return node.elm && node.elm.getAttribute && node.elm.matches(selector) } - var nodes = findMatchingVNodes(this.vnode, selector); + var nodes = findVNodesBySelector(this.vnode, selector); var matchingNodes = nodes.filter(function (node) { return nodeMatchesSelector(node, selector); }); return new WrapperArray(matchingNodes.map(function (node) { return new Wrapper(node, this$1.update, this$1.options); })) @@ -3296,20 +3390,23 @@ Wrapper.prototype.html = function html () { * Checks if node matches selector */ Wrapper.prototype.is = function is (selector) { - if (!isValidSelector(selector)) { - throwError('wrapper.is() must be passed a valid CSS selector or a Vue constructor'); - } + var selectorType = getSelectorTypeOrThrow(selector, 'is'); - if (typeof selector === 'object') { - if (!this.isVueComponent) { - return false - } + if (selectorType === selectorTypes.VUE_COMPONENT && this.isVueComponent) { if (typeof selector.name !== 'string') { throwError('a Component used as a selector must have a name property'); } return vmCtorMatchesName(this.vm, selector.name) } + if (selectorType === selectorTypes.OPTIONS_OBJECT) { + throwError('$ref selectors can not be used with wrapper.is()'); + } + + if (typeof selector === 'object') { + return false + } + return !!(this.element && this.element.getAttribute && this.element.matches(selector)) @@ -3354,6 +3451,14 @@ Wrapper.prototype.setData = function setData (data) { // $FlowIgnore : Problem with possibly null this.vm this$1.vm.$set(this$1.vm, [key], data[key]); }); + + Object.keys(data).forEach(function (key) { + // $FlowIgnore : Problem with possibly null this.vm + this$1.vm._watchers.forEach(function (watcher) { + if (watcher.expression === key) { watcher.run(); } + }); + }); + this.update(); }; @@ -3448,7 +3553,7 @@ Wrapper.prototype.text = function text () { throwError('cannot call wrapper.text() on a wrapper without an element'); } - return this.element.textContent + return this.element.textContent.trim() }; /** @@ -3567,7 +3672,7 @@ var VueWrapper = (function (Wrapper$$1) { // -function isValidSlot (slot) { +function isValidSlot$1 (slot) { return Array.isArray(slot) || (slot !== null && typeof slot === 'object') || typeof slot === 'string' } @@ -3595,7 +3700,7 @@ function addSlotToVm (vm, slotName, slotValue) { function addSlots (vm, slots) { Object.keys(slots).forEach(function (key) { - if (!isValidSlot(slots[key])) { + if (!isValidSlot$1(slots[key])) { throwError('slots[key] must be a Component, string or an array of Components'); } @@ -3639,12 +3744,10 @@ function addListeners (vm, listeners) { Vue.config.silent = originalVueConfig.silent; } -function addProvide (component, options) { - var provide = typeof options.provide === 'function' - ? options.provide - : Object.assign({}, options.provide); - - delete options.provide; +function addProvide (component, optionProvide, options) { + var provide = typeof optionProvide === 'function' + ? optionProvide + : Object.assign({}, optionProvide); options.beforeCreate = function vueTestUtilBeforeCreate () { this._provided = typeof provide === 'function' @@ -3659,6 +3762,16 @@ function compileTemplate (component) { Object.assign(component, vueTemplateCompiler.compileToFunctions(component.template)); } +function errorHandler (errorOrString, vm) { + var error = (typeof errorOrString === 'object') + ? errorOrString + : new Error(errorOrString); + + vm._error = error; + + throw error +} + // function createLocalVue () { @@ -3677,6 +3790,8 @@ function createLocalVue () { // config is not enumerable instance.config = cloneDeep_1(Vue.config); + instance.config.errorHandler = errorHandler; + // option merge strategies need to be exposed by reference // so that merge strats registered by plguins can work properly instance.config.optionMergeStrategies = Vue.config.optionMergeStrategies; @@ -3705,130 +3820,6 @@ function createLocalVue () { // -function createConstructor ( - component, - options -) { - var vue = options.localVue || createLocalVue(); - - if (options.mocks) { - addMocks(options.mocks, vue); - } - - if (component.functional) { - if (options.context && typeof options.context !== 'object') { - throwError('mount.context must be an object'); - } - var clonedComponent = cloneDeep_1(component); - component = { - render: function render (h) { - return h( - clonedComponent, - options.context || component.FunctionalRenderContext - ) - } - }; - } else if (options.context) { - throwError( - 'mount.context can only be used when mounting a functional component' - ); - } - - if (options.provide) { - addProvide(component, options); - } - - if (options.stubs) { - stubComponents(component, options.stubs); - } - - if (!component.render && component.template && !component.functional) { - compileTemplate(component); - } - - var Constructor = vue.extend(component); - - var vm = new Constructor(options); - - addAttrs(vm, options.attrs); - addListeners(vm, options.listeners); - - if (options.slots) { - addSlots(vm, options.slots); - } - - return vm -} - -// - -function createElement () { - if (document) { - var elem = document.createElement('div'); - - if (document.body) { - document.body.appendChild(elem); - } - return elem - } -} - -if (!Element.prototype.matches) { - Element.prototype.matches = - Element.prototype.matchesSelector || - Element.prototype.mozMatchesSelector || - Element.prototype.msMatchesSelector || - Element.prototype.oMatchesSelector || - Element.prototype.webkitMatchesSelector || - function (s) { - var matches = (this.document || this.ownerDocument).querySelectorAll(s); - var i = matches.length; - while (--i >= 0 && matches.item(i) !== this) {} - return i > -1 - }; -} - -// - -Vue.config.productionTip = false; - -function mount (component, options) { - if ( options === void 0 ) options = {}; - - var componentToMount = options.clone === false ? component : cloneDeep_1(component.extend ? component.options : component); - // Remove cached constructor - delete componentToMount._Ctor; - - var vm = createConstructor(componentToMount, options); - - if (options.attachToDocument) { - vm.$mount(createElement()); - } else { - vm.$mount(); - } - - return new VueWrapper(vm, { attachedToDocument: !!options.attachToDocument }) -} - -// - -function shallow (component, options) { - if ( options === void 0 ) options = {}; - - var vue = options.localVue || Vue; - var clonedComponent = cloneDeep_1(component.extend ? component.options : component); - - if (clonedComponent.components) { - stubAllComponents(clonedComponent); - } - - stubGlobalComponents(clonedComponent, vue); - - return mount(clonedComponent, options) -} - -// - function getRealChild (vnode) { var compOptions = vnode && vnode.componentOptions; if (compOptions && compOptions.Ctor.options.abstract) { @@ -3952,8 +3943,231 @@ var TransitionGroupStub = { } }; +var config = { + stubs: { + transition: TransitionStub, + 'transition-group': TransitionGroupStub + } +}; + +// +function getStubs (optionStubs) { + if (optionStubs || Object.keys(config.stubs).length > 0) { + if (Array.isArray(optionStubs)) { + return optionStubs.concat( Object.keys(config.stubs)) + } else { + return Object.assign({}, config.stubs, + optionStubs) + } + } +} + +function extractOptions ( + options +) { + return { + mocks: options.mocks, + context: options.context, + provide: options.provide, + stubs: getStubs(options.stubs), + attrs: options.attrs, + listeners: options.listeners, + slots: options.slots, + localVue: options.localVue + } +} + +function deleteMountingOptions (options) { + delete options.custom; + delete options.attachToDocument; + delete options.mocks; + delete options.slots; + delete options.localVue; + delete options.stubs; + delete options.context; + delete options.clone; + delete options.attrs; + delete options.listeners; +} + +// + +function isValidSlot (slot) { + return Array.isArray(slot) || (slot !== null && typeof slot === 'object') || typeof slot === 'string' +} + +function createFunctionalSlots (slots, h) { + if ( slots === void 0 ) slots = {}; + + if (Array.isArray(slots.default)) { + return slots.default.map(h) + } + + if (typeof slots.default === 'string') { + return [h(vueTemplateCompiler.compileToFunctions(slots.default))] + } + var children = []; + Object.keys(slots).forEach(function (slotType) { + if (Array.isArray(slots[slotType])) { + slots[slotType].forEach(function (slot) { + if (!isValidSlot(slot)) { + throwError('slots[key] must be a Component, string or an array of Components'); + } + var component = typeof slot === 'string' ? vueTemplateCompiler.compileToFunctions(slot) : slot; + var newSlot = h(component); + newSlot.data.slot = slotType; + children.push(newSlot); + }); + } else { + if (!isValidSlot(slots[slotType])) { + throwError('slots[key] must be a Component, string or an array of Components'); + } + var component = typeof slots[slotType] === 'string' ? vueTemplateCompiler.compileToFunctions(slots[slotType]) : slots[slotType]; + var slot = h(component); + slot.data.slot = slotType; + children.push(slot); + } + }); + return children +} + +function createConstructor ( + component, + options +) { + var mountingOptions = extractOptions(options); + + var vue = mountingOptions.localVue || createLocalVue(); + + if (mountingOptions.mocks) { + addMocks(mountingOptions.mocks, vue); + } + + if (component.functional) { + if (mountingOptions.context && typeof mountingOptions.context !== 'object') { + throwError('mount.context must be an object'); + } + + var clonedComponent = cloneDeep_1(component); + component = { + render: function render (h) { + return h( + clonedComponent, + mountingOptions.context || component.FunctionalRenderContext, + (mountingOptions.context && mountingOptions.context.children) || createFunctionalSlots(mountingOptions.slots, h) + ) + } + }; + } else if (mountingOptions.context) { + throwError( + 'mount.context can only be used when mounting a functional component' + ); + } + + if (mountingOptions.provide) { + addProvide(component, mountingOptions.provide, options); + } + + if (mountingOptions.stubs) { + stubComponents(component, mountingOptions.stubs); + } + + if (!component.render && component.template && !component.functional) { + compileTemplate(component); + } + + var Constructor = vue.extend(component); + + var instanceOptions = Object.assign({}, options); + deleteMountingOptions(instanceOptions); + + var vm = new Constructor(instanceOptions); + + addAttrs(vm, mountingOptions.attrs); + addListeners(vm, mountingOptions.listeners); + + if (mountingOptions.slots) { + addSlots(vm, mountingOptions.slots); + } + + return vm +} + +// + +function createElement () { + if (document) { + var elem = document.createElement('div'); + + if (document.body) { + document.body.appendChild(elem); + } + return elem + } +} + +if (!Element.prototype.matches) { + Element.prototype.matches = + Element.prototype.matchesSelector || + Element.prototype.mozMatchesSelector || + Element.prototype.msMatchesSelector || + Element.prototype.oMatchesSelector || + Element.prototype.webkitMatchesSelector || + function (s) { + var matches = (this.document || this.ownerDocument).querySelectorAll(s); + var i = matches.length; + while (--i >= 0 && matches.item(i) !== this) {} + return i > -1 + }; +} + +// + +Vue.config.productionTip = false; +Vue.config.errorHandler = errorHandler; + +function mount (component, options) { + if ( options === void 0 ) options = {}; + + var componentToMount = options.clone === false ? component : cloneDeep_1(component.extend ? component.options : component); + // Remove cached constructor + delete componentToMount._Ctor; + + var vm = createConstructor(componentToMount, options); + + if (options.attachToDocument) { + vm.$mount(createElement()); + } else { + vm.$mount(); + } + + if (vm._error) { + throw (vm._error) + } + + return new VueWrapper(vm, { attachedToDocument: !!options.attachToDocument }) +} + +// + +function shallow (component, options) { + if ( options === void 0 ) options = {}; + + var vue = options.localVue || Vue; + var clonedComponent = cloneDeep_1(component.extend ? component.options : component); + + if (clonedComponent.components) { + stubAllComponents(clonedComponent); + } + + stubGlobalComponents(clonedComponent, vue); + + return mount(clonedComponent, options) +} + var index = { createLocalVue: createLocalVue, + config: config, mount: mount, shallow: shallow, TransitionStub: TransitionStub, diff --git a/dist/vue-test-utils.js b/dist/vue-test-utils.js index 0d973b3b3..8e15d4d5e 100644 --- a/dist/vue-test-utils.js +++ b/dist/vue-test-utils.js @@ -94,13 +94,20 @@ function stubComponents (component, stubs) { if (Array.isArray(stubs)) { stubs.forEach(function (stub) { + if (stub === false) { + return + } + if (typeof stub !== 'string') { - throwError('each item in options.stub must be a string'); + throwError('each item in an options.stubs array must be a string'); } component.components[stub] = createBlankStub({}); }); } else { Object.keys(stubs).forEach(function (stub) { + if (stubs[stub] === false) { + return + } if (!isValidStub(stubs[stub])) { throwError('options.stub values must be passed a string or component'); } @@ -206,12 +213,62 @@ function isVueComponent (component) { return typeof component.render === 'function' } -function isValidSelector (selector) { + + +function isRefSelector (refOptionsObject) { + if (typeof refOptionsObject !== 'object') { + return false + } + + if (refOptionsObject === null) { + return false + } + + var validFindKeys = ['ref']; + var entries = Object.entries(refOptionsObject); + + if (!entries.length) { + return false + } + + var isValid = entries.every(function (ref) { + var key = ref[0]; + var value = ref[1]; + + return validFindKeys.includes(key) && typeof value === 'string' + }); + + return isValid +} + +// + +var selectorTypes = { + DOM_SELECTOR: 'DOM_SELECTOR', + VUE_COMPONENT: 'VUE_COMPONENT', + OPTIONS_OBJECT: 'OPTIONS_OBJECT' +}; + +function getSelectorType (selector) { if (isDomSelector(selector)) { - return true + return selectorTypes.DOM_SELECTOR + } + + if (isVueComponent(selector)) { + return selectorTypes.VUE_COMPONENT } - return isVueComponent(selector) + if (isRefSelector(selector)) { + return selectorTypes.OPTIONS_OBJECT + } +} + +function getSelectorTypeOrThrow (selector, methodName) { + var selectorType = getSelectorType(selector); + if (!selectorType) { + throwError(("wrapper." + methodName + "() must be passed a valid CSS selector, Vue constructor, or valid find option object")); + } + return selectorType } // @@ -264,10 +321,6 @@ function findAllVNodes (vnode, nodes) { return nodes } -function nodeMatchesSelector (node, selector) { - return node.elm && node.elm.getAttribute && node.elm.matches(selector) -} - function removeDuplicateNodes (vNodes) { var uniqueNodes = []; vNodes.forEach(function (vNode) { @@ -279,7 +332,13 @@ function removeDuplicateNodes (vNodes) { return uniqueNodes } -function findMatchingVNodes (vNode, selector) { +// + +function nodeMatchesSelector (node, selector) { + return node.elm && node.elm.getAttribute && node.elm.matches(selector) +} + +function findVNodesBySelector (vNode, selector) { var nodes = findAllVNodes(vNode); var filteredNodes = nodes.filter(function (node) { return nodeMatchesSelector(node, selector); }); return removeDuplicateNodes(filteredNodes) @@ -287,6 +346,20 @@ function findMatchingVNodes (vNode, selector) { // +function nodeMatchesRef (node, refName) { + return node.data && node.data.ref === refName +} + +function findVNodesByRef (vNode, refName) { + var nodes = findAllVNodes(vNode); + var refFilteredNodes = nodes.filter(function (node) { return nodeMatchesRef(node, refName); }); + // Only return refs defined on top-level VNode to provide the same behavior as selecting via vm.$ref.{someRefName} + var mainVNodeFilteredNodes = refFilteredNodes.filter(function (node) { return !!vNode.context.$refs[node.data.ref]; }); + return removeDuplicateNodes(mainVNodeFilteredNodes) +} + +// + var WrapperArray = function WrapperArray (wrappers) { @@ -561,16 +634,22 @@ Wrapper.prototype.at = function at () { * Checks if wrapper contains provided selector. */ Wrapper.prototype.contains = function contains (selector) { - if (!isValidSelector(selector)) { - throwError('wrapper.contains() must be passed a valid CSS selector or a Vue constructor'); - } + var selectorType = getSelectorTypeOrThrow(selector, 'contains'); - if (typeof selector === 'object') { + if (selectorType === selectorTypes.VUE_COMPONENT) { var vm = this.vm || this.vnode.context.$root; return findVueComponents(vm, selector.name).length > 0 } - if (typeof selector === 'string' && this.element instanceof HTMLElement) { + if (selectorType === selectorTypes.OPTIONS_OBJECT) { + if (!this.isVueComponent) { + throwError('$ref selectors can only be used on Vue component wrappers'); + } + var nodes = findVNodesByRef(this.vnode, selector.ref); + return nodes.length > 0 + } + + if (selectorType === selectorTypes.DOM_SELECTOR && this.element instanceof HTMLElement) { return this.element.querySelectorAll(selector).length > 0 } @@ -702,11 +781,9 @@ Wrapper.prototype.hasStyle = function hasStyle (style, value) { * Finds first node in tree of the current wrapper that matches the provided selector. */ Wrapper.prototype.find = function find (selector) { - if (!isValidSelector(selector)) { - throwError('wrapper.find() must be passed a valid CSS selector or a Vue constructor'); - } + var selectorType = getSelectorTypeOrThrow(selector, 'find'); - if (typeof selector === 'object') { + if (selectorType === selectorTypes.VUE_COMPONENT) { if (!selector.name) { throwError('.find() requires component to have a name property'); } @@ -718,7 +795,18 @@ Wrapper.prototype.find = function find (selector) { return new VueWrapper(components[0], this.options) } - var nodes = findMatchingVNodes(this.vnode, selector); + if (selectorType === selectorTypes.OPTIONS_OBJECT) { + if (!this.isVueComponent) { + throwError('$ref selectors can only be used on Vue component wrappers'); + } + var nodes$1 = findVNodesByRef(this.vnode, selector.ref); + if (nodes$1.length === 0) { + return new ErrorWrapper(("ref=\"" + (selector.ref) + "\"")) + } + return new Wrapper(nodes$1[0], this.update, this.options) + } + + var nodes = findVNodesBySelector(this.vnode, selector); if (nodes.length === 0) { return new ErrorWrapper(selector) @@ -732,11 +820,9 @@ Wrapper.prototype.find = function find (selector) { Wrapper.prototype.findAll = function findAll (selector) { var this$1 = this; - if (!isValidSelector(selector)) { - throwError('wrapper.findAll() must be passed a valid CSS selector or a Vue constructor'); - } + var selectorType = getSelectorTypeOrThrow(selector, 'findAll'); - if (typeof selector === 'object') { + if (selectorType === selectorTypes.VUE_COMPONENT) { if (!selector.name) { throwError('.findAll() requires component to have a name property'); } @@ -745,11 +831,19 @@ Wrapper.prototype.findAll = function findAll (selector) { return new WrapperArray(components.map(function (component) { return new VueWrapper(component, this$1.options); })) } + if (selectorType === selectorTypes.OPTIONS_OBJECT) { + if (!this.isVueComponent) { + throwError('$ref selectors can only be used on Vue component wrappers'); + } + var nodes$1 = findVNodesByRef(this.vnode, selector.ref); + return new WrapperArray(nodes$1.map(function (node) { return new Wrapper(node, this$1.update, this$1.options); })) + } + function nodeMatchesSelector (node, selector) { return node.elm && node.elm.getAttribute && node.elm.matches(selector) } - var nodes = findMatchingVNodes(this.vnode, selector); + var nodes = findVNodesBySelector(this.vnode, selector); var matchingNodes = nodes.filter(function (node) { return nodeMatchesSelector(node, selector); }); return new WrapperArray(matchingNodes.map(function (node) { return new Wrapper(node, this$1.update, this$1.options); })) @@ -766,20 +860,23 @@ Wrapper.prototype.html = function html () { * Checks if node matches selector */ Wrapper.prototype.is = function is (selector) { - if (!isValidSelector(selector)) { - throwError('wrapper.is() must be passed a valid CSS selector or a Vue constructor'); - } + var selectorType = getSelectorTypeOrThrow(selector, 'is'); - if (typeof selector === 'object') { - if (!this.isVueComponent) { - return false - } + if (selectorType === selectorTypes.VUE_COMPONENT && this.isVueComponent) { if (typeof selector.name !== 'string') { throwError('a Component used as a selector must have a name property'); } return vmCtorMatchesName(this.vm, selector.name) } + if (selectorType === selectorTypes.OPTIONS_OBJECT) { + throwError('$ref selectors can not be used with wrapper.is()'); + } + + if (typeof selector === 'object') { + return false + } + return !!(this.element && this.element.getAttribute && this.element.matches(selector)) @@ -824,6 +921,14 @@ Wrapper.prototype.setData = function setData (data) { // $FlowIgnore : Problem with possibly null this.vm this$1.vm.$set(this$1.vm, [key], data[key]); }); + + Object.keys(data).forEach(function (key) { + // $FlowIgnore : Problem with possibly null this.vm + this$1.vm._watchers.forEach(function (watcher) { + if (watcher.expression === key) { watcher.run(); } + }); + }); + this.update(); }; @@ -918,7 +1023,7 @@ Wrapper.prototype.text = function text () { throwError('cannot call wrapper.text() on a wrapper without an element'); } - return this.element.textContent + return this.element.textContent.trim() }; /** @@ -1037,7 +1142,7 @@ var VueWrapper = (function (Wrapper$$1) { // -function isValidSlot (slot) { +function isValidSlot$1 (slot) { return Array.isArray(slot) || (slot !== null && typeof slot === 'object') || typeof slot === 'string' } @@ -1065,7 +1170,7 @@ function addSlotToVm (vm, slotName, slotValue) { function addSlots (vm, slots) { Object.keys(slots).forEach(function (key) { - if (!isValidSlot(slots[key])) { + if (!isValidSlot$1(slots[key])) { throwError('slots[key] must be a Component, string or an array of Components'); } @@ -1109,12 +1214,10 @@ function addListeners (vm, listeners) { Vue.config.silent = originalVueConfig.silent; } -function addProvide (component, options) { - var provide = typeof options.provide === 'function' - ? options.provide - : Object.assign({}, options.provide); - - delete options.provide; +function addProvide (component, optionProvide, options) { + var provide = typeof optionProvide === 'function' + ? optionProvide + : Object.assign({}, optionProvide); options.beforeCreate = function vueTestUtilBeforeCreate () { this._provided = typeof provide === 'function' @@ -1129,6 +1232,16 @@ function compileTemplate (component) { Object.assign(component, vueTemplateCompiler.compileToFunctions(component.template)); } +function errorHandler (errorOrString, vm) { + var error = (typeof errorOrString === 'object') + ? errorOrString + : new Error(errorOrString); + + vm._error = error; + + throw error +} + // function createLocalVue () { @@ -1147,6 +1260,8 @@ function createLocalVue () { // config is not enumerable instance.config = cloneDeep(Vue.config); + instance.config.errorHandler = errorHandler; + // option merge strategies need to be exposed by reference // so that merge strats registered by plguins can work properly instance.config.optionMergeStrategies = Vue.config.optionMergeStrategies; @@ -1175,130 +1290,6 @@ function createLocalVue () { // -function createConstructor ( - component, - options -) { - var vue = options.localVue || createLocalVue(); - - if (options.mocks) { - addMocks(options.mocks, vue); - } - - if (component.functional) { - if (options.context && typeof options.context !== 'object') { - throwError('mount.context must be an object'); - } - var clonedComponent = cloneDeep(component); - component = { - render: function render (h) { - return h( - clonedComponent, - options.context || component.FunctionalRenderContext - ) - } - }; - } else if (options.context) { - throwError( - 'mount.context can only be used when mounting a functional component' - ); - } - - if (options.provide) { - addProvide(component, options); - } - - if (options.stubs) { - stubComponents(component, options.stubs); - } - - if (!component.render && component.template && !component.functional) { - compileTemplate(component); - } - - var Constructor = vue.extend(component); - - var vm = new Constructor(options); - - addAttrs(vm, options.attrs); - addListeners(vm, options.listeners); - - if (options.slots) { - addSlots(vm, options.slots); - } - - return vm -} - -// - -function createElement () { - if (document) { - var elem = document.createElement('div'); - - if (document.body) { - document.body.appendChild(elem); - } - return elem - } -} - -if (!Element.prototype.matches) { - Element.prototype.matches = - Element.prototype.matchesSelector || - Element.prototype.mozMatchesSelector || - Element.prototype.msMatchesSelector || - Element.prototype.oMatchesSelector || - Element.prototype.webkitMatchesSelector || - function (s) { - var matches = (this.document || this.ownerDocument).querySelectorAll(s); - var i = matches.length; - while (--i >= 0 && matches.item(i) !== this) {} - return i > -1 - }; -} - -// - -Vue.config.productionTip = false; - -function mount (component, options) { - if ( options === void 0 ) options = {}; - - var componentToMount = options.clone === false ? component : cloneDeep(component.extend ? component.options : component); - // Remove cached constructor - delete componentToMount._Ctor; - - var vm = createConstructor(componentToMount, options); - - if (options.attachToDocument) { - vm.$mount(createElement()); - } else { - vm.$mount(); - } - - return new VueWrapper(vm, { attachedToDocument: !!options.attachToDocument }) -} - -// - -function shallow (component, options) { - if ( options === void 0 ) options = {}; - - var vue = options.localVue || Vue; - var clonedComponent = cloneDeep(component.extend ? component.options : component); - - if (clonedComponent.components) { - stubAllComponents(clonedComponent); - } - - stubGlobalComponents(clonedComponent, vue); - - return mount(clonedComponent, options) -} - -// - function getRealChild (vnode) { var compOptions = vnode && vnode.componentOptions; if (compOptions && compOptions.Ctor.options.abstract) { @@ -1422,8 +1413,231 @@ var TransitionGroupStub = { } }; +var config = { + stubs: { + transition: TransitionStub, + 'transition-group': TransitionGroupStub + } +}; + +// +function getStubs (optionStubs) { + if (optionStubs || Object.keys(config.stubs).length > 0) { + if (Array.isArray(optionStubs)) { + return optionStubs.concat( Object.keys(config.stubs)) + } else { + return Object.assign({}, config.stubs, + optionStubs) + } + } +} + +function extractOptions ( + options +) { + return { + mocks: options.mocks, + context: options.context, + provide: options.provide, + stubs: getStubs(options.stubs), + attrs: options.attrs, + listeners: options.listeners, + slots: options.slots, + localVue: options.localVue + } +} + +function deleteMountingOptions (options) { + delete options.custom; + delete options.attachToDocument; + delete options.mocks; + delete options.slots; + delete options.localVue; + delete options.stubs; + delete options.context; + delete options.clone; + delete options.attrs; + delete options.listeners; +} + +// + +function isValidSlot (slot) { + return Array.isArray(slot) || (slot !== null && typeof slot === 'object') || typeof slot === 'string' +} + +function createFunctionalSlots (slots, h) { + if ( slots === void 0 ) slots = {}; + + if (Array.isArray(slots.default)) { + return slots.default.map(h) + } + + if (typeof slots.default === 'string') { + return [h(vueTemplateCompiler.compileToFunctions(slots.default))] + } + var children = []; + Object.keys(slots).forEach(function (slotType) { + if (Array.isArray(slots[slotType])) { + slots[slotType].forEach(function (slot) { + if (!isValidSlot(slot)) { + throwError('slots[key] must be a Component, string or an array of Components'); + } + var component = typeof slot === 'string' ? vueTemplateCompiler.compileToFunctions(slot) : slot; + var newSlot = h(component); + newSlot.data.slot = slotType; + children.push(newSlot); + }); + } else { + if (!isValidSlot(slots[slotType])) { + throwError('slots[key] must be a Component, string or an array of Components'); + } + var component = typeof slots[slotType] === 'string' ? vueTemplateCompiler.compileToFunctions(slots[slotType]) : slots[slotType]; + var slot = h(component); + slot.data.slot = slotType; + children.push(slot); + } + }); + return children +} + +function createConstructor ( + component, + options +) { + var mountingOptions = extractOptions(options); + + var vue = mountingOptions.localVue || createLocalVue(); + + if (mountingOptions.mocks) { + addMocks(mountingOptions.mocks, vue); + } + + if (component.functional) { + if (mountingOptions.context && typeof mountingOptions.context !== 'object') { + throwError('mount.context must be an object'); + } + + var clonedComponent = cloneDeep(component); + component = { + render: function render (h) { + return h( + clonedComponent, + mountingOptions.context || component.FunctionalRenderContext, + (mountingOptions.context && mountingOptions.context.children) || createFunctionalSlots(mountingOptions.slots, h) + ) + } + }; + } else if (mountingOptions.context) { + throwError( + 'mount.context can only be used when mounting a functional component' + ); + } + + if (mountingOptions.provide) { + addProvide(component, mountingOptions.provide, options); + } + + if (mountingOptions.stubs) { + stubComponents(component, mountingOptions.stubs); + } + + if (!component.render && component.template && !component.functional) { + compileTemplate(component); + } + + var Constructor = vue.extend(component); + + var instanceOptions = Object.assign({}, options); + deleteMountingOptions(instanceOptions); + + var vm = new Constructor(instanceOptions); + + addAttrs(vm, mountingOptions.attrs); + addListeners(vm, mountingOptions.listeners); + + if (mountingOptions.slots) { + addSlots(vm, mountingOptions.slots); + } + + return vm +} + +// + +function createElement () { + if (document) { + var elem = document.createElement('div'); + + if (document.body) { + document.body.appendChild(elem); + } + return elem + } +} + +if (!Element.prototype.matches) { + Element.prototype.matches = + Element.prototype.matchesSelector || + Element.prototype.mozMatchesSelector || + Element.prototype.msMatchesSelector || + Element.prototype.oMatchesSelector || + Element.prototype.webkitMatchesSelector || + function (s) { + var matches = (this.document || this.ownerDocument).querySelectorAll(s); + var i = matches.length; + while (--i >= 0 && matches.item(i) !== this) {} + return i > -1 + }; +} + +// + +Vue.config.productionTip = false; +Vue.config.errorHandler = errorHandler; + +function mount (component, options) { + if ( options === void 0 ) options = {}; + + var componentToMount = options.clone === false ? component : cloneDeep(component.extend ? component.options : component); + // Remove cached constructor + delete componentToMount._Ctor; + + var vm = createConstructor(componentToMount, options); + + if (options.attachToDocument) { + vm.$mount(createElement()); + } else { + vm.$mount(); + } + + if (vm._error) { + throw (vm._error) + } + + return new VueWrapper(vm, { attachedToDocument: !!options.attachToDocument }) +} + +// + +function shallow (component, options) { + if ( options === void 0 ) options = {}; + + var vue = options.localVue || Vue; + var clonedComponent = cloneDeep(component.extend ? component.options : component); + + if (clonedComponent.components) { + stubAllComponents(clonedComponent); + } + + stubGlobalComponents(clonedComponent, vue); + + return mount(clonedComponent, options) +} + var index = { createLocalVue: createLocalVue, + config: config, mount: mount, shallow: shallow, TransitionStub: TransitionStub, diff --git a/dist/vue-test-utils.umd.js b/dist/vue-test-utils.umd.js index 6bcc64b00..8b7f06c99 100644 --- a/dist/vue-test-utils.umd.js +++ b/dist/vue-test-utils.umd.js @@ -2627,13 +2627,20 @@ function stubComponents (component, stubs) { if (Array.isArray(stubs)) { stubs.forEach(function (stub) { + if (stub === false) { + return + } + if (typeof stub !== 'string') { - throwError('each item in options.stub must be a string'); + throwError('each item in an options.stubs array must be a string'); } component.components[stub] = createBlankStub({}); }); } else { Object.keys(stubs).forEach(function (stub) { + if (stubs[stub] === false) { + return + } if (!isValidStub(stubs[stub])) { throwError('options.stub values must be passed a string or component'); } @@ -2739,12 +2746,62 @@ function isVueComponent (component) { return typeof component.render === 'function' } -function isValidSelector (selector) { + + +function isRefSelector (refOptionsObject) { + if (typeof refOptionsObject !== 'object') { + return false + } + + if (refOptionsObject === null) { + return false + } + + var validFindKeys = ['ref']; + var entries = Object.entries(refOptionsObject); + + if (!entries.length) { + return false + } + + var isValid = entries.every(function (ref) { + var key = ref[0]; + var value = ref[1]; + + return validFindKeys.includes(key) && typeof value === 'string' + }); + + return isValid +} + +// + +var selectorTypes = { + DOM_SELECTOR: 'DOM_SELECTOR', + VUE_COMPONENT: 'VUE_COMPONENT', + OPTIONS_OBJECT: 'OPTIONS_OBJECT' +}; + +function getSelectorType (selector) { if (isDomSelector(selector)) { - return true + return selectorTypes.DOM_SELECTOR + } + + if (isVueComponent(selector)) { + return selectorTypes.VUE_COMPONENT } - return isVueComponent(selector) + if (isRefSelector(selector)) { + return selectorTypes.OPTIONS_OBJECT + } +} + +function getSelectorTypeOrThrow (selector, methodName) { + var selectorType = getSelectorType(selector); + if (!selectorType) { + throwError(("wrapper." + methodName + "() must be passed a valid CSS selector, Vue constructor, or valid find option object")); + } + return selectorType } // @@ -2797,10 +2854,6 @@ function findAllVNodes (vnode, nodes) { return nodes } -function nodeMatchesSelector (node, selector) { - return node.elm && node.elm.getAttribute && node.elm.matches(selector) -} - function removeDuplicateNodes (vNodes) { var uniqueNodes = []; vNodes.forEach(function (vNode) { @@ -2812,7 +2865,13 @@ function removeDuplicateNodes (vNodes) { return uniqueNodes } -function findMatchingVNodes (vNode, selector) { +// + +function nodeMatchesSelector (node, selector) { + return node.elm && node.elm.getAttribute && node.elm.matches(selector) +} + +function findVNodesBySelector (vNode, selector) { var nodes = findAllVNodes(vNode); var filteredNodes = nodes.filter(function (node) { return nodeMatchesSelector(node, selector); }); return removeDuplicateNodes(filteredNodes) @@ -2820,6 +2879,20 @@ function findMatchingVNodes (vNode, selector) { // +function nodeMatchesRef (node, refName) { + return node.data && node.data.ref === refName +} + +function findVNodesByRef (vNode, refName) { + var nodes = findAllVNodes(vNode); + var refFilteredNodes = nodes.filter(function (node) { return nodeMatchesRef(node, refName); }); + // Only return refs defined on top-level VNode to provide the same behavior as selecting via vm.$ref.{someRefName} + var mainVNodeFilteredNodes = refFilteredNodes.filter(function (node) { return !!vNode.context.$refs[node.data.ref]; }); + return removeDuplicateNodes(mainVNodeFilteredNodes) +} + +// + var WrapperArray = function WrapperArray (wrappers) { @@ -3094,16 +3167,22 @@ Wrapper.prototype.at = function at () { * Checks if wrapper contains provided selector. */ Wrapper.prototype.contains = function contains (selector) { - if (!isValidSelector(selector)) { - throwError('wrapper.contains() must be passed a valid CSS selector or a Vue constructor'); - } + var selectorType = getSelectorTypeOrThrow(selector, 'contains'); - if (typeof selector === 'object') { + if (selectorType === selectorTypes.VUE_COMPONENT) { var vm = this.vm || this.vnode.context.$root; return findVueComponents(vm, selector.name).length > 0 } - if (typeof selector === 'string' && this.element instanceof HTMLElement) { + if (selectorType === selectorTypes.OPTIONS_OBJECT) { + if (!this.isVueComponent) { + throwError('$ref selectors can only be used on Vue component wrappers'); + } + var nodes = findVNodesByRef(this.vnode, selector.ref); + return nodes.length > 0 + } + + if (selectorType === selectorTypes.DOM_SELECTOR && this.element instanceof HTMLElement) { return this.element.querySelectorAll(selector).length > 0 } @@ -3235,11 +3314,9 @@ Wrapper.prototype.hasStyle = function hasStyle (style, value) { * Finds first node in tree of the current wrapper that matches the provided selector. */ Wrapper.prototype.find = function find (selector) { - if (!isValidSelector(selector)) { - throwError('wrapper.find() must be passed a valid CSS selector or a Vue constructor'); - } + var selectorType = getSelectorTypeOrThrow(selector, 'find'); - if (typeof selector === 'object') { + if (selectorType === selectorTypes.VUE_COMPONENT) { if (!selector.name) { throwError('.find() requires component to have a name property'); } @@ -3251,7 +3328,18 @@ Wrapper.prototype.find = function find (selector) { return new VueWrapper(components[0], this.options) } - var nodes = findMatchingVNodes(this.vnode, selector); + if (selectorType === selectorTypes.OPTIONS_OBJECT) { + if (!this.isVueComponent) { + throwError('$ref selectors can only be used on Vue component wrappers'); + } + var nodes$1 = findVNodesByRef(this.vnode, selector.ref); + if (nodes$1.length === 0) { + return new ErrorWrapper(("ref=\"" + (selector.ref) + "\"")) + } + return new Wrapper(nodes$1[0], this.update, this.options) + } + + var nodes = findVNodesBySelector(this.vnode, selector); if (nodes.length === 0) { return new ErrorWrapper(selector) @@ -3265,11 +3353,9 @@ Wrapper.prototype.find = function find (selector) { Wrapper.prototype.findAll = function findAll (selector) { var this$1 = this; - if (!isValidSelector(selector)) { - throwError('wrapper.findAll() must be passed a valid CSS selector or a Vue constructor'); - } + var selectorType = getSelectorTypeOrThrow(selector, 'findAll'); - if (typeof selector === 'object') { + if (selectorType === selectorTypes.VUE_COMPONENT) { if (!selector.name) { throwError('.findAll() requires component to have a name property'); } @@ -3278,11 +3364,19 @@ Wrapper.prototype.findAll = function findAll (selector) { return new WrapperArray(components.map(function (component) { return new VueWrapper(component, this$1.options); })) } + if (selectorType === selectorTypes.OPTIONS_OBJECT) { + if (!this.isVueComponent) { + throwError('$ref selectors can only be used on Vue component wrappers'); + } + var nodes$1 = findVNodesByRef(this.vnode, selector.ref); + return new WrapperArray(nodes$1.map(function (node) { return new Wrapper(node, this$1.update, this$1.options); })) + } + function nodeMatchesSelector (node, selector) { return node.elm && node.elm.getAttribute && node.elm.matches(selector) } - var nodes = findMatchingVNodes(this.vnode, selector); + var nodes = findVNodesBySelector(this.vnode, selector); var matchingNodes = nodes.filter(function (node) { return nodeMatchesSelector(node, selector); }); return new WrapperArray(matchingNodes.map(function (node) { return new Wrapper(node, this$1.update, this$1.options); })) @@ -3299,20 +3393,23 @@ Wrapper.prototype.html = function html () { * Checks if node matches selector */ Wrapper.prototype.is = function is (selector) { - if (!isValidSelector(selector)) { - throwError('wrapper.is() must be passed a valid CSS selector or a Vue constructor'); - } + var selectorType = getSelectorTypeOrThrow(selector, 'is'); - if (typeof selector === 'object') { - if (!this.isVueComponent) { - return false - } + if (selectorType === selectorTypes.VUE_COMPONENT && this.isVueComponent) { if (typeof selector.name !== 'string') { throwError('a Component used as a selector must have a name property'); } return vmCtorMatchesName(this.vm, selector.name) } + if (selectorType === selectorTypes.OPTIONS_OBJECT) { + throwError('$ref selectors can not be used with wrapper.is()'); + } + + if (typeof selector === 'object') { + return false + } + return !!(this.element && this.element.getAttribute && this.element.matches(selector)) @@ -3357,6 +3454,14 @@ Wrapper.prototype.setData = function setData (data) { // $FlowIgnore : Problem with possibly null this.vm this$1.vm.$set(this$1.vm, [key], data[key]); }); + + Object.keys(data).forEach(function (key) { + // $FlowIgnore : Problem with possibly null this.vm + this$1.vm._watchers.forEach(function (watcher) { + if (watcher.expression === key) { watcher.run(); } + }); + }); + this.update(); }; @@ -3451,7 +3556,7 @@ Wrapper.prototype.text = function text () { throwError('cannot call wrapper.text() on a wrapper without an element'); } - return this.element.textContent + return this.element.textContent.trim() }; /** @@ -3570,7 +3675,7 @@ var VueWrapper = (function (Wrapper$$1) { // -function isValidSlot (slot) { +function isValidSlot$1 (slot) { return Array.isArray(slot) || (slot !== null && typeof slot === 'object') || typeof slot === 'string' } @@ -3598,7 +3703,7 @@ function addSlotToVm (vm, slotName, slotValue) { function addSlots (vm, slots) { Object.keys(slots).forEach(function (key) { - if (!isValidSlot(slots[key])) { + if (!isValidSlot$1(slots[key])) { throwError('slots[key] must be a Component, string or an array of Components'); } @@ -3642,12 +3747,10 @@ function addListeners (vm, listeners) { Vue.config.silent = originalVueConfig.silent; } -function addProvide (component, options) { - var provide = typeof options.provide === 'function' - ? options.provide - : Object.assign({}, options.provide); - - delete options.provide; +function addProvide (component, optionProvide, options) { + var provide = typeof optionProvide === 'function' + ? optionProvide + : Object.assign({}, optionProvide); options.beforeCreate = function vueTestUtilBeforeCreate () { this._provided = typeof provide === 'function' @@ -3662,6 +3765,16 @@ function compileTemplate (component) { Object.assign(component, vueTemplateCompiler.compileToFunctions(component.template)); } +function errorHandler (errorOrString, vm) { + var error = (typeof errorOrString === 'object') + ? errorOrString + : new Error(errorOrString); + + vm._error = error; + + throw error +} + // function createLocalVue () { @@ -3680,6 +3793,8 @@ function createLocalVue () { // config is not enumerable instance.config = cloneDeep_1(Vue.config); + instance.config.errorHandler = errorHandler; + // option merge strategies need to be exposed by reference // so that merge strats registered by plguins can work properly instance.config.optionMergeStrategies = Vue.config.optionMergeStrategies; @@ -3708,130 +3823,6 @@ function createLocalVue () { // -function createConstructor ( - component, - options -) { - var vue = options.localVue || createLocalVue(); - - if (options.mocks) { - addMocks(options.mocks, vue); - } - - if (component.functional) { - if (options.context && typeof options.context !== 'object') { - throwError('mount.context must be an object'); - } - var clonedComponent = cloneDeep_1(component); - component = { - render: function render (h) { - return h( - clonedComponent, - options.context || component.FunctionalRenderContext - ) - } - }; - } else if (options.context) { - throwError( - 'mount.context can only be used when mounting a functional component' - ); - } - - if (options.provide) { - addProvide(component, options); - } - - if (options.stubs) { - stubComponents(component, options.stubs); - } - - if (!component.render && component.template && !component.functional) { - compileTemplate(component); - } - - var Constructor = vue.extend(component); - - var vm = new Constructor(options); - - addAttrs(vm, options.attrs); - addListeners(vm, options.listeners); - - if (options.slots) { - addSlots(vm, options.slots); - } - - return vm -} - -// - -function createElement () { - if (document) { - var elem = document.createElement('div'); - - if (document.body) { - document.body.appendChild(elem); - } - return elem - } -} - -if (!Element.prototype.matches) { - Element.prototype.matches = - Element.prototype.matchesSelector || - Element.prototype.mozMatchesSelector || - Element.prototype.msMatchesSelector || - Element.prototype.oMatchesSelector || - Element.prototype.webkitMatchesSelector || - function (s) { - var matches = (this.document || this.ownerDocument).querySelectorAll(s); - var i = matches.length; - while (--i >= 0 && matches.item(i) !== this) {} - return i > -1 - }; -} - -// - -Vue.config.productionTip = false; - -function mount (component, options) { - if ( options === void 0 ) options = {}; - - var componentToMount = options.clone === false ? component : cloneDeep_1(component.extend ? component.options : component); - // Remove cached constructor - delete componentToMount._Ctor; - - var vm = createConstructor(componentToMount, options); - - if (options.attachToDocument) { - vm.$mount(createElement()); - } else { - vm.$mount(); - } - - return new VueWrapper(vm, { attachedToDocument: !!options.attachToDocument }) -} - -// - -function shallow (component, options) { - if ( options === void 0 ) options = {}; - - var vue = options.localVue || Vue; - var clonedComponent = cloneDeep_1(component.extend ? component.options : component); - - if (clonedComponent.components) { - stubAllComponents(clonedComponent); - } - - stubGlobalComponents(clonedComponent, vue); - - return mount(clonedComponent, options) -} - -// - function getRealChild (vnode) { var compOptions = vnode && vnode.componentOptions; if (compOptions && compOptions.Ctor.options.abstract) { @@ -3955,8 +3946,231 @@ var TransitionGroupStub = { } }; +var config = { + stubs: { + transition: TransitionStub, + 'transition-group': TransitionGroupStub + } +}; + +// +function getStubs (optionStubs) { + if (optionStubs || Object.keys(config.stubs).length > 0) { + if (Array.isArray(optionStubs)) { + return optionStubs.concat( Object.keys(config.stubs)) + } else { + return Object.assign({}, config.stubs, + optionStubs) + } + } +} + +function extractOptions ( + options +) { + return { + mocks: options.mocks, + context: options.context, + provide: options.provide, + stubs: getStubs(options.stubs), + attrs: options.attrs, + listeners: options.listeners, + slots: options.slots, + localVue: options.localVue + } +} + +function deleteMountingOptions (options) { + delete options.custom; + delete options.attachToDocument; + delete options.mocks; + delete options.slots; + delete options.localVue; + delete options.stubs; + delete options.context; + delete options.clone; + delete options.attrs; + delete options.listeners; +} + +// + +function isValidSlot (slot) { + return Array.isArray(slot) || (slot !== null && typeof slot === 'object') || typeof slot === 'string' +} + +function createFunctionalSlots (slots, h) { + if ( slots === void 0 ) slots = {}; + + if (Array.isArray(slots.default)) { + return slots.default.map(h) + } + + if (typeof slots.default === 'string') { + return [h(vueTemplateCompiler.compileToFunctions(slots.default))] + } + var children = []; + Object.keys(slots).forEach(function (slotType) { + if (Array.isArray(slots[slotType])) { + slots[slotType].forEach(function (slot) { + if (!isValidSlot(slot)) { + throwError('slots[key] must be a Component, string or an array of Components'); + } + var component = typeof slot === 'string' ? vueTemplateCompiler.compileToFunctions(slot) : slot; + var newSlot = h(component); + newSlot.data.slot = slotType; + children.push(newSlot); + }); + } else { + if (!isValidSlot(slots[slotType])) { + throwError('slots[key] must be a Component, string or an array of Components'); + } + var component = typeof slots[slotType] === 'string' ? vueTemplateCompiler.compileToFunctions(slots[slotType]) : slots[slotType]; + var slot = h(component); + slot.data.slot = slotType; + children.push(slot); + } + }); + return children +} + +function createConstructor ( + component, + options +) { + var mountingOptions = extractOptions(options); + + var vue = mountingOptions.localVue || createLocalVue(); + + if (mountingOptions.mocks) { + addMocks(mountingOptions.mocks, vue); + } + + if (component.functional) { + if (mountingOptions.context && typeof mountingOptions.context !== 'object') { + throwError('mount.context must be an object'); + } + + var clonedComponent = cloneDeep_1(component); + component = { + render: function render (h) { + return h( + clonedComponent, + mountingOptions.context || component.FunctionalRenderContext, + (mountingOptions.context && mountingOptions.context.children) || createFunctionalSlots(mountingOptions.slots, h) + ) + } + }; + } else if (mountingOptions.context) { + throwError( + 'mount.context can only be used when mounting a functional component' + ); + } + + if (mountingOptions.provide) { + addProvide(component, mountingOptions.provide, options); + } + + if (mountingOptions.stubs) { + stubComponents(component, mountingOptions.stubs); + } + + if (!component.render && component.template && !component.functional) { + compileTemplate(component); + } + + var Constructor = vue.extend(component); + + var instanceOptions = Object.assign({}, options); + deleteMountingOptions(instanceOptions); + + var vm = new Constructor(instanceOptions); + + addAttrs(vm, mountingOptions.attrs); + addListeners(vm, mountingOptions.listeners); + + if (mountingOptions.slots) { + addSlots(vm, mountingOptions.slots); + } + + return vm +} + +// + +function createElement () { + if (document) { + var elem = document.createElement('div'); + + if (document.body) { + document.body.appendChild(elem); + } + return elem + } +} + +if (!Element.prototype.matches) { + Element.prototype.matches = + Element.prototype.matchesSelector || + Element.prototype.mozMatchesSelector || + Element.prototype.msMatchesSelector || + Element.prototype.oMatchesSelector || + Element.prototype.webkitMatchesSelector || + function (s) { + var matches = (this.document || this.ownerDocument).querySelectorAll(s); + var i = matches.length; + while (--i >= 0 && matches.item(i) !== this) {} + return i > -1 + }; +} + +// + +Vue.config.productionTip = false; +Vue.config.errorHandler = errorHandler; + +function mount (component, options) { + if ( options === void 0 ) options = {}; + + var componentToMount = options.clone === false ? component : cloneDeep_1(component.extend ? component.options : component); + // Remove cached constructor + delete componentToMount._Ctor; + + var vm = createConstructor(componentToMount, options); + + if (options.attachToDocument) { + vm.$mount(createElement()); + } else { + vm.$mount(); + } + + if (vm._error) { + throw (vm._error) + } + + return new VueWrapper(vm, { attachedToDocument: !!options.attachToDocument }) +} + +// + +function shallow (component, options) { + if ( options === void 0 ) options = {}; + + var vue = options.localVue || Vue; + var clonedComponent = cloneDeep_1(component.extend ? component.options : component); + + if (clonedComponent.components) { + stubAllComponents(clonedComponent); + } + + stubGlobalComponents(clonedComponent, vue); + + return mount(clonedComponent, options) +} + var index = { createLocalVue: createLocalVue, + config: config, mount: mount, shallow: shallow, TransitionStub: TransitionStub, From 4cc66a07adf84e06d277bc045be8ac3bdfb26a42 Mon Sep 17 00:00:00 2001 From: eddyerburgh Date: Mon, 13 Nov 2017 17:57:31 +0000 Subject: [PATCH 0075/1136] release: 1.0.0-beta.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c9c18f139..cbce42e2b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vue-test-utils", - "version": "1.0.0-beta.4", + "version": "1.0.0-beta.5", "description": "Utilities for testing Vue components.", "main": "dist/vue-test-utils.js", "types": "types/index.d.ts", From 55bf3157af7f69ac502dec0103a21c0a2a00d8af Mon Sep 17 00:00:00 2001 From: eddyerburgh Date: Tue, 14 Nov 2017 17:53:20 +0000 Subject: [PATCH 0076/1136] docs: add title to docs --- docs/book.json | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/book.json b/docs/book.json index e6b4e1446..d77d9b969 100644 --- a/docs/book.json +++ b/docs/book.json @@ -1,4 +1,5 @@ { + "title": "vue-test-utils", "gitbook": ">3.0.0", "plugins": ["edit-link", "theme-vuejs", "-fontsettings", "github"], "pluginsConfig": { From b75672376ff16d8505a66c9ac5e065aac39ff199 Mon Sep 17 00:00:00 2001 From: Matt O'Connell Date: Thu, 16 Nov 2017 02:28:58 -0500 Subject: [PATCH 0077/1136] docs: add ref find object to selectors (#178) issue: https://github.com/vuejs/vue-test-utils/issues/177 --- docs/en/api/selectors.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/en/api/selectors.md b/docs/en/api/selectors.md index 0c6a47f3b..5ab5c426c 100644 --- a/docs/en/api/selectors.md +++ b/docs/en/api/selectors.md @@ -1,6 +1,6 @@ # Selectors -A lot of methods take a selector as an argument. A selector can either be a CSS selector, or a Vue component. +A lot of methods take a selector as an argument. A selector can either be a CSS selector, a Vue component, or a find option object. ## CSS Selectors @@ -41,3 +41,14 @@ import Foo from './Foo.vue' const wrapper = shallow(Foo) expect(wrapper.is(Foo)).toBe(true) ``` + +## Find Option Object + +### Ref + +Using a find option object, vue-test-utils allows for selecting elements by $ref on wrapper components. + +```js +const buttonWrapper = wrapper.find({ ref: 'myButton' }); +buttonWrapper.trigger('click'); +``` From 7610aa19362a35c207512adb5a83301edc6d0eb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?ChangJoo=20Park=28=EB=B0=95=EC=B0=BD=EC=A3=BC=29?= Date: Sat, 18 Nov 2017 03:59:05 +0900 Subject: [PATCH 0078/1136] docs: fix case vue.nextTick to Vue.nextTick (#182) It seems to be uppercase https://vuejs.org/v2/api/#Vue-nextTick --- docs/en/guides/dom-events.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/guides/dom-events.md b/docs/en/guides/dom-events.md index 1ed117610..091096874 100644 --- a/docs/en/guides/dom-events.md +++ b/docs/en/guides/dom-events.md @@ -193,4 +193,4 @@ A key name after the dot `keydown.up` is translated to a `keyCode`. This is supp ## Important -vue-test-utils triggers event synchronously. Consequently, `vue.nextTick` is not required. +vue-test-utils triggers event synchronously. Consequently, `Vue.nextTick` is not required. From 6f64e53e06de31cdf76f5100546abff7a525acbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?ChangJoo=20Park=28=EB=B0=95=EC=B0=BD=EC=A3=BC=29?= Date: Sat, 18 Nov 2017 03:59:24 +0900 Subject: [PATCH 0079/1136] docs: fix case jest to Jest (#183) --- docs/en/guides/testing-SFCs-with-jest.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/guides/testing-SFCs-with-jest.md b/docs/en/guides/testing-SFCs-with-jest.md index 8ca1e8f60..100544d2d 100644 --- a/docs/en/guides/testing-SFCs-with-jest.md +++ b/docs/en/guides/testing-SFCs-with-jest.md @@ -150,7 +150,7 @@ Then configure it in `package.json`: ### Placing Test Files -By default, jest will recursively pick up all files that have a `.spec.js` or `.test.js` extension in the entire project. If this does not fit your needs, it's possible [to change the testRegex](https://facebook.github.io/jest/docs/en/configuration.html#testregex-string) in the config section in the `package.json` file. +By default, Jest will recursively pick up all files that have a `.spec.js` or `.test.js` extension in the entire project. If this does not fit your needs, it's possible [to change the testRegex](https://facebook.github.io/jest/docs/en/configuration.html#testregex-string) in the config section in the `package.json` file. Jest recommends creating a `__tests__` directory right next to the code being tested, but feel free to structure your tests as you see fit. Just beware that Jest would create a `__snapshots__` directory next to test files that performs snapshot testing. From 2f7754444d68c26fbde292898837cde09b35bfe6 Mon Sep 17 00:00:00 2001 From: Lachlan Date: Sat, 18 Nov 2017 21:03:13 +0900 Subject: [PATCH 0080/1136] feat: emit api (#181) * Add print * Add test * Add better test * Specs * All passing * Add flow optional type * Update docs * Update emitted.md * Add types --- docs/en/api/wrapper/emitted.md | 13 +++++++++++ docs/ja/api/wrapper/emitted.md | 13 +++++++++++ src/wrappers/wrapper.js | 5 ++++- test/unit/specs/mount/Wrapper/emitted.spec.js | 22 +++++++++++++++++++ types/index.d.ts | 2 +- 5 files changed, 53 insertions(+), 2 deletions(-) diff --git a/docs/en/api/wrapper/emitted.md b/docs/en/api/wrapper/emitted.md index d754eab98..bbb13fd47 100644 --- a/docs/en/api/wrapper/emitted.md +++ b/docs/en/api/wrapper/emitted.md @@ -31,3 +31,16 @@ expect(wrapper.emitted().foo.length).toBe(2) // assert event payload expect(wrapper.emitted().foo[1]).toEqual([123]) ``` + +You can also write the above as follows: + +```js +// assert event has been emitted +expect(wrapper.emitted('foo')).toBeTruthy() + +// assert event count +expect(wrapper.emitted('foo').length).toBe(2) + +// assert event payload +expect(wrapper.emitted('foo')[1]).toEqual([123]) +``` diff --git a/docs/ja/api/wrapper/emitted.md b/docs/ja/api/wrapper/emitted.md index 46d683de6..f542736d7 100644 --- a/docs/ja/api/wrapper/emitted.md +++ b/docs/ja/api/wrapper/emitted.md @@ -31,3 +31,16 @@ expect(wrapper.emitted().foo.length).toBe(2) // イベントのペイロードを検証します expect(wrapper.emitted().foo[1]).toEqual([123]) ``` + +別の構文があります。 + +```js +// イベントが発行されたか検証します +expect(wrapper.emitted('foo')).toBeTruthy() + +// イベントの数を検証します +expect(wrapper.emitted('foo').length).toBe(2) + +// イベントのペイロードを検証します +expect(wrapper.emitted('foo')[1]).toEqual([123]) +``` diff --git a/src/wrappers/wrapper.js b/src/wrappers/wrapper.js index 56a4e5a48..a298a3fbd 100644 --- a/src/wrappers/wrapper.js +++ b/src/wrappers/wrapper.js @@ -62,10 +62,13 @@ export default class Wrapper implements BaseWrapper { /** * Returns an object containing custom events emitted by the Wrapper vm */ - emitted () { + emitted (event: ?string) { if (!this._emitted && !this.vm) { throwError('wrapper.emitted() can only be called on a Vue instance') } + if (event) { + return this._emitted[event] + } return this._emitted } diff --git a/test/unit/specs/mount/Wrapper/emitted.spec.js b/test/unit/specs/mount/Wrapper/emitted.spec.js index 0515d287e..449a62270 100644 --- a/test/unit/specs/mount/Wrapper/emitted.spec.js +++ b/test/unit/specs/mount/Wrapper/emitted.spec.js @@ -1,6 +1,28 @@ import mount from '~src/mount' describe('emitted', () => { + it('captures emitted events with a different api', () => { + const wrapper = mount({ + render: h => h('div') + }) + + wrapper.vm.$emit('foo') + expect(wrapper.emitted('foo')).to.exist + expect(wrapper.emitted('foo').length).to.equal(1) + expect(wrapper.emitted('foo')[0]).to.eql([]) + + expect(wrapper.emitted('bar')).not.to.exist + wrapper.vm.$emit('bar', 1, 2, 3) + expect(wrapper.emitted('bar')).to.exist + expect(wrapper.emitted('bar').length).to.equal(1) + expect(wrapper.emitted('bar')[0]).to.eql([1, 2, 3]) + + wrapper.vm.$emit('foo', 2, 3, 4) + expect(wrapper.emitted('foo')).to.exist + expect(wrapper.emitted('foo').length).to.equal(2) + expect(wrapper.emitted('foo')[1]).to.eql([2, 3, 4]) + }) + it('captures emitted events', () => { const wrapper = mount({ render: h => h('div') diff --git a/types/index.d.ts b/types/index.d.ts index 914970fa9..15677a50d 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -79,7 +79,7 @@ interface Wrapper extends BaseWrapper { text (): string name (): string - emitted (): { [name: string]: Array> } + emitted (string? event): { [name: string]: Array> } emittedByOrder (): Array<{ name: string, args: Array }> } From 6f5f1b052d52223d943e727199e9af82505c6564 Mon Sep 17 00:00:00 2001 From: Leonardo Vilarinho Date: Sat, 18 Nov 2017 14:46:07 -0200 Subject: [PATCH 0081/1136] docs: translate pt br (#185) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * start translate :fire: updating all menus * fix menu title * [to pt-br] translating getting started section * [to pt-br] translating common tips section * [to pt-br] translating dom events section * [to pt-br] translating choosing a test runner section * [to pt-br] translating testing sfcs with jest section * [to pt-br] translating testing sfcs with mocha + webpack section * [to pt-br] translating using with vue router section * [to pt-br] translating partially using with vuex section * feat(docs): add pt-br, add docs:serve command and lint markdown errors * feat(docs): [to pt-br] translating using with vuex section * feat(docs): [to pt-br] translating mount section * feat(docs): [to pt-br] translating shallow section * feat(docs): [to pt-br] translating options section * feat(docs): [to pt-br] translating readme wrapper section * feat(docs): [to pt-br] translating wrapper contains section * feat(docs): [to pt-br] translating wrapper emitted section * feat(docs): [to pt-br] translating wrapper emittedByOrder section * feat(docs): [to pt-br] translating wrapper find section * feat(docs): [to pt-br] translating wrapper findAll section * feat(docs): [to pt-br] translating wrapper hasAttribute section * feat(docs): [to pt-br] translating wrapper hasClass section * feat(docs): [to pt-br] translating wrapper hasProp section * feat(docs): [to pt-br] translating wrapper hasStyle section * feat(docs): [to pt-br] translating wrapper html section * feat(docs): [to pt-br] translating wrapper is section * feat(docs): [to pt-br] translating wrapper isEmpty section * feat(docs): [to pt-br] translating wrapper isVueInstance section * feat(docs): [to pt-br] translating wrapper name section * feat(docs): [to pt-br] translating wrapper setComputed section * feat(docs): [to pt-br] translating wrapper setData section * feat(docs): [to pt-br] translating wrapper setMethods section * feat(docs): [to pt-br] translating wrapper setProps section * feat(docs): [to pt-br] translating wrapper text section * feat(docs): [to pt-br] translating wrapper trigger section * feat(docs): [to pt-br] translating wrapper update section * feat(docs): [to pt-br] translating wrapper destroy section * feat(docs): [to pt-br] translating wrapperarray readme section * feat(docs): [to pt-br] translating wrapperarray at section * feat(docs): [to pt-br] translating wrapperarray contains section * feat(docs): [to pt-br] translating wrapperarray hasAttribute section * feat(docs): [to pt-br] translating wrapperarray hasClass section * feat(docs): [to pt-br] translating wrapperarray hasProp section * feat(docs): [to pt-br] translating wrapperarray hasStyle section * feat(docs): [to pt-br] translating wrapperarray is section * feat(docs): [to pt-br] translating wrapperarray isEmpty section * feat(docs): [to pt-br] translating wrapperarray isVueInstance section * feat(docs): [to pt-br] translating wrapperarray setComputed section * feat(docs): [to pt-br] translating wrapperarray setData section * feat(docs): [to pt-br] translating wrapperarray setMethods section * feat(docs): [to pt-br] translating wrapperarray setProps section * feat(docs): [to pt-br] translating wrapperarray triggers section * feat(docs): [to pt-br] translating wrapperarray update section * feat(docs): [to pt-br] translating wrapperarray destroy section * feat(docs): [to pt-br] translating components readme section * feat(docs): [to pt-br] translating components transition stub section * feat(docs): [to pt-br] translating components transition group stub section * feat(docs): [to pt-br] translating components selectors section * feat(docs): [to pt-br] translating components localVue section * feat(docs): [to pt-br] translating config section * feat(docs): change word 'embrulho' to 'wrapper' * feat(docs): change word 'stub' to 'esboço' * feat(docs): change word 'array' to 'Array' * feat(docs): change word 'string' to 'String' * feat(docs): change word 'boolean' to 'Boolean' * fix(docs): fix write errors * fix(docs): untranslate code api part * fix(docs): translate mock and debug * fix(docs): fix link wrapper array menu * fix(docs): change 'seletor' to 'selector' --- docs/LANGS.md | 1 + docs/pt-br/README.md | 74 ++ docs/pt-br/SUMMARY.md | 70 ++ docs/pt-br/api/README.md | 49 + docs/pt-br/api/components/README.md | 5 + .../api/components/TransitionGroupStub.md | 31 + docs/pt-br/api/components/TransitionStub.md | 31 + docs/pt-br/api/config.md | 25 + docs/pt-br/api/createLocalVue.md | 30 + docs/pt-br/api/mount.md | 138 +++ docs/pt-br/api/options.md | 165 ++++ docs/pt-br/api/selectors.md | 43 + docs/pt-br/api/shallow.md | 124 +++ docs/pt-br/api/wrapper-array/README.md | 11 + docs/pt-br/api/wrapper-array/at.md | 22 + docs/pt-br/api/wrapper-array/contains.md | 25 + docs/pt-br/api/wrapper-array/destroy.md | 19 + docs/pt-br/api/wrapper-array/hasAttribute.md | 22 + docs/pt-br/api/wrapper-array/hasClass.md | 21 + docs/pt-br/api/wrapper-array/hasProp.md | 25 + docs/pt-br/api/wrapper-array/hasStyle.md | 26 + docs/pt-br/api/wrapper-array/is.md | 21 + docs/pt-br/api/wrapper-array/isEmpty.md | 18 + docs/pt-br/api/wrapper-array/isVueInstance.md | 19 + docs/pt-br/api/wrapper-array/setComputed.md | 25 + docs/pt-br/api/wrapper-array/setData.md | 23 + docs/pt-br/api/wrapper-array/setMethods.md | 26 + docs/pt-br/api/wrapper-array/setProps.md | 23 + docs/pt-br/api/wrapper-array/trigger.md | 27 + docs/pt-br/api/wrapper-array/update.md | 22 + docs/pt-br/api/wrapper/README.md | 17 + docs/pt-br/api/wrapper/contains.md | 23 + docs/pt-br/api/wrapper/destroy.md | 22 + docs/pt-br/api/wrapper/emitted.md | 33 + docs/pt-br/api/wrapper/emittedByOrder.md | 28 + docs/pt-br/api/wrapper/exists.md | 22 + docs/pt-br/api/wrapper/find.md | 29 + docs/pt-br/api/wrapper/findAll.md | 29 + docs/pt-br/api/wrapper/hasAttribute.md | 38 + docs/pt-br/api/wrapper/hasClass.md | 21 + docs/pt-br/api/wrapper/hasProp.md | 24 + docs/pt-br/api/wrapper/hasStyle.md | 24 + docs/pt-br/api/wrapper/html.md | 16 + docs/pt-br/api/wrapper/is.md | 19 + docs/pt-br/api/wrapper/isEmpty.md | 16 + docs/pt-br/api/wrapper/isVueInstance.md | 16 + docs/pt-br/api/wrapper/name.md | 18 + docs/pt-br/api/wrapper/setComputed.md | 43 + docs/pt-br/api/wrapper/setData.md | 20 + docs/pt-br/api/wrapper/setMethods.md | 24 + docs/pt-br/api/wrapper/setProps.md | 48 + docs/pt-br/api/wrapper/text.md | 16 + docs/pt-br/api/wrapper/trigger.md | 38 + docs/pt-br/api/wrapper/update.md | 19 + docs/pt-br/guides/README.md | 10 + docs/pt-br/guides/choosing-a-test-runner.md | 46 + docs/pt-br/guides/common-tips.md | 138 +++ docs/pt-br/guides/dom-events.md | 196 ++++ docs/pt-br/guides/getting-started.md | 116 +++ docs/pt-br/guides/testing-SFCs-with-jest.md | 178 ++++ .../guides/testing-SFCs-with-mocha-webpack.md | 180 ++++ docs/pt-br/guides/using-with-vue-router.md | 71 ++ docs/pt-br/guides/using-with-vuex.md | 266 ++++++ package-lock.json | 902 +----------------- package.json | 1 + 65 files changed, 2967 insertions(+), 901 deletions(-) create mode 100644 docs/pt-br/README.md create mode 100644 docs/pt-br/SUMMARY.md create mode 100644 docs/pt-br/api/README.md create mode 100644 docs/pt-br/api/components/README.md create mode 100644 docs/pt-br/api/components/TransitionGroupStub.md create mode 100644 docs/pt-br/api/components/TransitionStub.md create mode 100644 docs/pt-br/api/config.md create mode 100644 docs/pt-br/api/createLocalVue.md create mode 100644 docs/pt-br/api/mount.md create mode 100644 docs/pt-br/api/options.md create mode 100644 docs/pt-br/api/selectors.md create mode 100644 docs/pt-br/api/shallow.md create mode 100644 docs/pt-br/api/wrapper-array/README.md create mode 100644 docs/pt-br/api/wrapper-array/at.md create mode 100644 docs/pt-br/api/wrapper-array/contains.md create mode 100644 docs/pt-br/api/wrapper-array/destroy.md create mode 100644 docs/pt-br/api/wrapper-array/hasAttribute.md create mode 100644 docs/pt-br/api/wrapper-array/hasClass.md create mode 100644 docs/pt-br/api/wrapper-array/hasProp.md create mode 100644 docs/pt-br/api/wrapper-array/hasStyle.md create mode 100644 docs/pt-br/api/wrapper-array/is.md create mode 100644 docs/pt-br/api/wrapper-array/isEmpty.md create mode 100644 docs/pt-br/api/wrapper-array/isVueInstance.md create mode 100644 docs/pt-br/api/wrapper-array/setComputed.md create mode 100644 docs/pt-br/api/wrapper-array/setData.md create mode 100644 docs/pt-br/api/wrapper-array/setMethods.md create mode 100644 docs/pt-br/api/wrapper-array/setProps.md create mode 100644 docs/pt-br/api/wrapper-array/trigger.md create mode 100644 docs/pt-br/api/wrapper-array/update.md create mode 100644 docs/pt-br/api/wrapper/README.md create mode 100644 docs/pt-br/api/wrapper/contains.md create mode 100644 docs/pt-br/api/wrapper/destroy.md create mode 100644 docs/pt-br/api/wrapper/emitted.md create mode 100644 docs/pt-br/api/wrapper/emittedByOrder.md create mode 100644 docs/pt-br/api/wrapper/exists.md create mode 100644 docs/pt-br/api/wrapper/find.md create mode 100644 docs/pt-br/api/wrapper/findAll.md create mode 100644 docs/pt-br/api/wrapper/hasAttribute.md create mode 100644 docs/pt-br/api/wrapper/hasClass.md create mode 100644 docs/pt-br/api/wrapper/hasProp.md create mode 100644 docs/pt-br/api/wrapper/hasStyle.md create mode 100644 docs/pt-br/api/wrapper/html.md create mode 100644 docs/pt-br/api/wrapper/is.md create mode 100644 docs/pt-br/api/wrapper/isEmpty.md create mode 100644 docs/pt-br/api/wrapper/isVueInstance.md create mode 100644 docs/pt-br/api/wrapper/name.md create mode 100644 docs/pt-br/api/wrapper/setComputed.md create mode 100644 docs/pt-br/api/wrapper/setData.md create mode 100644 docs/pt-br/api/wrapper/setMethods.md create mode 100644 docs/pt-br/api/wrapper/setProps.md create mode 100644 docs/pt-br/api/wrapper/text.md create mode 100644 docs/pt-br/api/wrapper/trigger.md create mode 100644 docs/pt-br/api/wrapper/update.md create mode 100644 docs/pt-br/guides/README.md create mode 100644 docs/pt-br/guides/choosing-a-test-runner.md create mode 100644 docs/pt-br/guides/common-tips.md create mode 100644 docs/pt-br/guides/dom-events.md create mode 100644 docs/pt-br/guides/getting-started.md create mode 100644 docs/pt-br/guides/testing-SFCs-with-jest.md create mode 100644 docs/pt-br/guides/testing-SFCs-with-mocha-webpack.md create mode 100644 docs/pt-br/guides/using-with-vue-router.md create mode 100644 docs/pt-br/guides/using-with-vuex.md diff --git a/docs/LANGS.md b/docs/LANGS.md index 734a128c2..2a971c5a0 100644 --- a/docs/LANGS.md +++ b/docs/LANGS.md @@ -1,3 +1,4 @@ * [English](en/) * [日本語](ja/) * [简体中文](zh-cn/) +* [Portuguese (Brazil)](pt-br/) diff --git a/docs/pt-br/README.md b/docs/pt-br/README.md new file mode 100644 index 000000000..f092ea048 --- /dev/null +++ b/docs/pt-br/README.md @@ -0,0 +1,74 @@ +# vue-test-utils + +`vue-test-utils` é a biblioteca oficial de testes de unidade para o Vue.js. + +## Índice + +* [Guia](guides/README.md) + * [Iniciando](guides/getting-started.md) + * [Dicas comuns](guides/common-tips.md) + * [Mouse, Tecla e outros eventos do DOM](guides/dom-events.md) + * [Escolhendo um executador de testes](guides/choosing-a-test-runner.md) + * [Testando SFCs com Jest](guides/testing-SFCs-with-jest.md) + * [Testando SFCs com Mocha + webpack](guides/testing-SFCs-with-mocha-webpack.md) + * [Usando com o Vue Router](guides/using-with-vue-router.md) + * [Usando com o Vuex](guides/using-with-vuex.md) +* [API](api/README.md) + * [mount](api/mount.md) + * [shallow](api/shallow.md) + * [Opções de montagem](api/options.md) + - [context](api/options.md#context) + - [slots](api/options.md#slots) + - [stubs](api/options.md#stubs) + - [mocks](api/options.md#mocks) + - [localVue](api/options.md#localvue) + - [attachToDocument](api/options.md#attachtodocument) + - [attrs](api/options.md#attrs) + - [listeners](api/options.md#listeners) + - [clone](api/options.md#clone) + * [Wrapper](api/wrapper/README.md) + * [contains](api/wrapper/contains.md) + * [emitted](api/wrapper/emitted.md) + * [emittedByOrder](api/wrapper/emittedByOrder.md) + * [find](api/wrapper/find.md) + * [findAll](api/wrapper/findAll.md) + * [hasAttribute](api/wrapper/hasAttribute.md) + * [hasClass](api/wrapper/hasClass.md) + * [hasProp](api/wrapper/hasProp.md) + * [hasStyle](api/wrapper/hasStyle.md) + * [html](api/wrapper/html.md) + * [is](api/wrapper/is.md) + * [isEmpty](api/wrapper/isEmpty.md) + * [isVueInstance](api/wrapper/isVueInstance.md) + * [name](api/wrapper/name.md) + * [setComputed](api/wrapper/setComputed.md) + * [setData](api/wrapper/setData.md) + * [setMethods](api/wrapper/setMethods.md) + * [setProps](api/wrapper/setProps.md) + * [text](api/wrapper/text.md) + * [trigger](api/wrapper/trigger.md) + * [update](api/wrapper/update.md) + * [destroy](api/wrapper/destroy.md) + * [WrapperArray](api/wrapper-array/README.md) + * [at](api/wrapper-array/at.md) + * [contains](api/wrapper-array/contains.md) + * [hasAttribute](api/wrapper-array/hasAttribute.md) + * [hasClass](api/wrapper-array/hasClass.md) + * [hasProp](api/wrapper-array/hasProp.md) + * [hasStyle](api/wrapper-array/hasStyle.md) + * [is](api/wrapper-array/is.md) + * [isEmpty](api/wrapper-array/isEmpty.md) + * [isVueInstance](api/wrapper-array/isVueInstance.md) + * [setComputed](api/wrapper-array/setComputed.md) + * [setData](api/wrapper-array/setData.md) + * [setMethods](api/wrapper-array/setMethods.md) + * [setProps](api/wrapper-array/setProps.md) + * [trigger](api/wrapper-array/trigger.md) + * [update](api/wrapper-array/update.md) + * [destroy](api/wrapper-array/destroy.md) + * [Componentes](api/components/README.md) + * [TransitionStub](api/components/TransitionStub.md) + * [TransitionGroupStub](api/components/TransitionGroupStub.md) + * [Seletores](api/selectors.md) + * [createLocalVue](api/createLocalVue.md) + * [config](api/config.md) diff --git a/docs/pt-br/SUMMARY.md b/docs/pt-br/SUMMARY.md new file mode 100644 index 000000000..0c5cdceb7 --- /dev/null +++ b/docs/pt-br/SUMMARY.md @@ -0,0 +1,70 @@ +## Índice + +* [Guia](guides/README.md) + * [Iniciando](guides/getting-started.md) + * [Dicas comuns](guides/common-tips.md) + * [Mouse, Tecla e outros eventos do DOM](guides/dom-events.md) + * [Escolhendo um executador de testes](guides/choosing-a-test-runner.md) + * [Testando SFCs com Jest](guides/testing-SFCs-with-jest.md) + * [Testando SFCs com Mocha + webpack](guides/testing-SFCs-with-mocha-webpack.md) + * [Usando com o Vue Router](guides/using-with-vue-router.md) + * [Usando com o Vuex](guides/using-with-vuex.md) +* [API](api/README.md) + * [mount](api/mount.md) + * [shallow](api/shallow.md) + * [Opções de montagem](api/options.md) + - [context](api/options.md#context) + - [slots](api/options.md#slots) + - [stubs](api/options.md#stubs) + - [mocks](api/options.md#mocks) + - [localVue](api/options.md#localvue) + - [attachToDocument](api/options.md#attachtodocument) + - [attrs](api/options.md#attrs) + - [listeners](api/options.md#listeners) + - [clone](api/options.md#clone) + * [Wrapper](api/wrapper/README.md) + * [contains](api/wrapper/contains.md) + * [emitted](api/wrapper/emitted.md) + * [emittedByOrder](api/wrapper/emittedByOrder.md) + * [find](api/wrapper/find.md) + * [findAll](api/wrapper/findAll.md) + * [hasAttribute](api/wrapper/hasAttribute.md) + * [hasClass](api/wrapper/hasClass.md) + * [hasProp](api/wrapper/hasProp.md) + * [hasStyle](api/wrapper/hasStyle.md) + * [html](api/wrapper/html.md) + * [is](api/wrapper/is.md) + * [isEmpty](api/wrapper/isEmpty.md) + * [isVueInstance](api/wrapper/isVueInstance.md) + * [name](api/wrapper/name.md) + * [setComputed](api/wrapper/setComputed.md) + * [setData](api/wrapper/setData.md) + * [setMethods](api/wrapper/setMethods.md) + * [setProps](api/wrapper/setProps.md) + * [text](api/wrapper/text.md) + * [trigger](api/wrapper/trigger.md) + * [update](api/wrapper/update.md) + * [destroy](api/wrapper/destroy.md) + * [WrapperArray](api/wrapper-array/README.md) + * [at](api/wrapper-array/at.md) + * [contains](api/wrapper-array/contains.md) + * [hasAttribute](api/wrapper-array/hasAttribute.md) + * [hasClass](api/wrapper-array/hasClass.md) + * [hasProp](api/wrapper-array/hasProp.md) + * [hasStyle](api/wrapper-array/hasStyle.md) + * [is](api/wrapper-array/is.md) + * [isEmpty](api/wrapper-array/isEmpty.md) + * [isVueInstance](api/wrapper-array/isVueInstance.md) + * [setComputed](api/wrapper-array/setComputed.md) + * [setData](api/wrapper-array/setData.md) + * [setMethods](api/wrapper-array/setMethods.md) + * [setProps](api/wrapper-array/setProps.md) + * [trigger](api/wrapper-array/trigger.md) + * [update](api/wrapper-array/update.md) + * [destroy](api/wrapper-array/destroy.md) + * [Componentes](api/components/README.md) + * [TransitionStub](api/components/TransitionStub.md) + * [TransitionGroupStub](api/components/TransitionGroupStub.md) + * [Seletores](api/selectors.md) + * [createLocalVue](api/createLocalVue.md) + * [config](api/config.md) diff --git a/docs/pt-br/api/README.md b/docs/pt-br/api/README.md new file mode 100644 index 000000000..6941e1a63 --- /dev/null +++ b/docs/pt-br/api/README.md @@ -0,0 +1,49 @@ +# API + +* [mount](./mount.md) +* [shallow](./shallow.md) +* [Opções de montagem](./options.md) + - [context](./options.md#context) + - [slots](./options.md#slots) + - [stubs](./options.md#stubs) + - [mocks](./options.md#mocks) + - [localVue](./options.md#localvue) + - [attachToDocument](./options.md#attachtodocument) + - [attrs](./options.md#attrs) + - [listeners](./options.md#listeners) + - [clone](./options.md#clone) +* [Wrapper](./wrapper/README.md) + * [contains](./wrapper/contains.md) + * [emitted](./wrapper/emitted.md) + * [emittedByOrder](./wrapper/emittedByOrder.md) + * [find](./wrapper/find.md) + * [hasAttribute](./wrapper/hasAttribute.md) + * [hasClass](./wrapper/hasClass.md) + * [hasProp](./wrapper/hasProp.md) + * [hasStyle](./wrapper/hasStyle.md) + * [html](./wrapper/html.md) + * [is](./wrapper/is.md) + * [isEmpty](./wrapper/isEmpty.md) + * [isVueInstance](./wrapper/isVueInstance.md) + * [name](./wrapper/name.md) + * [update](./wrapper/update.md) + * [setData](./wrapper/setData.md) + * [setProps](./wrapper/setProps.md) + * [text](./wrapper/text.md) + * [trigger](./wrapper/trigger.md) +* [WrapperArray](./wrapper-array/README.md) + * [at](./wrapper-array/at.md) + * [contains](./wrapper-array/contains.md) + * [hasAttribute](./wrapper-array/hasAttribute.md) + * [hasClass](./wrapper-array/hasClass.md) + * [hasProp](./wrapper-array/hasProp.md) + * [hasStyle](./wrapper-array/hasStyle.md) + * [is](./wrapper-array/is.md) + * [isEmpty](./wrapper-array/isEmpty.md) + * [isVueInstance](./wrapper-array/isVueInstance.md) + * [update](./wrapper-array/update.md) + * [setData](./wrapper-array/setData.md) + * [setProps](./wrapper-array/setProps.md) + * [trigger](./wrapper-array/trigger.md) +* [createLocalVue](./createLocalVue.md) +* [Seletores](./selectors.md) diff --git a/docs/pt-br/api/components/README.md b/docs/pt-br/api/components/README.md new file mode 100644 index 000000000..9fe182248 --- /dev/null +++ b/docs/pt-br/api/components/README.md @@ -0,0 +1,5 @@ +# Componentes + +O vue-test-utils apresenta alguns componentes utilitários para que você possa esboçar componentes. + +O [TransitionStub](./TransitionStub.md) e o [TransitionGroupStub](./TransitionGroupStub.md) por padrão são usados para esboçar componentes de transições e grupo de transições. Mas você pode editar esses esboços editando as configurações do mesmo. diff --git a/docs/pt-br/api/components/TransitionGroupStub.md b/docs/pt-br/api/components/TransitionGroupStub.md new file mode 100644 index 000000000..20ce86cb1 --- /dev/null +++ b/docs/pt-br/api/components/TransitionGroupStub.md @@ -0,0 +1,31 @@ +# TransitionGroupStub (Esboço de grupo de transições) + +É um componente para substituir o componente `transition-group`. Em vez de executar as transições do grupo de forma assíncrona, ele retorna os componentes filhos de forma síncrona. + +Por padrão é configurado para trocar todos os componentes `transitions-group` da sua aplicação. Para desativar o componente `transition-group`, passe `config.stubs['transition-group']` para false: + +```js +import VueTestUtils from 'vue-test-utils' + +VueTestUtils.config.stubs['transition-group'] = false +``` + +Para redefinir os grupos de transições no esboço, use: + +```js +import VueTestUtils, { TransitionGroupStub } from 'vue-test-utils' + +VueTestUtils.config.stubs['transition-group'] = TransitionGroupStub +``` + +Para configura-lo na montagem, use: + +```js +import { mount, TransitionGroupStub } from 'vue-test-utils' + +mount(Component, { + stubs: { + 'transition-group': TransitionGroupStub + } +}) +``` diff --git a/docs/pt-br/api/components/TransitionStub.md b/docs/pt-br/api/components/TransitionStub.md new file mode 100644 index 000000000..ca537fc3c --- /dev/null +++ b/docs/pt-br/api/components/TransitionStub.md @@ -0,0 +1,31 @@ +# TransitionStub (esboço de transição) + +É um componente para substituir o componente `transition`. Em vez de executar as transições de forma assíncrona, ele retorna o componente filho de forma síncrona. + +Por padrão é configurado para trocar todos os componentes `transitions` da sua aplicação. Para desativar o componente `transition`, passe `config.stubs.transition` para false: + +```js +import VueTestUtils from 'vue-test-utils' + +VueTestUtils.config.stubs.transition = false +``` + +Para redefini-lo como componente de transição, use: + +```js +import VueTestUtils, { TransitionStub } from 'vue-test-utils' + +VueTestUtils.config.stubs.transition = TransitionStub +``` + +Para configura-lo como um esboço nas opções de montagem, use: + +```js +import { mount, TransitionStub } from 'vue-test-utils' + +mount(Component, { + stubs: { + transition: TransitionStub + } +}) +``` diff --git a/docs/pt-br/api/config.md b/docs/pt-br/api/config.md new file mode 100644 index 000000000..dce461a84 --- /dev/null +++ b/docs/pt-br/api/config.md @@ -0,0 +1,25 @@ +# Configurações + +O vue-test-utils apresenta um objeto de configuração para que você possa definir as opções usadas no wrapper. + +## Opções de configurações do `vue-test-utils + +### Esboços + +- tipo: `Object` +- padrão: `{ + transition: TransitionStub, + 'transition-group': TransitionGroupStub +}` + +Esboços são usados em todos componentes. Eles são substituídos pelos esboços passados nas opções da montagem. + +Quando você passa os esboços como um Array de String nas opções de montagens, o `config.stubs` é convertido em um Array e os componentes são esboçados com um componente que retorna uma div. + +Exemplo: + +```js +import VueTestUtils from 'vue-test-utils' + +VueTestUtils.config.stubs['meu-componente'] = '
' +``` diff --git a/docs/pt-br/api/createLocalVue.md b/docs/pt-br/api/createLocalVue.md new file mode 100644 index 000000000..e735e3864 --- /dev/null +++ b/docs/pt-br/api/createLocalVue.md @@ -0,0 +1,30 @@ +# createLocalVue() + +- **Retorna:** + - `{Component}` + +- **Uso:** + +O `createLocalVue` retorna uma classe do Vue para que você possa adicionar componentes, mixins e plugins sem poluir sua classe global do Vue. + +Usando com o `options.localVue` + +```js +import { createLocalVue, shallow } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const localVue = createLocalVue() +const wrapper = shallow(Foo, { + localVue, + mocks: { foo: true } +}) + +expect(wrapper.vm.foo).toBe(true) + +const wrapperSemMock = shallow(Foo) + +expect(wrapperSemMock.vm.foo).toBe(false) +``` + +- **Veja também:** [Dicas comuns](../guides/common-tips.md#applying-global-plugins-and-mixins) diff --git a/docs/pt-br/api/mount.md b/docs/pt-br/api/mount.md new file mode 100644 index 000000000..b219581f6 --- /dev/null +++ b/docs/pt-br/api/mount.md @@ -0,0 +1,138 @@ +# mount(component {, options}]) + +- **Argumentos:** + + - `{Component} component` + - `{Object} options` + +- **Retorna:** `{Wrapper}` + +- **Opções:** + +Veja [opções](options.md) + +- **Uso:** + +Retorna um [`Wrapper`](wrapper/README.md) do primeiro elemento do DOM ou o componente Vue correspondente ao seletor. + +Use qualquer [seletor](selectors.md) válido. + +**Sem opções:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +describe('Foo', () => { + it('renderiza uma div', () => { + const wrapper = mount(Foo) + expect(wrapper.contains('div')).toBe(true) + }) +}) +``` + +**Com opções do Vue:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +describe('Foo', () => { + it('verifica valor padrão da cor', () => { + const wrapper = mount(Foo, { + propsData: { + cor: 'vermelha' + } + }) + expect(wrapper.hasProp('cor', 'vermelha')).toBe(true) + }) +}) +``` + +**Anexar ao DOM:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +describe('Foo', () => { + it('adiciona anexado ao DOM', () => { + const wrapper = mount(Foo, { + attachToDocument: true + }) + expect(wrapper.contains('div')).toBe(true) + }) +}) +``` + +**Com slots padrões ou nomeados:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' +import Bar from './Bar.vue' +import FooBar from './FooBar.vue' + +describe('Foo', () => { + it('registra slots padrões e nomeados', () => { + const wrapper = mount(Foo, { + slots: { + default: [Bar, FooBar], + fooBar: FooBar, // Corresponde a , + foo: '
' + } + }) + expect(wrapper.contains('div')).toBe(true) + }) +}) +``` + +**Adicionando propriedades globais:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +describe('Foo', () => { + it('adicionando mock global do $route', () => { + const $route = { path: 'http://www.meusite.com.br' } + const wrapper = mount(Foo, { + mocks: { + $route + } + }) + expect(wrapper.vm.$route.path).toBe($route.path) + }) +}) +``` + +**Esboçando componentes filhos:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' +import Bar from './Bar.vue' +import Faz from './Faz.vue' + +describe('Foo', () => { + it('verifica componentes filhos de Foo', () => { + const wrapper = mount(Foo, { + stub: { + Bar: '
, + foo: '
' + } +}) +expect(wrapper.find('div')).toBe(true) +``` + +### Esboços + +- tipo: `{ [name: String]: Component | Boolean } | Array` + +Esboça os componentes filhos. Pode ser um Array com os nomes dos componentes ou um objeto. + +Exemplo: + +```js +import Foo from './Foo.vue' + +mount(Component, { + stubs: ['componente-registrado'] +}) + +shallow(Component, { + stubs: { + // esboço com uma implementação específica + 'componente-registrado': Foo, + // criar um esboço padrão (simulado com mock) + 'outro componente': true + } +}) +``` + +### `mocks` + +- tipo: `Object` + +Adiciona uma propriedade adicional à instância. Ótimo para simular injeções globais. + +Exemplo: + +```js +import { expect } from 'chai' + +const $route = { path: 'http://www.meusite.com.br' } +const wrapper = shallow(Component, { + mocks: { + $route + } +}) +expect(wrapper.vm.$route.path).toBe($route.path) +``` + +### `localVue` + +- tipo: `Vue` + +Uma cópia local do Vue é criada pelo [createLocalVue](./createLocalVue.md) para usar quando for montar um componente. A instalação de plugins e outros nessa cópia previne que seu Vue original seja poluído. + +Exemplo: + +```js +import { createLocalVue, mount } from 'vue-test-utils' +import VueRouter from 'vue-router' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const localVue = createLocalVue() +localVue.use(VueRouter) + +const routes = [ + { path: '/foo', component: Foo } +] + +const router = new VueRouter({ + routes +}) + +const wrapper = mount(Component, { + localVue, + router +}) +expect(wrapper.vm.$route).toBeInstanceOf(Object) +``` + +### `attachToDocument` + +- tipo: `Boolean` +- padrão: `false` + +O componente será anexado ao DOM quando configurado como `true`. Isso pode ser usado com o [`hasStyle`](wrapper/hasStyle.md) para verificar os seletores do CSS de vários elementos. + +### `attrs` + +- tipo: `Object` + +Define o objeto `$attrs` da instância do componente. + +### `listeners` + +- tipo: `Object` + +Define o objeto `$listeners` da instância do componente. + +### `clone` + +- tipo: `Boolean` +- padrão: `true` + +Clona o componente antes de monta-lo se o valor for `true`, evitando qualquer mutação no componente original. + +`options.mocks` (`Object`): Adiciona variáveis global à instância do Vue. + +`options.localVue` (`Object`): classe do Vue usada no método `mount`. Veja [createLocalVue](createLocalVue.md) diff --git a/docs/pt-br/api/selectors.md b/docs/pt-br/api/selectors.md new file mode 100644 index 000000000..ce97f2483 --- /dev/null +++ b/docs/pt-br/api/selectors.md @@ -0,0 +1,43 @@ +# Seletores + +Muitos métodos desse wrapper leva um seletor como argumento. Um seletor pode ser um seletor CSS ou um componente do Vue. + +## Seletores CSS + +O método `mount` controla e suporta qualquer seletor válido: + +- seletores de tag (div, foo, bar) +- seletores de classes (.foo, .bar) +- seletores de atributos ([foo], [foo="bar"]) +- seletores de ids (#foo, #bar) +- pseudo seletores (div:first-of-type) + +Você também pode usar qualquer combinação: + +- combinador de descendente direto (div > #bar > .foo) +- combinador de descendente geral (div #bar .foo) +- seletor de irmão adjacente (div + .foo) +- seletor geral de irmãos (div ~ .foo) + +## Componentes do Vue + +Os componentes do Vue também são seletores válidos. + +O vue-test-utils usa a propriedade `name` para buscar a instância na árvore de componentes do Vue. + +```js +// Foo.vue + +export default{ + name: 'FooComponente' +} +``` + +```js +import { shallow } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = shallow(Foo) +expect(wrapper.is(Foo)).toBe(true) +``` diff --git a/docs/pt-br/api/shallow.md b/docs/pt-br/api/shallow.md new file mode 100644 index 000000000..e3189be2a --- /dev/null +++ b/docs/pt-br/api/shallow.md @@ -0,0 +1,124 @@ +# shallow(component {, options}]) + +- **Argumentos:** + + - `{Component} component` + - `{Object} options` + - `{Boolean} attachToDocument` + - `{Object} context` + - `{Object} slots` + - `{Array|Component|String} default` + - `{Array|Component|String} named` + - `{Object} mocks` + - `{Object|Array} stubs` + - `{Boolean} clone` + - `{Object} children` + - `{Vue} localVue` + +- **Retorna:** `{Wrapper}` + +- **Opções:** + +Veja as [opçoes](./options.md) + +- **Uso:** + +Retorna um [`Wrapper`](wrapper/README.md) do primeiro elemento do DOM ou o componente Vue correspondente ao seletor. + +Esboça todos os componentes filhos. + +Use qualquer [seletor](selectors.md) válido. + +**Sem opções:** + +```js +import { shallow } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +describe('Foo', () => { + it('Renderiza uma div', () => { + const wrapper = shallow(Foo) + expect(wrapper.contains('div')).toBe(true) + }) +}) +``` + +**With Vue options:** + +```js +import { shallow } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +describe('Foo', () => { + it('renders a div', () => { + const wrapper = shallow(Foo, { + propsData: { + color: 'red' + } + }) + expect(wrapper.hasProp('color', 'red')).toBe(true) + }) +}) +``` + +**Anexa ao DOM:** + +```js +import { shallow } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +describe('Foo', () => { + it('adiciona anexado ao DOM', () => { + const wrapper = shallow(Foo, { + attachToDocument: true + }) + expect(wrapper.contains('div')).toBe(true) + }) +}) +``` + +**Com slots padrões ou nomeados:** + +```js +import { shallow } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' +import Bar from './Bar.vue' +import FooBar from './FooBar.vue' + +describe('Foo', () => { + it('adiciona slots ao componente', () => { + const wrapper = shallow(Foo, { + slots: { + default: [Bar, FooBar], + fooBar: FooBar, // Corresponde a , + foo: '
' + } + }) + expect(wrapper.find('div')).toBe(true) + }) +}) +``` + +**Adicionando propriedades globais:** + +```js +import { shallow } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +describe('Foo', () => { + it('adicionando mock global do $route', () => { + const $route = { path: 'http://www.meusite.com.br' } + const wrapper = shallow(Foo, { + mocks: { + $route + } + }) + expect(wrapper.vm.$route.path).toBe($route.path) + }) +}) +``` diff --git a/docs/pt-br/api/wrapper-array/README.md b/docs/pt-br/api/wrapper-array/README.md new file mode 100644 index 000000000..eec9b4d17 --- /dev/null +++ b/docs/pt-br/api/wrapper-array/README.md @@ -0,0 +1,11 @@ +# WrapperArray (Array de wrappers) + +Um Array de wrappers é um objeto que contem uma lista com [Wrappers](../wrapper/README.md), e os alguns métodos para testar esses wrappers. + +- **Propriedades:** + +`length` `number`: O número de wrappers contidos nesse Array. + +- **Métodos:** + +Nessa documentação você tem uma lista detalhada dos métodos apresentados na seção WrapperArray. diff --git a/docs/pt-br/api/wrapper-array/at.md b/docs/pt-br/api/wrapper-array/at.md new file mode 100644 index 000000000..30d331b8a --- /dev/null +++ b/docs/pt-br/api/wrapper-array/at.md @@ -0,0 +1,22 @@ +# at(indice) + +Retorna o wrapper correspondente ao `indice` passado. Use números para corresponder ao item do arra, por exemplo o `indice` 0 para o primeiro elemento. + +- **Argumentos:** + - `{number} indice` + +- **Retorna:** `{Wrapper}` + +- **Exemplo:** + +```js +import { shallow } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = shallow(Foo) +const divArray = wrapper.findAll('div') + +const segundaDiv = divArray.at(1) +expect(segundaDiv.is('p')).toBe(true) +``` diff --git a/docs/pt-br/api/wrapper-array/contains.md b/docs/pt-br/api/wrapper-array/contains.md new file mode 100644 index 000000000..870d355e2 --- /dev/null +++ b/docs/pt-br/api/wrapper-array/contains.md @@ -0,0 +1,25 @@ +# contains(selector) + +Verifica se cada wrapper do Array contém correspondência do seletor informado. + +Use qualquer [seletor](../selectors.md) válido. + +- **Argumentos:** + - `{String|Component} selector` + +- **Retorna:** `{Boolean}` + +- **Exemplo:** + +```js +import { shallow } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' +import Bar from './Bar.vue' + +const wrapper = shallow(Foo) +const divArray = wrapper.findAll('div') + +expect(divArray.contains('p')).toBe(true) +expect(divArray.contains(Bar)).toBe(true) +``` diff --git a/docs/pt-br/api/wrapper-array/destroy.md b/docs/pt-br/api/wrapper-array/destroy.md new file mode 100644 index 000000000..0cf53a90d --- /dev/null +++ b/docs/pt-br/api/wrapper-array/destroy.md @@ -0,0 +1,19 @@ +# destroy() + +Destroí a instância do Vue da cada um dos wrappers do Array. + +- **Exemplo:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) + +const divArray = wrapper.findAll('div') +expect(divArray.contains('p')).toBe(true) +divArray.destroy() + +expect(divArray.contains('p')).toBe(false) +``` diff --git a/docs/pt-br/api/wrapper-array/hasAttribute.md b/docs/pt-br/api/wrapper-array/hasAttribute.md new file mode 100644 index 000000000..faaa0cc54 --- /dev/null +++ b/docs/pt-br/api/wrapper-array/hasAttribute.md @@ -0,0 +1,22 @@ +# hasAttribute(attribute, value) + +Verifica se algum wrapper do Array tem o `atributo` com `value` correspondente no elemento do DOM. + +- **Argumentos:** + - `{String} attribute` + - `{String} value` + +- **Retorna:** `{Boolean}` + +- **Exemplo:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +const divArray = wrapper.findAll('div') + +expect(divArray.hasAttribute('id', 'foo')).toBe(true) +``` diff --git a/docs/pt-br/api/wrapper-array/hasClass.md b/docs/pt-br/api/wrapper-array/hasClass.md new file mode 100644 index 000000000..4dc2ad891 --- /dev/null +++ b/docs/pt-br/api/wrapper-array/hasClass.md @@ -0,0 +1,21 @@ +# hasClass(className) + +Verifica se algum wrapper do Array contém uma classe com o nome `className` no elemento do DOM. + +- **Argumentos:** + - `{String} className` + +- **Retorna:** `{Boolean}` + +- **Exemplo:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +const divArray = wrapper.findAll('div') + +expect(divArray.hasClass('bar')).toBe(true) +``` diff --git a/docs/pt-br/api/wrapper-array/hasProp.md b/docs/pt-br/api/wrapper-array/hasProp.md new file mode 100644 index 000000000..d2649b55a --- /dev/null +++ b/docs/pt-br/api/wrapper-array/hasProp.md @@ -0,0 +1,25 @@ +# hasProp(propriedade, value) + +Verifica se algum wrapper do Array possui a `propriedade` com o `value` no `vm`. + +**Nota: o wrapper deve ser uma intância do Vue.** + +- **Argumentos:** + - `{String} propriedade` + - `{any} value` + +- **Retorna:** `{Boolean}` + +- **Exemplo:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' +import Bar from './Bar.vue' + +const wrapper = mount(Foo) +const barArray = wrapper.findAll(Bar) + +expect(barArray.hasProp('bar', 10)).toBe(true) +``` diff --git a/docs/pt-br/api/wrapper-array/hasStyle.md b/docs/pt-br/api/wrapper-array/hasStyle.md new file mode 100644 index 000000000..24e5e45bc --- /dev/null +++ b/docs/pt-br/api/wrapper-array/hasStyle.md @@ -0,0 +1,26 @@ +# hasStyle(style, value) + +Verifica se algum wrapper do Array tem o `style` com o `value` no elemento do DOM. + +Retorna `true` se o wrapper contém o `style` com o `value`. + +**Nota: para detectarmos os styles deve-se usar o `jsdom`.** + +- **Argumentos:** + - `{String} style` + - `{String} value` + +- **Retorna:** `{Boolean}` + +- **Exemplo:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +const divArray = wrapper.findAll('div') + +expect(divArray.hasStyle('cor', 'vermelha')).toBe(true) +``` diff --git a/docs/pt-br/api/wrapper-array/is.md b/docs/pt-br/api/wrapper-array/is.md new file mode 100644 index 000000000..f2ee1a422 --- /dev/null +++ b/docs/pt-br/api/wrapper-array/is.md @@ -0,0 +1,21 @@ +# is(selector) + +Verifica se algum wrapper do Array possui o [seletor](../selectors.md) no seu `vm`. + +- **Argumentos:** + - `{String|Component} selector` + +- **Retorna:** `{Boolean}` + +- **Exemplo:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +const divArray = wrapper.find('div') + +expect(divArray.is('div')).toBe(true) +``` diff --git a/docs/pt-br/api/wrapper-array/isEmpty.md b/docs/pt-br/api/wrapper-array/isEmpty.md new file mode 100644 index 000000000..83fc9b324 --- /dev/null +++ b/docs/pt-br/api/wrapper-array/isEmpty.md @@ -0,0 +1,18 @@ +# isEmpty() + +Verifica se algum wrapper do Array não tem um elemento filho. + +- **Retorna:** `{Boolean}` + +- **Exemplo:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +const divArray = wrapper.findAll('div') + +expect(divArray.isEmpty()).toBe(true) +``` diff --git a/docs/pt-br/api/wrapper-array/isVueInstance.md b/docs/pt-br/api/wrapper-array/isVueInstance.md new file mode 100644 index 000000000..e6a58ac8c --- /dev/null +++ b/docs/pt-br/api/wrapper-array/isVueInstance.md @@ -0,0 +1,19 @@ +# isVueInstance() + +Verifica se algum wrapper do Array é uma instância do Vue. + +- **Retorna:** `{Boolean}` + +- **Exemplo:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' +import Bar from './Bar.vue' + +const wrapper = mount(Foo) +const barArray = wrapper.findAll(Bar) + +expect(barArray.isVueInstance()).toBe(true) +``` diff --git a/docs/pt-br/api/wrapper-array/setComputed.md b/docs/pt-br/api/wrapper-array/setComputed.md new file mode 100644 index 000000000..70abc1df0 --- /dev/null +++ b/docs/pt-br/api/wrapper-array/setComputed.md @@ -0,0 +1,25 @@ +# setComputed(computedProperties) + +Define as propriedades computadas e força a atualização de cada um dos wrappers no Array. + +**Nota: cada wrapper deve ser uma instância do Vue.** +**Nota2: cada instância de cada wrapper deve ter as propriedades computadas já declaradas, pois esse método apenas simular o seu valor.** + +- **Argumentos:** + - `{Object} computedPropertiess` + +- **Exemplo:** + +```js +import { mount } from 'vue-test-utils' +import Foo from './Foo.vue' +import Bar from './Bar.vue' + +const wrapper = mount(Foo) +const barArray = wrapper.findAll(Bar) + +barArray.setComputed({ + propriedade1: 'nova-propriedade1', + propriedade2: 'nova-propriedade2' +}) +``` diff --git a/docs/pt-br/api/wrapper-array/setData.md b/docs/pt-br/api/wrapper-array/setData.md new file mode 100644 index 000000000..0dd0d2940 --- /dev/null +++ b/docs/pt-br/api/wrapper-array/setData.md @@ -0,0 +1,23 @@ +# setData(data) + +Define os dados e força a atualização de cada wrapper presente no Array. + +**Nota: cada wrapper deve ser uma instância do Vue.** + +- **Argumentos:** + - `{Object} data` + +- **Exemplho:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' +import Bar from './Bar.vue' + +const wrapper = mount(Foo) +const barArray = wrapper.findAll(Bar) + +barArray.setData({ foo: 'bar' }) +expect(barArray.at(0).vm.foo).toBe('bar') +``` diff --git a/docs/pt-br/api/wrapper-array/setMethods.md b/docs/pt-br/api/wrapper-array/setMethods.md new file mode 100644 index 000000000..bb877a95d --- /dev/null +++ b/docs/pt-br/api/wrapper-array/setMethods.md @@ -0,0 +1,26 @@ +# setMethods(methods) + +Define os métodos do componente e força sua atualização para cada wrapper no Array. + +**Nota: cada wrapper deve ser uma instância do Vue.** + +- **Argumentos:** + - `{Object} methods` + +- **Exemplo:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import sinon from 'sinon' +import Foo from './Foo.vue' +import Bar from './Bar.vue' + +const wrapper = mount(Foo) +const barArray = wrapper.findAll(Bar) +const mockClique = sinon.stub() + +barArray.setMethods({ methodoClique: mockClique }) +barArray.at(0).trigger('click') +expect(mockClique.called).toBe(true) +``` diff --git a/docs/pt-br/api/wrapper-array/setProps.md b/docs/pt-br/api/wrapper-array/setProps.md new file mode 100644 index 000000000..b6e19d410 --- /dev/null +++ b/docs/pt-br/api/wrapper-array/setProps.md @@ -0,0 +1,23 @@ +# setProps(props) + +Define as `propriedades` do componente e força sua atualização para cada wrapper no Array. + +**Nota: cada wrapper deve ser uma instância do Vue.** + +- **Argumentos:** + - `{Object} propriedades` + +- **Exemplo:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' +import Bar from './Bar.vue' + +const wrapper = mount(Foo) +const barArray = wrapper.findAll(Bar) + +barArray.setProps({ foo: 'bar' }) +expect(barArray.at(0).vm.foo).toBe('bar') +``` diff --git a/docs/pt-br/api/wrapper-array/trigger.md b/docs/pt-br/api/wrapper-array/trigger.md new file mode 100644 index 000000000..b73fb1ad5 --- /dev/null +++ b/docs/pt-br/api/wrapper-array/trigger.md @@ -0,0 +1,27 @@ +# trigger(eventName) + +Aciona um evento no elemeto do DOM de cada wrapper no Array. + +**Nota: cada wrapper deve ser uma instância do Vue.** + +- **Argumentos:** + - `{String} eventName` + +- **Exemplo:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import sinon from 'sinon' +import Foo from './Foo.vue' + +const mockDoClique = sinon.stub() +const wrapper = mount(Foo, { + propsData: { mockDoClique } +}) + +const divArray = wrapper.findAll('div') +divArray.trigger('click') + +expect(mockDoClique.called).toBe(true) +``` diff --git a/docs/pt-br/api/wrapper-array/update.md b/docs/pt-br/api/wrapper-array/update.md new file mode 100644 index 000000000..a67237565 --- /dev/null +++ b/docs/pt-br/api/wrapper-array/update.md @@ -0,0 +1,22 @@ +# update() + +Força a atualização e redesenho do componente Vue de cada wrapper do Array. + +Se for chamado a partir de um componente Vue, força a atualização de cada componente do Array. + +- **Exemplo:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +const divArray = wrapper.findAll('div') + +expect(divArray.at(0).vm.bar).toBe('bar') +divArray.at(0).vm.bar = 'novo valor' +divArray.update() + +expect(divArray.at(0).vm.bar).toBe('novo valor') +``` diff --git a/docs/pt-br/api/wrapper/README.md b/docs/pt-br/api/wrapper/README.md new file mode 100644 index 000000000..b7eccd817 --- /dev/null +++ b/docs/pt-br/api/wrapper/README.md @@ -0,0 +1,17 @@ +# Wrapper + +vue-test-utils é uma API baseada em *wrapper*. + +Um `Wrapper` é um objeto que contém um componente montado ou um vnode e alguns métodos para testar esse item envelopado. + +- **Propriedades:** + +`vm` `Component`: é uma instância Vue. Você pode acessar todos os [métodos de instância e propriedades de um vm](https://vuejs.org/v2/api/#Instance-Properties) com o `wrapper.vm`. Ela só exite em wrappers de componentes Vue. + +`element` `HTMLElement`: elemento raiz do DOM do wrapper. + +`options` `Object`: Objeto que contém as opções do vue-test-utils para ser passado para o `mount` ou `shallow`. + +- **Métodos:** + +Exite uma lista detalhada de métodos na seção Wrapper dessa documentação. diff --git a/docs/pt-br/api/wrapper/contains.md b/docs/pt-br/api/wrapper/contains.md new file mode 100644 index 000000000..115b7d365 --- /dev/null +++ b/docs/pt-br/api/wrapper/contains.md @@ -0,0 +1,23 @@ +# contains(selector) + +Verifica se o wrapper contém um elemento ou componente com o [seletor](../selectors.md) informado. + +- **Argumentos:** + - `{String|Component} selector` + +- **Retorna:** `{Boolean}` + +- **Exemplo:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' +import Bar from './Bar.vue' + +const wrapper = mount(Foo) +expect(wrapper.contains('p')).toBe(true) +expect(wrapper.contains(Bar)).toBe(true) +``` + +- **Veja também:** [seletores](../selectors.md) diff --git a/docs/pt-br/api/wrapper/destroy.md b/docs/pt-br/api/wrapper/destroy.md new file mode 100644 index 000000000..fc04358b9 --- /dev/null +++ b/docs/pt-br/api/wrapper/destroy.md @@ -0,0 +1,22 @@ +# destroy() + +Destrói a instância do componente Vue. + +- **Exemplo:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import sinon from 'sinon' + +const spy = sinon.stub() + +mount({ + render: null, + destroyed () { + spy() + } +}).destroy() + +expect(spy.calledOnce).to.equal(true) +``` diff --git a/docs/pt-br/api/wrapper/emitted.md b/docs/pt-br/api/wrapper/emitted.md new file mode 100644 index 000000000..e62207bd9 --- /dev/null +++ b/docs/pt-br/api/wrapper/emitted.md @@ -0,0 +1,33 @@ +# emitted() + +Retorna um objeto contendo os eventos cutomizados emitidos pelo `vm` do wrapper. + +- **Retorna:** `{ [name: String]: Array> }` + +- **Exemplo:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' + +const wrapper = mount(Componente) + +wrapper.vm.$emit('foo') +wrapper.vm.$emit('foo', 123) + +/* +wrapper.emitted() retorna o seguinte objeto: +{ + foo: [[], [123]] +} +*/ + +// Verifica se o evento foi emitido +expect(wrapper.emitted().foo).toBeTruthy() + +// Verifica aquantidade de emissões do evento +expect(wrapper.emitted().foo.length).toBe(2) + +// Verifica a carga do segundo evento foo emitido +expect(wrapper.emitted().foo[1]).toEqual([123]) +``` diff --git a/docs/pt-br/api/wrapper/emittedByOrder.md b/docs/pt-br/api/wrapper/emittedByOrder.md new file mode 100644 index 000000000..5421e8c91 --- /dev/null +++ b/docs/pt-br/api/wrapper/emittedByOrder.md @@ -0,0 +1,28 @@ +# emittedByOrder() + +Retorna um Array contendo os eventos customizados emitidos pelo `vm` do wrapper. + +- **Retorna:** `Array<{ name: String, args: Array }>` + +- **Exemplo:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' + +const wrapper = mount(Component) + +wrapper.vm.$emit('foo') +wrapper.vm.$emit('bar', 123) + +/* +wrapper.emittedByOrder() retorna o seguinte Array: +[ + { name: 'foo', args: [] }, + { name: 'bar', args: [123] } +] +*/ + +// Verifica a ordem dos eventos chamados +expect(wrapper.emittedByOrder().map(e => e.name)).toEqual(['foo', 'bar']) +``` diff --git a/docs/pt-br/api/wrapper/exists.md b/docs/pt-br/api/wrapper/exists.md new file mode 100644 index 000000000..3a79864e0 --- /dev/null +++ b/docs/pt-br/api/wrapper/exists.md @@ -0,0 +1,22 @@ +# exists() + +Verifica se o `Wrapper` ou o `WrapperArray` existe. + +Retorna `false` se chamado com um `Wrapper` ou `WrapperArray` vazio. + +- **Retorna:** `{Boolean}` + +- **Exemplo:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) + +expect(wrapper.exists()).toBe(true) +expect(wrapper.find('nao-existe').exists()).toBe(false) +expect(wrapper.findAll('div').exists()).toBe(true) +expect(wrapper.findAll('nao-existe').exists()).toBe(false) +``` diff --git a/docs/pt-br/api/wrapper/find.md b/docs/pt-br/api/wrapper/find.md new file mode 100644 index 000000000..aae3f1a47 --- /dev/null +++ b/docs/pt-br/api/wrapper/find.md @@ -0,0 +1,29 @@ +# find(selector) + +Retorna um wrapper [`Wrapper`](README.md) com o primeiro elmento do DOM ou o componente Vue encontrado a partir do seletor + +Use qualquer [seletor](../selectors.md) válido. + +- **Argumentos:** + - `{String|Component} selector` + +- **Retorna:** `{Wrapper}` + +- **Exemplo:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' +import Bar from './Bar.vue' + +const wrapper = mount(Foo) + +const div = wrapper.find('div') +expect(div.is('div')).toBe(true) + +const bar = wrapper.find(Bar) +expect(bar.is(Bar)).toBe(true) +``` + +- **Veja também:** [Wrapper](README.md) diff --git a/docs/pt-br/api/wrapper/findAll.md b/docs/pt-br/api/wrapper/findAll.md new file mode 100644 index 000000000..bc89034a4 --- /dev/null +++ b/docs/pt-br/api/wrapper/findAll.md @@ -0,0 +1,29 @@ +# findAll(selector) + +Retorna um [`WrapperArray`](../wrapper-array/README.md) de [Wrappers](README.md). + +Use qualquer [seletor](../selectors.md) válido. + +- **Argumentos:** + - `{String|Component} selector` + +- **Retorna:** `{WrapperArray}` + +- **Exemplo:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' +import Bar from './Bar.vue' + +const wrapper = mount(Foo) + +const div = wrapper.findAll('div').at(0) +expect(div.is('div')).toBe(true) + +const bar = wrapper.findAll(Bar).at(0) +expect(bar.is(Bar)).toBe(true) +``` + +- **Veja também:** [Wrapper](README.md) diff --git a/docs/pt-br/api/wrapper/hasAttribute.md b/docs/pt-br/api/wrapper/hasAttribute.md new file mode 100644 index 000000000..e41123238 --- /dev/null +++ b/docs/pt-br/api/wrapper/hasAttribute.md @@ -0,0 +1,38 @@ +# hasAttribute(attribute, value) + +Verifica se o wrapper contém o atributo mencionado no seu elemento do DOM. + +Retorna `true` se o wrapper contém o atributo. + +- **Argumentos:** + - `{String} attribute` + - `{String} value` + +- **Retorna:** `{Boolean}` + +- **Exemplo:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +// Exemplo
...
+const wrapper = mount(Foo) +expect(wrapper.hasAttribute('id', 'foo')).toBe(true) +``` + +- **Alternativa:** + +Você poderia obter o atributo do `Wrapper.element` para então verificar baseado no valor retornado: + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +expect(wrapper.element.getAttribute('id')).toBe('foo') +``` + +Isso faz com que o erro da asserção sejá mais informativo. diff --git a/docs/pt-br/api/wrapper/hasClass.md b/docs/pt-br/api/wrapper/hasClass.md new file mode 100644 index 000000000..32e69fae9 --- /dev/null +++ b/docs/pt-br/api/wrapper/hasClass.md @@ -0,0 +1,21 @@ +# hasClass(className) + +Verifica se o wrapper do elemento do DOM contém uma classe informada pelo `className`. + +Retorna `true` se o wrapper contém a classe. + +- **Argumentos:** + - `{String} className` + +- **Retorna:** `{Boolean}` + +- **Exemplo:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +expect(wrapper.hasClass('bar')).toBe(true) +``` diff --git a/docs/pt-br/api/wrapper/hasProp.md b/docs/pt-br/api/wrapper/hasProp.md new file mode 100644 index 000000000..7b9dd2f7e --- /dev/null +++ b/docs/pt-br/api/wrapper/hasProp.md @@ -0,0 +1,24 @@ +# hasProp(propriedade, value) + +Verifica se o `vm` do wrapper possui uma pripriedade com o valor definido. + +Retorna `true` se o `vm` do wrapper tem a `propriedade` com o `value` passado. + +**Nota: o wrapper deve conter uma instância do Vue.** + +- **Argumentos:** + - `{String} propriedade` + - `{any} value` + +- **Retorna:** `{Boolean}` + +- **Exemplo:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +expect(wrapper.hasProp('bar', 10)).toBe(true) +``` diff --git a/docs/pt-br/api/wrapper/hasStyle.md b/docs/pt-br/api/wrapper/hasStyle.md new file mode 100644 index 000000000..2a1b29265 --- /dev/null +++ b/docs/pt-br/api/wrapper/hasStyle.md @@ -0,0 +1,24 @@ +# hasStyle(style, value) + +Verifica se o elemento do DOM do wrapper possui uma propriedade de estilo com esse valor. + +Retorna `true` se o wrapper possui um `style` com o `value`. + +**Nota: só iremos detectar os estilos quando executado com o `jsdom`.** + +- **Argumentos:** + - `{String} style` + - `{String} value` + +- **Retorna:** `{Boolean}` + +- **Exemplo:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +expect(wrapper.hasStyle('color', 'red')).toBe(true) +``` diff --git a/docs/pt-br/api/wrapper/html.md b/docs/pt-br/api/wrapper/html.md new file mode 100644 index 000000000..8f504fda3 --- /dev/null +++ b/docs/pt-br/api/wrapper/html.md @@ -0,0 +1,16 @@ +# html() + +Retorna o HTML do elemento do wrapper como uma String. + +- **Retorna:** `{String}` + +- **Exemplo:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +expect(wrapper.html()).toBe('

Foo

') +``` diff --git a/docs/pt-br/api/wrapper/is.md b/docs/pt-br/api/wrapper/is.md new file mode 100644 index 000000000..d90eff3b4 --- /dev/null +++ b/docs/pt-br/api/wrapper/is.md @@ -0,0 +1,19 @@ +# is(selector) + +Verifica se o `vm` do wrapper possui o [seletor](../selectors.md) informado. + +- **Argumentos:** + - `{String|Component} selector` + +- **Retorna:** `{Boolean}` + +- **Exemplo:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +expect(wrapper.is('div')).toBe(true) +``` diff --git a/docs/pt-br/api/wrapper/isEmpty.md b/docs/pt-br/api/wrapper/isEmpty.md new file mode 100644 index 000000000..c27344a0e --- /dev/null +++ b/docs/pt-br/api/wrapper/isEmpty.md @@ -0,0 +1,16 @@ +# isEmpty() + +Verifica se o wrapper não contem elementos filhos. + +- **Retorna:** `{Boolean}` + +- **Exemplo:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +expect(wrapper.isEmpty()).toBe(true) +``` diff --git a/docs/pt-br/api/wrapper/isVueInstance.md b/docs/pt-br/api/wrapper/isVueInstance.md new file mode 100644 index 000000000..f73fa8685 --- /dev/null +++ b/docs/pt-br/api/wrapper/isVueInstance.md @@ -0,0 +1,16 @@ +# isVueInstance() + +Verifica se o wrapper é uma intância do Vue. + +- **Retorna:** `{Boolean}` + +- **Exemplo:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +expect(wrapper.isVueInstance()).toBe(true) + ``` diff --git a/docs/pt-br/api/wrapper/name.md b/docs/pt-br/api/wrapper/name.md new file mode 100644 index 000000000..787a2ae83 --- /dev/null +++ b/docs/pt-br/api/wrapper/name.md @@ -0,0 +1,18 @@ +# name() + +Retorna o nome do componente se o wrapper for uma instância do Vue, ou então o nome da tag se o wrapper for um elemento do DOM e não for uma instância do Vue. + +- **Retorna:** `{String}` + +- **Exemplo:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +expect(wrapper.name()).toBe('Foo') +const p = wrapper.find('p') +expect(p.name()).toBe('p') +``` diff --git a/docs/pt-br/api/wrapper/setComputed.md b/docs/pt-br/api/wrapper/setComputed.md new file mode 100644 index 000000000..925fc96e5 --- /dev/null +++ b/docs/pt-br/api/wrapper/setComputed.md @@ -0,0 +1,43 @@ +# setComputed(computedProperties) + +Define as propriedades computadas do `vm` do wrapper e força a sua atualização. + +**Nota: o wrapper deve ser uma instância do Vue.** +**Nota2: a instância já deve ter as propriedades computadas passadas para o setComputed declaradas.** + + +- **Argumentos:** + - `{Object} computedProperties` + +- **Exemplo:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' + +const wrapper = mount({ + template: '
{{ propriedade1 }} {{ propriedade2 }}
', + data () { + return { + inicial: 'inicial' + } + }, + computed: { + propriedade1 () { + return this.inicial + }, + propriedade2 () { + return this.inicial + } + } +}) + +expect(wrapper.html()).toBe('
inicial inicial
') + +wrapper.setComputed({ + propriedade1: 'nova-propriedade1', + propriedade2: 'nova-propriedade2' +}) + +expect(wrapper.html()).toBe('
nova-propriedade1 nova-propriedade2
') +``` diff --git a/docs/pt-br/api/wrapper/setData.md b/docs/pt-br/api/wrapper/setData.md new file mode 100644 index 000000000..ac7e70acf --- /dev/null +++ b/docs/pt-br/api/wrapper/setData.md @@ -0,0 +1,20 @@ +# setData(data) + +Define os dados do `vm` do wrapper e força a sua atualização. + +**Nota: o wrapper deve ser uma instância do Vue.** + +- **Argumentos:** + - `{Object} data` + +- **Exemplo:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +wrapper.setData({ foo: 'bar' }) +expect(wrapper.vm.foo).toBe('bar') +``` diff --git a/docs/pt-br/api/wrapper/setMethods.md b/docs/pt-br/api/wrapper/setMethods.md new file mode 100644 index 000000000..a73fabc64 --- /dev/null +++ b/docs/pt-br/api/wrapper/setMethods.md @@ -0,0 +1,24 @@ +# setMethods(methods) + +Define os métodos do `vm` do wrapper e força sua atualização. + +**Nota: o wrapper deve ser uma instância do Vue.** + +- **Argumentos:** + - `{Object} methods` + +- **Exemplo:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import sinon from 'sinon' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +const mockClique = sinon.stub() + +wrapper.setMethods({ metodoClique: mockClique }) +wrapper.find('button').trigger('click') +expect(mockClique.called).toBe(true) +``` diff --git a/docs/pt-br/api/wrapper/setProps.md b/docs/pt-br/api/wrapper/setProps.md new file mode 100644 index 000000000..c18efe20e --- /dev/null +++ b/docs/pt-br/api/wrapper/setProps.md @@ -0,0 +1,48 @@ +# setProps(props) + +Define as propriedades do `vm` do wrapper e força sua atualização. + +**Nota: o wrapper deve ser uma instância do Vue.** + +- **Argumentos:** + - `{Object} propriedades` + +- **Exemplo:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +wrapper.setProps({ foo: 'bar' }) +expect(wrapper.vm.foo).to.equal('bar') +``` + +Além disso, você pode passar o objeto `propsData`, que irá inicializar a instância do Vue com os valores passados. + +``` js +// Foo.vue +export default { + props: { + foo: { + type: String, + required: true + } + } +} +``` + +``` js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo, { + propsData: { + foo: 'bar' + } +}) + +expect(wrapper.vm.foo).to.equal('bar') +``` diff --git a/docs/pt-br/api/wrapper/text.md b/docs/pt-br/api/wrapper/text.md new file mode 100644 index 000000000..e1a4e52b3 --- /dev/null +++ b/docs/pt-br/api/wrapper/text.md @@ -0,0 +1,16 @@ +# text() + +Retorna o texto contido no wrapper. + +- **Retorna:** `{String}` + +- **Exemplo:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +expect(wrapper.text()).toBe('bar') +``` diff --git a/docs/pt-br/api/wrapper/trigger.md b/docs/pt-br/api/wrapper/trigger.md new file mode 100644 index 000000000..f2882e941 --- /dev/null +++ b/docs/pt-br/api/wrapper/trigger.md @@ -0,0 +1,38 @@ +# trigger(eventName {, options}]) + +Aciona um evento do elemento do wrapper. + +O método `trigger` usa o objeto opicional `options`, essas opções serão adicionadas ao evento. + +Você pode rodar o preventDefault em um evento passando `preventDefault: true` no objeto de `options`. + +- **Argumentos:** + - `{String} eventName` + - `{Object} options` + - `{Boolean} preventDefault` + +- **Exemplo:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import sinon from 'sinon' +import Foo from './Foo' + +const eventoClique = sinon.stub() +const wrapper = mount(Foo, { + propsData: { eventoClique } +}) + +wrapper.trigger('click') + +wrapper.trigger('click', { + botao: 0 +}) + +wrapper.trigger('click', { + preventDefault: true +}) + +expect(clickHandler.called).toBe(true) +``` diff --git a/docs/pt-br/api/wrapper/update.md b/docs/pt-br/api/wrapper/update.md new file mode 100644 index 000000000..c138dc589 --- /dev/null +++ b/docs/pt-br/api/wrapper/update.md @@ -0,0 +1,19 @@ +# update() + +Força o componente Vue a ser redesenhado. + +Se você chamar esse método em um wrapper que contém `vm`, ele forçará o `vm` do wrapper a se redesenhar. + +- **Exemplo:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +expect(wrapper.vm.bar).toBe('bar') +wrapper.vm.bar = 'novo valor' +wrapper.update() +expect(wrapper.vm.bar).toBe('novo valor') +``` diff --git a/docs/pt-br/guides/README.md b/docs/pt-br/guides/README.md new file mode 100644 index 000000000..0983fe375 --- /dev/null +++ b/docs/pt-br/guides/README.md @@ -0,0 +1,10 @@ +# Guia + +* [Iniciando](./getting-started.md) +* [Dicas comuns](./common-tips.md) +* [Escolhendo um executador de testes](./choosing-a-test-runner.md) +* [Usando com o Jest](./using-with-jest.md) +* [Testando SFCs com Jest](./testing-SFCs-with-jest.md) +* [Testando SFCs com Mocha + webpack](./testing-SFCs-with-mocha-webpack.md) +* [Usando com o Vue Router](./using-with-vue-router.md) +* [Usando com o Vuex](./using-with-vuex.md) diff --git a/docs/pt-br/guides/choosing-a-test-runner.md b/docs/pt-br/guides/choosing-a-test-runner.md new file mode 100644 index 000000000..94a6ec1a0 --- /dev/null +++ b/docs/pt-br/guides/choosing-a-test-runner.md @@ -0,0 +1,46 @@ +# Escolhendo um executador de testes + +Um *test runner* é um programa com objetivo de executar seus testes. + +Existem muitos executadores de testes populares no Javascript, o `vue-test-utils` funciona com todos eles. + +Há algumas coisas a considerar ao escolher um *test runner*: conjunto de recursos, desempenho e suporte a pré-compilação de componentes de arquivo único (SFC). Depois de comparar cuidadosamente as bibliotecas existentes, aqui estão dois *test runner* que recomendamos: + +- [Jest](https://facebook.github.io/jest/docs/en/getting-started.html#content) é o executador de testes mais completo. Ele requer menos configuração, usa JSDOM por padrão, e fornece uma ótima experiência de usuário na linha de comando. Contudo, você vai precisar de um pré-processador para ser possível importar componentes SFC em seus testes. Nos criamos o pré-processador `vue-jest`, para lidar com os recursos SFC mais comuns, mas atualmente não possuímos 100% de compatibilidade com o `vue-loader`. + +- [mocha-webpack](https://github.com/zinserjan/mocha-webpack) é um +wrapper que envolve o webpack e o Mocha, mas com uma interface simplificada e um modo observador. O benefício dessa configuração é o suporte SFC completo via webpack + `vue-loader`, mas isso requer maiores configurações antecipadas. + +## Ambiente de navegador + +O `vue-test-utils` depende de um ambiente de navegador. Tecnicamente, você pode executa-lo em um navegador real, mas não recomendamos isso por causa da complexidade de executar navegadores reais em diferentes plataformas. Portanto, recomendamos que execute seus testes no Node.js com um ambiente virtual de navegador usando o [JSDOM](https://github.com/tmpvar/jsdom). + +O Jets configura automaticamente o JSDOM. Para outros *test runners*, você pode configurar o JSDOM manualmente para os seus testes usando o [jsdom-global](https://github.com/rstacruz/jsdom-global) na entrada dos testes: + +``` bash +npm install --save-dev jsdom jsdom-global +``` +--- +``` js +// na entrada ou configurações dos testes +require('jsdom-global')() +``` + +## Testando componentes de arquivo único (SFC) + +Os componentes de arquivo único do Vue requerem uma pré-compilação antes que possam ser executados no Node ou no navegador. Há duas maneiras recomendadas para executar essa compilação: com o pré-processador Jest ou diretamente usando o webpack. + +O pré-processador `vue-jest` suporta as funcionalidades básicas dos SFCs, mas atualmente não manipula blocos de estilos ou personalizados, que são suportados apenas no `vue-loader`. Se você depender desses recursos ou de outras configurações específicas do webpack, você precisará usar uma configuração baseada no webpack + `vue-loader`. + +Leia os guias a seguir para cofigurações diferentes: + +- [Testando SFCs com Jest](./testing-SFCs-with-jest.md) +- [Testando SFCs com Mocha + webpack](./testing-SFCs-with-mocha-webpack.md) + +## Recursos + +- [Comparação de desempenho entre test runners](https://github.com/eddyerburgh/vue-unit-test-perf-comparison) +- [Projeto de exemplo com o Jest](https://github.com/vuejs/vue-test-utils-jest-example) +- [Projeto de exemplo com o Mocha](https://github.com/vuejs/vue-test-utils-mocha-webpack-example) +- [Projeto de exemplo com o tape](https://github.com/eddyerburgh/vue-test-utils-tape-example) +- [Projeto de exemplo com o AVA](https://github.com/eddyerburgh/vue-test-utils-ava-example) diff --git a/docs/pt-br/guides/common-tips.md b/docs/pt-br/guides/common-tips.md new file mode 100644 index 000000000..798245c80 --- /dev/null +++ b/docs/pt-br/guides/common-tips.md @@ -0,0 +1,138 @@ +# Dicas comuns + +## Sabendo o que testar + +Para componentes com interface não recomendamos o foco na cobertura completa baseada em linha, porque ela leva muito o foco para detalhes internos da implementação dos componentes, podendo resultar em testes frágeis. + +Em vez disso, nós recomendamos escrever testes que verifiquem seus componentes com interface pública e trate os internos como uma caixa preta. Um caso de teste único irá verificar se a entrada (interações do usuário ou troca de props) forneceu o resultado (renderização ou emissão de eventos) esperado para o componente. + +Por exemplo, para o componente `Counter` que incrementa um contador em cada vez que um botão é clicado, seu caso de teste teria que silumar o clique e verificar se a saída renderizada se incrementou. O teste não se importa sobre como `Counter` incrementou o valor, ele apenas se preocupa com a entrada e a saída dos dados. + +O benefício desta abordagem é que enquanto a interface pública do seu componente permanece a mesma, seus testes irão passar sem se importar de como o componente faz a implementação do código interno e se houve ou não mudanças por lá. + +Esse tópico é discutido com mais detalhes em uma [excelente apresentação de Matt O'Connell](http://slides.com/mattoconnell/deck#/). + +## Renderização superficial + +Em testes de unidade, normalmente queremos nos focar no componente a ser testeado, isolando-o como uma unidade e evitando verificações indiretas em comportamentos dos seus filhos. + +Além disso, para componentes que possuem muitos componentes filhos, toda a árvore de renderização pode ficar realmente grande. A repetição de renderização de todos componentes filhos vão deixar seus testes lentos. + +O `vue-test-utils` permite que você monte um componente sem renderizar seus componentes filhos, para isso, use o método `shallow`: + +```js +import { shallow } from 'vue-test-utils' + +// obtém o wrapper contendo a instância montada de Componente +const wrapper = shallow(Componente) +wrapper.vm // instância do Vue já montada +``` + +## Verificando os eventos emitidos + +Cada wrapper montado grava automaticamente todos os eventos emitidos pela instância Vue contida. Você pode recuperar os eventos registrados usando o método `emitted`: + +``` js +wrapper.vm.$emit('foo') +wrapper.vm.$emit('foo', 123) + +/* +wrapper.emitted() retorna o objeto a seguir: +{ + foo: [ [], [123] ] +} +*/ +``` + +Então você pode criar asserções baseadas nesses dados: + +``` js +import { expect } from 'chai' + +// verifica se o evento 'foo' foi emitido +expect(wrapper.emitted().foo).toBeTruthy() + +// verifica as vezes que o evento 'foo' doi emitido +expect(wrapper.emitted().foo.length).toBe(2) + +// verifica a carga do segundo evento 'foo' emitido +expect(wrapper.emitted().foo[1]).toEqual([123]) +``` + +Além disso, você pode pegar um Array dos eventos emitidos em ordem de chamada com o método [wrapper.emittedByOrder()](../api/wrapper/emittedByOrder.md). + +## Manipulando o estado do componente + +Você pode manipular diretamente o estado do componente usando os métodos `setData` ou `setProps` no wrapper: + +```js +wrapper.setData({ contador: 10 }) + +wrapper.setProps({ foo: 'bar' }) +``` + +## Simulando propriedades + +Você pode passar propriedades para o componente usando a opção `propsData` integrada no Vue: + +```js +import { mount } from 'vue-test-utils' + +mount(Component, { + propsData: { + umaProp: 'qualquer valor' + } +}) +``` + +Você também pode atualizar as propriedades mesmo com o componente já montado, para isso use o método `wrapper.setProps({})`. + +*Para ver a lista completa de opções, por favor veja a seção [opções de montagem](../api/options.md) nessa documentação.* + +## Aplicando plugins e mixins globais + +Alguns dos seus componentes podem ter características injetadas por um plugin ou mixin global, por exemplo o `vuex`, `vue-router` e `vue-multilanguage`. + +Se você está escrevendo testes para componentes de uma aplicação específica, você pode configurar os plugins e mixins globais uma vez na entrada dos seus testes. Mas, em alguns casos, por exemplo, testando um pacote de componentes genéricos que podem ser compartilhados em diferentes aplicações, será melhor testar seus componentes com uma configuração mais isolada, sem popular o construtor global do Vue. Nós podemos usar o método [createLocalVue](../api/createLocalVue.md) para conseguir isso: + +``` js +import createLocalVue from 'vue-test-utils' + +// criando um construtor ampliado do Vue +const localVue = createLocalVue() + +// Instalando normalmente os seus plugins +localVue.use(MeuPlugin) + +// Passe o localVue para o wrapper do componente +mount(Componente, { + localVue +}) +``` + +## Simulando injeções + +Outra estratégia para injetar propriedades é simplesmente simular ela. Você pode fazer isso com a opção `mocks`: + +```js +import { mount } from 'vue-test-utils' + +const $route = { + path: '/', + hash: '', + params: { id: '123' }, + query: { q: 'hello' } +} + +mount(Component, { + mocks: { + // adiciona o objeto $route simulado na instância Vue + // antes da montagem do componente + $route + } +}) +``` + +## Lidando com o roteamento + +Uma vez que o roteamento, por definição, tem a ver com a estrutura geral da aplicação e envolve muitos componentes, é melhor testado atráves de testes de integração ou de ponta a ponta. Para componentes individuais que dependem dos recursos do `vue-router`, você pode simula-lo usando as técnicas mencionadas acima. diff --git a/docs/pt-br/guides/dom-events.md b/docs/pt-br/guides/dom-events.md new file mode 100644 index 000000000..ebc526af8 --- /dev/null +++ b/docs/pt-br/guides/dom-events.md @@ -0,0 +1,196 @@ +# Testando mouse, teclas e outros eventos do DOM + +## Desencadear eventos + +O wrapper (wrapper) expõe o método `trigger`. Ele pode ser usado para desencadear eventos do DOM. + +```js +const wrapper = mount(MeuBotao) + +wrapper.trigger('click') +``` + +Você deve estar ciente que esse método também retorna um wrapper. Assumindo que em `MeuComponente` há um botão, o código a seguir simula o clique no botão. + +```js +const wrapper = mount(MeuComponente) + +wrapper.find('button').trigger('click') +``` + +## Opções + +O método `trigger` pode receber um objeto `options` opcional. As propriedades no objeto `options` serão adicionadas no evento. + +Você pode executar `preventDefault` no evento passando `preventDefault: true` no objeto `options`: + +```js +const wrapper = mount(MeuBotao) + +wrapper.trigger('click', { preventDefault: true }) +``` + +## Exemplo de clique no mouse + +**Componente que será testado** + +```html + + + +``` + +**Teste** + +```js +import SimNaoComponente from '@/componentes/SimNaoComponente' +import { mount } from 'vue-test-utils' +import sinon from 'sinon' + +describe('Evento de clique', () => { + it('Clieque no botão sim chama o método com o argumento "sim"', () => { + const spy = sinon.spy() + const wrapper = mount(SimNaoComponente, { + propsData: { + meLigou: spy + } + }) + wrapper.find('button.sim').trigger('click') + + spy.should.have.been.calledWith('sim') + }) +}) +``` + +## Exemplo do teclado + +**Componente a ser testado** + +Esse componente permite incrementar ou decrementar o contador usando várias teclas do teclado. + +```html + + + +``` + +**Test** + +```js +import ContadorComponente from '@/componentes/ContadorComponente' +import { mount } from 'vue-test-utils' + +describe('Testes de eventos de tecla', () => { + it('Contador é zero por padrão', () => { + const wrapper = mount(ContadorComponente) + expect(wrapper.vm.contador).to.equal(0) + }) + + it('Tecla para cima incrementa contador para um', () => { + const wrapper = mount(ContadorComponente) + wrapper.trigger('keydown.up') + expect(wrapper.vm.contador).to.equal(1) + }) + + it('Tecla para baixo decrementa contador para quatro', () => { + const wrapper = mount(ContadorComponente) + wrapper.vm.contador = 5 + wrapper.trigger('keydown.down') + expect(wrapper.vm.contador).to.equal(4) + }) + + it('Tecla esc volta o contador para zero', () => { + const wrapper = mount(ContadorComponente) + wrapper.vm.contador = 5 + wrapper.trigger('keydown.esc') + expect(wrapper.vm.contador).to.equal(0) + }) + + it('Tecla A define o contador para 13', () => { + const wrapper = mount(ContadorComponente) + wrapper.trigger('keydown', { + which: 65 + }) + expect(wrapper.vm.contador).to.equal(13) + }) +}) + +``` + +**Limitações** + +O nome da tecla depois do ponto em `keydown.up` é traduzido para o `keyCode`. Isso é suportado para os seguintes nomes: + +* enter, tab, delete, esc, space, up, down, left, right + +## Importante + +vue-test-utils desencadeia eventos de forma síncrona. Consequentemente, o uso do `vue.nextTick` não é necessário. diff --git a/docs/pt-br/guides/getting-started.md b/docs/pt-br/guides/getting-started.md new file mode 100644 index 000000000..f311e289e --- /dev/null +++ b/docs/pt-br/guides/getting-started.md @@ -0,0 +1,116 @@ +# Iniciando + +## Configuração + +Para obter um exemplo rápido de uso do `vue-test-utils` clone nosso repositório de demonstração que contém as configurações básicas e instale as dependências: + +``` bash +git clone https://github.com/vuejs/vue-test-utils-getting-started +cd vue-test-utils-getting-started +npm install +``` + +Você verá que o projeto inclui um componente simples, chamado `counter.js`: + +```js +// counter.js + +export default { + template: ` +
+ {{ count }} + +
+ `, + + data () { + return { + count: 0 + } + }, + + methods: { + increment () { + this.count++ + } + } +} +``` + +### Montando seus componentes + +O `vue-test-utils` testa os componentes do Vue montando-os isoladamente, simulando as entradas necessárias (propriedades, slots e eventos de usuário) e verificando as saídas (renderização, eventos personalizados emitidos). + +Os componentes montados são retornados em um wrapper (wrapper) que expõe muitos métodos para manipular, percorrer e consultar a instância do componente Vue correspondente. + +Você pode criar essses wrappers usando o método `mount`. Vamos criar um chamado `test.js`: + +```js +// test.js + +// Importando o método mount() do test utils +// e o componente Counter para ser testado +import { mount } from 'vue-test-utils' +import Counter from './counter' + +// Agora montamos o componente e obtermos o wrapper +const wrapper = mount(Counter) + +// Você pode acessar a instância atual do Vue através de wrapper.vm +const vm = wrapper.vm + +// Para inspecionar a composição do wrapper exiba-o no console +// ...Sua aventura com o vue-test-utils começa agora :) +console.log(wrapper) +``` + +### Testar a saída HTML do componente + +Agora que nós já temos o wrapper, a primeira coisa que podemos fazer é verificar se a saída HTML que componente nos entrega é a esperada. + +```js +import { mount } from 'vue-test-utils' +import Counter from './counter' + +describe('Counter', () => { + // Montando o componente e obtendo o wrapper + const wrapper = mount(Counter) + + it('renderiza o HTML correto', () => { + expect(wrapper.html()).toContain('0') + }) + + // Também é fácil verificar se um elemento existe + it('verifica se o botão foi desenhado', () => { + expect(wrapper.contains('button')).toBe(true) + }) +}) +``` + +Agora execute os testes com o comando `npm tests`. Você deve ver os testes passando. + +### Simulando a interação do usuário + +Nosso `Counter` deve incrementar o contador quando o usuário clica no botão. Para simular esse comportamento, primeiro precisamos localizar o botão com `wrapper.find()`, que retorna um wrapper (wrapper) para o elemento do botão. Agora nós podemos simular o evento de clique, chamando o método `trigger()` no wrapper do botão: + +```js +it('o clique do botão deve incrementar a contagem', () => { + expect(wrapper.vm.count).toBe(0) + const button = wrapper.find('button') + button.trigger('click') + expect(wrapper.vm.count).toBe(1) +}) +``` + +### E quanto ao `nextTick`? + +Os lotes Vue estão pendentes de atualizações do DOM, aplicando-as de forma assíncrona para evitar re-renders desnecessários causados por múltiplas mutações de dados. É por isso que, na prática, muitas vezes temos que usar `Vue.nextTick` para esperar até que o Vue realize a atualização real do DOM, depois de ativar algumas mudanças de estado. + +Para simplificar o uso, o `vue-test-utils` aplica todas as atualizações de forma síncrona, então você não precisa usar o `Vue.nextTick` para esperar por atualizações do DOM nos seus testes. + +*Nota: o `nextTick` ainda é necessário para quando você precisa avançar explicitamente o ciclo do evento, ou seja, para operações como retorno de chamadas assíncronas ou resultados de uma Promise.* + +## A seguir + +- Integre o `vue-test-utils` no seu projeto [escolhendo seu executador de testes](./choosing-a-test-runner.md) +- Leia mais sobre [técnicas comuns ao escrever testes](./common-tips.md) diff --git a/docs/pt-br/guides/testing-SFCs-with-jest.md b/docs/pt-br/guides/testing-SFCs-with-jest.md new file mode 100644 index 000000000..6eb88cb6c --- /dev/null +++ b/docs/pt-br/guides/testing-SFCs-with-jest.md @@ -0,0 +1,178 @@ +# Testando componentes de arquivo único com Jest + +> Um projeto de exemplo para esta configuração está disponível no [GitHub](https://github.com/vuejs/vue-test-utils-jest-example). + +Jest é um executador de teste desenvolvido pelo Facebook, visa entregar uma bateria de testes de unidade. Você pode aprender mais sobre o Jets na sua [documentanção oficial](https://facebook.github.io/jest/). + +## Configurando o Jest + +Nós vamos assumir que você está iniciando com a configuração que já possui o webpack, vue-loader e Babel configurados corretamente - por exemplo o template `webpack-simple` fornecido pelo `vue-cli`. + +A primeira coisa para se fazer é instalar o Jest e o `vue-test-utils`: + +```bash +$ npm install --save-dev jest vue-test-utils +``` + +Posteriormente, devemos definir um script novo no `package.json`: + +```json +// package.json +{ + "scripts": { + "test": "jest" + } +} +``` + +## Processando SFCs com o Jest + +Para ensinar o Jest cmo processar arquivos `*.vue`, precisamos instalar e configurar o pré-processador `vue-jest`: + +``` bash +npm install --save-dev vue-jest +``` + +Agora, crie um bloco chamado `jest` no `package.json`: + +``` json +{ + // ... + "jest": { + "moduleFileExtensions": [ + "js", + "json", + // diga que o Jest irá reconhecer arquivos vue + "vue" + ], + "transform": { + // processa arquivos vue com o vue-jest + ".*\\.(vue)$": "/node_modules/vue-jest" + }, + "mapCoverage": true + } +} +``` + +> **Nota:** o `vue-jest` atualmente não suporta todos os recursos do `vue-loader`, por exemplo, blocos personalizados e estilo de carregamento. Além disso, alguns recursos específicos do webpack, como o [code-splitting](http://www.vuejs-brasil.com.br/separando-codigo-vue), também não são suportados. Para usá-los, leia o guia [testando SFCs com Mocha + webpack](./testing-SFCs-with-mocha-webpack.md). + +## Manipulação de alias do webpack + +Se você usa um alias de resolução na configuração do webpack, por exemplo usar `@` como atalho para `/src`, você precisará adicionar uma configuração correspondente para o Jest, usando a opção `moduleNameMapper`: + +``` json +{ + // ... + "jest": { + // ... + // suporta o mapeamento @ para /src do código fonte + "moduleNameMapper": { + "^@/(.*)$": "/src/$1" + } + } +} +``` + +## Configurando o Babel no Jest + +Apesar das últimas versões do Node já suportar muitos recursos do ES2015, você ainda pode querer usar a síntaxe e stage-x nos módulos ES em seus testes. Para isso, precisamos instalar o `babel-jest`: + +``` bash +npm install --save-dev babel-jest +``` + +Agora, nos precisamos dizer ao Jest para processar o arquivos de teste em Javascript com o `babel-jest`. Para isso, adicionamos uma entrada `jest.transform` no `package.json`: + +``` json +{ + // ... + "jest": { + // ... + "transform": { + // ... + // processar arquivos .js com o babel-jest + "^.+\\.js$": "/node_modules/babel-jest" + }, + // ... + } +} +``` + +> Por padrão, o `babel-jest` configura-se automaticamente enquanto estiver instalado. Contudo, adicionamos explicitamente uma transformação para arquivos `*.vue`, então agora precisamos configurar isso no `babel-jest` também. + +Assumindo que você usa o `babel-preset-env` com o webpack, a configuração padrão do Babel desabilitará a transpilação dos módulos ES porque o webpack já sabe como lidar com módulos ES. Entretanto, precisamos habilitar isso para nossos testes, porque o Jest executa seus testes diretamente no Node. + +Além disso, podemos dizer ao `babel-preset-env` para segmentar a versão do Node que estamos usando. Isso ignora a transposição de recursos desnecessários e faz com que nossos testes sejam mais rápidos. + +Para aplicar todas essas opções apenas para os testes, coloque-as em uma configuração separada em `env.test` (isso será automaticamente pego pelo `babel-jest`). + +Exemplo do novo `.babelrc`: + +``` json +{ + "presets": [ + ["env", { "modules": false }] + ], + "env": { + "test": { + "presets": [ + ["env", { "targets": { "node": "current" }}] + ] + } + } +} +``` + +### Teste instantâneo + +Você pode usar o [`vue-server-renderer`](https://github.com/vuejs/vue/tree/dev/packages/vue-server-renderer) para transformar um componente em uma String para que ele possa ser salvo como instântaneo para o [teste instântaneo com Jest](https://facebook.github.io/jest/docs/en/snapshot-testing.html). + +O resultado do `vue-server-renderer` inclui alguns atributos específicos de SSR e ignora espaços em branco, dificultando a detecção de um diff. Podemos melhorar o instantâneo salvo com um serializador personalizado: + +``` bash +npm install --save-dev jest-serializer-vue +``` + +Em seguida, configure-o no `package.json`: + +``` json +{ + // ... + "jest": { + // ... + // serializador para o instantâneo + "snapshotSerializers": [ + "/node_modules/jest-serializer-vue" + ] + } +} +``` + +### Colocando arquivos de teste + +Por padrão, o Jest irá recursivamente pegar todos os arquivosque tenham uma extensão `.spec.js` ou `.test.js` em todo o seu projeto. Se isso não for de acordo com o seu esperado, é possível [alterar o testRegex](https://facebook.github.io/jest/docs/en/configuration.html#testregex-String) na seção `config` no arquivo `package.json`. + +O Jest recomenda a criação de um diretório `__tests__` logo ao lado do código que está sendo testado, mas sinta-se livre para estruturar seus testes conforme entender. Apenas tenha cuidado com o fato de o Jest criar um diretório `__snapshots__` ao lado dos arquivos de teste que executam testes instantâneos. + +### Exemplo de spec + +Se você está familiarizado com o Jasmine, você deve se sentir em casa com a [API de asserção](https://facebook.github.io/jest/docs/en/expect.html#content) do Jest: + +```js +import { mount } from 'vue-test-utils' +import Componente from './componente' + +describe('Componente', () => { + test('é uma instância do Vue', () => { + const wrapper = mount(Componente) + expect(wrapper.ehInstanciaVue()).toBeTruthy() + }) +}) +``` + +### Recursos + +- [Exemplo de projeto para esta configuração](https://github.com/vuejs/vue-test-utils-jest-example) +- [Exemplos e slides do Vue Conf 2017](https://github.com/codebryo/vue-testing-with-jest-conf17) +- [Jest](https://facebook.github.io/jest/) +- [Babel preset env](https://github.com/babel/babel-preset-env) diff --git a/docs/pt-br/guides/testing-SFCs-with-mocha-webpack.md b/docs/pt-br/guides/testing-SFCs-with-mocha-webpack.md new file mode 100644 index 000000000..5d51059a0 --- /dev/null +++ b/docs/pt-br/guides/testing-SFCs-with-mocha-webpack.md @@ -0,0 +1,180 @@ +# Testando componentes de arquivo único com o Mocha + webpack + +> Um projeto de exemplo com essa configuração está disponível no [GitHub](https://github.com/vuejs/vue-test-utils-mocha-webpack-example). + +Outra estratégia para testar SFCs é compilar todos os seus testes via webpack e depois rodar em um *test runner*. A vantagem dessa abordagem é poder ter o suporte total para todos os recursos do webpack e `vue-loader`, por isso não temos que fazer compromissos em nosso código-fonte. + +Tecnicamente você pode usar qualquer executador de teste que você goste e alinhar as coisas de forma manual, mas descobrimos o [`mocha-webpack`](https://github.com/zinserjan/mocha-webpack) para fornecer uma experiência muito simplificada para essa tarefa específica. + +## Configurando o `mocha-webpack` + +Assumiremos que você está começando com o webpack, vue-loader e babel corretamente configurados, por exemplo com o template `webpack-simple` fornecido pelo `vue-cli`. + +A primeira coisa a se fazer é instalar as dependências dos testes: + +``` bash +npm install --save-dev vue-test-utils mocha mocha-webpack +``` + +Posteriormente, defina o script `test` no `package.json`: + +```json +// package.json +{ + "scripts": { + "test": "mocha-webpack --webpack-config webpack.config.js --require test/setup.js test/**/*.spec.js" + } +} +``` + +Temos algumas coisas a serem observadas aqui: + +- A flag `--webpack-config` especifica o arquivo de configuração do webpack para seus testes. Na maioria dos casos, ele é idêntico à configuração que você usa para o projeto real, mas com um pequeno ajuste que falaremos mais tarde. + +- A flag `--require` garante que o arquivo `test/setup.js` seja executado antes de qualquer teste, nele nós podemos configurar o ambiente global para nossos testes que irão ser executados. + +- O último argumento é um glob para os arquivos de teste a serem incluídos no pacote de teste. + +### Configurações extras do webpack + +#### Externalização das dependências do NPM + +Em nossos testes, provavelmente importaremos uma série de dependências do NPM - algumas dessas dependências podem ser escritas sem o uso do navegador em mente e simplesmente não serão empacotadas corretamente pelo webpack. Então consideramos a externalização das dependências, que melhora consideravelmente a velocidade na inicialização dos testes. Podemos externalizar todas as dependências do NPM com o `webpack-node-externals`: + +```js +// webpack.config.js +const nodeExternals = require('webpack-node-externals') + +module.exports = { + // ... + externals: [nodeExternals()] +} +``` + +#### Mapas de origem + +Os mapas de origem precisam ser incorporados pra ser capturados pelo `mocha-webpack`. A configuração recomendada é: + +``` js +module.exports = { + // ... + devtool: 'inline-cheap-module-source-map' +} +``` + +Se for depurar pela IDE, também recomendamos adicionar o seguinte: + +``` js +module.exports = { + // ... + output: { + // ... + // use caminhos absolutos nos mapas de origem (depuração via IDE) + devtoolModuleFilenameTemplate: '[absolute-resource-path]', + devtoolFallbackModuleFilenameTemplate: '[absolute-resource-path]?[hash]' + } +} +``` + +### Configurando o ambiente de navegador + +O `vue-test-utils` requere um ambiente de navegador para ser executado. Nos simulamos isso no Node.js usando o `jsdom-global`: + +```bash +npm install --save-dev jsdom jsdom-global +``` + +Então, em `test/setup.js`: + +``` js +require('jsdom-global')() +``` + +Isso adiciona um ambiente de navegador ao Node, de modo que o `vue-test-utils` possa ser executado corretamente. + +### Escolhendo uma biblioteca de asserção + +[Chai](http://chaijs.com/) é uma biblioteca popular que é comumente usada ao lado do Mocha. Você também pode querer usar o [Sinon](http://sinonjs.org/) para criar spies e esboços. + +Como alternativa, você pode usar o `expect` que agora é uma parte do Jest e expõe [exatamente a mesma API](http://facebook.github.io/jest/docs/en/expect.html#content) na documentação do Jest. + +Estaremos usando o `expect`aqui e o tornaremos disponível globlamente para que não tenhamos que importá-lo em cada teste: + +``` bash +npm install --save-dev expect +``` + +Então, em `test/setup.js`: + +``` js +require('jsdom-global')() + +global.expect = require('expect') +``` + +### Otimizando o Babel para os testes + +Observe que estamos usando o `babel-loader` para lidar com o JavaScript. Você já deve ter o Babel configurado se existir um arquivo `.babelrc` no seu projeto. O `babel-loader` usará automaticamente o mesmo arquivo para realizar a configuração. + +Uma coisa deve ser observada se você está usando o Node 6+, a partir dessa versão ele suporta a maioria dos recursos do ES2015, então você pode configurar separadamente a opção Babel [env option](https://babeljs.io/docs/usage/babelrc/#env-option) que transpila somente os recursos que ainda não são suportados na versão do Node instalada, por exemplo o stage-2 ou o suporte de síntaxe de fluxo, entre outros. + +### Adicionando um teste + +Crie um arquivo no diretório `src` chamado `Contador.vue`: + +``` html + + + +``` + +Agora crie um arquivo de teste chamado `test/Contador.spec.js` com o código a seguir: + +```js +import { shallow } from 'vue-test-utils' +import Contador from '../src/Contador.vue' + +describe('Contador.vue', () => { + it('incrementa o contador quando o botão é clicado', () => { + const wrapper = shallow(Contador) + wrapper.find('button').trigger('click') + expect(wrapper.find('div').text()).toMatch('1') + }) +}) +``` + +Agora nós podemos executar o teste usando: + +``` +npm run unit +``` + +Woohoo :o, nossos testes estão rodando! + +### Recursos + +- [Projeto de exemplo com essa configuração](https://github.com/vuejs/vue-test-utils-mocha-webpack-example) +- [Mocha](https://mochajs.org/) +- [mocha-webpack](http://zinserjan.github.io/mocha-webpack/) +- [Chai](http://chaijs.com/) +- [Sinon](http://sinonjs.org/) +- [jest/expect](http://facebook.github.io/jest/docs/en/expect.html#content) diff --git a/docs/pt-br/guides/using-with-vue-router.md b/docs/pt-br/guides/using-with-vue-router.md new file mode 100644 index 000000000..395792fda --- /dev/null +++ b/docs/pt-br/guides/using-with-vue-router.md @@ -0,0 +1,71 @@ +# Usando com o Vue Router + +## Instalando o Vue Router nos testes + +Você nunca deveria instalar o Vue Router no construtor base do Vue dos seus testes. A instalação do Vue Router adiciona `$route` e `$router` como propriedades de somente leitura no protótipo dos componentes Vue. + +Para evitar isso, nós criamos o `localVue` e instalamos o Vue Router no seu interior. + +```js +import VueRouter from 'vue-router' +const localVue = createLocalVue() + +localVue.use(VueRouter) + +shallow(Component, { + localVue +}) +``` + +## Testando componentes que usam `router-link` ou `router-view` + +Quando você instala o Vue Router, os componentes `router-link` e `router-view` são registrados. Isso significa que podemos usa-los em qualquer lugar da nossa aplicação sem precisar importá-los. + +Quando executamos os testes, nós precisamos disponibilizar os componentes do Vue Router para os componentes que estamos montando. Existem dois métodos para se fazer isso. + +### Usando esboços + +```js +shallow(Componente, { + stubs: ['router-link', 'router-view'] +}) +``` + +### Instalando o Vue Router com o localVue + +```js +import VueRouter from 'vue-router' +const localVue = createLocalVue() + +localVue.use(VueRouter) + +shallow(Componente, { + localVue +}) +``` + +## Simulando o `$route` e o `$router` + +Às vezes você quer testar que um componente faz algo com os parâmetros dos objetos `$route` e `$router`. Para fazer isso você pode passar mocks personalizados para a instância do Vue. + +```js +const $route = { + path: '/rota/qualquer' +} + +const wrapper = shallow(Component, { + mocks: { + $route + } +}) + +wrapper.vm.$router // /rota/qualquer +``` + +## Obstáculos comuns + +A instalação do Vue Router adiciona `$route` e `$router` como propriedades de somente leitura do protótipo Vue. + +Isso significa que todos os testes futuros que tentam simular o `$route` ou o `$router` irão falhar. + +Para evitar isso, nunca instale o Vue Router quando estiver executando seus testes. diff --git a/docs/pt-br/guides/using-with-vuex.md b/docs/pt-br/guides/using-with-vuex.md new file mode 100644 index 000000000..7fd355a75 --- /dev/null +++ b/docs/pt-br/guides/using-with-vuex.md @@ -0,0 +1,266 @@ +# Usando com o Vuex + +Nesse guia verems como testar o Vuex nos componentes com o `vue-test-utils`. + +## Simulando ações + +Vejamos algum código... + +Esse é o componente que queremos testar. Ele chama ações do Vuex. + +``` html + + + +``` + +Para os fins desse teste, não nos importa o que cada ação do Vuex faz ou como a store é criada. Nos precisamos apenas saber que essas ações estão sendo disparadas quando deveriam, e que elas são disparadas com os valores esperados. + +Para testar isso, precisamos passar um mock da store para o Vue quando envelopamos nosso componente. + +Em vez de passar a store para o construtor do Vue, nós passamos um [localVue](../api/options.md#localvue). Um localVue é um construtor local do Vue com escopo que permite alterações sem afetar o construtor global. + +Vamos ver como isso se apreenta no código: + +``` js +import { shallow, createLocalVue } from 'vue-test-utils' +import Vuex from 'vuex' +import Acoes from '../../../src/componentes/Acoes' + +const localVue = createLocalVue() + +localVue.use(Vuex) + +describe('Acoes.vue', () => { + let acoes + let store + + beforeEach(() => { + acoes = { + acaoDeClique: jest.fn(), + acaoInput: jest.fn() + } + + store = new Vuex.Store({ + state: {}, + acoes + }) + }) + + it('chama a ação acaoInput da store quando o valor do input é inserido e um evento do input é disparado', () => { + const wrapper = shallow(Acoes, { store, localVue }) + const input = wrapper.find('input') + input.element.value = 'input' + input.trigger('input') + expect(acoes.acaoInput).toHaveBeenCalled() + }) + + it('não liga a ação acaoInput da store quando o valor do input não é inserido e um evento do input é disparado', () => { + const wrapper = shallow(Acoes, { store, localVue }) + const input = wrapper.find('input') + input.element.value = 'not input' + input.trigger('input') + expect(acoes.acaoInput).not.toHaveBeenCalled() + }) + + it('calls store action actionClick when button is clicked', () => { + const wrapper = shallow(Actions, { store, localVue }) + wrapper.find('button').trigger('click') + expect(actions.actionClick).toHaveBeenCalled() + }) +}) +``` + +O que está acontecendo aqui? Primeiro contamos ao localVue que ele usará o Vuex no método `localVue.use`. Este é apenas um wrapper do `Vue.use`. + +Em seguida, fazemos uma store simulada chamando o método `Vuex.Store` com os valores do mock. Nós apenas passamos as ações, já que é o que nos importa no momento. + +As ações são [funções de mock do Jest](https://facebook.github.io/jest/docs/en/mock-functions.html). Essas funções simuladas nos dão alguns métodos para verificar se as determinadas ações foram ou não chamadas. + +Então, podemos fazer a asserção nos nossos testes esperando que essas ações do Vuex foram chamadas no momento esperado. + +A forma como definimos a store pode parecer um pouco estranha para você. + +Nós usamos o `beforeEach` para garantir que teremos uma store limpa antes de cada teste. O `beforeEach` é um método gancho do Mocha que é chamado antes de cada teste. Em nosso teste, reatribuímos os valores da store. Se nós não fizessemos isso, as funções simuladas deveriam ser reiniciadas automaticamente. Esse método também permite que alteremos o estado nos testes sem que afete os testes posteriores, pois ele será redefinido entre esses testes. + +A coisa mais impotante a ser notada neste teste é que **criamos um mock da store e depois passamos ela para o vue-test-utils**. + +Ótimo, agora que nós já conseguimos simular as actions, vamos ver como simular o getters. + +## Simulando os getters + +``` html + + + +``` + +Esse é um componente bastante simples, Ele mostra os resultados capturados pelos getters `cliques` e `valorDoInput`. Mais um vez, nós não importamos com o que esses getters retornam e o que fazem no seu interior, mas sim com o resultado que ele acarretará no componente a ser testado. + +Valos ver o teste do componente: + +``` js +import { shallow, createLocalVue } from 'vue-test-utils' +import Vuex from 'vuex' +import GettersComponente from '../../../src/componentes/GettersComponente' + +const localVue = createLocalVue() + +localVue.use(Vuex) + +describe('GettersComponente.vue', () => { + let getters + let store + + beforeEach(() => { + getters = { + cliques: () => 2, + valorDoInput: () => 'input' + } + + store = new Vuex.Store({ + getters + }) + }) + + it('Renderiza o valor do input na primeira tag P', () => { + const wrapper = shallow(GettersComponente, { store, localVue }) + const p = wrapper.find('p') + expect(p.text()).toBe(getters.valorDoInput()) + }) + + it('Renderiza o valor de cliques na segunda tag P', () => { + const wrapper = shallow(GettersComponente, { store, localVue }) + const p = wrapper.findAll('p').at(1) + expect(p.text()).toBe(getters.cliques().toString()) + }) +}) +``` + +Esse teste é bastante similar com nosso teste de actions. Criamos um mock da store antes de cada teste, passamos ele como uma opção do método `shallow`, e verificamos o valor retornado pelo getter, verificando se o mesmo foi renderizado no template do componente. + +Isso é ótimo, mas se quisermos garantir que nossos getters estão retornando a parte correta do nosso state? + +## Criando mocks com módulos + +Os [módulos](https://vuex.vuejs.org/en/modules.html) são úteis para separar nossa store em partes gerenciáveis. Podemos usa-los em nossos testes. + +Dê uma olhada nesse nosso componente: + +``` html + + + +``` + +Esse simples componente incluí uma ação e um getter. + +E seu teste fica assim: + +``` js +import { shallow, createLocalVue } from 'vue-test-utils' +import Vuex from 'vuex' +import ModuloComponente from '../../../src/componentes/ModuloComponente' +import modulo from '../../../src/store/modulo' + +const localVue = createLocalVue() + +localVue.use(Vuex) + +describe('ModuloComponente.vue', () => { + let actions + let state + let store + + beforeEach(() => { + state = { + modulo: { + cliques: 2 + } + } + + actions = { + cliqueEmModulo: jest.fn() + } + + store = new Vuex.Store({ + state, + actions, + getters: modulo.getters + }) + }) + + it('chama a ação cliqueEmModulo quand o botão é clicado', () => { + const wrapper = shallow(ModuloComponente, { store, localVue }) + const button = wrapper.find('button') + button.trigger('click') + expect(actions.cliqueEmModulo).toHaveBeenCalled() + }) + + it('Renderiza os cliques do state do módulo no primeiro P', () => { + const wrapper = shallow(ModuloComponente, { store, localVue }) + const p = wrapper.find('p') + expect(p.text()).toBe(state.modulo.cliques.toString()) + }) +}) +``` + +### Recursos + +- [Projeto de exemplo com essa configuração](https://github.com/eddyerburgh/vue-test-utils-vuex-example) +- [localVue](../api/options.md#localvue) +- [createLocalVue](../api/createLocalVue.md) diff --git a/package-lock.json b/package-lock.json index 13c90f371..3386e28e1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "vue-test-utils", - "version": "1.0.0-beta.4", + "version": "1.0.0-beta.5", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1710,7 +1710,6 @@ "requires": { "anymatch": "1.3.2", "async-each": "1.0.1", - "fsevents": "1.1.2", "glob-parent": "2.0.0", "inherits": "2.0.3", "is-binary-path": "1.0.1", @@ -3628,905 +3627,6 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, - "fsevents": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.2.tgz", - "integrity": "sha512-Sn44E5wQW4bTHXvQmvSHwqbuiXtduD6Rrjm2ZtUEGbyrig+nUH3t/QD4M4/ZXViY556TBpRgZkHLDx3JxPwxiw==", - "dev": true, - "optional": true, - "requires": { - "nan": "2.7.0", - "node-pre-gyp": "0.6.36" - }, - "dependencies": { - "abbrev": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "ajv": { - "version": "4.11.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "co": "4.6.0", - "json-stable-stringify": "1.0.1" - } - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "dev": true - }, - "aproba": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "delegates": "1.0.0", - "readable-stream": "2.2.9" - } - }, - "asn1": { - "version": "0.2.3", - "bundled": true, - "dev": true, - "optional": true - }, - "assert-plus": { - "version": "0.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "asynckit": { - "version": "0.4.0", - "bundled": true, - "dev": true, - "optional": true - }, - "aws-sign2": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "aws4": { - "version": "1.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "balanced-match": { - "version": "0.4.2", - "bundled": true, - "dev": true - }, - "bcrypt-pbkdf": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "tweetnacl": "0.14.5" - } - }, - "block-stream": { - "version": "0.0.9", - "bundled": true, - "dev": true, - "requires": { - "inherits": "2.0.3" - } - }, - "boom": { - "version": "2.10.1", - "bundled": true, - "dev": true, - "requires": { - "hoek": "2.16.3" - } - }, - "brace-expansion": { - "version": "1.1.7", - "bundled": true, - "dev": true, - "requires": { - "balanced-match": "0.4.2", - "concat-map": "0.0.1" - } - }, - "buffer-shims": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "caseless": { - "version": "0.12.0", - "bundled": true, - "dev": true, - "optional": true - }, - "co": { - "version": "4.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "combined-stream": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "requires": { - "delayed-stream": "1.0.0" - } - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "dev": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "cryptiles": { - "version": "2.0.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "boom": "2.10.1" - } - }, - "dashdash": { - "version": "1.14.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "debug": { - "version": "2.6.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ms": "2.0.0" - } - }, - "deep-extend": { - "version": "0.4.2", - "bundled": true, - "dev": true, - "optional": true - }, - "delayed-stream": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "ecc-jsbn": { - "version": "0.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "jsbn": "0.1.1" - } - }, - "extend": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "extsprintf": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "forever-agent": { - "version": "0.6.1", - "bundled": true, - "dev": true, - "optional": true - }, - "form-data": { - "version": "2.1.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.5", - "mime-types": "2.1.15" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "fstream": { - "version": "1.0.11", - "bundled": true, - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "inherits": "2.0.3", - "mkdirp": "0.5.1", - "rimraf": "2.6.1" - } - }, - "fstream-ignore": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "fstream": "1.0.11", - "inherits": "2.0.3", - "minimatch": "3.0.4" - } - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "aproba": "1.1.1", - "console-control-strings": "1.1.0", - "has-unicode": "2.0.1", - "object-assign": "4.1.1", - "signal-exit": "3.0.2", - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wide-align": "1.1.2" - } - }, - "getpass": { - "version": "0.1.7", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "glob": { - "version": "7.1.2", - "bundled": true, - "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "graceful-fs": { - "version": "4.1.11", - "bundled": true, - "dev": true - }, - "har-schema": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "optional": true - }, - "har-validator": { - "version": "4.2.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ajv": "4.11.8", - "har-schema": "1.0.5" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "hawk": { - "version": "3.1.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "boom": "2.10.1", - "cryptiles": "2.0.5", - "hoek": "2.16.3", - "sntp": "1.0.9" - } - }, - "hoek": { - "version": "2.16.3", - "bundled": true, - "dev": true - }, - "http-signature": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "0.2.0", - "jsprim": "1.4.0", - "sshpk": "1.13.0" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.3", - "bundled": true, - "dev": true - }, - "ini": { - "version": "1.3.4", - "bundled": true, - "dev": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "number-is-nan": "1.0.1" - } - }, - "is-typedarray": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "isstream": { - "version": "0.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "jodid25519": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "jsbn": "0.1.1" - } - }, - "jsbn": { - "version": "0.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "json-schema": { - "version": "0.2.3", - "bundled": true, - "dev": true, - "optional": true - }, - "json-stable-stringify": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "jsonify": "0.0.0" - } - }, - "json-stringify-safe": { - "version": "5.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "jsonify": { - "version": "0.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "jsprim": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.0.2", - "json-schema": "0.2.3", - "verror": "1.3.6" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "mime-db": { - "version": "1.27.0", - "bundled": true, - "dev": true - }, - "mime-types": { - "version": "2.1.15", - "bundled": true, - "dev": true, - "requires": { - "mime-db": "1.27.0" - } - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "dev": true, - "requires": { - "brace-expansion": "1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true, - "dev": true - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "node-pre-gyp": { - "version": "0.6.36", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "mkdirp": "0.5.1", - "nopt": "4.0.1", - "npmlog": "4.1.0", - "rc": "1.2.1", - "request": "2.81.0", - "rimraf": "2.6.1", - "semver": "5.3.0", - "tar": "2.2.1", - "tar-pack": "3.4.0" - } - }, - "nopt": { - "version": "4.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "abbrev": "1.1.0", - "osenv": "0.1.4" - } - }, - "npmlog": { - "version": "4.1.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "are-we-there-yet": "1.1.4", - "console-control-strings": "1.1.0", - "gauge": "2.7.4", - "set-blocking": "2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "oauth-sign": { - "version": "0.8.2", - "bundled": true, - "dev": true, - "optional": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "requires": { - "wrappy": "1.0.2" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "osenv": { - "version": "0.1.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "performance-now": { - "version": "0.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "process-nextick-args": { - "version": "1.0.7", - "bundled": true, - "dev": true - }, - "punycode": { - "version": "1.4.1", - "bundled": true, - "dev": true, - "optional": true - }, - "qs": { - "version": "6.4.0", - "bundled": true, - "dev": true, - "optional": true - }, - "rc": { - "version": "1.2.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "deep-extend": "0.4.2", - "ini": "1.3.4", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "readable-stream": { - "version": "2.2.9", - "bundled": true, - "dev": true, - "requires": { - "buffer-shims": "1.0.0", - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "string_decoder": "1.0.1", - "util-deprecate": "1.0.2" - } - }, - "request": { - "version": "2.81.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "aws-sign2": "0.6.0", - "aws4": "1.6.0", - "caseless": "0.12.0", - "combined-stream": "1.0.5", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.1.4", - "har-validator": "4.2.1", - "hawk": "3.1.3", - "http-signature": "1.1.1", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.15", - "oauth-sign": "0.8.2", - "performance-now": "0.2.0", - "qs": "6.4.0", - "safe-buffer": "5.0.1", - "stringstream": "0.0.5", - "tough-cookie": "2.3.2", - "tunnel-agent": "0.6.0", - "uuid": "3.0.1" - } - }, - "rimraf": { - "version": "2.6.1", - "bundled": true, - "dev": true, - "requires": { - "glob": "7.1.2" - } - }, - "safe-buffer": { - "version": "5.0.1", - "bundled": true, - "dev": true - }, - "semver": { - "version": "5.3.0", - "bundled": true, - "dev": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "sntp": { - "version": "1.0.9", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "hoek": "2.16.3" - } - }, - "sshpk": { - "version": "1.13.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "asn1": "0.2.3", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.1", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.1", - "getpass": "0.1.7", - "jodid25519": "1.0.2", - "jsbn": "0.1.1", - "tweetnacl": "0.14.5" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - } - }, - "string_decoder": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "requires": { - "safe-buffer": "5.0.1" - } - }, - "stringstream": { - "version": "0.0.5", - "bundled": true, - "dev": true, - "optional": true - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "requires": { - "ansi-regex": "2.1.1" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "tar": { - "version": "2.2.1", - "bundled": true, - "dev": true, - "requires": { - "block-stream": "0.0.9", - "fstream": "1.0.11", - "inherits": "2.0.3" - } - }, - "tar-pack": { - "version": "3.4.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "debug": "2.6.8", - "fstream": "1.0.11", - "fstream-ignore": "1.0.5", - "once": "1.4.0", - "readable-stream": "2.2.9", - "rimraf": "2.6.1", - "tar": "2.2.1", - "uid-number": "0.0.6" - } - }, - "tough-cookie": { - "version": "2.3.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "punycode": "1.4.1" - } - }, - "tunnel-agent": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "bundled": true, - "dev": true, - "optional": true - }, - "uid-number": { - "version": "0.0.6", - "bundled": true, - "dev": true, - "optional": true - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "uuid": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "verror": { - "version": "1.3.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "extsprintf": "1.0.2" - } - }, - "wide-align": { - "version": "1.1.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "string-width": "1.0.2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "dev": true - } - } - }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", diff --git a/package.json b/package.json index cbce42e2b..2cb48fea9 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "build": "node build/build.js", "docs": "cd docs && gitbook install && gitbook serve", "docs:deploy": "build/update-docs.sh", + "docs:serve": "cd docs && gitbook serve", "flow": "flow check", "lint": "eslint --ext js,vue src test flow build --ignore-path .gitignore", "lint:docs": "eslint --ext js,vue,md docs --ignore-path .gitignore", From ac4f82f6d375af8e17c36d8cda97f55d62ce4ef2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8B=BE=E4=B8=89=E8=82=A1=E5=9B=9B?= Date: Mon, 20 Nov 2017 16:16:24 +0800 Subject: [PATCH 0082/1136] docs: fix typos and formattings (#190) * [en] typo * typo * typo * [en] typo * [en] updated contents * typo * [en] typo + format * [en] typo --- docs/en/api/README.md | 15 +++++++- docs/en/api/components/TransitionGroupStub.md | 5 ++- docs/en/api/createLocalVue.md | 4 +- docs/en/api/mount.md | 5 ++- docs/en/api/options.md | 24 ++++++------ docs/en/api/selectors.md | 26 ++++++------- docs/en/api/shallow.md | 2 +- docs/en/api/wrapper-array/README.md | 4 +- docs/en/api/wrapper-array/destroy.md | 1 - docs/en/api/wrapper-array/setComputed.md | 2 +- docs/en/api/wrapper/README.md | 10 ++--- docs/en/api/wrapper/contains.md | 2 +- docs/en/api/wrapper/hasClass.md | 2 +- docs/en/api/wrapper/hasStyle.md | 2 +- docs/en/api/wrapper/setComputed.md | 2 +- docs/en/api/wrapper/trigger.md | 4 +- docs/en/guides/README.md | 1 + docs/en/guides/choosing-a-test-runner.md | 2 +- docs/en/guides/common-tips.md | 16 ++++---- docs/en/guides/dom-events.md | 6 +-- docs/en/guides/getting-started.md | 10 ++--- docs/en/guides/testing-SFCs-with-jest.md | 11 +++--- .../guides/testing-SFCs-with-mocha-webpack.md | 6 +-- docs/en/guides/using-with-vuex.md | 38 ++++++++++--------- 24 files changed, 108 insertions(+), 92 deletions(-) diff --git a/docs/en/api/README.md b/docs/en/api/README.md index 231254061..c48a26180 100644 --- a/docs/en/api/README.md +++ b/docs/en/api/README.md @@ -17,6 +17,7 @@ * [emitted](./wrapper/emitted.md) * [emittedByOrder](./wrapper/emittedByOrder.md) * [find](./wrapper/find.md) + * [findAll](./wrapper/findAll.md) * [hasAttribute](./wrapper/hasAttribute.md) * [hasClass](./wrapper/hasClass.md) * [hasProp](./wrapper/hasProp.md) @@ -26,11 +27,14 @@ * [isEmpty](./wrapper/isEmpty.md) * [isVueInstance](./wrapper/isVueInstance.md) * [name](./wrapper/name.md) - * [update](./wrapper/update.md) + * [setComputed](./wrapper/setComputed.md) * [setData](./wrapper/setData.md) + * [setMethods](./wrapper/setMethods.md) * [setProps](./wrapper/setProps.md) * [text](./wrapper/text.md) * [trigger](./wrapper/trigger.md) + * [update](./wrapper/update.md) + * [destroy](./wrapper/destroy.md) * [WrapperArray](./wrapper-array/README.md) * [at](./wrapper-array/at.md) * [contains](./wrapper-array/contains.md) @@ -41,9 +45,16 @@ * [is](./wrapper-array/is.md) * [isEmpty](./wrapper-array/isEmpty.md) * [isVueInstance](./wrapper-array/isVueInstance.md) - * [update](./wrapper-array/update.md) + * [setComputed](./wrapper-array/setComputed.md) * [setData](./wrapper-array/setData.md) + * [setMethods](./wrapper-array/setMethods.md) * [setProps](./wrapper-array/setProps.md) * [trigger](./wrapper-array/trigger.md) + * [update](./wrapper-array/update.md) + * [destroy](./wrapper-array/destroy.md) +* [components](./components/README.md) + * [TransitionStub](./components/TransitionStub.md) + * [TransitionGroupStub](./components/TransitionGroupStub.md) * [createLocalVue](./createLocalVue.md) * [Selectors](./selectors.md) +* [config](./config.md) diff --git a/docs/en/api/components/TransitionGroupStub.md b/docs/en/api/components/TransitionGroupStub.md index f7061f5c3..ef66d9f72 100644 --- a/docs/en/api/components/TransitionGroupStub.md +++ b/docs/en/api/components/TransitionGroupStub.md @@ -2,15 +2,16 @@ A component to stub the `transition-group` wrapper component. Instead of performing transitions asynchronously, it returns the child components synchronously. -This is set to stub all `transition-group` components by default in the vue-test-utils config. To use the built-in `transition-group` wrapper component set `config.stubs[transition-group]` to false: +This is set to stub all `transition-group` components by default in the `vue-test-utils` config. To use the built-in `transition-group` wrapper component set `config.stubs['transition-group']` to false: ```js import VueTestUtils from 'vue-test-utils' -VueTestUtils.config.stubs.transition = false +VueTestUtils.config.stubs['transition-group'] = false ``` To reset it to stub transition components: + ```js import VueTestUtils, { TransitionGroupStub } from 'vue-test-utils' diff --git a/docs/en/api/createLocalVue.md b/docs/en/api/createLocalVue.md index a3cbfcee3..b6d07aae9 100644 --- a/docs/en/api/createLocalVue.md +++ b/docs/en/api/createLocalVue.md @@ -1,4 +1,4 @@ -# createLocalVue() +# `createLocalVue()` - **Returns:** - `{Component}` @@ -7,7 +7,7 @@ `createLocalVue` returns a Vue class for you to add components, mixins and install plugins without polluting the global Vue class. -Use it with `options.localVue` +Use it with `options.localVue`: ```js import { createLocalVue, shallow } from 'vue-test-utils' diff --git a/docs/en/api/mount.md b/docs/en/api/mount.md index aaddd3e2f..84b12362a 100644 --- a/docs/en/api/mount.md +++ b/docs/en/api/mount.md @@ -1,4 +1,4 @@ -# mount(component {, options}]) +# `mount(component {, options}])` - **Arguments:** @@ -31,6 +31,7 @@ describe('Foo', () => { }) }) ``` + **With Vue options:** ```js @@ -80,7 +81,7 @@ describe('Foo', () => { const wrapper = mount(Foo, { slots: { default: [Bar, FooBar], - fooBar: FooBar, // Will match , + fooBar: FooBar, // Will match ``. foo: '
' } }) diff --git a/docs/en/api/options.md b/docs/en/api/options.md index 862992dae..cb2d6f6ed 100644 --- a/docs/en/api/options.md +++ b/docs/en/api/options.md @@ -6,15 +6,15 @@ Vue options are passed to the component when a new instance is created. , e.g. ` ## `vue-test-utils` Specific Mounting Options -- [context](#context) -- [slots](#slots) -- [stubs](#stubs) -- [mocks](#mocks) -- [localVue](#localvue) -- [attachToDocument](#attachtodocument) -- [attrs](#attrs) -- [listeners](#listeners) -- [clone](#clone) +- [`context`](#context) +- [`slots`](#slots) +- [`stubs`](#stubs) +- [`mocks`](#mocks) +- [`localVue`](#localvue) +- [`attachToDocument`](#attachtodocument) +- [`attrs`](#attrs) +- [`listeners`](#listeners) +- [`clone`](#clone) ### `context` @@ -50,7 +50,7 @@ import Bar from './Bar.vue' const wrapper = shallow(Component, { slots: { default: [Foo, Bar], - fooBar: Foo, // Will match , + fooBar: Foo, // Will match ``. foo: '
' } }) @@ -106,7 +106,7 @@ expect(wrapper.vm.$route.path).toBe($route.path) - type: `Vue` -A local copy of Vue created by [createLocalVue](./createLocalVue.md) to use when mounting the component. Installing plugins on this copy of Vue prevents polluting the original `Vue` copy. +A local copy of Vue created by [`createLocalVue`](./createLocalVue.md) to use when mounting the component. Installing plugins on this copy of `Vue` prevents polluting the original `Vue` copy. Example: @@ -162,4 +162,4 @@ Clones component before mounting if `true`, which avoids mutating the original c `options.mocks` (`Object`): Add globals to Vue instance. -`options.localVue` (`Object`): vue class to use in `mount`. See [createLocalVue](createLocalVue.md) +`options.localVue` (`Object`): `Vue` class to use in `mount`. See [`createLocalVue`](createLocalVue.md). diff --git a/docs/en/api/selectors.md b/docs/en/api/selectors.md index 5ab5c426c..a47a56e38 100644 --- a/docs/en/api/selectors.md +++ b/docs/en/api/selectors.md @@ -4,31 +4,31 @@ A lot of methods take a selector as an argument. A selector can either be a CSS ## CSS Selectors -mount handles any valid CSS selector: +Mount handles any valid CSS selector: -- tag selectors (div, foo, bar) -- class selectors (.foo, .bar) -- attribute selectors ([foo], [foo="bar"]) -- id selectors (#foo, #bar) -- pseudo selectors (div:first-of-type) +- tag selectors (`div`, `foo`, `bar`) +- class selectors (`.foo`, `.bar`) +- attribute selectors (`[foo]`, `[foo="bar"]`) +- id selectors (`#foo`, `#bar`) +- pseudo selectors (`div:first-of-type`) You can also use combinators: -- direct descendant combinator (div > #bar > .foo) -- general descendant combinator (div #bar .foo) -- adjacent sibling selector (div + .foo) -- general sibling selector (div ~ .foo) +- direct descendant combinator (`div > #bar > .foo`) +- general descendant combinator (`div #bar .foo`) +- adjacent sibling selector (`div + .foo`) +- general sibling selector (`div ~ .foo`) ## Vue Components Vue components are also valid selectors. -vue-test-utils uses the `name` property to search the instance tree for matching Vue components. +`vue-test-utils` uses the `name` property to search the instance tree for matching Vue components. ```js // Foo.vue -export default{ +export default { name: 'FooComponent' } ``` @@ -46,7 +46,7 @@ expect(wrapper.is(Foo)).toBe(true) ### Ref -Using a find option object, vue-test-utils allows for selecting elements by $ref on wrapper components. +Using a find option object, `vue-test-utils` allows for selecting elements by `$ref` on wrapper components. ```js const buttonWrapper = wrapper.find({ ref: 'myButton' }); diff --git a/docs/en/api/shallow.md b/docs/en/api/shallow.md index fa16e9d24..bda43e09a 100644 --- a/docs/en/api/shallow.md +++ b/docs/en/api/shallow.md @@ -1,4 +1,4 @@ -# shallow(component {, options}]) +# `shallow(component {, options}])` - **Arguments:** diff --git a/docs/en/api/wrapper-array/README.md b/docs/en/api/wrapper-array/README.md index 2a6d40ccc..e23be091e 100644 --- a/docs/en/api/wrapper-array/README.md +++ b/docs/en/api/wrapper-array/README.md @@ -1,6 +1,6 @@ # WrapperArray -A `WrapperArray` is an object that contains an array of [Wrappers](../wrapper/README.md), and methods to test the `Wrappers`. +A `WrapperArray` is an object that contains an array of [`Wrappers`](../wrapper/README.md), and methods to test the `Wrappers`. - **Properties:** @@ -8,4 +8,4 @@ A `WrapperArray` is an object that contains an array of [Wrappers](../wrapper/RE - **Methods:** -There is a detailed list of methods in the WrapperArray section of the docs. +There is a detailed list of methods in the `WrapperArray` section of the docs. diff --git a/docs/en/api/wrapper-array/destroy.md b/docs/en/api/wrapper-array/destroy.md index 875936ecb..13eb16ebd 100644 --- a/docs/en/api/wrapper-array/destroy.md +++ b/docs/en/api/wrapper-array/destroy.md @@ -14,5 +14,4 @@ const divArray = wrapper.findAll('div') expect(divArray.contains('p')).toBe(true) divArray.destroy() expect(divArray.contains('p')).toBe(false) - ``` diff --git a/docs/en/api/wrapper-array/setComputed.md b/docs/en/api/wrapper-array/setComputed.md index 7531132cf..35d2dcce4 100644 --- a/docs/en/api/wrapper-array/setComputed.md +++ b/docs/en/api/wrapper-array/setComputed.md @@ -4,7 +4,7 @@ Sets `Wrapper` `vm` computed and forces update on each `Wrapper` in `WrapperArray`. **Note every `Wrapper` must contain a Vue instance.** -**Note every Vue instance must already have the computed properties passed to setComputed.** +**Note every Vue instance must already have the computed properties passed to `setComputed`.** - **Arguments:** - `{Object} computed properties` diff --git a/docs/en/api/wrapper/README.md b/docs/en/api/wrapper/README.md index 6469b8745..171ba0aa9 100644 --- a/docs/en/api/wrapper/README.md +++ b/docs/en/api/wrapper/README.md @@ -1,15 +1,15 @@ -# Wrapper +# `Wrapper` -vue-test-utils is a wrapper based API. +`vue-test-utils` is a wrapper based API. A `Wrapper` is an object that contains a mounted component or vnode and methods to test the component or vnode. - **Properties:** -`vm` `Component`: this is the vue instance. You can access all the [instance methods and properties of a vm](https://vuejs.org/v2/api/#Instance-Properties) with `wrapper.vm`. This only exists on Vue component wrappers +`vm` `Component`: this is the `Vue` instance. You can access all the [instance methods and properties of a vm](https://vuejs.org/v2/api/#Instance-Properties) with `wrapper.vm`. This only exists on Vue component wrappers `element` `HTMLElement`: the root DOM node of the wrapper -`options` `Object`: Object containing vue-test-utils options passed to `mount` or `shallow` -`options.attachedToDom` `Boolean`: True if attachToDom was passed to `mount` or `shallow` +`options` `Object`: Object containing `vue-test-utils` options passed to `mount` or `shallow` +`options.attachedToDom` `Boolean`: True if `attachToDom` was passed to `mount` or `shallow` - **Methods:** diff --git a/docs/en/api/wrapper/contains.md b/docs/en/api/wrapper/contains.md index a91b0353b..98a13b920 100644 --- a/docs/en/api/wrapper/contains.md +++ b/docs/en/api/wrapper/contains.md @@ -1,4 +1,4 @@ -# contains(selector) +# `contains(selector)` Assert `Wrapper` contains an element or component matching [selector](../selectors.md). diff --git a/docs/en/api/wrapper/hasClass.md b/docs/en/api/wrapper/hasClass.md index c70b21ef9..8f578bd24 100644 --- a/docs/en/api/wrapper/hasClass.md +++ b/docs/en/api/wrapper/hasClass.md @@ -2,7 +2,7 @@ Assert `Wrapper` DOM node has class contains `className`. -Returns `true` if `Wrapper` DOM node contains class. +Returns `true` if `Wrapper` DOM node contains the class. - **Arguments:** - `{string} className` diff --git a/docs/en/api/wrapper/hasStyle.md b/docs/en/api/wrapper/hasStyle.md index 0281d8401..a9122f983 100644 --- a/docs/en/api/wrapper/hasStyle.md +++ b/docs/en/api/wrapper/hasStyle.md @@ -1,6 +1,6 @@ # hasStyle(style, value) -Assert `Wrapper` DOM node has style matching value +Assert `Wrapper` DOM node has style matching value. Returns `true` if `Wrapper` DOM node has `style` matching `value`. diff --git a/docs/en/api/wrapper/setComputed.md b/docs/en/api/wrapper/setComputed.md index b5db5549e..132a2d509 100644 --- a/docs/en/api/wrapper/setComputed.md +++ b/docs/en/api/wrapper/setComputed.md @@ -3,7 +3,7 @@ Sets `Wrapper` `vm` computed property and forces update. **Note the Wrapper must contain a Vue instance.** -**Note every Vue instance must already have the computed properties passed to setComputed.** +**Note the Vue instance must already have the computed properties passed to `setComputed`.** - **Arguments:** diff --git a/docs/en/api/wrapper/trigger.md b/docs/en/api/wrapper/trigger.md index 537cda9e6..c830963bd 100644 --- a/docs/en/api/wrapper/trigger.md +++ b/docs/en/api/wrapper/trigger.md @@ -2,9 +2,9 @@ Triggers an event on the `Wrapper` DOM node. -Trigger takes an optional `options` object. The properties in the `options` object are added to the Event. +`trigger` takes an optional `options` object. The properties in the `options` object are added to the Event. -You can run preventDefault on the event by passing `preventDefault: true` in `options`. +You can run `preventDefault` on the event by passing `preventDefault: true` in `options`. - **Arguments:** - `{string} eventName` diff --git a/docs/en/guides/README.md b/docs/en/guides/README.md index b347c3734..8b6b6a4f7 100644 --- a/docs/en/guides/README.md +++ b/docs/en/guides/README.md @@ -2,6 +2,7 @@ * [Getting Started](./getting-started.md) * [Common Tips](./common-tips.md) +* [Mouse, Key and other DOM Events](./dom-events.md) * [Choosing a test runner](./choosing-a-test-runner.md) * [Using with Jest](./using-with-jest.md) * [Testing SFCs with Jest](./testing-SFCs-with-jest.md) diff --git a/docs/en/guides/choosing-a-test-runner.md b/docs/en/guides/choosing-a-test-runner.md index 8d69377b1..b803c8196 100644 --- a/docs/en/guides/choosing-a-test-runner.md +++ b/docs/en/guides/choosing-a-test-runner.md @@ -12,7 +12,7 @@ There are a few things to consider when choosing a test runner though: feature s ## Browser Environment -`vue-test-utils` relies on a browser environment. Technically you can run it in a real browser, but it's not recommended due to the complexity of launching real browsers on different platforms. Instead, we recommend running the tests in Node.js with a virtual browser environment using [JSDOM](https://github.com/tmpvar/jsdom). +`vue-test-utils` relies on a browser environment. Technically you can run it in a real browser, but it's not recommended due to the complexity of launching real browsers on different platforms. Instead, we recommend running the tests in Node with a virtual browser environment using [JSDOM](https://github.com/tmpvar/jsdom). The Jest test runner sets up JSDOM automatically. For other test runners, you can manually set up JSDOM for the tests using [jsdom-global](https://github.com/rstacruz/jsdom-global) in the entry for your tests: diff --git a/docs/en/guides/common-tips.md b/docs/en/guides/common-tips.md index abd7500a7..0ff8b4c15 100644 --- a/docs/en/guides/common-tips.md +++ b/docs/en/guides/common-tips.md @@ -6,7 +6,7 @@ For UI components, we don't recommend aiming for complete line-based coverage, b Instead, we recommend writing tests that assert your component's public interface, and treat its internals as a black box. A single test case would assert that some input (user interaction or change of props) provided to the component results in the expected output (render result or emitted custom events). -For example, for the `Counter` component which increments a display counter by 1 each time a button is clicked, its test case would simulate the click and assert that the rendered output has increased by 1. The test doesn't care about how the Counter increments the value, it only cares about the input and the output. +For example, for the `Counter` component which increments a display counter by 1 each time a button is clicked, its test case would simulate the click and assert that the rendered output has increased by 1. The test doesn't care about how the `Counter` increments the value, it only cares about the input and the output. The benefit of this approach is that as long as your component's public interface remains the same, your tests will pass no matter how the component's internal implementation changes over time. @@ -36,7 +36,7 @@ wrapper.vm.$emit('foo') wrapper.vm.$emit('foo', 123) /* -wrapper.emitted() returns the following object: +`wrapper.emitted()` returns the following object: { foo: [[], [123]] } @@ -58,7 +58,7 @@ expect(wrapper.emitted().foo.length).toBe(2) expect(wrapper.emitted().foo[1]).toEqual([123]) ``` -You can also get an Array of the events in their emit order by calling [wrapper.emittedByOrder()](../api/wrapper/emittedByOrder.md). +You can also get an Array of the events in their emit order by calling [`wrapper.emittedByOrder()`](../api/wrapper/emittedByOrder.md). ## Manipulating Component State @@ -92,18 +92,18 @@ You can also update the props of an already-mounted component with the `wrapper. Some of the components may rely on features injected by a global plugin or mixin, for example `vuex` and `vue-router`. -If you are writing tests for components in a specific app, you can setup the same global plugins and mixins once in the entry of your tests. But in some cases, for example testing a generic component suite that may get shared across different apps, it's better to test your components in a more isolated setup, without polluting the global `Vue` constructor. We can use the [createLocalVue](../api/createLocalVue.md) method to achieve that: +If you are writing tests for components in a specific app, you can setup the same global plugins and mixins once in the entry of your tests. But in some cases, for example testing a generic component suite that may get shared across different apps, it's better to test your components in a more isolated setup, without polluting the global `Vue` constructor. We can use the [`createLocalVue`](../api/createLocalVue.md) method to achieve that: ``` js import createLocalVue from 'vue-test-utils' -// create an extended Vue constructor +// create an extended `Vue` constructor const localVue = createLocalVue() // install plugins as normal localVue.use(MyPlugin) -// pass the localVue to the mount options +// pass the `localVue` to the mount options mount(Component, { localVue }) @@ -111,7 +111,7 @@ mount(Component, { ## Mocking Injections -Another strategy for injected properties is simply mocking them. You can do that with the `mocks` option: +Another strategy for injected props is simply mocking them. You can do that with the `mocks` option: ```js import { mount } from 'vue-test-utils' @@ -125,7 +125,7 @@ const $route = { mount(Component, { mocks: { - $route // adds the mocked $route object to the Vue instance before mounting component + $route // adds the mocked `$route` object to the Vue instance before mounting component } }) ``` diff --git a/docs/en/guides/dom-events.md b/docs/en/guides/dom-events.md index 091096874..36b2c7957 100644 --- a/docs/en/guides/dom-events.md +++ b/docs/en/guides/dom-events.md @@ -22,7 +22,7 @@ wrapper.find('button').trigger('click') The trigger method takes an optional `options` object. The properties in the `options` object are added to the Event. -You can run preventDefault on the event by passing `preventDefault: true` in `options`. +You can run `preventDefault` on the event by passing `preventDefault: true` in `options`. ```js const wrapper = mount(MyButton) @@ -189,8 +189,8 @@ describe('Key event tests', () => { A key name after the dot `keydown.up` is translated to a `keyCode`. This is supported for the following names: -* enter, tab, delete, esc, space, up, down, left, right +* `enter`, `tab`, `delete`, `esc`, `space`, `up`, `down`, `left`, `right` ## Important -vue-test-utils triggers event synchronously. Consequently, `Vue.nextTick` is not required. +`vue-test-utils` triggers event synchronously. Consequently, `Vue.nextTick` is not required. diff --git a/docs/en/guides/getting-started.md b/docs/en/guides/getting-started.md index 5c023d6dc..ad024164f 100644 --- a/docs/en/guides/getting-started.md +++ b/docs/en/guides/getting-started.md @@ -48,7 +48,7 @@ You can create wrappers using the `mount` method. Let's create a file called `te ```js // test.js -// Import the mount() method from the test utils +// Import the `mount()` method from the test utils // and the component you want to test import { mount } from 'vue-test-utils' import Counter from './counter' @@ -56,11 +56,11 @@ import Counter from './counter' // Now mount the component and you have the wrapper const wrapper = mount(Counter) -// You can access the actual Vue instance via wrapper.vm +// You can access the actual Vue instance via `wrapper.vm` const vm = wrapper.vm // To inspect the wrapper deeper just log it to the console -// and your adventure with the vue-test-utils begins +// and your adventure with the `vue-test-utils` begins console.log(wrapper) ``` @@ -112,5 +112,5 @@ To simplify usage, `vue-test-utils` applies all updates synchronously so you don ## What's Next -- Integrate `vue-test-utils` into your project by [choosing a test runner](./choosing-a-test-runner.md) -- Learn more about [common techniques when writing tests](./common-tips.md) +- Integrate `vue-test-utils` into your project by [choosing a test runner](./choosing-a-test-runner.md). +- Learn more about [common techniques when writing tests](./common-tips.md). diff --git a/docs/en/guides/testing-SFCs-with-jest.md b/docs/en/guides/testing-SFCs-with-jest.md index 100544d2d..838a6ae02 100644 --- a/docs/en/guides/testing-SFCs-with-jest.md +++ b/docs/en/guides/testing-SFCs-with-jest.md @@ -42,12 +42,12 @@ Next, create a `jest` block in `package.json`: "moduleFileExtensions": [ "js", "json", - // tell Jest to handle *.vue files + // tell Jest to handle `*.vue` files "vue" ], "transform": { - // process *.vue files with vue-jest - ".*\\.(vue)$": "/node_modules/vue-jest" + // process `*.vue` files with `jest-vue` + ".*\\.(vue)$": "/node_modules/jest-vue" }, "mapCoverage": true } @@ -75,6 +75,7 @@ If you use a resolve alias in the webpack config, e.g. aliasing `@` to `/src`, y ## Configuring Babel for Jest + Although latest versions of Node already supports most ES2015 features, you may still want to use ES modules syntax and stage-x features in your tests. For that we need to install `babel-jest`: ``` bash @@ -90,7 +91,7 @@ Next, we need to tell Jest to process JavaScript test files with `babel-jest` by // ... "transform": { // ... - // process js with babel-jest + // process js with `babel-jest` "^.+\\.js$": "/node_modules/babel-jest" }, // ... @@ -150,7 +151,7 @@ Then configure it in `package.json`: ### Placing Test Files -By default, Jest will recursively pick up all files that have a `.spec.js` or `.test.js` extension in the entire project. If this does not fit your needs, it's possible [to change the testRegex](https://facebook.github.io/jest/docs/en/configuration.html#testregex-string) in the config section in the `package.json` file. +By default, Jest will recursively pick up all files that have a `.spec.js` or `.test.js` extension in the entire project. If this does not fit your needs, it's possible [to change the `testRegex`](https://facebook.github.io/jest/docs/en/configuration.html#testregex-string) in the config section in the `package.json` file. Jest recommends creating a `__tests__` directory right next to the code being tested, but feel free to structure your tests as you see fit. Just beware that Jest would create a `__snapshots__` directory next to test files that performs snapshot testing. diff --git a/docs/en/guides/testing-SFCs-with-mocha-webpack.md b/docs/en/guides/testing-SFCs-with-mocha-webpack.md index f7013a015..fc79e6453 100644 --- a/docs/en/guides/testing-SFCs-with-mocha-webpack.md +++ b/docs/en/guides/testing-SFCs-with-mocha-webpack.md @@ -78,7 +78,7 @@ module.exports = { ### Setting Up Browser Environment -`vue-test-utils` requires a browser environment to run. We can simulate it in Node.js using `jsdom-global`: +`vue-test-utils` requires a browser environment to run. We can simulate it in Node using `jsdom-global`: ```bash npm install --save-dev jsdom jsdom-global @@ -90,7 +90,7 @@ Then in `test/setup.js`: require('jsdom-global')() ``` -This adds a browser environment to node, so that `vue-test-utils` can run correctly. +This adds a browser environment to Node, so that `vue-test-utils` can run correctly. ### Picking an Assertion Library @@ -116,7 +116,7 @@ global.expect = require('expect') Notice that we are using `babel-loader` to handle JavaScript. You should already have Babel configured if you are using it in your app via a `.babelrc` file. Here `babel-loader` will automatically use the same config file. -One thing to note is that if you are using Node 6+, which already supports the majority of ES2015 features, you can configure a separate Babel [env option](https://babeljs.io/docs/usage/babelrc/#env-option) that only transpiles features that are not already supported in the Node version you are using (e.g. `stage-2` or flow syntax support, etc.) +One thing to note is that if you are using Node 6+, which already supports the majority of ES2015 features, you can configure a separate Babel [env option](https://babeljs.io/docs/usage/babelrc/#env-option) that only transpiles features that are not already supported in the Node version you are using (e.g. `stage-2` or flow syntax support, etc.). ### Adding a test diff --git a/docs/en/guides/using-with-vuex.md b/docs/en/guides/using-with-vuex.md index 85f40d7fd..9ed2841b7 100644 --- a/docs/en/guides/using-with-vuex.md +++ b/docs/en/guides/using-with-vuex.md @@ -10,10 +10,10 @@ This is the component we want to test. It calls Vuex actions. ``` html ``` + Simple component that includes one action and one getter. And the test: @@ -242,14 +244,14 @@ describe('Modules.vue', () => { }) }) - it('calls store action moduleActionClick when button is clicked', () => { + it('calls store action "moduleActionClick" when button is clicked', () => { const wrapper = shallow(Modules, { store, localVue }) const button = wrapper.find('button') button.trigger('click') expect(actions.moduleActionClick).toHaveBeenCalled() }) - it('Renders state.inputValue in first p tag', () => { + it('Renders "state.inputValue" in first p tag', () => { const wrapper = shallow(Modules, { store, localVue }) const p = wrapper.find('p') expect(p.text()).toBe(state.module.clicks.toString()) @@ -260,5 +262,5 @@ describe('Modules.vue', () => { ### Resources - [Example project for this guide](https://github.com/eddyerburgh/vue-test-utils-vuex-example) -- [localVue](../api/options.md#localvue) -- [createLocalVue](../api/createLocalVue.md) +- [`localVue`](../api/options.md#localvue) +- [`createLocalVue`](../api/createLocalVue.md) From 12de8541005ca42c8409359ab3cd5abdb656edd6 Mon Sep 17 00:00:00 2001 From: Tim Hutchinson Date: Mon, 20 Nov 2017 03:18:59 -0500 Subject: [PATCH 0083/1136] feat: add classes, attributes, and props methods (#141) * Add classes wrapper method * Add support for css modules * Add attributes wrapper method * Added props wrapper method --- src/wrappers/error-wrapper.js | 12 +++++ src/wrappers/wrapper-array.js | 18 +++++++ src/wrappers/wrapper.js | 49 +++++++++++++++++++ .../components/component-with-css-modules.vue | 7 ++- .../specs/mount/Wrapper/attributes.spec.js | 25 ++++++++++ test/unit/specs/mount/Wrapper/classes.spec.js | 24 +++++++++ test/unit/specs/mount/Wrapper/props.spec.js | 28 +++++++++++ .../mount/WrapperArray/attributes.spec.js | 18 +++++++ .../specs/mount/WrapperArray/classes.spec.js | 18 +++++++ .../specs/mount/WrapperArray/props.spec.js | 18 +++++++ .../unit/specs/wrappers/error-wrapper.spec.js | 21 ++++++++ .../unit/specs/wrappers/wrapper-array.spec.js | 36 ++++++++++++++ 12 files changed, 273 insertions(+), 1 deletion(-) create mode 100644 test/unit/specs/mount/Wrapper/attributes.spec.js create mode 100644 test/unit/specs/mount/Wrapper/classes.spec.js create mode 100644 test/unit/specs/mount/Wrapper/props.spec.js create mode 100644 test/unit/specs/mount/WrapperArray/attributes.spec.js create mode 100644 test/unit/specs/mount/WrapperArray/classes.spec.js create mode 100644 test/unit/specs/mount/WrapperArray/props.spec.js diff --git a/src/wrappers/error-wrapper.js b/src/wrappers/error-wrapper.js index 16fe1e8bc..a75cc1f14 100644 --- a/src/wrappers/error-wrapper.js +++ b/src/wrappers/error-wrapper.js @@ -12,6 +12,14 @@ export default class ErrorWrapper implements BaseWrapper { throwError(`find did not return ${this.selector}, cannot call at() on empty Wrapper`) } + attributes (): void { + throwError(`find did not return ${this.selector}, cannot call attributes() on empty Wrapper`) + } + + classes (): void { + throwError(`find did not return ${this.selector}, cannot call classes() on empty Wrapper`) + } + contains (): void { throwError(`find did not return ${this.selector}, cannot call contains() on empty Wrapper`) } @@ -72,6 +80,10 @@ export default class ErrorWrapper implements BaseWrapper { throwError(`find did not return ${this.selector}, cannot call name() on empty Wrapper`) } + props (): void { + throwError(`find did not return ${this.selector}, cannot call props() on empty Wrapper`) + } + text (): void { throwError(`find did not return ${this.selector}, cannot call text() on empty Wrapper`) } diff --git a/src/wrappers/wrapper-array.js b/src/wrappers/wrapper-array.js index 8cbddbd0d..03be500d1 100644 --- a/src/wrappers/wrapper-array.js +++ b/src/wrappers/wrapper-array.js @@ -20,6 +20,18 @@ export default class WrapperArray implements BaseWrapper { return this.wrappers[index] } + attributes (): void { + this.throwErrorIfWrappersIsEmpty('attributes') + + throwError('attributes must be called on a single wrapper, use at(i) to access a wrapper') + } + + classes (): void { + this.throwErrorIfWrappersIsEmpty('classes') + + throwError('classes must be called on a single wrapper, use at(i) to access a wrapper') + } + contains (selector: Selector): boolean { this.throwErrorIfWrappersIsEmpty('contains') @@ -107,6 +119,12 @@ export default class WrapperArray implements BaseWrapper { throwError('name must be called on a single wrapper, use at(i) to access a wrapper') } + props (): void { + this.throwErrorIfWrappersIsEmpty('props') + + throwError('props must be called on a single wrapper, use at(i) to access a wrapper') + } + text (): void { this.throwErrorIfWrappersIsEmpty('text') diff --git a/src/wrappers/wrapper.js b/src/wrappers/wrapper.js index a298a3fbd..b8fd62d8e 100644 --- a/src/wrappers/wrapper.js +++ b/src/wrappers/wrapper.js @@ -33,6 +33,38 @@ export default class Wrapper implements BaseWrapper { throwError('at() must be called on a WrapperArray') } + /** + * Returns an Object containing all the attribute/value pairs on the element. + */ + attributes (): { [name: string]: string } { + const attributes = [...this.element.attributes] // NameNodeMap is not iterable + const attributeMap = {} + attributes.forEach((att) => { + attributeMap[att.localName] = att.value + }) + return attributeMap + } + + /** + * Returns an Array containing all the classes on the element + */ + classes (): Array { + let classes = [...this.element.classList] + // Handle converting cssmodules identifiers back to the original class name + if (this.vm && this.vm.$style) { + const cssModuleIdentifiers = {} + let moduleIdent + Object.keys(this.vm.$style).forEach((key) => { + moduleIdent = this.vm.$style[key] + // CSS Modules may be multi-class if they extend others. Extended classes should be already present in $style. + moduleIdent = moduleIdent.split(' ')[0] + cssModuleIdentifiers[moduleIdent] = key + }) + classes = classes.map(className => cssModuleIdentifiers[className] || className) + } + return classes + } + /** * Checks if wrapper contains provided selector. */ @@ -309,6 +341,23 @@ export default class Wrapper implements BaseWrapper { return this.vnode.tag } + /** + * Returns an Object containing the prop name/value pairs on the element + */ + props (): { [name: string]: any } { + if (!this.isVueComponent) { + throwError('wrapper.props() must be called on a Vue instance') + } + // $props object does not exist in Vue 2.1.x, so use $options.propsData instead + let _props + if (this.vm && this.vm.$options && this.vm.$options.propsData) { + _props = this.vm.$options.propsData + } else { + _props = this.vm.$props + } + return _props || {} // Return an empty object if no props exist + } + /** * Sets vm data */ diff --git a/test/resources/components/component-with-css-modules.vue b/test/resources/components/component-with-css-modules.vue index 93a017026..95b6efb56 100644 --- a/test/resources/components/component-with-css-modules.vue +++ b/test/resources/components/component-with-css-modules.vue @@ -1,11 +1,16 @@ + +``` + +**Test** + +```js +import YesNoComponent from '@/components/YesNoComponent' +import { mount } from 'vue-test-utils' +import sinon from 'sinon' + +describe('Évènement click', () => { + it('Cliquer sur le bouton oui appelle notre méthode avec l\'argument "oui"', () => { + const spy = sinon.spy() + const wrapper = mount(YesNoComponent, { + propsData: { + callMe: spy + } + }) + wrapper.find('button.yes').trigger('click') + + spy.should.have.been.calledWith('oui') + }) +}) +``` + +## Exemple de test clavier + +**Composant à tester** + +Ce composant permet d'incrémenter / décrémenter la quantité en utilisant différentes touches. + +```html + + + +``` + +**Test** + +```js +import QuantityComponent from '@/components/QuantityComponent' +import { mount } from 'vue-test-utils' + +describe('Tests événement clavier', () => { + it('La quantité est zéro par défaut', () => { + const wrapper = mount(QuantityComponent) + expect(wrapper.vm.quantity).to.equal(0) + }) + + it('La flèche du haut positionne la quantité à 1', () => { + const wrapper = mount(QuantityComponent) + wrapper.trigger('keydown.up') + expect(wrapper.vm.quantity).to.equal(1) + }) + + it('La flèche du bas réduit la quantité de 1', () => { + const wrapper = mount(QuantityComponent) + wrapper.vm.quantity = 5 + wrapper.trigger('keydown.down') + expect(wrapper.vm.quantity).to.equal(4) + }) + + it('La touche Échap positionne la quantité à 0', () => { + const wrapper = mount(QuantityComponent) + wrapper.vm.quantity = 5 + wrapper.trigger('keydown.esc') + expect(wrapper.vm.quantity).to.equal(0) + }) + + it('Le caractère magique "a" positionne la quantité à 13', () => { + const wrapper = mount(QuantityComponent) + wrapper.trigger('keydown', { + which: 65 + }) + expect(wrapper.vm.quantity).to.equal(13) + }) +}) + +``` + +**Limitations** + +Un nom de touche après le point `keydown.up` est traduit vers un `keyCode`. Cela est supporté pour les noms suivant : + +* `enter`, `tab`, `delete`, `esc`, `space`, `up`, `down`, `left`, `right` + +## Important + +`vue-test-utils` déclenche les évènements de façon synchrone. Par conséquent, `vue.nextTick` n'est pas requis. diff --git a/docs/fr/guides/getting-started.md b/docs/fr/guides/getting-started.md new file mode 100644 index 000000000..c007c84f8 --- /dev/null +++ b/docs/fr/guides/getting-started.md @@ -0,0 +1,116 @@ +# Pour commencer + +## Installation + +Pour avoir un rapide avant-gout de `vue-test-utils`, clonez notre répertoire de démonstration avec l'installation de base puis installez les dépendances : + +``` bash +git clone https://github.com/vuejs/vue-test-utils-getting-started +cd vue-test-utils-getting-started +npm install +``` + +Vous allez voir que le projet possède un simple composant, `counter.js` : + +```js +// counter.js + +export default { + template: ` +
+ {{ count }} + +
+ `, + + data () { + return { + count: 0 + } + }, + + methods: { + increment () { + this.count++ + } + } +} +``` + +### Montages de composants + +`vue-test-utils` teste les composants Vue en les isolants puis en les montant, il simule les entrées nécessaires (props, injections et évènements utilisateur) et asserte les sorties (le rendu, les évènements émis). + +Les composants montés sont retournés dans un [Wrapper](./api/wrapper.md), qui expose de nombreuses méthodes pour manipuler, traverser et interroger l'instance du composant Vue en question. + +Vous pouvez créer des wrappers en utilisant la méthode `mount`. Créons un fichier nommé `test.js` : + +```js +// test.js + +// Importe la méthode `mount()` depuis test utils +// et le composant qu'on souhaite tester +import { mount } from 'vue-test-utils' +import Counter from './counter' + +// On monte le composant et nous voilà avec un wrapper +const wrapper = mount(Counter) + +// On accède à l'instance actuelle de Vue via `wrapper.vm` +const vm = wrapper.vm + +// Pour inspecter le wrapper en profondeur, utilisez console +// puis votre aventure avec vue-test-utils commence ! +console.log(wrapper) +``` + +### Tester le contenu du rendu HTML d'un composant + +Nous avons maintenant un wrapper, la première chose que l'on peut faire, c'est de vérifier que le contenu du rendu HTML du composant correspond à celui attendu. + +```js +import { mount } from 'vue-test-utils' +import Counter from './counter' + +describe('Counter', () => { + // On monte le composant et nous voilà avec un wrapper + const wrapper = mount(Counter) + + it('fait le rendu correctement', () => { + expect(wrapper.html()).toContain('0') + }) + + // c'est aussi très simple de vérifier qu'un élément existe + it('a un bouton', () => { + expect(wrapper.contains('button')).toBe(true) + }) +}) +``` + +On peut maintenant lancer les tests avec `npm test`. Vous devriez voir les tests se lancer et réussir. + +### Simulation de l'interaction utilisateur + +Notre compteur devrait s'incrémenter quand l'utilisateur clique sur le bouton. Pour simuler ce comportement, on doit tout d'abord localiser le bouton avec `wrapper.find()`, qui retourne un **wrapper pour l'élément bouton**. On peut ensuite simuler un clic en appelant `.trigger()` sur le wrapper du bouton : + +```js +it('le clic sur le bouton devrait incrémenter le compteur', () => { + expect(wrapper.vm.count).toBe(0) + const button = wrapper.find('button') + button.trigger('click') + expect(wrapper.vm.count).toBe(1) +}) +``` + +### Et quid de `nextTick` ? + +Vue groupe les mises à jour du DOM en attentes puis les appliquent de façon asynchrone pour prévenir d'éventuels multiples rendus causés par de multiples mutations de données. C'est pourquoi en pratique, on a souvent à utiliser `Vue.nextTick` pour attendre que Vue modifie le DOM actuel après avoir lancé quelques changements d'état. + +Pour simplifier cela, `vue-test-utils` applique toutes les mises à jour de façon synchrone afin que vous n'ayez pas besoin d'utiliser `Vue.nextTick` pour attendre des mises à jour du DOM dans vos tests. + +*Note : `nextTick` est toujours nécessaire quand vous souhaitez explicitement faire avancer la boucle des évènements, pour des opérations telles que des fonctions de rappel ou des résolutions de promesses.* + +## Et après ? + +- Intégrez `vue-test-utils` dans votre projet en [choisissant votre lanceur de tests](./choosing-a-test-runner.md) +- En apprendre plus sur les [techniques et astuces pour écrire des tests](./common-tips.md) diff --git a/docs/fr/guides/testing-SFCs-with-jest.md b/docs/fr/guides/testing-SFCs-with-jest.md new file mode 100644 index 000000000..02fd660d6 --- /dev/null +++ b/docs/fr/guides/testing-SFCs-with-jest.md @@ -0,0 +1,179 @@ +# Tester des composants monofichiers avec Jest + +> Un exemple de projet pour cette installation est disponible sur [GitHub](https://github.com/vuejs/vue-test-utils-jest-example). + +Jest est un lanceur de tests développé par Facebook. Il a pour but de procurer une solution complète de tests unitaires. Vous pouvez en apprendre plus sur Jest sur [sa documentation officielle](https://facebook.github.io/jest/). + +## Installer Jest + +On va supposer que vous commencez avec une installation qui a déjà webpack, vue-loader et Babel correctement configurés (ex. le template `webpack-simple` via `vue-cli`). + +La première chose à faire est d'installer Jest et `vue-test-utils` : + +```bash +$ npm install --save-dev jest vue-test-utils +``` + +Ensuite, on doit définir un script dans notre `package.json`. + +```json +// package.json +{ + "scripts": { + "test": "jest" + } +} +``` + +## Traiter les composants monofichiers dans Jest + +Pour indiquer à Jest comment traiter les fichiers `*.vue`, on va avoir besoin d'installer et de configurer le préprocesseur `vue-jest` : + +``` bash +npm install --save-dev vue-jest +``` + +Ensuite, créez un objet `jest` dans `package.json` : + +``` json +{ + // ... + "jest": { + "moduleFileExtensions": [ + "js", + "json", + // indique à Jest de gérer les fichiers `*.vue` + "vue" + ], + "transform": { + // traite les fichiers `*.vue` avec `vue-jest` + ".*\\.(vue)$": "/node_modules/vue-jest" + }, + "mapCoverage": true + } +} +``` + +> **Note :** `vue-jest` ne supporte actuellement pas toutes les fonctionnalités de `vue-loader`, par exemple le support des blocs personnalisés et du chargement de styles. De plus, quelques fonctionnalités spécifiques à webpack comme la scission de code ne sont pas supportées. Pour les utiliser, lisez le guide sur [tester des composants monofichiers avec Mocha et webpack](./testing-SFCs-with-mocha-webpack.md). + +## Gérer les alias webpack + +Si vous utilisez un alias de résolution dans la configuration de webpack, c.-à-d. faire un alias `@` pour `/src`, vous devez aussi ajouter une configuration pour Jest en utilisant l'option `moduleNameMapper` : + +``` json +{ + // ... + "jest": { + // ... + // supporte la même concordonance d'alias @ -> src dans le code source + "moduleNameMapper": { + "^@/(.*)$": "/src/$1" + } + } +} +``` + +## Configurer Babel pour Jest + + +Même si les dernières version de Node.js supportent la plupart des fonctionnalités ES2015, vous souhaitez quand même utiliser la syntaxe des modules ES ainsi que les fonctionnalités `stage-x` dans vos tests. Pour cela, on doit installer `babel-jest` : + +``` bash +npm install --save-dev babel-jest +``` + +Ensuite, on doit indiquer à Jest de gérer les fichiers de tests JavaScript avec `babel-jest` en ajoutant une entrée sous `jest.transform` dans `package.json` : + +``` json +{ + // ... + "jest": { + // ... + "transform": { + // ... + // gérer le JavaScript avec `babel-jest` + "^.+\\.js$": "/node_modules/babel-jest" + }, + // ... + } +} +``` + +> Par défaut, `babel-jest` va automatiquement s'autoconfigurer dès l'installation. Cependant, comme nous avons explicitement ajouté une transformation pour les fichiers `*.vue`, on se doit aussi d'explicitement configurer `babel-jest`. + +En supposant que vous utilisez `babel-preset-env` avec webpack, la configuration par défaut de Babel désactive la transpilation des modules ES car webpack sait déjà comment traiter ces modules. Nous devons, cependant, l'activer pour nos tests car, Jest fonctionne directement dans Node.js. + +On doit aussi indiquer à `babel-preset-env` d'utiliser la version de Node.js que nous utilisons. Cela empêchera de transpiler inutilement des fonctionnalités et lancera nos tests plus rapidement. + +Pour appliquer ces options uniquement aux tests, mettez-les dans un fichier de configuration différent sous `env.test` (cela va être automatiquement être utilisé par `babel-jest`). + +Exemple `.babelrc`: + +``` json +{ + "presets": [ + ["env", { "modules": false }] + ], + "env": { + "test": { + "presets": [ + ["env", { "targets": { "node": "current" }}] + ] + } + } +} +``` + +### Test d'instantanés + +Vous pouvez utiliser [`vue-server-renderer`](https://github.com/vuejs/vue/tree/dev/packages/vue-server-renderer) pour transformer un composant en une chaine de caractères afin de le sauvegarder dans un instantané pour [Jest tests d'instantanés](https://facebook.github.io/jest/docs/en/snapshot-testing.html). + +Le résultat du rendu de `vue-server-renderer` inclut quelques attributs spécifiques au rendu côté serveur. Il ignore les espaces, cela rend plus dur d'analyser une différence. On peut améliorer l'instantané sauvegardé avec un sérialiseur personnalisé : + +``` bash +npm install --save-dev jest-serializer-vue +``` + +Puis configurez-le dans `package.json`: + +``` json +{ + // ... + "jest": { + // ... + // sérialiseur pour les instantanés + "snapshotSerializers": [ + "/node_modules/jest-serializer-vue" + ] + } +} +``` + +### Placer les fichiers de tests + +Par défaut, Jest va récursivement récupérer tous les fichiers qui ont une extension en `.spec.js` ou `.test.js` dans le projet. Si cela ne correspond pas à vos besoins, il est possible [de changer l'expression régulière `testRegex`](https://facebook.github.io/jest/docs/en/configuration.html#testregex-string) dans la configuration se trouvant dans `package.json`. + +Jest recommande de créer un répertoire `__tests__` au même niveau que le code testé, mais soyez libre de structurer vos tests selon vos besoins. Soyez juste au courant que Jest créera un répertoire `__snapshots__` au même niveau que les fichiers de tests qui travaillent sur des instantanés. + +### Exemple d'une spécification + +Si vous êtes habitué à Jasmine, vous devriez très bien vous en sortir avec [l'API d'assertions de Jest](https://facebook.github.io/jest/docs/en/expect.html#content) : + +```js +import { mount } from 'vue-test-utils' +import Component from './component' + +describe('Component', () => { + test('est une instance de Vue', () => { + const wrapper = mount(Component) + expect(wrapper.isVueInstance()).toBeTruthy() + }) +}) +``` + +### Ressources + +- [Projet exemple pour cette installation](https://github.com/vuejs/vue-test-utils-jest-example) +- [Exemples et diapositives depuis la Vue Conf 2017](https://github.com/codebryo/vue-testing-with-jest-conf17) +- [Jest](https://facebook.github.io/jest/) +- [Babel preset env](https://github.com/babel/babel-preset-env) diff --git a/docs/fr/guides/testing-SFCs-with-mocha-webpack.md b/docs/fr/guides/testing-SFCs-with-mocha-webpack.md new file mode 100644 index 000000000..f04110d9d --- /dev/null +++ b/docs/fr/guides/testing-SFCs-with-mocha-webpack.md @@ -0,0 +1,180 @@ +# Tester des composants monofichiers avec Mocha + webpack + +> Un exemple de projet pour cette installation est disponible sur [GitHub](https://github.com/vuejs/vue-test-utils-mocha-webpack-example). + +Une des stratégies pour tester des composants monofichiers est de compiler tous nos tests via webpack puis de les passer dans un lanceur de tests. L'avantage de cette approche est qu'elle procure un support complet pour les fonctionnalités de webpack et de `vue-loader`, et ce, afin de ne pas réaliser de compromis dans notre code. + +Techniquement, vous pouvez utiliser n'importe quel lanceur de tests et relier le tout manuellement. Cependant, nous avons trouvé [`mocha-webpack`](https://github.com/zinserjan/mocha-webpack) qui procure une expérience très simplifiée pour cette tâche particulière. + +## Mettre en place `mocha-webpack` + +On va supposer que vous commencez avec une installation qui a déjà webpack, vue-loader et Babel correctement configurés (cf. le template `webpack-simple` via `vue-cli`). + +La première chose à faire est d'installer les dépendances de tests : + +``` bash +npm install --save-dev vue-test-utils mocha mocha-webpack +``` + +Ensuite, on doit définir un script test dans notre `package.json`. + +```json +// package.json +{ + "scripts": { + "test": "mocha-webpack --webpack-config webpack.config.js --require test/setup.js test/**/*.spec.js" + } +} +``` + +Quelques éléments importants à noter : + +- Le paramètre `--webpack-config` indique le fichier de configuration webpack à utiliser pour les tests. Dans la plupart des cas, c'est identique à la configuration du projet actuel avec une petite modification. On en reparlera plus tard. + +- Le paramètre `--require` permet de s'assurer que le fichier `test/setup.js` est bien exécuté avant les tests. Dans celui-ci, on met en place l'environnement où nos tests vont être exécutés. + +- Le dernier paramètre est un glob pour indiquer les fichiers de tests à inclure dans le paquetage. + +### Configuration supplémentaire pour webpack + +#### Externaliser les dépendances npm + +Dans nos tests, nous allons surement importer un nombre conséquent de dépendances npm, certaines d'entre elles n'ont pas été conçues pour une utilisation dans un navigateur et ne peuvent être empaquetées par webpack. Il faut aussi considérer qu'externaliser les dépendances augmente énormément la vitesse de lancement des tests. On peut externaliser toutes les dépendances npm avec `webpack-node-externals` : + +```js +// webpack.config.js +const nodeExternals = require('webpack-node-externals') + +module.exports = { + // ... + externals: [nodeExternals()] +} +``` + +#### Coordinateur de sources + +La coordinateur de sources (« Source maps ») doit être indiquée pour être utilisé par `mocha-webpack`. La configuration recommandée est la suivante : + +``` js +module.exports = { + // ... + devtool: 'inline-cheap-module-source-map' +} +``` + +Si vous déboguez via votre IDE, il est recommandé d'ajouter la configuration suivante : + +``` js +module.exports = { + // ... + output: { + // ... + // utiliser des chemins absolus (c'est important si vous déboguez avec un IDE) + devtoolModuleFilenameTemplate: '[absolute-resource-path]', + devtoolFallbackModuleFilenameTemplate: '[absolute-resource-path]?[hash]' + } +} +``` + +### Mettre en place l'environnement du navigateur + +`vue-test-utils` requiert en environnement de navigateur pour fonctionner. On peut le simuler avec Node.js en utilisant `jsdom-global` : + +```bash +npm install --save-dev jsdom jsdom-global +``` + +Puis dans `test/setup.js`: + +``` js +require('jsdom-global')() +``` + +Cela ajoute un environnement de navigateur dans Node.js afin que `vue-test-utils` fonctionne correctement. + +### Choisir une bibliothèque d'assertions + +[Chai](http://chaijs.com/) est une bibliothèque populaire qui est généralement utilisée avec Mocha. Vous pouvez aussi jeter un coup d'œil à [Sinon](http://sinonjs.org/) pour créer des espions et des fonctions avec un comportement pré-programmé (« stubs »). + +Vous pouvez utiliser, alternativement, `expect` qui fait maintenant partie de Jest et expose [la même API](http://facebook.github.io/jest/docs/en/expect.html#content) dans la documentation de Jest. + +On va utiliser `expect` et le rendre globalement accessible afin de ne pas avoir à l'importer pour chaque test : + +``` bash +npm install --save-dev expect +``` + +Puis dans `test/setup.js`: + +``` js +require('jsdom-global')() + +global.expect = require('expect') +``` + +### Optimiser Babel pour les tests + +Notez que nous utilisons `babel-loader` pour gérer JavaScript. Vous devriez déjà avoir Babel de configuré si vous l'utilisez dans votre application via un fichier `.babelrc`. Ici `babel-loader` va automatiquement utiliser le même fichier de configuration. + +Une autre chose à noter est que si vous utilisez une version de Node.js 6+, qui supporte déjà une majorité des fonctionnalités d'ES2015, vous pouvez configurer séparément un autre Babel [env option](https://babeljs.io/docs/usage/babelrc/#env-option) qui va uniquement transpiler les fonctionnalités non supportées dans la version de Node.js que vous utilisez (c.-à-d. `stage-2` ou le support de la syntaxe flow, etc.). + +### Ajouter un test + +Créez dans le dossier `src` un fichier nommé `Counter.vue`: + +``` html + + + +``` + +Puis créez un test nommé `test/Counter.spec.js` avec le code suivant : + +```js +import { shallow } from 'vue-test-utils' +import Counter from '../src/Counter.vue' + +describe('Counter.vue', () => { + it('incrémente le compteur quand le bouton est cliqué', () => { + const wrapper = shallow(Counter) + wrapper.find('button').trigger('click') + expect(wrapper.find('div').text()).toMatch('1') + }) +}) +``` + +Et maintenant on peut lancer le test avec : + +``` +npm run unit +``` + +Woohoo, nos tests fonctionnent ! + +### Ressources + +- [Projet exemple pour cette installation](https://github.com/vuejs/vue-test-utils-mocha-webpack-example) +- [Mocha](https://mochajs.org/) +- [mocha-webpack](http://zinserjan.github.io/mocha-webpack/) +- [Chai](http://chaijs.com/) +- [Sinon](http://sinonjs.org/) +- [jest/expect](http://facebook.github.io/jest/docs/en/expect.html#content) diff --git a/docs/fr/guides/using-with-vue-router.md b/docs/fr/guides/using-with-vue-router.md new file mode 100644 index 000000000..d52e812e6 --- /dev/null +++ b/docs/fr/guides/using-with-vue-router.md @@ -0,0 +1,71 @@ +# Utilisation avec Vue Router + +## Installer Vue Router pour nos tests + +Vous ne devez jamais installer Vue Router sur le constructeur de base de Vue pour vos tests. Installer Vue Router de cette manière ajoute `$route` et `$router` en tant que propriétés en lecture seule sur le prototype Vue. + +Pour éviter cela, on peut utiliser une `localVue`, et installer Vue Router dessus. + +```js +import VueRouter from 'vue-router' +const localVue = createLocalVue() + +localVue.use(VueRouter) + +shallow(Component, { + localVue +}) +``` + +## Tester des composants qui utilisent `router-link` ou `router-view` + +Quand vous installez Vue Router, les composants `router-link` et `router-view` sont enregistrés. Cela veut dire que l'on peut les utiliser n'importe où dans notre application sans avoir besoin de les importer. + +On doit donc rendre ces composants Vue Router disponibles au composant que nous testons. Il y a deux méthodes pour cela. + +### Utiliser des stubs + +```js +shallow(Component, { + stubs: ['router-link', 'router-view'] +}) +``` + +### Installer Vue Router et `localVue` + +```js +import VueRouter from 'vue-router' +const localVue = createLocalVue() + +localVue.use(VueRouter) + +shallow(Component, { + localVue +}) +``` + +## Simuler `$route` et `$router` + +Quelques fois, vous souhaitez tester qu'un composant réagisse correctement avec les paramètres des objets `$route` et `$router`. Pour cela, vous pouvez passer des imitations à l'instance de Vue. + +```js +const $route = { + path: '/un/super/chemin' +} + +const wrapper = shallow(Component, { + mocks: { + $route + } +}) + +wrapper.vm.$route.path // `'/un/super/chemin'` +``` + +## Trucs et astuces + +Installer Vue Router ajoute `$route` et `$router` en tant que propriétés en lecture seule au prototype de Vue. + +Cela veut dire que n'importe quel futur test qui va essayer de modifier `$route` ou `$router` va échouer. + +Pour éviter cela, n'installez jamais Vue Router quand vous lancez vos tests. diff --git a/docs/fr/guides/using-with-vuex.md b/docs/fr/guides/using-with-vuex.md new file mode 100644 index 000000000..717be1873 --- /dev/null +++ b/docs/fr/guides/using-with-vuex.md @@ -0,0 +1,266 @@ +# Utiliser avec Vuex + +Dans ce guide, nous allons voir comment tester Vuex dans des composants grâce à `vue-test-utils`. + +## Simuler des actions + +Regardons un peu de code ! + +Ci-dessous, le composant que nous voulons tester. Il fait appel à des actions Vuex. + +``` html + + + +``` + +Pour les objectifs de ce test, on se fiche de ce que les actions font, ou à ce quoi le store ressemble. On doit juste savoir si ces actions sont lancées lorsqu'elles sont supposées l'être, et ce, avec les valeurs attendues. + +Pour tester cela, on doit passer un store fictif à Vue lorsque l'on isole notre composant. + +Au lieu de passer le store au constructeur de base de Vue, on peut le passer à - [`localVue`](../api/options.md#localvue). Un `localVue` est un constructeur à portée limitée de Vue sur lequel on peut effectuer des changements sans avoir à affecter le constructeur global. + +Voyons à quoi cela ressemble : + +``` js +import { shallow, createLocalVue } from 'vue-test-utils' +import Vuex from 'vuex' +import Actions from '../../../src/components/Actions' + +const localVue = createLocalVue() + +localVue.use(Vuex) + +describe('Actions.vue', () => { + let actions + let store + + beforeEach(() => { + actions = { + actionClick: jest.fn(), + actionInput: jest.fn() + } + store = new Vuex.Store({ + state: {}, + actions + }) + }) + + it("appelle l'action `actionInput` du store quand la valeur du champ est `input` et qu'un évènement `input` est lancé", () => { + const wrapper = shallow(Actions, { store, localVue }) + const input = wrapper.find('input') + input.element.value = 'input' + input.trigger('input') + expect(actions.actionInput).toHaveBeenCalled() + }) + + it("n'appelle pas l'action `actionInput` du store quand la valeur du champ n'est pas `input` et qu'un évènement `input` est lancé", () => { + const wrapper = shallow(Actions, { store, localVue }) + const input = wrapper.find('input') + input.element.value = 'not input' + input.trigger('input') + expect(actions.actionInput).not.toHaveBeenCalled() + }) + + it("appelle l'action `actionClick` quand le bouton est cliqué", () => { + const wrapper = shallow(Actions, { store, localVue }) + wrapper.find('button').trigger('click') + expect(actions.actionClick).toHaveBeenCalled() + }) +}) +``` + +Que se passe-t-il ici ? Premièrement, on indique à Vue d'utiliser Vuex avec la méthode `use`. C'est tout simplement une surcouche de `Vue.use`. + +On va ensuite créer un store fictif en appelant `new Vuex.Store` avec nos propres valeurs. À noter que l'on indique uniquement nos actions, car on ne s'intéresse qu'à elles. + +Les actions sont des [fonctions de simulations de Jest](https://facebook.github.io/jest/docs/en/mock-functions.html). Ces fonctions nous donnent accès à des méthodes afin de réaliser des assertions si l'action a été appelée ou non. + +On peut ensuite s'assurer dans nos tests que les actions ont été appelées au bon moment. + +La manière dont on définit le store peut vous paraitre un peu étrange. + +On utilise `beforeEach` pour s'assurer que nous avons un store propre avant chaque test. `beforeEach` est un hook de Mocha qui est appelé avant chaque test. Dans nos tests, on réassigne des valeurs aux variables du store. Si on ne le fait pas, les fonctions de simulations auraient besoin d'être automatiquement réinitialisées. Cela nous laisse la possibilité de changer l'état dans nos tests, sans avoir à affecter les prochains. + +La chose la plus importante à noter dans ce test est que **l'on crée une simulation d'un store Vuex, qui est ensuite passé à `vue-test-utils`**. + +Génial, on peut désormais simuler des actions. Allons avoir comment simuler des accesseurs ! + +## Simuler des accesseurs + + +``` html + + + +``` + +C'est un composant relativement simple. Il affiche le résultat des accesseurs `clicks` et `inputValue`. Encore une fois, on se fiche de savoir ce que ces accesseurs retournent. On souhaite juste savoir si les résultats sont affichés correctement. + +Jetons un œil à un test : + +``` js +import { shallow, createLocalVue } from 'vue-test-utils' +import Vuex from 'vuex' +import Actions from '../../../src/components/Getters' + +const localVue = createLocalVue() + +localVue.use(Vuex) + +describe('Getters.vue', () => { + let getters + let store + + beforeEach(() => { + getters = { + clicks: () => 2, + inputValue: () => 'input' + } + + store = new Vuex.Store({ + getters + }) + }) + + it('affiche `state.inputValue` dans la première balise

', () => { + const wrapper = shallow(Actions, { store, localVue }) + const p = wrapper.find('p') + expect(p.text()).toBe(getters.inputValue()) + }) + + it('affiche `stat.clicks` dans la seconde balise

', () => { + const wrapper = shallow(Actions, { store, localVue }) + const p = wrapper.findAll('p').at(1) + expect(p.text()).toBe(getters.clicks().toString()) + }) +}) +``` + +Ce test est similaire à notre test sur les actions. On créer un store fictif avant chaque test, on le passe ensuite comme une option lorsque l'on appelle `shallow`. Pour finir, on asserte que la valeur retournée par nos accesseurs fictifs est bien affichée. + +C'est génial, mais comment faisons-nous pour vérifier que nos accesseurs retournent correctement les parties de l'état ? + +## Simulation avec des modules + +Les [modules](https://vuex.vuejs.org/en/modules.html) sont utiles pour séparer un store en plusieurs morceaux. Ils exportent des accesseurs que l'on peut utiliser dans nos tests. + +Jetons un œil à ce composant : + +``` html + + + +``` + +Simple composant qui possède une action et un accesseur. + +Et le test : + +``` js +import { shallow, createLocalVue } from 'vue-test-utils' +import Vuex from 'vuex' +import Modules from '../../../src/components/Modules' +import module from '../../../src/store/module' + +const localVue = createLocalVue() + +localVue.use(Vuex) + +describe('Modules.vue', () => { + let actions + let state + let store + + beforeEach(() => { + state = { + module: { + clicks: 2 + } + } + + actions = { + moduleActionClick: jest.fn() + } + + store = new Vuex.Store({ + state, + actions, + getters: module.getters + }) + }) + + it("appelle l'action du store moduleActionClick quand le bouton est cliqué", () => { + const wrapper = shallow(Modules, { store, localVue }) + const button = wrapper.find('button') + button.trigger('click') + expect(actions.moduleActionClick).toHaveBeenCalled() + }) + + it("affiche `state.inputValue` dans la première balise

", () => { + const wrapper = shallow(Modules, { store, localVue }) + const p = wrapper.find('p') + expect(p.text()).toBe(state.module.clicks.toString()) + }) +}) +``` + +### Ressources + +- [Projet exemple pour ce guide](https://github.com/eddyerburgh/vue-test-utils-vuex-example) +- [`localVue`](../api/options.md#localvue) +- [`createLocalVue`](../api/createLocalVue.md) From 4753be74c0cfe8128297bab65866bdbe14085b96 Mon Sep 17 00:00:00 2001 From: Alexander Sokolov Date: Wed, 22 Nov 2017 21:26:25 +0300 Subject: [PATCH 0095/1136] docs: update testing-SFCs-with-jest.md (#196) --- docs/en/guides/testing-SFCs-with-jest.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/guides/testing-SFCs-with-jest.md b/docs/en/guides/testing-SFCs-with-jest.md index 838a6ae02..5e1d53bf1 100644 --- a/docs/en/guides/testing-SFCs-with-jest.md +++ b/docs/en/guides/testing-SFCs-with-jest.md @@ -46,8 +46,8 @@ Next, create a `jest` block in `package.json`: "vue" ], "transform": { - // process `*.vue` files with `jest-vue` - ".*\\.(vue)$": "/node_modules/jest-vue" + // process `*.vue` files with `vue-jest` + ".*\\.(vue)$": "/node_modules/vue-jest" }, "mapCoverage": true } From fce9da7ee045cd0f79de81139f16d165d01b1104 Mon Sep 17 00:00:00 2001 From: ross francis Date: Thu, 23 Nov 2017 07:04:49 +0000 Subject: [PATCH 0096/1136] fix: render children depending on createElement correctly (#198) --- src/lib/create-instance.js | 2 +- test/unit/specs/mount/options/context.spec.js | 21 ++++++++++++++++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/lib/create-instance.js b/src/lib/create-instance.js index 727a8c56c..15c141e31 100644 --- a/src/lib/create-instance.js +++ b/src/lib/create-instance.js @@ -74,7 +74,7 @@ export default function createConstructor ( return h( clonedComponent, mountingOptions.context || component.FunctionalRenderContext, - (mountingOptions.context && mountingOptions.context.children) || createFunctionalSlots(mountingOptions.slots, h) + (mountingOptions.context && mountingOptions.context.children && mountingOptions.context.children.map(x => typeof x === 'function' ? x(h) : x)) || createFunctionalSlots(mountingOptions.slots, h) ) } } diff --git a/test/unit/specs/mount/options/context.spec.js b/test/unit/specs/mount/options/context.spec.js index a14425adb..100aa1586 100644 --- a/test/unit/specs/mount/options/context.spec.js +++ b/test/unit/specs/mount/options/context.spec.js @@ -66,7 +66,7 @@ describe('context', () => { expect(wrapper.element.textContent).to.equal(defaultValue) }) - it('mounts functional component with a defined context.children', () => { + it('mounts functional component with a defined context.children text', () => { const Component = { functional: true, render: (h, { children }) => { @@ -75,9 +75,24 @@ describe('context', () => { } const wrapper = mount(Component, { context: { - children: ['hello'] + children: ['render text'] } }) - expect(wrapper.text()).to.equal('hello') + expect(wrapper.text()).to.equal('render text') + }) + + it('mounts functional component with a defined context.children element', () => { + const Component = { + functional: true, + render: (h, { children }) => { + return h('div', children) + } + } + const wrapper = mount(Component, { + context: { + children: [h => h('div', 'render component')] + } + }) + expect(wrapper.text()).to.equal('render component') }) }) From eaf6182978b03acdc45aa1d2473009b4156bbd40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barth=C3=A9l=C3=A9my=20Ledoux?= Date: Thu, 23 Nov 2017 01:05:49 -0600 Subject: [PATCH 0097/1136] feat(types): add missing types to type definition (#199) When using typescript on a vue site that has animation, it becomes quite vital to be able to check that they are called. In order to do just that in TS too. I add the missing types. --- types/index.d.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/types/index.d.ts b/types/index.d.ts index ae74e950b..dfea479b4 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -108,7 +108,12 @@ interface MountOptions extends ComponentOptions { type ShallowOptions = MountOptions +interface VueTestUtilsConfigOptions { + stubs?: Stubs +} + export declare function createLocalVue (): typeof Vue +export declare let config: VueTestUtilsConfigOptions export declare function mount = VueClass> (component: Ctor, options?: MountOptions): Wrapper export declare function mount (component: ComponentOptions, options?: MountOptions): Wrapper @@ -117,3 +122,6 @@ export declare function mount (component: FunctionalComponentOptions, options?: export declare function shallow = VueClass> (component: Ctor, options?: ShallowOptions): Wrapper export declare function shallow (component: ComponentOptions, options?: ShallowOptions): Wrapper export declare function shallow (component: FunctionalComponentOptions, options?: ShallowOptions): Wrapper + +export declare let TransitionStub: Component | string | true +export declare let TransitionGroupStub: Component | string | true From 5e28449d3a03abfe8739ed7cc339beb3d6da1599 Mon Sep 17 00:00:00 2001 From: 38elements <38elements@users.noreply.github.com> Date: Sat, 25 Nov 2017 00:42:16 +0900 Subject: [PATCH 0098/1136] docs: improve TransitionGroupStub.md and TransitionStub.md (#200) * Update TransitionGroupStub.md * Update TransitionStub.md * Update TransitionStub.md * Update TransitionGroupStub.md --- docs/en/api/components/TransitionGroupStub.md | 6 +++--- docs/en/api/components/TransitionStub.md | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/en/api/components/TransitionGroupStub.md b/docs/en/api/components/TransitionGroupStub.md index ef66d9f72..c8ff8c87f 100644 --- a/docs/en/api/components/TransitionGroupStub.md +++ b/docs/en/api/components/TransitionGroupStub.md @@ -1,8 +1,8 @@ # TransitionGroupStub -A component to stub the `transition-group` wrapper component. Instead of performing transitions asynchronously, it returns the child components synchronously. +A component to stub the `transition-group` component. Instead of performing transitions asynchronously, it returns the child components synchronously. -This is set to stub all `transition-group` components by default in the `vue-test-utils` config. To use the built-in `transition-group` wrapper component set `config.stubs['transition-group']` to false: +This is set to stub all `transition-group` components by default in the `vue-test-utils` config. To use the built-in `transition-group` component set `config.stubs['transition-group']` to false: ```js import VueTestUtils from 'vue-test-utils' @@ -10,7 +10,7 @@ import VueTestUtils from 'vue-test-utils' VueTestUtils.config.stubs['transition-group'] = false ``` -To reset it to stub transition components: +To reset it to stub `transition-group` components: ```js import VueTestUtils, { TransitionGroupStub } from 'vue-test-utils' diff --git a/docs/en/api/components/TransitionStub.md b/docs/en/api/components/TransitionStub.md index 8a11d34eb..131ef5867 100644 --- a/docs/en/api/components/TransitionStub.md +++ b/docs/en/api/components/TransitionStub.md @@ -1,8 +1,8 @@ # TransitionStub -A component to stub the `transition` wrapper component. Instead of performing transitions asynchronously, it returns the child component synchronously. +A component to stub the `transition` component. Instead of performing transitions asynchronously, it returns the child component synchronously. -This is set to stub all `transition` components by default in the vue-test-utils config. To use the built-in `transition` wrapper component set `config.stubs.transition` to false: +This is set to stub all `transition` components by default in the vue-test-utils config. To use the built-in `transition` component set `config.stubs.transition` to false: ```js import VueTestUtils from 'vue-test-utils' @@ -10,7 +10,7 @@ import VueTestUtils from 'vue-test-utils' VueTestUtils.config.stubs.transition = false ``` -To reset it to stub transition components: +To reset it to stub `transition` components: ```js import VueTestUtils, { TransitionStub } from 'vue-test-utils' From 8a5924accd706d619b87dc23ba744430570859c2 Mon Sep 17 00:00:00 2001 From: 38elements <38elements@users.noreply.github.com> Date: Sat, 25 Nov 2017 19:23:15 +0900 Subject: [PATCH 0099/1136] docs: add document in docs/ja (#197) * Update docs/ja * Update TransitionGroupStub.md * Update TransitionStub.md * Update TransitionGroupStub.md * Update TransitionGroupStub.md * Update TransitionStub.md * Update TransitionGroupStub.md * Update TransitionStub.md --- docs/ja/README.md | 4 +++ docs/ja/SUMMARY.md | 8 +++++ docs/ja/api/README.md | 4 +++ docs/ja/api/components/README.md | 5 +++ docs/ja/api/components/TransitionGroupStub.md | 31 +++++++++++++++++++ docs/ja/api/components/TransitionStub.md | 31 +++++++++++++++++++ docs/ja/api/config.md | 25 +++++++++++++++ docs/ja/api/options.md | 2 +- 8 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 docs/ja/api/components/README.md create mode 100644 docs/ja/api/components/TransitionGroupStub.md create mode 100644 docs/ja/api/components/TransitionStub.md create mode 100644 docs/ja/api/config.md diff --git a/docs/ja/README.md b/docs/ja/README.md index 669223b2c..a1f7a4240 100644 --- a/docs/ja/README.md +++ b/docs/ja/README.md @@ -63,5 +63,9 @@ * [trigger](api/wrapper-array/trigger.md) * [update](api/wrapper-array/update.md) * [destroy](api/wrapper-array/destroy.md) + * [コンポーネント](api/components/README.md) + * [TransitionStub](api/components/TransitionStub.md) + * [TransitionGroupStub](api/components/TransitionGroupStub.md) * [セレクタ](api/selectors.md) * [createLocalVue](api/createLocalVue.md) + * [config](api/config.md) diff --git a/docs/ja/SUMMARY.md b/docs/ja/SUMMARY.md index 0a0da8b4e..ee6314620 100644 --- a/docs/ja/SUMMARY.md +++ b/docs/ja/SUMMARY.md @@ -36,12 +36,14 @@ * [isEmpty](api/wrapper/isEmpty.md) * [isVueInstance](api/wrapper/isVueInstance.md) * [name](api/wrapper/name.md) + * [setComputed](api/wrapper/setComputed.md) * [setData](api/wrapper/setData.md) * [setMethods](api/wrapper/setMethods.md) * [setProps](api/wrapper/setProps.md) * [text](api/wrapper/text.md) * [trigger](api/wrapper/trigger.md) * [update](api/wrapper/update.md) + * [destroy](api/wrapper/destroy.md) * [WrapperArray](api/wrapper-array/README.md) * [at](api/wrapper-array/at.md) * [contains](api/wrapper-array/contains.md) @@ -52,10 +54,16 @@ * [is](api/wrapper-array/is.md) * [isEmpty](api/wrapper-array/isEmpty.md) * [isVueInstance](api/wrapper-array/isVueInstance.md) + * [setComputed](api/wrapper-array/setComputed.md) * [setData](api/wrapper-array/setData.md) * [setMethods](api/wrapper-array/setMethods.md) * [setProps](api/wrapper-array/setProps.md) * [trigger](api/wrapper-array/trigger.md) * [update](api/wrapper-array/update.md) + * [destroy](api/wrapper-array/destroy.md) + * [コンポーネント](api/components/README.md) + * [TransitionStub](api/components/TransitionStub.md) + * [TransitionGroupStub](api/components/TransitionGroupStub.md) * [セレクタ](api/selectors.md) * [createLocalVue](api/createLocalVue.md) + * [config](api/config.md) diff --git a/docs/ja/api/README.md b/docs/ja/api/README.md index fdceb6d35..fec587c5c 100644 --- a/docs/ja/api/README.md +++ b/docs/ja/api/README.md @@ -52,5 +52,9 @@ * [trigger](./wrapper-array/trigger.md) * [update](./wrapper-array/update.md) * [destroy](./wrapper-array/destroy.md) +* [コンポーネント](./components/README.md) + * [TransitionStub](./components/TransitionStub.md) + * [TransitionGroupStub](./components/TransitionGroupStub.md) * [createLocalVue](./createLocalVue.md) * [セレクタ](./selectors.md) +* [config](./config.md) diff --git a/docs/ja/api/components/README.md b/docs/ja/api/components/README.md new file mode 100644 index 000000000..ebc2e5f80 --- /dev/null +++ b/docs/ja/api/components/README.md @@ -0,0 +1,5 @@ +# コンポーネント + +vue-test-utils にはコンポーネントをスタブするためのユーティリティコンポーネントがあります。 + +[TransitionStub](./TransitionStub.md) と [TransitionGroupStub](./TransitionGroupStub.md) はデフォルトで `transition` コンポーネントと `transition-group` コンポーネントをスタブすることに使用されます。 これらのスタブは `config` で変更することができます。 diff --git a/docs/ja/api/components/TransitionGroupStub.md b/docs/ja/api/components/TransitionGroupStub.md new file mode 100644 index 000000000..7f4e11703 --- /dev/null +++ b/docs/ja/api/components/TransitionGroupStub.md @@ -0,0 +1,31 @@ +# TransitionGroupStub + +`transition-group` コンポーネントをスタブするためのコンポーネントです。これはトランジションを非同期で実行する代わりに、子コンポーネントを同期的に返します。 + +デフォルトではすべての `transition-group` コンポーネントは `TransitionGroupStub` でスタブされます。これは `vue-test-utils` の `config` で設定されています。スタブせずに既存の `transition-group` コンポーネントを使用したい場合は、 `config.stubs['transition-group']` に `false` をセットします。: + +```js +import VueTestUtils from 'vue-test-utils' + +VueTestUtils.config.stubs['transition-group'] = false +``` + +`transition-group` コンポーネントをスタブするために再びセットするには以下のようにします。 + +```js +import VueTestUtils, { TransitionGroupStub } from 'vue-test-utils' + +VueTestUtils.config.stubs['transition-group'] = TransitionGroupStub +``` + +マウンティングオプションでスタブとしてセットするには以下のようにします。 + +```js +import { mount, TransitionGroupStub } from 'vue-test-utils' + +mount(Component, { + stubs: { + 'transition-group': TransitionGroupStub + } +}) +``` diff --git a/docs/ja/api/components/TransitionStub.md b/docs/ja/api/components/TransitionStub.md new file mode 100644 index 000000000..3badcc5b5 --- /dev/null +++ b/docs/ja/api/components/TransitionStub.md @@ -0,0 +1,31 @@ +# TransitionStub + +`transition` コンポーネントをスタブするためのコンポーネントです。これはトランジションを非同期で実行する代わりに、子コンポーネントを同期的に返します。 + +デフォルトではすべての `transition` コンポーネントは `TransitionStub` でスタブされます。これは `vue-test-utils` の `config` で設定されています。スタブせずに既存の `transition` コンポーネントを使用したい場合は、 `config.stubs['transition']` に `false` をセットします。: + +```js +import VueTestUtils from 'vue-test-utils' + +VueTestUtils.config.stubs.transition = false +``` + +`transition` コンポーネントをスタブするために再びセットするには以下のようにします。 + +```js +import VueTestUtils, { TransitionStub } from 'vue-test-utils' + +VueTestUtils.config.stubs.transition = TransitionStub +``` + +マウンティングオプションでスタブとしてセットするには以下のようにします。 + +```js +import { mount, TransitionStub } from 'vue-test-utils' + +mount(Component, { + stubs: { + transition: TransitionStub + } +}) +``` diff --git a/docs/ja/api/config.md b/docs/ja/api/config.md new file mode 100644 index 000000000..35351fc41 --- /dev/null +++ b/docs/ja/api/config.md @@ -0,0 +1,25 @@ +# config + +vue-test-utils にはオプションを定義するための `config` オプションがあります。 + +## `vue-test-utils` `config` オプション + +### `stubs` + +- 型: `Object` +- デフォルト: `{ + transition: TransitionStub, + 'transition-group': TransitionGroupStub +}` + +コンポーネントで使用するスタブはマウンティングオプションの `stabs` で設定します。 + +マウンティングオプションの `stubs` が配列である場合、`config.stubs` は配列に変換されます。その場合、使用されるスタブは``を返す基本的なコンポーネントになります。 + +例: + +```js +import VueTestUtils from 'vue-test-utils' + +VueTestUtils.config.stubs['my-compomnent'] = '

' +``` diff --git a/docs/ja/api/options.md b/docs/ja/api/options.md index eb7f01a93..95d23c360 100644 --- a/docs/ja/api/options.md +++ b/docs/ja/api/options.md @@ -61,7 +61,7 @@ expect(wrapper.find('div')).toBe(true) - type: `{ [name: string]: Component | boolean } | Array` -子のコンポーネントをスタブします。スタブまたはオブジェクトに対するコンポーネント名の配列になります。 +子のコンポーネントをスタブします。スタブまたはオブジェクトに対するコンポーネント名の配列になります。`stabs` が配列の場合、すべてのスタブは `` になります。 例: From ed7673c93bdc45f706c7f5350e75f14525a76622 Mon Sep 17 00:00:00 2001 From: 38elements <38elements@users.noreply.github.com> Date: Sun, 26 Nov 2017 17:39:47 +0900 Subject: [PATCH 0100/1136] docs: remove unused link in guides/README.md (#201) * Update README.md * Update README.md * Update README.md * Update README.md --- docs/en/guides/README.md | 1 - docs/fr/guides/README.md | 1 - docs/ja/guides/README.md | 1 - docs/pt-br/guides/README.md | 1 - 4 files changed, 4 deletions(-) diff --git a/docs/en/guides/README.md b/docs/en/guides/README.md index 8b6b6a4f7..344cb6f43 100644 --- a/docs/en/guides/README.md +++ b/docs/en/guides/README.md @@ -4,7 +4,6 @@ * [Common Tips](./common-tips.md) * [Mouse, Key and other DOM Events](./dom-events.md) * [Choosing a test runner](./choosing-a-test-runner.md) -* [Using with Jest](./using-with-jest.md) * [Testing SFCs with Jest](./testing-SFCs-with-jest.md) * [Testing SFCs with Mocha + webpack](./testing-SFCs-with-mocha-webpack.md) * [Using with Vue Router](./using-with-vue-router.md) diff --git a/docs/fr/guides/README.md b/docs/fr/guides/README.md index 223989ad0..7cedb95d6 100644 --- a/docs/fr/guides/README.md +++ b/docs/fr/guides/README.md @@ -4,7 +4,6 @@ * [Astuces](./common-tips.md) * [La souris, le clavier et les autres évènements DOM](./dom-events.md) * [Choisir un lanceur de tests](./choosing-a-test-runner.md) -* [Utiliser avec Jest](./using-with-jest.md) * [Tester des composants monofichiers avec Jest](./testing-SFCs-with-jest.md) * [Tester des composants monofichiers avec Mocha et webpack](./testing-SFCs-with-mocha-webpack.md) * [Utiliser avec Vue Router](./using-with-vue-router.md) diff --git a/docs/ja/guides/README.md b/docs/ja/guides/README.md index ce9946627..e087ca652 100644 --- a/docs/ja/guides/README.md +++ b/docs/ja/guides/README.md @@ -3,7 +3,6 @@ * [はじめる](./getting-started.md) * [よくある落とし穴](./common-tips.md) * [テストランナを選ぶ](./choosing-a-test-runner.md) -* [Jest と一緒に使う](./using-with-jest.md) * [Jest による単一ファイルコンポーネントのテスト](./testing-SFCs-with-jest.md) * [Mocha + webpack による単一ファイルコンポーネントのテスト](./testing-SFCs-with-mocha-webpack.md) * [Vuex と一緒に使う](./using-with-vuex.md) diff --git a/docs/pt-br/guides/README.md b/docs/pt-br/guides/README.md index 0983fe375..8739bb4bc 100644 --- a/docs/pt-br/guides/README.md +++ b/docs/pt-br/guides/README.md @@ -3,7 +3,6 @@ * [Iniciando](./getting-started.md) * [Dicas comuns](./common-tips.md) * [Escolhendo um executador de testes](./choosing-a-test-runner.md) -* [Usando com o Jest](./using-with-jest.md) * [Testando SFCs com Jest](./testing-SFCs-with-jest.md) * [Testando SFCs com Mocha + webpack](./testing-SFCs-with-mocha-webpack.md) * [Usando com o Vue Router](./using-with-vue-router.md) From dc0a2d85d9d5cb660017d7cbc32e35a0d9c70836 Mon Sep 17 00:00:00 2001 From: Chris Camaratta Date: Mon, 27 Nov 2017 22:42:39 -0800 Subject: [PATCH 0101/1136] fix: add workaround for IE in trigger --- src/wrappers/wrapper.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/wrappers/wrapper.js b/src/wrappers/wrapper.js index 336e5710f..80926c1ff 100644 --- a/src/wrappers/wrapper.js +++ b/src/wrappers/wrapper.js @@ -512,10 +512,18 @@ export default class Wrapper implements BaseWrapper { const event = type.split('.') - const eventObject = new window.Event(event[0], { - bubbles: true, - cancelable: true - }) + let eventObject + + // Fallback for IE10,11 - https://stackoverflow.com/questions/26596123 + if (typeof (window.Event) === 'function') { + eventObject = new window.Event(event[0], { + bubbles: true, + cancelable: true + }) + } else { + eventObject = document.createEvent('Event') + eventObject.initEvent(event[0], true, true) + } if (options && options.preventDefault) { eventObject.preventDefault() From 9189518aa04fdf150be8ae7f475ba6aa73c924f8 Mon Sep 17 00:00:00 2001 From: Callum Macrae Date: Tue, 28 Nov 2017 09:35:23 +0000 Subject: [PATCH 0102/1136] docs: improved docs for nextTick and thrown errors (#207) * Improved docs for nextTick and thrown errors * Fixed linting errors --- docs/en/guides/getting-started.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/docs/en/guides/getting-started.md b/docs/en/guides/getting-started.md index ad024164f..d7094a9c6 100644 --- a/docs/en/guides/getting-started.md +++ b/docs/en/guides/getting-started.md @@ -110,6 +110,34 @@ To simplify usage, `vue-test-utils` applies all updates synchronously so you don *Note: `nextTick` is still necessary when you need to explictly advance the event loop, for operations such as asynchronous callbacks or promise resolution.* +If you do still need to use `nextTick` in your test files, be aware that any errors thrown inside it may not be caught by your test runner as it uses promises internally. There are two approaches to fixing this: either you can set the `done` callback as Vue's global error handler at the start of the test, or you can call `nextTick` without an argument and return it as a promise: + +```js +// this will not be caught +it('will time out', (done) => { + Vue.nextTick(() => { + expect(true).toBe(false) + done() + }) +}) + +// the two following tests will work as expected +it('will catch the error using done', (done) => { + Vue.config.errorHandler = done + Vue.nextTick(() => { + expect(true).toBe(false) + done() + }) +}) + +it('will catch the error using a promise', () => { + return Vue.nextTick() + .then(function () { + expect(true).toBe(false) + }) +}) +``` + ## What's Next - Integrate `vue-test-utils` into your project by [choosing a test runner](./choosing-a-test-runner.md). From 24fd4a8d63e2195383abdf3faccbaece9d6c1096 Mon Sep 17 00:00:00 2001 From: eddyerburgh Date: Tue, 28 Nov 2017 12:52:38 +0000 Subject: [PATCH 0103/1136] docs: fix linting errors --- docs/fr/api/selectors.md | 4 ++-- docs/fr/guides/using-with-vuex.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/fr/api/selectors.md b/docs/fr/api/selectors.md index f41e6bbd2..c4cc177c7 100644 --- a/docs/fr/api/selectors.md +++ b/docs/fr/api/selectors.md @@ -49,6 +49,6 @@ expect(wrapper.is(Foo)).toBe(true) Using a find option object, `vue-test-utils` allows for selecting elements by `$ref` on wrapper components. ```js -const buttonWrapper = wrapper.find({ ref: 'myButton' }); -buttonWrapper.trigger('click'); +const buttonWrapper = wrapper.find({ ref: 'myButton' }) +buttonWrapper.trigger('click') ``` diff --git a/docs/fr/guides/using-with-vuex.md b/docs/fr/guides/using-with-vuex.md index 717be1873..b87e71e85 100644 --- a/docs/fr/guides/using-with-vuex.md +++ b/docs/fr/guides/using-with-vuex.md @@ -244,14 +244,14 @@ describe('Modules.vue', () => { }) }) - it("appelle l'action du store moduleActionClick quand le bouton est cliqué", () => { + it('appelle l\'action du store moduleActionClick quand le bouton est cliqué', () => { const wrapper = shallow(Modules, { store, localVue }) const button = wrapper.find('button') button.trigger('click') expect(actions.moduleActionClick).toHaveBeenCalled() }) - it("affiche `state.inputValue` dans la première balise

", () => { + it('affiche `state.inputValue` dans la première balise

', () => { const wrapper = shallow(Modules, { store, localVue }) const p = wrapper.find('p') expect(p.text()).toBe(state.module.clicks.toString()) From 0f3ee6f7d1de871fe9f9b9bb4649afa6a2ac1d67 Mon Sep 17 00:00:00 2001 From: eddyerburgh Date: Tue, 28 Nov 2017 13:05:43 +0000 Subject: [PATCH 0104/1136] test: fix flow errors --- src/wrappers/wrapper.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/wrappers/wrapper.js b/src/wrappers/wrapper.js index 80926c1ff..da900cc6a 100644 --- a/src/wrappers/wrapper.js +++ b/src/wrappers/wrapper.js @@ -531,11 +531,13 @@ export default class Wrapper implements BaseWrapper { if (options) { Object.keys(options).forEach(key => { + // $FlowIgnore eventObject[key] = options[key] }) } if (event.length === 2) { + // $FlowIgnore eventObject.keyCode = modifiers[event[1]] } From 02fcffa0fc30672832ab3e41826eb88df9e951dd Mon Sep 17 00:00:00 2001 From: eddyerburgh Date: Tue, 28 Nov 2017 13:12:39 +0000 Subject: [PATCH 0105/1136] build: run tests in circle --- circle.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/circle.yml b/circle.yml index d3884381f..bd08fff41 100644 --- a/circle.yml +++ b/circle.yml @@ -6,4 +6,5 @@ dependencies: - npm install -g npm@5.2.0 test: override: + - npm run test - npm run test:compatibility From 60e360aa2e9e5b62fecbe7579385d0df001d5c86 Mon Sep 17 00:00:00 2001 From: Dennis Levin Date: Wed, 29 Nov 2017 01:03:37 -0600 Subject: [PATCH 0106/1136] docs: fix description of code (#212) --- docs/en/guides/using-with-vuex.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/guides/using-with-vuex.md b/docs/en/guides/using-with-vuex.md index 9ed2841b7..dacdc253a 100644 --- a/docs/en/guides/using-with-vuex.md +++ b/docs/en/guides/using-with-vuex.md @@ -91,7 +91,7 @@ describe('Actions.vue', () => { }) ``` -What’s happening here? First we tell Vue to use Vuex with the `Vue.use` method. This is just a wrapper around `Vue.use`. +What’s happening here? First we tell Vue to use Vuex with the `localVue.use` method. This is just a wrapper around `Vue.use`. We then make a mock store by calling new `Vuex.store` with our mock values. We only pass it the actions, since that’s all we care about. From 9ab23e90fd404b763755a48847f7a59dd6d036a6 Mon Sep 17 00:00:00 2001 From: 38elements <38elements@users.noreply.github.com> Date: Wed, 29 Nov 2017 16:05:23 +0900 Subject: [PATCH 0107/1136] docs: add docs/ja/guides/dom-events.md (#204) --- docs/ja/README.md | 1 + docs/ja/SUMMARY.md | 1 + docs/ja/guides/README.md | 4 +- docs/ja/guides/dom-events.md | 196 +++++++++++++++++++++++++++++++++++ 4 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 docs/ja/guides/dom-events.md diff --git a/docs/ja/README.md b/docs/ja/README.md index a1f7a4240..9ad9b806a 100644 --- a/docs/ja/README.md +++ b/docs/ja/README.md @@ -5,6 +5,7 @@ * [ガイド](guides/README.md) * [はじめる](guides/getting-started.md) * [一般的なヒント](guides/common-tips.md) + * [キー、マウス、その他の DOM イベントのテスト](guides/dom-events.md) * [テストランナを選ぶ](guides/choosing-a-test-runner.md) * [Jest による単一ファイルコンポーネントのテスト](guides/testing-SFCs-with-jest.md) * [Mocha + webpack による単一ファイルコンポーネントのテスト](guides/testing-SFCs-with-mocha-webpack.md) diff --git a/docs/ja/SUMMARY.md b/docs/ja/SUMMARY.md index ee6314620..d23ba35f5 100644 --- a/docs/ja/SUMMARY.md +++ b/docs/ja/SUMMARY.md @@ -3,6 +3,7 @@ * [ガイド](guides/README.md) * [はじめる](guides/getting-started.md) * [一般的なヒント](guides/common-tips.md) + * [キー、マウス、その他の DOM イベントのテスト](guides/dom-events.md) * [テストランナを選ぶ](guides/choosing-a-test-runner.md) * [Jest による単一ファイルコンポーネントのテスト](guides/testing-SFCs-with-jest.md) * [Mocha + webpack による単一ファイルコンポーネントのテスト](guides/testing-SFCs-with-mocha-webpack.md) diff --git a/docs/ja/guides/README.md b/docs/ja/guides/README.md index e087ca652..dacde665a 100644 --- a/docs/ja/guides/README.md +++ b/docs/ja/guides/README.md @@ -1,8 +1,10 @@ # ガイド * [はじめる](./getting-started.md) -* [よくある落とし穴](./common-tips.md) +* [一般的なヒント](./common-tips.md) +* [キー、マウス、その他の DOM イベントのテスト](./dom-events.md) * [テストランナを選ぶ](./choosing-a-test-runner.md) * [Jest による単一ファイルコンポーネントのテスト](./testing-SFCs-with-jest.md) * [Mocha + webpack による単一ファイルコンポーネントのテスト](./testing-SFCs-with-mocha-webpack.md) +* [Vue Router と一緒に使う](./using-with-vue-router.md) * [Vuex と一緒に使う](./using-with-vuex.md) diff --git a/docs/ja/guides/dom-events.md b/docs/ja/guides/dom-events.md new file mode 100644 index 000000000..c03aef443 --- /dev/null +++ b/docs/ja/guides/dom-events.md @@ -0,0 +1,196 @@ +# キー、マウス、その他の DOM イベントのテスト + +## イベントをトリガする + +`Wrapper` の `trigger` メソッドで DOM イベントをトリガすることができます。 + +```js +const wrapper = mount(MyButton) + +wrapper.trigger('click') +``` + +`find` メソッドは `mount` メソッドと同じように `Wrapper` を返します。 `MyComponent` 内に `button` があると仮定すると、以下のコードは、 `button` をクリックします。 + +```js +const wrapper = mount(MyComponent) + +wrapper.find('button').trigger('click') +``` + +## オプション + +`trigger` メソッドはオプションで `options` オブジェクトを引数として取ります。`options` オブジェクトのプロパティはイベントオブジェクトのプロパティに追加されます。 + +`preventDefault: true` を `options` に渡すと、 `event.preventDefault()` を実行することができます。 + +```js +const wrapper = mount(MyButton) + +wrapper.trigger('click', { preventDefault: true }) +``` + + +## マウスクリックの例 + +**テスト対象のコンポーネント** + +```html + + + +``` + +**テスト** + +```js +import YesNoComponent from '@/components/YesNoComponent' +import { mount } from 'vue-test-utils' +import sinon from 'sinon' + +describe('Click event', () => { + it('Click on yes button calls our method with argument "yes"', () => { + const spy = sinon.spy() + const wrapper = mount(YesNoComponent, { + propsData: { + callMe: spy + } + }) + wrapper.find('button.yes').trigger('click') + + spy.should.have.been.calledWith('yes') + }) +}) +``` + +## キーボードの例 + +**テスト対象のコンポーネント** + +このコンポーネントはいくつかのキーを使用して `quantity` を増減することができます。 + +```html + + + +``` + +**テスト** + +```js +import QuantityComponent from '@/components/QuantityComponent' +import { mount } from 'vue-test-utils' + +describe('Key event tests', () => { + it('Quantity is zero by default', () => { + const wrapper = mount(QuantityComponent) + expect(wrapper.vm.quantity).to.equal(0) + }) + + it('Cursor up sets quantity to 1', () => { + const wrapper = mount(QuantityComponent) + wrapper.trigger('keydown.up') + expect(wrapper.vm.quantity).to.equal(1) + }) + + it('Cursor down reduce quantity by 1', () => { + const wrapper = mount(QuantityComponent) + wrapper.vm.quantity = 5 + wrapper.trigger('keydown.down') + expect(wrapper.vm.quantity).to.equal(4) + }) + + it('Escape sets quantity to 0', () => { + const wrapper = mount(QuantityComponent) + wrapper.vm.quantity = 5 + wrapper.trigger('keydown.esc') + expect(wrapper.vm.quantity).to.equal(0) + }) + + it('Magic character "a" sets quantity to 13', () => { + const wrapper = mount(QuantityComponent) + wrapper.trigger('keydown', { + which: 65 + }) + expect(wrapper.vm.quantity).to.equal(13) + }) +}) + +``` + +**制限事項** + +`.` の後のキー名( `keydown.up` の場合 `up` )は `keyCode` に変換されます。以下のキー名が変換されます。 + +* `enter`, `tab`, `delete`, `esc`, `space`, `up`, `down`, `left`, `right` + +## 重要事項 + +`vue-test-utils` は同期的にイベントをトリガします。従って、 `Vue.nextTick()` を実行する必要はありません。 From f2fbfefc23146b42a6801a677bb91455528a5afe Mon Sep 17 00:00:00 2001 From: Alan Lu Date: Fri, 1 Dec 2017 16:17:18 +0800 Subject: [PATCH 0108/1136] docs: fix typos (#219) --- docs/zh-cn/guides/using-with-vuex.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zh-cn/guides/using-with-vuex.md b/docs/zh-cn/guides/using-with-vuex.md index c97e0bd0c..ac395d95f 100644 --- a/docs/zh-cn/guides/using-with-vuex.md +++ b/docs/zh-cn/guides/using-with-vuex.md @@ -1,4 +1,4 @@ -# 配合 Vuex 实用 +# 配合 Vuex 使用 在本教程中,我们将会看到如何用 `vue-test-utils` 测试组件中的 Vuex。 From d97de8479791466829ecc62d976d40109cb962f0 Mon Sep 17 00:00:00 2001 From: Alan Lu Date: Fri, 1 Dec 2017 16:17:25 +0800 Subject: [PATCH 0109/1136] docs: fix typos (#218) --- docs/zh-cn/SUMMARY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zh-cn/SUMMARY.md b/docs/zh-cn/SUMMARY.md index 1b7842103..3cadd2418 100644 --- a/docs/zh-cn/SUMMARY.md +++ b/docs/zh-cn/SUMMARY.md @@ -8,7 +8,7 @@ * [用 Jest 测试单文件组件](guides/testing-SFCs-with-jest.md) * [用 Mocha 和 webpack 测试单文件组件](guides/testing-SFCs-with-mocha-webpack.md) * [配合 Vue Router 使用](guides/using-with-vue-router.md) - * [配合 Vuex 实用](guides/using-with-vuex.md) +  * [配合 Vuex 使用](guides/using-with-vuex.md) * [API](api/README.md) * [mount](api/mount.md) * [shallow](api/shallow.md) From 8904ddd407eade79611f6ffaaa79ad544d0a7e0f Mon Sep 17 00:00:00 2001 From: Plasmatium Date: Fri, 1 Dec 2017 02:17:32 -0600 Subject: [PATCH 0110/1136] typo fix "docs/zh-cn/guides/using-with-vuex.md" (#220) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit typo fixed with line 1: "实用" => "使用" From 54590330dd02011566d41a733b6bbefe5c84bd44 Mon Sep 17 00:00:00 2001 From: 38elements <38elements@users.noreply.github.com> Date: Sat, 2 Dec 2017 15:35:51 +0900 Subject: [PATCH 0111/1136] Update docs/ja (#223) * Update docs/ja * Update selectors.md * Update getting-started.md * Update selectors.md * Update selectors.md * Update getting-started.md --- docs/ja/api/selectors.md | 13 ++++++++++++- docs/ja/api/wrapper/README.md | 8 ++++---- docs/ja/guides/getting-started.md | 28 ++++++++++++++++++++++++++++ docs/ja/guides/using-with-vuex.md | 2 +- 4 files changed, 45 insertions(+), 6 deletions(-) diff --git a/docs/ja/api/selectors.md b/docs/ja/api/selectors.md index 7b9ae8ab5..0f9171b48 100644 --- a/docs/ja/api/selectors.md +++ b/docs/ja/api/selectors.md @@ -1,6 +1,6 @@ # セレクタ -多くのメソッドがセレクタを引数とします。セレクタは、CSS セレクタまたは Vue コンポーネントのいずれかです。 +多くのメソッドがセレクタを引数とします。セレクタは、CSS セレクタ、 Vue コンポーネント、または find メソッドのオプションオブジェクトのいずれかです。 ## CSS セレクタ @@ -41,3 +41,14 @@ import Foo from './Foo.vue' const wrapper = shallow(Foo) expect(wrapper.is(Foo)).toBe(true) ``` + +## find メソッドのオプションオブジェクト + +### ref + +find メソッドのオプションオブジェクトを使用すると、`Wrapper` コンポーネントの `$ref` プロパティに一致する要素を取得することができます。 + +```js +const buttonWrapper = wrapper.find({ ref: 'myButton' }) +buttonWrapper.trigger('click') +``` diff --git a/docs/ja/api/wrapper/README.md b/docs/ja/api/wrapper/README.md index cc1c94218..49754dc82 100644 --- a/docs/ja/api/wrapper/README.md +++ b/docs/ja/api/wrapper/README.md @@ -6,10 +6,10 @@ vue-test-utils はラッパベースの API です。 - **プロパティ:** -`vm` `Component`:これは vue のインスタンスです。`wrapper.vm` を使って [vm のプロパティとインスタンスメソッド](https://jp.vuejs.org/v2/api/#インスタンスプロパティ)にアクセスできます。これは、Vue コンポーネントラッパにのみ存在します。 -`element` `HTMLElement`: ラッパのルート DOM -`options` `Object`: `mount` または `shallow` に渡された vue-test-utils オプションを含むオブジェクト -`options.attachedToDom` `Boolean`: `mount` か `shallow` に渡された場合は True です。 +`vm` `Component`:これは vue のインスタンスです。`wrapper.vm` を使って [vm のプロパティとインスタンスメソッド](https://jp.vuejs.org/v2/api/#インスタンスプロパティ)にアクセスできます。これは、Vue コンポーネントラッパにのみ存在します。 +`element` `HTMLElement`: ラッパのルート DOM +`options` `Object`: `mount` または `shallow` に渡された vue-test-utils オプションを含むオブジェクト +`options.attachedToDom` `Boolean`: `mount` か `shallow` に渡された場合は True です。 - **メソッド:** diff --git a/docs/ja/guides/getting-started.md b/docs/ja/guides/getting-started.md index 094b59df1..d997a6003 100644 --- a/docs/ja/guides/getting-started.md +++ b/docs/ja/guides/getting-started.md @@ -110,6 +110,34 @@ Vue は保留した DOM 更新をまとめて処理し、非同期に適用し *注意: 非同期コールバックやプロミスの解決などの操作のために、イベントループを明示的に進める必要がある場合は、まだ `nextTick` が必要です。* +テストファイルで `nextTick` をまだ使う必要がある場合は、 `nextTick` の内部で Promise を使っているので、 `nextTick` 内で発生したエラーはテストランナーによって捕捉されないことに注意してください。これを解決するには2つの方法があります。 1つ目はテストの最初でVueのグローバルエラーハンドラに `done` コールバックをセットする方法です。2つ目は `nextTick` を引数なしで実行して、それを Promise としてテストランナーに返す方法です。 + +```js +// これは捕捉されない +it('will time out', (done) => { + Vue.nextTick(() => { + expect(true).toBe(false) + done() + }) +}) + +// 以下の2つのテストは期待通り動作します +it('will catch the error using done', (done) => { + Vue.config.errorHandler = done + Vue.nextTick(() => { + expect(true).toBe(false) + done() + }) +}) + +it('will catch the error using a promise', () => { + return Vue.nextTick() + .then(function () { + expect(true).toBe(false) + }) +}) +``` + ## 次は何をするのか - [テストランナを選ぶ](./choosing-a-test-runner.md)で `vue-test-utils` をプロジェクトに組み込む diff --git a/docs/ja/guides/using-with-vuex.md b/docs/ja/guides/using-with-vuex.md index 126eff821..b0438eb0b 100644 --- a/docs/ja/guides/using-with-vuex.md +++ b/docs/ja/guides/using-with-vuex.md @@ -91,7 +91,7 @@ describe('Actions.vue', () => { }) ``` -ここでは何が起こっているでしょうか?まず、Vue に `Vue.use` メソッドを使用して Vuex を使用するように指示しています。これは、単なる `Vue.use` のラッパです。 +ここでは何が起こっているでしょうか?まず、Vue に `localVue.use` メソッドを使用して Vuex を使用するように指示しています。これは、単なる `Vue.use` のラッパです。 次に、新しい `Vuex.store` をモックした値で呼び出すことによってモックのストアを作成します。それをアクションに渡すだけです。それが気にしなければならないことの全てだからです。 From bb52a73fa67f59fb580984c6e34b53dd9f5a0629 Mon Sep 17 00:00:00 2001 From: Alexander Sokolov Date: Sat, 2 Dec 2017 21:05:57 +0300 Subject: [PATCH 0112/1136] docs(ru): Translation ready (#188) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Скопированы en -> ru * Переведены страницы с содержаниями * dom-events.md добавлен новый файл * README.md добавлен новый файл в оглавления * choosing-a-test-runner.md правки * testing-SFCs-with-jest.md правки * setProps.md новые правки * Добавлены новые файлы для destroy * Приведены к единому виду readme & summary * dom-events.md правки * dom-events.md переведён * choosing-a-test-runner.md переведён * WIP перевод * WIP переведены общие заголовки * isVueInstance.md мелкая правка * WIP перевод * WIP перевод * WIP перевод * Допереведены SUMMARY * emitted.md переведён * emittedByOrder.md переведён * WIP перевод * name.md переведён * README.md правки * trigger.md переведён * update.md переведён * at.md переведён * contains.md правки * hasStyle.md переведён * README.md переведён * update.md переведён * createLocalVue.md переведён * mount.md переведен * options.md переведён * selectors.md переведён * shallow.md переведён * WIP перевод * testing-SFCs-with-jest WIP * README.md Добавлены ссылки на setComputed * Добавлены файлы setComputed * Добавлены новые файлы * Мелкие правки * Добавлена ссылка на config.md * config.md * Исправлена опечатка * selectors.md новая секция * dom-events.md правка * testing-SFCs-with-jest.md правка * emitted.md добавлеа секция * LANGS.md правка * selectors.md WIP * choosing-a-test-runner.md правка * common-tips.md переведён * choosing-a-test-runner.md правка * dom-events.md переводы в примерах * getting-started.md допереведён * testing-SFCs-with-jest.md переведен * using-with-vue-router.md переведён * testing-SFCs-with-mocha-webpack.md переведен * using-with-vuex.md * Правки заголовков * options.md правка * options.md правка * Мелкая правка * dom-events.md правка * Новые правки * dom-events.md правка * readme.md удалена лишняя ссылка * Правки по страницам заглушек компонентов --- docs/LANGS.md | 1 + docs/ru/README.md | 74 +++++ docs/ru/SUMMARY.md | 70 +++++ docs/ru/api/README.md | 60 ++++ docs/ru/api/components/README.md | 5 + docs/ru/api/components/TransitionGroupStub.md | 31 ++ docs/ru/api/components/TransitionStub.md | 30 ++ docs/ru/api/config.md | 25 ++ docs/ru/api/createLocalVue.md | 28 ++ docs/ru/api/mount.md | 138 +++++++++ docs/ru/api/options.md | 165 +++++++++++ docs/ru/api/selectors.md | 54 ++++ docs/ru/api/shallow.md | 124 ++++++++ docs/ru/api/wrapper-array/README.md | 11 + docs/ru/api/wrapper-array/at.md | 21 ++ docs/ru/api/wrapper-array/contains.md | 24 ++ docs/ru/api/wrapper-array/destroy.md | 17 ++ docs/ru/api/wrapper-array/hasAttribute.md | 21 ++ docs/ru/api/wrapper-array/hasClass.md | 20 ++ docs/ru/api/wrapper-array/hasProp.md | 24 ++ docs/ru/api/wrapper-array/hasStyle.md | 25 ++ docs/ru/api/wrapper-array/is.md | 20 ++ docs/ru/api/wrapper-array/isEmpty.md | 17 ++ docs/ru/api/wrapper-array/isVueInstance.md | 18 ++ docs/ru/api/wrapper-array/setComputed.md | 25 ++ docs/ru/api/wrapper-array/setData.md | 22 ++ docs/ru/api/wrapper-array/setMethods.md | 27 ++ docs/ru/api/wrapper-array/setProps.md | 22 ++ docs/ru/api/wrapper-array/trigger.md | 26 ++ docs/ru/api/wrapper-array/update.md | 20 ++ docs/ru/api/wrapper/README.md | 16 ++ docs/ru/api/wrapper/contains.md | 23 ++ docs/ru/api/wrapper/destroy.md | 20 ++ docs/ru/api/wrapper/emitted.md | 46 +++ docs/ru/api/wrapper/emittedByOrder.md | 28 ++ docs/ru/api/wrapper/exists.md | 21 ++ docs/ru/api/wrapper/find.md | 27 ++ docs/ru/api/wrapper/findAll.md | 27 ++ docs/ru/api/wrapper/hasAttribute.md | 37 +++ docs/ru/api/wrapper/hasClass.md | 21 ++ docs/ru/api/wrapper/hasProp.md | 24 ++ docs/ru/api/wrapper/hasStyle.md | 24 ++ docs/ru/api/wrapper/html.md | 16 ++ docs/ru/api/wrapper/is.md | 19 ++ docs/ru/api/wrapper/isEmpty.md | 16 ++ docs/ru/api/wrapper/isVueInstance.md | 16 ++ docs/ru/api/wrapper/name.md | 18 ++ docs/ru/api/wrapper/setComputed.md | 43 +++ docs/ru/api/wrapper/setData.md | 20 ++ docs/ru/api/wrapper/setMethods.md | 24 ++ docs/ru/api/wrapper/setProps.md | 48 ++++ docs/ru/api/wrapper/text.md | 16 ++ docs/ru/api/wrapper/trigger.md | 38 +++ docs/ru/api/wrapper/update.md | 19 ++ docs/ru/guides/README.md | 10 + docs/ru/guides/choosing-a-test-runner.md | 45 +++ docs/ru/guides/common-tips.md | 135 +++++++++ docs/ru/guides/dom-events.md | 196 +++++++++++++ docs/ru/guides/getting-started.md | 116 ++++++++ docs/ru/guides/testing-SFCs-with-jest.md | 179 ++++++++++++ .../guides/testing-SFCs-with-mocha-webpack.md | 180 ++++++++++++ docs/ru/guides/using-with-vue-router.md | 71 +++++ docs/ru/guides/using-with-vuex.md | 265 ++++++++++++++++++ 63 files changed, 2969 insertions(+) create mode 100644 docs/ru/README.md create mode 100644 docs/ru/SUMMARY.md create mode 100644 docs/ru/api/README.md create mode 100644 docs/ru/api/components/README.md create mode 100644 docs/ru/api/components/TransitionGroupStub.md create mode 100644 docs/ru/api/components/TransitionStub.md create mode 100644 docs/ru/api/config.md create mode 100644 docs/ru/api/createLocalVue.md create mode 100644 docs/ru/api/mount.md create mode 100644 docs/ru/api/options.md create mode 100644 docs/ru/api/selectors.md create mode 100644 docs/ru/api/shallow.md create mode 100644 docs/ru/api/wrapper-array/README.md create mode 100644 docs/ru/api/wrapper-array/at.md create mode 100644 docs/ru/api/wrapper-array/contains.md create mode 100644 docs/ru/api/wrapper-array/destroy.md create mode 100644 docs/ru/api/wrapper-array/hasAttribute.md create mode 100644 docs/ru/api/wrapper-array/hasClass.md create mode 100644 docs/ru/api/wrapper-array/hasProp.md create mode 100644 docs/ru/api/wrapper-array/hasStyle.md create mode 100644 docs/ru/api/wrapper-array/is.md create mode 100644 docs/ru/api/wrapper-array/isEmpty.md create mode 100644 docs/ru/api/wrapper-array/isVueInstance.md create mode 100644 docs/ru/api/wrapper-array/setComputed.md create mode 100644 docs/ru/api/wrapper-array/setData.md create mode 100644 docs/ru/api/wrapper-array/setMethods.md create mode 100644 docs/ru/api/wrapper-array/setProps.md create mode 100644 docs/ru/api/wrapper-array/trigger.md create mode 100644 docs/ru/api/wrapper-array/update.md create mode 100644 docs/ru/api/wrapper/README.md create mode 100644 docs/ru/api/wrapper/contains.md create mode 100644 docs/ru/api/wrapper/destroy.md create mode 100644 docs/ru/api/wrapper/emitted.md create mode 100644 docs/ru/api/wrapper/emittedByOrder.md create mode 100644 docs/ru/api/wrapper/exists.md create mode 100644 docs/ru/api/wrapper/find.md create mode 100644 docs/ru/api/wrapper/findAll.md create mode 100644 docs/ru/api/wrapper/hasAttribute.md create mode 100644 docs/ru/api/wrapper/hasClass.md create mode 100644 docs/ru/api/wrapper/hasProp.md create mode 100644 docs/ru/api/wrapper/hasStyle.md create mode 100644 docs/ru/api/wrapper/html.md create mode 100644 docs/ru/api/wrapper/is.md create mode 100644 docs/ru/api/wrapper/isEmpty.md create mode 100644 docs/ru/api/wrapper/isVueInstance.md create mode 100644 docs/ru/api/wrapper/name.md create mode 100644 docs/ru/api/wrapper/setComputed.md create mode 100644 docs/ru/api/wrapper/setData.md create mode 100644 docs/ru/api/wrapper/setMethods.md create mode 100644 docs/ru/api/wrapper/setProps.md create mode 100644 docs/ru/api/wrapper/text.md create mode 100644 docs/ru/api/wrapper/trigger.md create mode 100644 docs/ru/api/wrapper/update.md create mode 100644 docs/ru/guides/README.md create mode 100644 docs/ru/guides/choosing-a-test-runner.md create mode 100644 docs/ru/guides/common-tips.md create mode 100644 docs/ru/guides/dom-events.md create mode 100644 docs/ru/guides/getting-started.md create mode 100644 docs/ru/guides/testing-SFCs-with-jest.md create mode 100644 docs/ru/guides/testing-SFCs-with-mocha-webpack.md create mode 100644 docs/ru/guides/using-with-vue-router.md create mode 100644 docs/ru/guides/using-with-vuex.md diff --git a/docs/LANGS.md b/docs/LANGS.md index 1de6eac8b..c390e0dbe 100644 --- a/docs/LANGS.md +++ b/docs/LANGS.md @@ -3,3 +3,4 @@ * [日本語](ja/) * [简体中文](zh-cn/) * [Portuguese (Brazil)](pt-br/) +* [Русский](ru/) diff --git a/docs/ru/README.md b/docs/ru/README.md new file mode 100644 index 000000000..67f530d91 --- /dev/null +++ b/docs/ru/README.md @@ -0,0 +1,74 @@ +# vue-test-utils + +`vue-test-utils` — официальная библиотека модульного тестирования для Vue.js. + +## Содержание + +* [Руководства](guides/README.md) + * [Введение](guides/getting-started.md) + * [Общие советы](guides/common-tips.md) + * [Мышь, клавиши и другие события DOM](guides/dom-events.md) + * [Чем запускать тесты](guides/choosing-a-test-runner.md) + * [Тестирование однофайловых компонентов с Jest](guides/testing-SFCs-with-jest.md) + * [Тестирование однофайловых компонентов с Mocha + webpack](guides/testing-SFCs-with-mocha-webpack.md) + * [Использование с Vue Router](guides/using-with-vue-router.md) + * [Использование с Vuex](guides/using-with-vuex.md) +* [API](api/README.md) + * [mount](api/mount.md) + * [shallow](api/shallow.md) + * [Опции монтирования](api/options.md) + - [context](api/options.md#context) + - [slots](api/options.md#slots) + - [stubs](api/options.md#stubs) + - [mocks](api/options.md#mocks) + - [localVue](api/options.md#localvue) + - [attachToDocument](api/options.md#attachtodocument) + - [attrs](api/options.md#attrs) + - [listeners](api/options.md#listeners) + - [clone](api/options.md#clone) + * [Wrapper](api/wrapper/README.md) + * [contains](api/wrapper/contains.md) + * [emitted](api/wrapper/emitted.md) + * [emittedByOrder](api/wrapper/emittedByOrder.md) + * [find](api/wrapper/find.md) + * [findAll](api/wrapper/findAll.md) + * [hasAttribute](api/wrapper/hasAttribute.md) + * [hasClass](api/wrapper/hasClass.md) + * [hasProp](api/wrapper/hasProp.md) + * [hasStyle](api/wrapper/hasStyle.md) + * [html](api/wrapper/html.md) + * [is](api/wrapper/is.md) + * [isEmpty](api/wrapper/isEmpty.md) + * [isVueInstance](api/wrapper/isVueInstance.md) + * [name](api/wrapper/name.md) + * [setComputed](api/wrapper/setComputed.md) + * [setData](api/wrapper/setData.md) + * [setMethods](api/wrapper/setMethods.md) + * [setProps](api/wrapper/setProps.md) + * [text](api/wrapper/text.md) + * [trigger](api/wrapper/trigger.md) + * [update](api/wrapper/update.md) + * [destroy](api/wrapper/destroy.md) + * [WrapperArray](api/wrapper-array/README.md) + * [at](api/wrapper-array/at.md) + * [contains](api/wrapper-array/contains.md) + * [hasAttribute](api/wrapper-array/hasAttribute.md) + * [hasClass](api/wrapper-array/hasClass.md) + * [hasProp](api/wrapper-array/hasProp.md) + * [hasStyle](api/wrapper-array/hasStyle.md) + * [is](api/wrapper-array/is.md) + * [isEmpty](api/wrapper-array/isEmpty.md) + * [isVueInstance](api/wrapper-array/isVueInstance.md) + * [setComputed](api/wrapper-array/setComputed.md) + * [setData](api/wrapper-array/setData.md) + * [setMethods](api/wrapper-array/setMethods.md) + * [setProps](api/wrapper-array/setProps.md) + * [trigger](api/wrapper-array/trigger.md) + * [update](api/wrapper-array/update.md) + * [destroy](api/wrapper-array/destroy.md) + * [Компоненты](api/components/README.md) + * [TransitionStub](api/components/TransitionStub.md) + * [TransitionGroupStub](api/components/TransitionGroupStub.md) + * [Селекторы](api/selectors.md) + * [createLocalVue](api/createLocalVue.md) + * [Конфигурация](api/config.md) diff --git a/docs/ru/SUMMARY.md b/docs/ru/SUMMARY.md new file mode 100644 index 000000000..6d1ddea32 --- /dev/null +++ b/docs/ru/SUMMARY.md @@ -0,0 +1,70 @@ +## Содержание + +* [Руководства](guides/README.md) + * [Введение](guides/getting-started.md) + * [Общие советы](guides/common-tips.md) + * [Мышь, клавиши и другие события DOM](guides/dom-events.md) + * [Чем запускать тесты](guides/choosing-a-test-runner.md) + * [Тестирование однофайловых компонентов с Jest](guides/testing-SFCs-with-jest.md) + * [Тестирование однофайловых компонентов с Mocha + webpack](guides/testing-SFCs-with-mocha-webpack.md) + * [Использование с Vue Router](guides/using-with-vue-router.md) + * [Использование с Vuex](guides/using-with-vuex.md) +* [API](api/README.md) + * [mount](api/mount.md) + * [shallow](api/shallow.md) + * [Опции монтирования](api/options.md) + - [context](api/options.md#context) + - [slots](api/options.md#slots) + - [stubs](api/options.md#stubs) + - [mocks](api/options.md#mocks) + - [localVue](api/options.md#localvue) + - [attachToDocument](api/options.md#attachtodocument) + - [attrs](api/options.md#attrs) + - [listeners](api/options.md#listeners) + - [clone](api/options.md#clone) + * [Wrapper](api/wrapper/README.md) + * [contains](api/wrapper/contains.md) + * [emitted](api/wrapper/emitted.md) + * [emittedByOrder](api/wrapper/emittedByOrder.md) + * [find](api/wrapper/find.md) + * [findAll](api/wrapper/findAll.md) + * [hasAttribute](api/wrapper/hasAttribute.md) + * [hasClass](api/wrapper/hasClass.md) + * [hasProp](api/wrapper/hasProp.md) + * [hasStyle](api/wrapper/hasStyle.md) + * [html](api/wrapper/html.md) + * [is](api/wrapper/is.md) + * [isEmpty](api/wrapper/isEmpty.md) + * [isVueInstance](api/wrapper/isVueInstance.md) + * [name](api/wrapper/name.md) + * [setComputed](api/wrapper/setComputed.md) + * [setData](api/wrapper/setData.md) + * [setMethods](api/wrapper/setMethods.md) + * [setProps](api/wrapper/setProps.md) + * [text](api/wrapper/text.md) + * [trigger](api/wrapper/trigger.md) + * [update](api/wrapper/update.md) + * [destroy](api/wrapper/destroy.md) + * [WrapperArray](api/wrapper-array/README.md) + * [at](api/wrapper-array/at.md) + * [contains](api/wrapper-array/contains.md) + * [hasAttribute](api/wrapper-array/hasAttribute.md) + * [hasClass](api/wrapper-array/hasClass.md) + * [hasProp](api/wrapper-array/hasProp.md) + * [hasStyle](api/wrapper-array/hasStyle.md) + * [is](api/wrapper-array/is.md) + * [isEmpty](api/wrapper-array/isEmpty.md) + * [isVueInstance](api/wrapper-array/isVueInstance.md) + * [setComputed](api/wrapper-array/setComputed.md) + * [setData](api/wrapper-array/setData.md) + * [setMethods](api/wrapper-array/setMethods.md) + * [setProps](api/wrapper-array/setProps.md) + * [trigger](api/wrapper-array/trigger.md) + * [update](api/wrapper-array/update.md) + * [destroy](api/wrapper-array/destroy.md) + * [Компоненты](api/components/README.md) + * [TransitionStub](api/components/TransitionStub.md) + * [TransitionGroupStub](api/components/TransitionGroupStub.md) + * [Селекторы](api/selectors.md) + * [createLocalVue](api/createLocalVue.md) + * [Конфигурация](api/config.md) diff --git a/docs/ru/api/README.md b/docs/ru/api/README.md new file mode 100644 index 000000000..e3dfb2c6e --- /dev/null +++ b/docs/ru/api/README.md @@ -0,0 +1,60 @@ +# API + +* [mount](./mount.md) +* [shallow](./shallow.md) +* [Опции монтирования](./options.md) + - [context](./options.md#context) + - [slots](./options.md#slots) + - [stubs](./options.md#stubs) + - [mocks](./options.md#mocks) + - [localVue](./options.md#localvue) + - [attachToDocument](./options.md#attachtodocument) + - [attrs](./options.md#attrs) + - [listeners](./options.md#listeners) + - [clone](./options.md#clone) +* [Wrapper](./wrapper/README.md) + * [contains](./wrapper/contains.md) + * [emitted](./wrapper/emitted.md) + * [emittedByOrder](./wrapper/emittedByOrder.md) + * [find](./wrapper/find.md) + * [findAll](./wrapper/findAll.md) + * [hasAttribute](./wrapper/hasAttribute.md) + * [hasClass](./wrapper/hasClass.md) + * [hasProp](./wrapper/hasProp.md) + * [hasStyle](./wrapper/hasStyle.md) + * [html](./wrapper/html.md) + * [is](./wrapper/is.md) + * [isEmpty](./wrapper/isEmpty.md) + * [isVueInstance](./wrapper/isVueInstance.md) + * [name](./wrapper/name.md) + * [setComputed](./wrapper/setComputed.md) + * [setData](./wrapper/setData.md) + * [setMethods](./wrapper/setMethods.md) + * [setProps](./wrapper/setProps.md) + * [text](./wrapper/text.md) + * [trigger](./wrapper/trigger.md) + * [update](./wrapper/update.md) + * [destroy](./wrapper/destroy.md) +* [WrapperArray](./wrapper-array/README.md) + * [at](./wrapper-array/at.md) + * [contains](./wrapper-array/contains.md) + * [hasAttribute](./wrapper-array/hasAttribute.md) + * [hasClass](./wrapper-array/hasClass.md) + * [hasProp](./wrapper-array/hasProp.md) + * [hasStyle](./wrapper-array/hasStyle.md) + * [is](./wrapper-array/is.md) + * [isEmpty](./wrapper-array/isEmpty.md) + * [isVueInstance](./wrapper-array/isVueInstance.md) + * [setComputed](./wrapper-array/setComputed.md) + * [setData](./wrapper-array/setData.md) + * [setMethods](./wrapper-array/setMethods.md) + * [setProps](./wrapper-array/setProps.md) + * [trigger](./wrapper-array/trigger.md) + * [update](./wrapper-array/update.md) + * [destroy](./wrapper-array/destroy.md) +* [Компоненты](./components/README.md) + * [TransitionStub](./components/TransitionStub.md) + * [TransitionGroupStub](./components/TransitionGroupStub.md) +* [Селекторы](./selectors.md) +* [createLocalVue](./createLocalVue.md) +* [Конфигурация](./config.md) diff --git a/docs/ru/api/components/README.md b/docs/ru/api/components/README.md new file mode 100644 index 000000000..517cd1895 --- /dev/null +++ b/docs/ru/api/components/README.md @@ -0,0 +1,5 @@ +# Компоненты + +vue-test-utils включает утилиты, которые вы можете использовать для создания заглушек компонентов. + +[TransitionStub](./TransitionStub.md) и [TransitionGroupStub](./TransitionGroupStub.md) используются для создания заглушек компонентов `transition` и `transition-group` по умолчанию. Вы можете изменить эти заглушки отредактировав конфигурацию. \ No newline at end of file diff --git a/docs/ru/api/components/TransitionGroupStub.md b/docs/ru/api/components/TransitionGroupStub.md new file mode 100644 index 000000000..e394a607c --- /dev/null +++ b/docs/ru/api/components/TransitionGroupStub.md @@ -0,0 +1,31 @@ +# TransitionGroupStub + +Компонент для создания заглушки компонента `transition-group`. Вместо асинхронного выполнения переходов он возвращает дочерние компоненты синхронно. + +Это настроено на заглушку всех компонентов `transition-group` по умолчанию в конфигурации vue-test-utils. Чтобы использовать стандартный компонент `transition-group` установите `config.stubs['transition-group']` в значение false: + +```js +import VueTestUtils from 'vue-test-utils' + +VueTestUtils.config.stubs['transition-group'] = false +``` + +Чтобы переустановить обратно на заглушки компонентов `transition-group`: + +```js +import VueTestUtils, { TransitionGroupStub } from 'vue-test-utils' + +VueTestUtils.config.stubs['transition-group'] = TransitionGroupStub +``` + +Для установки заглушек в настройках монтирования: + +```js +import { mount, TransitionGroupStub } from 'vue-test-utils' + +mount(Component, { + stubs: { + 'transition-group': TransitionGroupStub + } +}) +``` \ No newline at end of file diff --git a/docs/ru/api/components/TransitionStub.md b/docs/ru/api/components/TransitionStub.md new file mode 100644 index 000000000..d52a4358b --- /dev/null +++ b/docs/ru/api/components/TransitionStub.md @@ -0,0 +1,30 @@ +# TransitionStub + +Компонент для создания заглушки компонента `transition`. Вместо асинхронного выполнения переходов он возвращает дочерний компонент синхронно. + +Это настроено на заглушку всех компонентов `transition` по умолчанию в конфигурации vue-test-utils. Чтобы использовать стандартный компонент `transition` установите `config.stubs.transition` в значение false: + +```js +import VueTestUtils from 'vue-test-utils' + +VueTestUtils.config.stubs.transition = false +``` + +Чтобы переустановить обратно на заглушки компонентов `transition`: +```js +import VueTestUtils, { TransitionStub } from 'vue-test-utils' + +VueTestUtils.config.stubs.transition = TransitionStub +``` + +Для установки заглушек в настройках монтирования: + +```js +import { mount, TransitionStub } from 'vue-test-utils' + +mount(Component, { + stubs: { + transition: TransitionStub + } +}) +``` \ No newline at end of file diff --git a/docs/ru/api/config.md b/docs/ru/api/config.md new file mode 100644 index 000000000..06743f90d --- /dev/null +++ b/docs/ru/api/config.md @@ -0,0 +1,25 @@ +# Конфигурация + +vue-test-utils включает в себя объект конфигурации для определения опций, используемых vue-test-utils. + +## Конфигурация настроек `vue-test-utils` + +### `stubs` + +- Тип: `Object` +- По умолчанию: `{ + transition: TransitionStub, + 'transition-group': TransitionGroupStub +}` + +Заглушки, используемые в компонентах. Они перезаписываются значениями `stubs` переданными в настройках монтирования. + +При передаче `stubs` в качестве массива в настройках монтирования, `config.stubs` будет преобразована в массив, и будут создаваться компоненты заглушки с базовым компонентом, который возвращает div. + +Пример: + +```js +import VueTestUtils from 'vue-test-utils' + +VueTestUtils.config.stubs['my-component'] = '

' +``` \ No newline at end of file diff --git a/docs/ru/api/createLocalVue.md b/docs/ru/api/createLocalVue.md new file mode 100644 index 000000000..efb8ac0a6 --- /dev/null +++ b/docs/ru/api/createLocalVue.md @@ -0,0 +1,28 @@ +# `createLocalVue()` + +- **Возвращает:** + - `{Component}` + +- **Использование:** + +`createLocalVue` возвращает класс Vue, чтобы вы могли добавлять компоненты, примеси и устанавливать плагины без загрузнения глобального класса Vue. + +Используйте вместе с `options.localVue`: + +```js +import { createLocalVue, shallow } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const localVue = createLocalVue() +const wrapper = shallow(Foo, { + localVue, + mocks: { foo: true } +}) +expect(wrapper.vm.foo).toBe(true) + +const freshWrapper = shallow(Foo) +expect(freshWrapper.vm.foo).toBe(false) +``` + +- **См. также:** [Общие советы](../guides/common-tips.md#applying-global-plugins-and-mixins) diff --git a/docs/ru/api/mount.md b/docs/ru/api/mount.md new file mode 100644 index 000000000..be3ae512f --- /dev/null +++ b/docs/ru/api/mount.md @@ -0,0 +1,138 @@ +# `mount(component {, options}])` + +- **Принимает:** + + - `{Component} component` + - `{Object} options` + +- **Возвращает:** `{Wrapper}` + +- **Опции:** + +См. [опции монтирования](options.md) + +- **Использование:** + +Возвращает [`Wrapper`](wrapper/README.md) первого DOM-узла или компонента Vue, соответствующего селектору. + +Используйте любой валидный [селектор](selectors.md). + +**Без опций:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +describe('Foo', () => { + it('renders a div', () => { + const wrapper = mount(Foo) + expect(wrapper.contains('div')).toBe(true) + }) +}) +``` + +**С опциями Vue:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +describe('Foo', () => { + it('renders a div', () => { + const wrapper = mount(Foo, { + propsData: { + color: 'red' + } + }) + expect(wrapper.hasProp('color', 'red')).toBe(true) + }) +}) +``` + +**Прикрепление к DOM:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +describe('Foo', () => { + it('renders a div', () => { + const wrapper = mount(Foo, { + attachToDocument: true + }) + expect(wrapper.contains('div')).toBe(true) + }) +}) +``` + +**Слот по умолчанию и именованные слоты:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' +import Bar from './Bar.vue' +import FooBar from './FooBar.vue' + +describe('Foo', () => { + it('renders a div', () => { + const wrapper = mount(Foo, { + slots: { + default: [Bar, FooBar], + fooBar: FooBar, // будет соответствовать `` + foo: '
' + } + }) + expect(wrapper.contains('div')).toBe(true) + }) +}) +``` + +**Заглушки глобальных свойств:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +describe('Foo', () => { + it('renders a div', () => { + const $route = { path: 'http://www.example-path.com' } + const wrapper = mount(Foo, { + mocks: { + $route + } + }) + expect(wrapper.vm.$route.path).toBe($route.path) + }) +}) +``` + +**Заглушки компонентов:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' +import Bar from './Bar.vue' +import Faz from './Faz.vue' + +describe('Foo', () => { + it('renders a div', () => { + const wrapper = mount(Foo, { + stub: { + Bar: '
` + foo: '
' + } +}) +expect(wrapper.find('div')).toBe(true) +``` + +### `stubs` + +- Тип: `{ [name: string]: Component | boolean } | Array` + +Заглушки дочерних компонентов. Может быть массивом имен компонентов заменяемых заглушкой, или объектом. + +Пример: + +```js +import Foo from './Foo.vue' + +mount(Component, { + stubs: ['registered-component'] +}) + +shallow(Component, { + stubs: { + // заглушка со специальной реализацией + 'registered-component': Foo, + // создание обычной заглушки + 'another-component': true + } +}) +``` + +### `mocks` + +- Тип: `Object` + +Дополнительные свойства для экземпляра. Полезно при создании моков глобальных инъекций. + +Пример: + +```js +import { expect } from 'chai' + +const $route = { path: 'http://www.example-path.com' } +const wrapper = shallow(Component, { + mocks: { + $route + } +}) +expect(wrapper.vm.$route.path).toBe($route.path) +``` + +### `localVue` + +- Тип: `Vue` + +Локальная копия Vue, созданная с помощью [`createLocalVue`](./createLocalVue.md) для использования при монтировании компонента. Установка плагинов на этой копии `Vue` предотвращает загрязнение оригинальной копии `Vue`. + +Пример: + +```js +import { createLocalVue, mount } from 'vue-test-utils' +import VueRouter from 'vue-router' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const localVue = createLocalVue() +localVue.use(VueRouter) + +const routes = [ + { path: '/foo', component: Foo } +] + +const router = new VueRouter({ + routes +}) + +const wrapper = mount(Component, { + localVue, + router +}) +expect(wrapper.vm.$route).toBeInstanceOf(Object) +``` + +### `attachToDocument` + +- Тип: `boolean` +- По умолчанию: `false` + +Компонент будет прикрепляться к DOM при рендеринге, если установлено в `true`. Это может использоваться с [`hasStyle`](wrapper/hasStyle.md) для проверки селекторов CSS на нескольких элементах. + +### `attrs` + +- Тип: `Object` + +Устанавливает объект `$attrs` на экземпляре компонента. + +### `listeners` + +- Тип: `Object` + +Устанавливает объект `$listeners` на экземпляре компонента. + +### `clone` + +- Тип: `boolean` +- По умолчанию: `true` + +Клонирует компонент перед монтированием, если установлено в `true`, что позволяет избежать мутаций оригинального определения компонента. + +`options.mocks` (`Object`): Добавляет глобальные свойства в экземпляр Vue. + +`options.localVue` (`Object`): `Vue` класс для использования в `mount`. См. также [`createLocalVue`](createLocalVue.md) diff --git a/docs/ru/api/selectors.md b/docs/ru/api/selectors.md new file mode 100644 index 000000000..64b66a968 --- /dev/null +++ b/docs/ru/api/selectors.md @@ -0,0 +1,54 @@ +# Селекторы + +Многие методы принимают селектор в качестве аргумента. Селектором может быть CSS селектор, компонент Vue или опция поиска объекта. + +## CSS селекторы + +Обрабатывают любой допустимый CSS селектор: + +- селекторы тегов (`div`, `foo`, `bar`) +- селекторы классов (`.foo`, `.bar`) +- селекторы атрибутов (`[foo]`, `[foo="bar"]`) +- селекторы id (`#foo`, `#bar`) +- селекторы псевдо-элементов (`div:first-of-type`) + +Вы также можете использовать комбинации: + +- выбор только непосредственных потомков (`div > #bar > .foo`) +- выбор элементов, являющихся потомками (`div #bar .foo`) +- селектор выбора соседа идущего за элементом (`div + .foo`) +- селектор выбора соседей идущих после элемента (`div ~ .foo`) + +## Компоненты Vue + +Компоненты Vue также являются допустимыми селекторами. + +`vue-test-utils` использует свойство `name` для поиска экземпляра в дереве компонентов, соответствующих компоненту Vue. + +```js +// Foo.vue + +export default { + name: 'FooComponent' +} +``` + +```js +import { shallow } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = shallow(Foo) +expect(wrapper.is(Foo)).toBe(true) +``` + +## Опция поиска объекта + +### Ref + +Использование опции поиска объекта позволяет `vue-test-utils` выбирать элементы по `$ref` на компонентах обёрток. + +```js +const buttonWrapper = wrapper.find({ ref: 'myButton' }); +buttonWrapper.trigger('click'); +``` \ No newline at end of file diff --git a/docs/ru/api/shallow.md b/docs/ru/api/shallow.md new file mode 100644 index 000000000..3704b333c --- /dev/null +++ b/docs/ru/api/shallow.md @@ -0,0 +1,124 @@ +# `shallow(component {, options}])` + +- **Принимает:** + + - `{Component} component` + - `{Object} options` + - `{boolean} attachToDocument` + - `{Object} context` + - `{Object} slots` + - `{Array|Component|String} default` + - `{Array|Component|String} named` + - `{Object} mocks` + - `{Object|Array} stubs` + - `{boolean} clone` + - `{Object} children` + - `{Vue} localVue` + +- **Возвращает:** `{Wrapper}` + +- **Опции:** + +См. [опции монтирования](./options.md) + +- **Использование:** + +Возвращает [`Wrapper`](./wrapper/README.md) первого DOM-узла или компонента Vue, соответствующего селектору. + +Создаёт заглушки для всех дочерних компонентов. + +Используйте любой валидный [селектор](./selectors.md). + +**Без опций:** + +```js +import { shallow } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +describe('Foo', () => { + it('renders a div', () => { + const wrapper = shallow(Foo) + expect(wrapper.contains('div')).toBe(true) + }) +}) +``` + +**С опциями Vue:** + +```js +import { shallow } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +describe('Foo', () => { + it('renders a div', () => { + const wrapper = shallow(Foo, { + propsData: { + color: 'red' + } + }) + expect(wrapper.hasProp('color', 'red')).toBe(true) + }) +}) +``` + +**Прикрепление к DOM:** + +```js +import { shallow } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +describe('Foo', () => { + it('renders a div', () => { + const wrapper = shallow(Foo, { + attachToDocument: true + }) + expect(wrapper.contains('div')).toBe(true) + }) +}) +``` + +**Слот по умолчанию и именованные слоты:** + +```js +import { shallow } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' +import Bar from './Bar.vue' +import FooBar from './FooBar.vue' + +describe('Foo', () => { + it('renders a div', () => { + const wrapper = shallow(Foo, { + slots: { + default: [Bar, FooBar], + fooBar: FooBar, // будет соответствовать , + foo: '
' + } + }) + expect(wrapper.find('div')).toBe(true) + }) +}) +``` + +**Заглушки глобальных свойств:** + +```js +import { shallow } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +describe('Foo', () => { + it('renders a div', () => { + const $route = { path: 'http://www.example-path.com' } + const wrapper = shallow(Foo, { + mocks: { + $route + } + }) + expect(wrapper.vm.$route.path).toBe($route.path) + }) +}) +``` diff --git a/docs/ru/api/wrapper-array/README.md b/docs/ru/api/wrapper-array/README.md new file mode 100644 index 000000000..5281b2476 --- /dev/null +++ b/docs/ru/api/wrapper-array/README.md @@ -0,0 +1,11 @@ +# WrapperArray + +`WrapperArray` — это объект, содержащий массив [`Wrappers`](../wrapper/README.md), и методы для тестирования `Wrappers`. + +- **Свойства:** + +`length` `number`: the number of `Wrappers` contained in the `WrapperArray` + + - **Методы:** + +Подробный список методов можно изучить в разделе документации о `WrapperArray`. diff --git a/docs/ru/api/wrapper-array/at.md b/docs/ru/api/wrapper-array/at.md new file mode 100644 index 000000000..371956c3e --- /dev/null +++ b/docs/ru/api/wrapper-array/at.md @@ -0,0 +1,21 @@ +# at(index) + +Возвращает `Wrapper` по указанному индексу `index`. Используется нумерация с нуля (т.е. первый элемент имеет индекс 0). + +- **Принимает:** + - `{number} index` + +- **Возвращает:** `{Wrapper}` + +- **Пример:** + +```js +import { shallow } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = shallow(Foo) +const divArray = wrapper.findAll('div') +const secondDiv = divArray.at(1) +expect(secondDiv.is('p')).toBe(true) +``` diff --git a/docs/ru/api/wrapper-array/contains.md b/docs/ru/api/wrapper-array/contains.md new file mode 100644 index 000000000..626efb138 --- /dev/null +++ b/docs/ru/api/wrapper-array/contains.md @@ -0,0 +1,24 @@ +# contains(selector) + +Проверка, что каждый `Wrapper` в `WrapperArray` содержит указанный селектор. + +Используйте любой валидный [селектор](../selectors.md). + +- **Принимает:** + - `{string|Component} selector` + +- **Возвращает:** `{boolean}` + +- **Пример:** + +```js +import { shallow } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' +import Bar from './Bar.vue' + +const wrapper = shallow(Foo) +const divArray = wrapper.findAll('div') +expect(divArray.contains('p')).toBe(true) +expect(divArray.contains(Bar)).toBe(true) +``` diff --git a/docs/ru/api/wrapper-array/destroy.md b/docs/ru/api/wrapper-array/destroy.md new file mode 100644 index 000000000..681d43192 --- /dev/null +++ b/docs/ru/api/wrapper-array/destroy.md @@ -0,0 +1,17 @@ +# destroy() + +Уничтожает каждый `Wrapper` Vue в `WrapperArray`. + +- **Пример:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +const divArray = wrapper.findAll('div') +expect(divArray.contains('p')).toBe(true) +divArray.destroy() +expect(divArray.contains('p')).toBe(false) +``` \ No newline at end of file diff --git a/docs/ru/api/wrapper-array/hasAttribute.md b/docs/ru/api/wrapper-array/hasAttribute.md new file mode 100644 index 000000000..d63337f01 --- /dev/null +++ b/docs/ru/api/wrapper-array/hasAttribute.md @@ -0,0 +1,21 @@ +# hasAttribute(attribute, value) + +Проверка, что каждый `Wrapper` в `WrapperArray` DOM узле имеет атрибут `attribute` с значением `value`. + +- **Принимает:** + - `{string} attribute` + - `{string} value` + +- **Возвращает:** `{boolean}` + +- **Пример:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +const divArray = wrapper.findAll('div') +expect(divArray.hasAttribute('id', 'foo')).toBe(true) +``` diff --git a/docs/ru/api/wrapper-array/hasClass.md b/docs/ru/api/wrapper-array/hasClass.md new file mode 100644 index 000000000..d19a54b1d --- /dev/null +++ b/docs/ru/api/wrapper-array/hasClass.md @@ -0,0 +1,20 @@ +# hasClass(className) + +Проверка, что каждый `Wrapper` в `WrapperArray` DOM узле имеет класс `className`. + +- **Принимает:** + - `{string} className` + +- **Возвращает:** `{boolean}` + +- **Пример:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +const divArray = wrapper.findAll('div') +expect(divArray.hasClass('bar')).toBe(true) +``` diff --git a/docs/ru/api/wrapper-array/hasProp.md b/docs/ru/api/wrapper-array/hasProp.md new file mode 100644 index 000000000..34b33dde5 --- /dev/null +++ b/docs/ru/api/wrapper-array/hasProp.md @@ -0,0 +1,24 @@ +# hasProp(prop, value) + +Проверка, что каждый `Wrapper` в `WrapperArray` `vm` имеет входной параметр `prop` с значением `value`. + +**Обратите внимание, что `Wrapper` должен содержать экземпляр Vue.** + +- **Принимает:** + - `{string} prop` + - `{any} value` + +- **Возвращает:** `{boolean}` + +- **Пример:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' +import Bar from './Bar.vue' + +const wrapper = mount(Foo) +const barArray = wrapper.findAll(Bar) +expect(barArray.hasProp('bar', 10)).toBe(true) +``` diff --git a/docs/ru/api/wrapper-array/hasStyle.md b/docs/ru/api/wrapper-array/hasStyle.md new file mode 100644 index 000000000..021977cf1 --- /dev/null +++ b/docs/ru/api/wrapper-array/hasStyle.md @@ -0,0 +1,25 @@ +# hasStyle(style, value) + +Проверка, что каждый `Wrapper` в `WrapperArray` DOM-узле имеет стиль с указанным значением. + +Возвращает `true` если `Wrapper` DOM-узел имеет `style` с значением `value`. + +**Обратите внимание, что определяются только inline-стили при запуске в `jsdom`.** + +- **Принимает:** + - `{string} style` + - `{string} value` + +- **Возвращает:** `{boolean}` + +- **Пример:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +const divArray = wrapper.findAll('div') +expect(divArray.hasStyle('color', 'red')).toBe(true) +``` diff --git a/docs/ru/api/wrapper-array/is.md b/docs/ru/api/wrapper-array/is.md new file mode 100644 index 000000000..f9725e5b4 --- /dev/null +++ b/docs/ru/api/wrapper-array/is.md @@ -0,0 +1,20 @@ +# is(selector) + +Проверка, что каждый `Wrapper` в `WrapperArray` DOM узле или `vm` соответствует [селектору](../selectors.md). + +- **Принимает:** + - `{string|Component} selector` + +- **Возвращает:** `{boolean}` + +- **Пример:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +const divArray = wrapper.find('div') +expect(divArray.is('div')).toBe(true) +``` diff --git a/docs/ru/api/wrapper-array/isEmpty.md b/docs/ru/api/wrapper-array/isEmpty.md new file mode 100644 index 000000000..3e780544b --- /dev/null +++ b/docs/ru/api/wrapper-array/isEmpty.md @@ -0,0 +1,17 @@ +# isEmpty() + +Проверка, что каждый `Wrapper` в `WrapperArray` не содержит дочерних узлов. + +- **Возвращает:** `{boolean}` + +- **Пример:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +const divArray = wrapper.findAll('div') +expect(divArray.isEmpty()).toBe(true) +``` diff --git a/docs/ru/api/wrapper-array/isVueInstance.md b/docs/ru/api/wrapper-array/isVueInstance.md new file mode 100644 index 000000000..0ce020352 --- /dev/null +++ b/docs/ru/api/wrapper-array/isVueInstance.md @@ -0,0 +1,18 @@ +# isVueInstance() + +Проверка, что каждый `Wrapper` в `WrapperArray` является экземпляром Vue. + +- **Возвращает:** `{boolean}` + +- **Пример:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' +import Bar from './Bar.vue' + +const wrapper = mount(Foo) +const barArray = wrapper.findAll(Bar) +expect(barArray.isVueInstance()).toBe(true) +``` diff --git a/docs/ru/api/wrapper-array/setComputed.md b/docs/ru/api/wrapper-array/setComputed.md new file mode 100644 index 000000000..686d44f70 --- /dev/null +++ b/docs/ru/api/wrapper-array/setComputed.md @@ -0,0 +1,25 @@ +# setComputed(computedObjects) + +Устанавливает для `Wrapper` `vm` вычисляемое свойство и принудительно обновляет каждый `Wrapper` в `WrapperArray`. + +**Каждый `Wrapper` должен содержать экземпляр Vue.** +**Каждый экземпляр Vue должен уже иметь вычисляемое свойство, переданные в `setComputed`.** + +- **Аргументы:** + - `{Object} вычисляемые свойства` + +- **Пример:** + +```js +import { mount } from 'vue-test-utils' +import Foo from './Foo.vue' +import Bar from './Bar.vue' + +const wrapper = mount(Foo) +const barArray = wrapper.findAll(Bar) + +barArray.setComputed({ + computed1: 'new-computed1', + computed2: 'new-computed2' +}) +``` \ No newline at end of file diff --git a/docs/ru/api/wrapper-array/setData.md b/docs/ru/api/wrapper-array/setData.md new file mode 100644 index 000000000..72df94746 --- /dev/null +++ b/docs/ru/api/wrapper-array/setData.md @@ -0,0 +1,22 @@ +# setData(data) + +Устанавливает данные `Wrapper` `vm` и выполняет принудительное обновление каждого `Wrapper` в `WrapperArray`. + +**Обратите внимание, что каждый `Wrapper` должен содержать экземпляр Vue.** + +- **Принимает:** + - `{Object} data` + +- **Пример:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' +import Bar from './Bar.vue' + +const wrapper = mount(Foo) +const barArray = wrapper.findAll(Bar) +barArray.setData({ foo: 'bar' }) +expect(barArray.at(0).vm.foo).toBe('bar') +``` diff --git a/docs/ru/api/wrapper-array/setMethods.md b/docs/ru/api/wrapper-array/setMethods.md new file mode 100644 index 000000000..dc34a7981 --- /dev/null +++ b/docs/ru/api/wrapper-array/setMethods.md @@ -0,0 +1,27 @@ + +# setMethods(methods) + +Устанавливает методы `Wrapper` `vm` и выполняет принудительное обновление каждого `Wrapper` в `WrapperArray`. + +**Обратите внимание, что каждый `Wrapper` должен содержать экземпляр Vue.** + +- **Принимает:** + - `{Object} methods` + +- **Пример:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import sinon from 'sinon' +import Foo from './Foo.vue' +import Bar from './Bar.vue' + +const wrapper = mount(Foo) +const barArray = wrapper.findAll(Bar) +const clickMethodStub = sinon.stub() + +barArray.setMethods({ clickMethod: clickMethodStub }) +barArray.at(0).trigger('click') +expect(clickMethodStub.called).toBe(true) +``` diff --git a/docs/ru/api/wrapper-array/setProps.md b/docs/ru/api/wrapper-array/setProps.md new file mode 100644 index 000000000..b85290ef0 --- /dev/null +++ b/docs/ru/api/wrapper-array/setProps.md @@ -0,0 +1,22 @@ +# setProps(props) + +Устанавливает входные параметры `Wrapper` `vm` и выполняет принудительное обновление каждого `Wrapper` в `WrapperArray`. + +**Обратите внимание, что каждый `Wrapper` должен содержать экземпляр Vue.** + +- **Принимает:** + - `{Object} props` + +- **Пример:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' +import Bar from './Bar.vue' + +const wrapper = mount(Foo) +const barArray = wrapper.findAll(Bar) +barArray.setProps({ foo: 'bar' }) +expect(barArray.at(0).vm.foo).toBe('bar') +``` diff --git a/docs/ru/api/wrapper-array/trigger.md b/docs/ru/api/wrapper-array/trigger.md new file mode 100644 index 000000000..5122ddb2f --- /dev/null +++ b/docs/ru/api/wrapper-array/trigger.md @@ -0,0 +1,26 @@ +# trigger(eventName) + +Генерирует событие на каждом `Wrapper` в `WrapperArray` DOM узле. + +**Обратите внимание, что каждый `Wrapper` должен содержать экземпляр Vue.** + +- **Принимает:** + - `{string} eventName` + +- **Пример:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import sinon from 'sinon' +import Foo from './Foo.vue' + +const clickHandler = sinon.stub() +const wrapper = mount(Foo, { + propsData: { clickHandler } +}) + +const divArray = wrapper.findAll('div') +divArray.trigger('click') +expect(clickHandler.called).toBe(true) +``` diff --git a/docs/ru/api/wrapper-array/update.md b/docs/ru/api/wrapper-array/update.md new file mode 100644 index 000000000..d0afb3526 --- /dev/null +++ b/docs/ru/api/wrapper-array/update.md @@ -0,0 +1,20 @@ +# update() + +Вызывает принудительный перерендеринг корневого компонента Vue у каждого `Wrapper` в `WrapperArray`. + +Если вызывается на массиве wrapper, содержащем компоненты Vue, то будет вызван принудительный перерендеринг каждого компонента Vue. + +- **Пример:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +const divArray = wrapper.findAll('div') +expect(divArray.at(0).vm.bar).toBe('bar') +divArray.at(0).vm.bar = 'new value' +divArray.update() +expect(divArray.at(0).vm.bar).toBe('new value') +``` diff --git a/docs/ru/api/wrapper/README.md b/docs/ru/api/wrapper/README.md new file mode 100644 index 000000000..2ac3e8235 --- /dev/null +++ b/docs/ru/api/wrapper/README.md @@ -0,0 +1,16 @@ +# `Wrapper` + +`vue-test-utils` — это API основанное на использовании обёрток (wrapper). + +`Wrapper` — это объект, который содержит примонтированный компонент или VNode и методы для тестирования компонента или VNnode. + +- **Свойства:** + +`vm` `Component`: это экземпляр `Vue`. Вы можете получить доступ ко всем [методам и свойствам экземпляра](https://ru.vuejs.org/v2/api/index.html#Опции-—-данные) через `wrapper.vm`. Это существует только в обёртках для компонентов Vue +`element` `HTMLElement`: корневой DOM-узел обёртки +`options` `Object`: Объект содержащий опции `vue-test-utils`, передаваемые в `mount` или `shallow` +`options.attachedToDom` `Boolean`: `true` если был передан `attachToDom` в `mount` или `shallow` + +- **Методы:** + +Подробный список методов можно изучить в разделе документации про wrapper. diff --git a/docs/ru/api/wrapper/contains.md b/docs/ru/api/wrapper/contains.md new file mode 100644 index 000000000..7621013ab --- /dev/null +++ b/docs/ru/api/wrapper/contains.md @@ -0,0 +1,23 @@ +# `contains(selector)` + +Проверка, что `Wrapper` содержит элемент или компонент, соответствующий [селектору](../selectors.md). + +- **Принимает:** + - `{string|Component} selector` + +- **Возвращает:** `{boolean}` + +- **Пример:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' +import Bar from './Bar.vue' + +const wrapper = mount(Foo) +expect(wrapper.contains('p')).toBe(true) +expect(wrapper.contains(Bar)).toBe(true) +``` + +- **См. также:** [Селекторы](../selectors.md) diff --git a/docs/ru/api/wrapper/destroy.md b/docs/ru/api/wrapper/destroy.md new file mode 100644 index 000000000..851ad783e --- /dev/null +++ b/docs/ru/api/wrapper/destroy.md @@ -0,0 +1,20 @@ +# destroy() + +Уничтожает экземпляр компонента Vue. + +- **Пример:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import sinon from 'sinon' + +const spy = sinon.stub() +mount({ + render: null, + destroyed () { + spy() + } +}).destroy() +expect(spy.calledOnce).to.equal(true) +``` \ No newline at end of file diff --git a/docs/ru/api/wrapper/emitted.md b/docs/ru/api/wrapper/emitted.md new file mode 100644 index 000000000..df0b1a7aa --- /dev/null +++ b/docs/ru/api/wrapper/emitted.md @@ -0,0 +1,46 @@ +# emitted() + +Возвращает объект, содержащий вызванные пользовательские события в `Wrapper` `vm`. + +- **Возвращает:** `{ [name: string]: Array> }` + +- **Пример:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' + +const wrapper = mount(Component) + +wrapper.vm.$emit('foo') +wrapper.vm.$emit('foo', 123) + +/* +wrapper.emitted() возвращает следующий объект: +{ + foo: [[], [123]] +} +*/ + +// проверка, что событие было вызвано +expect(wrapper.emitted().foo).toBeTruthy() + +// проверка, что событие вызывалось определённое число раз +expect(wrapper.emitted().foo.length).toBe(2) + +// проверка, что с событием были переданы определённые данные +expect(wrapper.emitted().foo[1]).toEqual([123]) +``` + +Вы также можете написать это так: + +```js +// проверка, что событие было вызвано +expect(wrapper.emitted('foo')).toBeTruthy() + +// проверка, что событие вызывалось определённое число раз +expect(wrapper.emitted('foo').length).toBe(2) + +// проверка, что с событием были переданы определённые данные +expect(wrapper.emitted('foo')[1]).toEqual([123]) +``` \ No newline at end of file diff --git a/docs/ru/api/wrapper/emittedByOrder.md b/docs/ru/api/wrapper/emittedByOrder.md new file mode 100644 index 000000000..92cbf8946 --- /dev/null +++ b/docs/ru/api/wrapper/emittedByOrder.md @@ -0,0 +1,28 @@ +# emittedByOrder() + +Возвращает массив, содержащий вызванные пользовательские события в `Wrapper` `vm`. + +- **Возвращает:** `Array<{ name: string, args: Array }>` + +- **Пример:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' + +const wrapper = mount(Component) + +wrapper.vm.$emit('foo') +wrapper.vm.$emit('bar', 123) + +/* +wrapper.emittedByOrder() возвращает следующий массив: +[ + { name: 'foo', args: [] }, + { name: 'bar', args: [123] } +] +*/ + +// проверка, что события были вызваны в определённом порядке +expect(wrapper.emittedByOrder().map(e => e.name)).toEqual(['foo', 'bar']) +``` diff --git a/docs/ru/api/wrapper/exists.md b/docs/ru/api/wrapper/exists.md new file mode 100644 index 000000000..16ced1177 --- /dev/null +++ b/docs/ru/api/wrapper/exists.md @@ -0,0 +1,21 @@ +# exists() + +Проверка, что `Wrapper` или `WrapperArray` существует. + +Возвращает `false` если вызывается на пустом `Wrapper` или `WrapperArray`. + +- **Возвращает:** `{boolean}` + +- **Пример:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +expect(wrapper.exists()).toBe(true) +expect(wrapper.find('does-not-exist').exists()).toBe(false) +expect(wrapper.findAll('div').exists()).toBe(true) +expect(wrapper.findAll('does-not-exist').exists()).toBe(false) +``` diff --git a/docs/ru/api/wrapper/find.md b/docs/ru/api/wrapper/find.md new file mode 100644 index 000000000..203e273b8 --- /dev/null +++ b/docs/ru/api/wrapper/find.md @@ -0,0 +1,27 @@ +# find(selector) + +Возвращает [`Wrapper`](README.md) первого DOM узла или компонента Vue, соответствующий селектору. + +Используйте любой валидный [селектор](../selectors.md). + +- **Принимает:** + - `{string|Component} selector` + +- **Возвращает:** `{Wrapper}` + +- **Пример:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' +import Bar from './Bar.vue' + +const wrapper = mount(Foo) +const div = wrapper.find('div') +expect(div.is('div')).toBe(true) +const bar = wrapper.find(Bar) +expect(bar.is(Bar)).toBe(true) +``` + +- **См. также:** [Wrapper](README.md) diff --git a/docs/ru/api/wrapper/findAll.md b/docs/ru/api/wrapper/findAll.md new file mode 100644 index 000000000..8cc97bf1b --- /dev/null +++ b/docs/ru/api/wrapper/findAll.md @@ -0,0 +1,27 @@ +# findAll(selector) + +Возвращает [`WrapperArray`](../wrapper-array/README.md), состоящий из [Wrappers](README.md). + +Используйте любой валидный [селектор](../selectors.md). + +- **Принимает:** + - `{string|Component} selector` + +- **Возвращает:** `{WrapperArray}` + +- **Пример:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' +import Bar from './Bar.vue' + +const wrapper = mount(Foo) +const div = wrapper.findAll('div').at(0) +expect(div.is('div')).toBe(true) +const bar = wrapper.findAll(Bar).at(0) +expect(bar.is(Bar)).toBe(true) +``` + +- **См. также:** [Wrapper](README.md) diff --git a/docs/ru/api/wrapper/hasAttribute.md b/docs/ru/api/wrapper/hasAttribute.md new file mode 100644 index 000000000..4d9742dfa --- /dev/null +++ b/docs/ru/api/wrapper/hasAttribute.md @@ -0,0 +1,37 @@ +# hasAttribute(attribute, value) + +Проверка, что `Wrapper` DOM узел имеет атрибут с указанным значением. + +Возвращает `true` если `Wrapper` DOM узел содержит атрибут с указанным значением. + +- **Принимает:** + - `{string} attribute` + - `{string} value` + +- **Возвращает:** `{boolean}` + +- **Пример:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +expect(wrapper.hasAttribute('id', 'foo')).toBe(true) +``` + +- **Альтернатива:** + +Вы можете получить атрибут из `Wrapper.element` чтобы получить значение для проверки: + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +expect(wrapper.element.getAttribute('id')).toBe('foo') +``` + +Это сделает ошибку при проверке более информативной. diff --git a/docs/ru/api/wrapper/hasClass.md b/docs/ru/api/wrapper/hasClass.md new file mode 100644 index 000000000..450823782 --- /dev/null +++ b/docs/ru/api/wrapper/hasClass.md @@ -0,0 +1,21 @@ +# hasClass(className) + +Проверка, что `Wrapper` DOM узел имеет класс, содержащий `className`. + +Возвращает `true` если `Wrapper` DOM узел содержит класс. + +- **Принимает:** + - `{string} className` + +- **Возвращает:** `{boolean}` + +- **Пример:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +expect(wrapper.hasClass('bar')).toBe(true) +``` diff --git a/docs/ru/api/wrapper/hasProp.md b/docs/ru/api/wrapper/hasProp.md new file mode 100644 index 000000000..4294068f8 --- /dev/null +++ b/docs/ru/api/wrapper/hasProp.md @@ -0,0 +1,24 @@ +# hasProp(prop, value) + +Проверка, что `Wrapper` `vm` имеет `prop` с значением `value`. + +Возвращает `true` если `Wrapper` `vm` имеет `prop` с значением `value`. + +**Обратите внимание, что `Wrapper` должен содержать экземпляр Vue.** + +- **Принимает:** + - `{string} prop` + - `{any} value` + +- **Возвращает:** `{boolean}` + +- **Пример:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +expect(wrapper.hasProp('bar', 10)).toBe(true) +``` diff --git a/docs/ru/api/wrapper/hasStyle.md b/docs/ru/api/wrapper/hasStyle.md new file mode 100644 index 000000000..c4a801d3d --- /dev/null +++ b/docs/ru/api/wrapper/hasStyle.md @@ -0,0 +1,24 @@ +# hasStyle(style, value) + +Проверка, что `Wrapper` DOM узел имеет стиль, соответствующий указанном значению + +Возвращает `true` если `Wrapper` DOM узел имеет стиль `style` совпадающий с `value`. + +**Обратите внимание, что определяются только inline-стили при запуске в `jsdom`.** + +- **Принимает:** + - `{string} style` + - `{string} value` + +- **Возвращает:** `{boolean}` + +- **Пример:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +expect(wrapper.hasStyle('color', 'red')).toBe(true) +``` diff --git a/docs/ru/api/wrapper/html.md b/docs/ru/api/wrapper/html.md new file mode 100644 index 000000000..6a12609dc --- /dev/null +++ b/docs/ru/api/wrapper/html.md @@ -0,0 +1,16 @@ +# html() + +Возвращает HTML `Wrapper` DOM узла в виде строки. + +- **Возвращает:** `{string}` + +- **Пример:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +expect(wrapper.html()).toBe('

Foo

') +``` diff --git a/docs/ru/api/wrapper/is.md b/docs/ru/api/wrapper/is.md new file mode 100644 index 000000000..2f658e532 --- /dev/null +++ b/docs/ru/api/wrapper/is.md @@ -0,0 +1,19 @@ +# is(selector) + +Проверяет, что DOM-элемент `Wrapper` или `vm` соответствуют [селектору](../selectors.md). + +- **Принимает:** + - `{string|Component} selector` + +- **Возвращает:** `{boolean}` + +- **Пример:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +expect(wrapper.is('div')).toBe(true) +``` diff --git a/docs/ru/api/wrapper/isEmpty.md b/docs/ru/api/wrapper/isEmpty.md new file mode 100644 index 000000000..97fea64b2 --- /dev/null +++ b/docs/ru/api/wrapper/isEmpty.md @@ -0,0 +1,16 @@ +# isEmpty() + +Проверяет, что `Wrapper` не содержит дочерних узлов. + +- **Возвращает:** `{boolean}` + +- **Пример:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +expect(wrapper.isEmpty()).toBe(true) +``` diff --git a/docs/ru/api/wrapper/isVueInstance.md b/docs/ru/api/wrapper/isVueInstance.md new file mode 100644 index 000000000..91168eeb7 --- /dev/null +++ b/docs/ru/api/wrapper/isVueInstance.md @@ -0,0 +1,16 @@ +# isVueInstance() + +Проверка, что `Wrapper` является экземпляром Vue. + +- **Возвращает:** `{boolean}` + +- **Пример:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +expect(wrapper.isVueInstance()).toBe(true) +``` diff --git a/docs/ru/api/wrapper/name.md b/docs/ru/api/wrapper/name.md new file mode 100644 index 000000000..c9c9cbcfb --- /dev/null +++ b/docs/ru/api/wrapper/name.md @@ -0,0 +1,18 @@ +# name() + +Возвращает имя компонента, если `Wrapper` содержит экземпляр Vue, или имя тега DOM-узла `Wrapper` если `Wrapper` не содержит экземпляра Vue. + +- **Возвращает:** `{string}` + +- **Пример:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +expect(wrapper.name()).toBe('Foo') +const p = wrapper.find('p') +expect(p.name()).toBe('p') +``` diff --git a/docs/ru/api/wrapper/setComputed.md b/docs/ru/api/wrapper/setComputed.md new file mode 100644 index 000000000..975630fe1 --- /dev/null +++ b/docs/ru/api/wrapper/setComputed.md @@ -0,0 +1,43 @@ +# setComputed(computedProperties) + +Устанавливает для `Wrapper` `vm` вычисляемое свойство и вызывает принудительное обновление. + +**Wrapper должен содержать экземпляр Vue.** +**Экземпляр Vue должен уже содержать вычисляемые свойства, переданные в `setComputed`.** + + +- **Аргументы:** + - `{Object} вычисляемые свойства` + +- **Пример:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' + +const wrapper = mount({ + template: '
{{ computed1 }} {{ computed2 }}
', + data () { + return { + initial: 'initial' + }; + }, + computed: { + computed1 () { + return this.initial + }, + computed2 () { + return this.initial + }, + } +}) + +expect(wrapper.html()).toBe('
initial initial
') + +wrapper.setComputed({ + computed1: 'new-computed1', + computed2: 'new-computed2' +}) + +expect(wrapper.html()).toBe('
new-computed1 new-computed2
') +``` \ No newline at end of file diff --git a/docs/ru/api/wrapper/setData.md b/docs/ru/api/wrapper/setData.md new file mode 100644 index 000000000..98a4a6094 --- /dev/null +++ b/docs/ru/api/wrapper/setData.md @@ -0,0 +1,20 @@ +# setData(data) + +Устанавливает данные `Wrapper` `vm` и выполняет принудительное обновление. + +**Обратите внимание, что `Wrapper` должен содержать экземпляр Vue.** + +- **Принимает:** + - `{Object} data` + +- **Пример:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +wrapper.setData({ foo: 'bar' }) +expect(wrapper.vm.foo).toBe('bar') +``` diff --git a/docs/ru/api/wrapper/setMethods.md b/docs/ru/api/wrapper/setMethods.md new file mode 100644 index 000000000..896265988 --- /dev/null +++ b/docs/ru/api/wrapper/setMethods.md @@ -0,0 +1,24 @@ +# setMethods(methods) + +Устанавливает методы `Wrapper` `vm` и выполняет принудительное обновление. + +**Обратите внимание, что `Wrapper` должен содержать экземпляр Vue.** + +- **Принимает:** + - `{Object} methods` + +- **Пример:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import sinon from 'sinon' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +const clickMethodStub = sinon.stub() + +wrapper.setMethods({ clickMethod: clickMethodStub }) +wrapper.find('button').trigger('click') +expect(clickMethodStub.called).toBe(true) +``` diff --git a/docs/ru/api/wrapper/setProps.md b/docs/ru/api/wrapper/setProps.md new file mode 100644 index 000000000..483ff36e7 --- /dev/null +++ b/docs/ru/api/wrapper/setProps.md @@ -0,0 +1,48 @@ +# setProps(props) + +- **Принимает:** + - `{Object} props` + +- **Использование:** + +Устанавливает входные параметры `Wrapper` `vm` и выполняет принудительное обновление. + +**Обратите внимание, что `Wrapper` должен содержать экземпляр Vue.** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +wrapper.setProps({ foo: 'bar' }) +expect(wrapper.vm.foo).to.equal('bar') +``` + +Вы также можете передать объект `propsData`, который инициализирует экземпляр Vue с переданными значениями. + +``` js +// Foo.vue +export default { + props: { + foo: { + type: String, + required: true + } + } +} +``` + +``` js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo, { + propsData: { + foo: 'bar' + } +}) + +expect(wrapper.vm.foo).to.equal('bar') +``` \ No newline at end of file diff --git a/docs/ru/api/wrapper/text.md b/docs/ru/api/wrapper/text.md new file mode 100644 index 000000000..39991469f --- /dev/null +++ b/docs/ru/api/wrapper/text.md @@ -0,0 +1,16 @@ +# text() + +Возвращает текстовое содержимое `Wrapper`. + +- **Возвращает:** `{string}` + +- **Пример:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +expect(wrapper.text()).toBe('bar') +``` diff --git a/docs/ru/api/wrapper/trigger.md b/docs/ru/api/wrapper/trigger.md new file mode 100644 index 000000000..a7ad80ef9 --- /dev/null +++ b/docs/ru/api/wrapper/trigger.md @@ -0,0 +1,38 @@ +# trigger(eventName) + +Вызывает событие на `Wrapper` DOM узле. + +В `trigger` также можно передать опциональный объект `options`. Свойства объекта `options` будут добавлены к Event. + +Вы можете вызвать `preventDefault` на событие передав `preventDefault: true` в `options`. + +- **Принимает:** + - `{string} eventName` + - `{Object} options` + - `{boolean} preventDefault` + +- **Пример:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import sinon from 'sinon' +import Foo from './Foo' + +const clickHandler = sinon.stub() +const wrapper = mount(Foo, { + propsData: { clickHandler } +}) + +wrapper.trigger('click') + +wrapper.trigger('click', { + button: 0 +}) + +wrapper.trigger('click', { + preventDefault: true +}) + +expect(clickHandler.called).toBe(true) +``` diff --git a/docs/ru/api/wrapper/update.md b/docs/ru/api/wrapper/update.md new file mode 100644 index 000000000..0f5eb6c16 --- /dev/null +++ b/docs/ru/api/wrapper/update.md @@ -0,0 +1,19 @@ +# update() + +Принудительный перерендеринг корневого компонента Vue. + +Если вызывается на `Wrapper` содержащем `vm`, то будет вызван принудительный перерендеринг `Wrapper` `vm`. + +- **Пример:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +expect(wrapper.vm.bar).toBe('bar') +wrapper.vm.bar = 'new value' +wrapper.update() +expect(wrapper.vm.bar).toBe('new value') +``` diff --git a/docs/ru/guides/README.md b/docs/ru/guides/README.md new file mode 100644 index 000000000..ac385ad92 --- /dev/null +++ b/docs/ru/guides/README.md @@ -0,0 +1,10 @@ +# Руководства + +* [Введение](./getting-started.md) +* [Общие советы](./common-tips.md) +* [Мышь, клавиши и другие события DOM](./dom-events.md) +* [Чем запускать тесты](./choosing-a-test-runner.md) +* [Тестирование однофайловых компонентов с Jest](./testing-SFCs-with-jest.md) +* [Тестирование однофайловых компонентов с Mocha + webpack](./testing-SFCs-with-mocha-webpack.md) +* [Использование с Vue Router](./using-with-vue-router.md) +* [Использование с Vuex](./using-with-vuex.md) diff --git a/docs/ru/guides/choosing-a-test-runner.md b/docs/ru/guides/choosing-a-test-runner.md new file mode 100644 index 000000000..4769a193a --- /dev/null +++ b/docs/ru/guides/choosing-a-test-runner.md @@ -0,0 +1,45 @@ +# Чем запускать тесты + +Test runner — это программа, которая запускает тесты. + +Есть много популярных программ запуска тестов для JavaScript, и `vue-test-utils` работает с любой из них. Нет зависимости от используемого test runner. + +Есть несколько вещей, которые следует учитывать при выборе программы запуска тестов: набор функций, производительность и поддержка предварительной компиляции однофайловых компонентов. После тщательного сравнения существующих библиотек мы рекомендуем два варианта конфигурации: + +- [Jest](https://facebook.github.io/jest/docs/en/getting-started.html#content) наиболее функциональный способ запуска тестов. Он меньше всех требует конфигурирования, устанавливает JSDOM по умолчанию, предоставляет встроенные проверки (assertions) и имеет отличный интерфейс для работы через командную строку. Тем не менее, вам понадобится пре-процессор, чтобы иметь возможность импортировать однофайловые компоненты в свои тесты. Мы создали пре-процессор `vue-jest`, который может обрабатывать наиболее распространённые функции однофайловых компонентов, но в настоящее время он не имеет 100% паритетности функций с `vue-loader`. + +- [mocha-webpack](https://github.com/zinserjan/mocha-webpack) — это обёртка вокруг webpack + Mocha, но с более оптимизированным интерфейсом и режимом отслеживания. Преимущества этой конфигурации в том, что мы можем получить полную поддержку однофайловых компонентов с помощью webpack + `vue-loader`, но для этого требуется больше настройки. + +## Браузерное окружение + +`vue-test-utils` полагается на браузерное окружение. Технически вы можете запустить его в реальном браузере, но это не рекомендуется из-за сложности запуска реальных браузеров на разных платформах. Вместо этого мы рекомендуем запускать тесты в Node с виртуальным браузерным окружением, реализуемым с помощью [JSDOM](https://github.com/tmpvar/jsdom). + +Jest настраивает JSDOM автоматически. Для других программ запуска тестов вы можете вручную настроить JSDOM для тестов с помощью [jsdom-global](https://github.com/rstacruz/jsdom-global) в записи для ваших тестов: + +``` bash +npm install --save-dev jsdom jsdom-global +``` +--- +``` js +// в настройке теста / entry +require('jsdom-global')() +``` + +## Тестирование однофайловых компонентов + +Однофайловые компоненты Vue требуют предварительной компиляции, прежде чем могут быть запущены в Node или в браузере. Существует два рекомендуемых способа выполнения компиляции: с пре-процессором Jest, или непосредственно с помощью webpack. + +Пре-процессор `vue-jest` поддерживает базовую функциональность однофайловых компонентов, но в настоящее время не обрабатывает блоки стилей или пользовательские блоки, которые поддерживаются только в `vue-loader`. Если вы полагаетесь на эти функции или другие конфигурации, специфичные для webpack, вам нужно будет использовать связку webpack + `vue-loader`. + +Изучите следующие руководства по вариантам настройки: + +- [Тестирование однофайловых компонентов с Jest](./testing-SFCs-with-jest.md) +- [Тестирование однофайловых компонентов с Mocha + webpack](./testing-SFCs-with-mocha-webpack.md) + +## Дополнительные ресурсы + +- [Сравнение производительностей программ для запуска тестов](https://github.com/eddyerburgh/vue-unit-test-perf-comparison) +- [Пример проекта с Jest](https://github.com/vuejs/vue-test-utils-jest-example) +- [Пример проекта с Mocha](https://github.com/vuejs/vue-test-utils-mocha-webpack-example) +- [Пример проекта с tape](https://github.com/eddyerburgh/vue-test-utils-tape-example) +- [Пример проекта с AVA](https://github.com/eddyerburgh/vue-test-utils-ava-example) diff --git a/docs/ru/guides/common-tips.md b/docs/ru/guides/common-tips.md new file mode 100644 index 000000000..039711e82 --- /dev/null +++ b/docs/ru/guides/common-tips.md @@ -0,0 +1,135 @@ +# Общие советы + +## Понимание что тестировать + +Для компонентов пользовательского интерфейса мы не рекомендуем стремиться к покрытию каждой строки кода, поскольку это приводит к слишком большому фокусу на деталях внутренней реализации компонентов и может привести к созданию хрупких тестов. + +Вместо этого, мы рекомендуем писать тесты, которые проверяют ваш публичный интерфейс взаимодействия с компонентом и относиться к его внутренностям как к чёрному ящику. Один тестовый пример должен проверять, что некоторые входные данные (взаимодействие пользователя или изменение входных параметров), предоставляемые компоненту будут приводить к ожидаемому результату (результату рендеринга или вызванным пользовательским событиям). + +Например, для компонента `Counter`, который при каждом нажатии кнопки будет увеличивать отображаемый счётчик на 1, может тестироваться с помощью симуляции клика и проверке, что в отрендренном результате значение будет увеличено на 1. Тест не заботится о том, каким образом `Counter` увеличивает значение, он сосредоточен только на входных данных и результате. + +Преимуществом этого подхода в том, что до тех пор пока интерфейс вашего компонента остаётся прежним, ваши тесты будут проходить независимо от того, как будет меняться внутренняя реализация компонента с течением времени. + +Эта тема обсуждается более подробно в [отличной презентации Matt O'Connell](http://slides.com/mattoconnell/deck#/). + +## Поверхностный рендеринг + +В модульных тестах мы обычно хотим сосредоточиться на тестируемом компоненте, как на изолированном блоке и избежать неявной проверки поведения его дочерних компонентов. + +Кроме того, для компонентов, которые содержат много дочерних компонентов, отрендеренное дерево целиком может стать очень большим. Повторяющийся рендеринг всех дочерних компонентов может замедлить наши тесты. + +`vue-test-utils` позволяет вам монтировать компонент без рендеринга его дочерних компонентов (заменяя их заглушками) с помощью метода `shallow`: + +```js +import { shallow } from 'vue-test-utils' + +const wrapper = shallow(Component) // возвращает Wrapper, содержащий примонтированный экземпляр компонента +wrapper.vm // примонтированный экземпляр Vue +``` + +## Проверка вызванных событий + +Каждая примонтированная обёртка автоматически записывает все события, вызванные на экземпляре Vue. Вы можете получить записанные события с помощью метода `wrapper.emitted()`: + +``` js +wrapper.vm.$emit('foo') +wrapper.vm.$emit('foo', 123) + +/* +`wrapper.emitted()` возвращает указанный объект: +{ + foo: [[], [123]] +} +*/ +``` + +Затем вы можете добавить проверки на основе этих данных: + +``` js +import { expect } from 'chai' + +// проверка, что событие было вызвано +expect(wrapper.emitted().foo).toBeTruthy() + +// проверка, что событие вызывалось определённое число раз +expect(wrapper.emitted().foo.length).toBe(2) + +// проверка, что с событием были переданы определённые данные +expect(wrapper.emitted().foo[1]).toEqual([123]) +``` + +Вы также можете получить массив событий в порядке их вызова с помощью [`wrapper.emittedByOrder()`](../api/wrapper/emittedByOrder.md). + +## Манипулирование состоянием компонента + +Вы можете напрямую манипулировать состоянием компонента с помощью методов `setData` или `setProps` на обёртке: + +```js +wrapper.setData({ count: 10 }) + +wrapper.setProps({ foo: 'bar' }) +``` + +## Моки входных параметров + +Вы можете передать входные параметры в компонент с использованием встроенной во Vue опции `propsData`: + +```js +import { mount } from 'vue-test-utils' + +mount(Component, { + propsData: { + aProp: 'some value' + } +}) +``` + +Вы также можете обновить входные параметры на уже примонтированном компоненте с помощью метода `wrapper.setProps({})`. + +*Полный список опции можно посмотреть в [секции настроек монтирования](../api/options.md) документации.* + +## Добавление глобальных плагинов и примесей + +Некоторые из компонентов могут полагаться на функции, добавляемые глобальным плагином или примесью, к примеру `vuex` и `vue-router`. + +Если вы пишете тесты для компонентов определённого приложения, вы можете настроить одни и те же глобальные плагины и примеси один раз перед началом ваших тестов. Но в некоторых случаях, например при тестировании набора общих компонентов, которые могут использоваться в разных приложениях, гораздо лучше протестировать ваши компоненты в более изолированной конфигурации, без загрязнения глобального конструктора `Vue`. Мы можем использовать метод [`createLocalVue`](../api/createLocalVue.md) для достижения этого: + +``` js +import createLocalVue from 'vue-test-utils' + +// создаём расширенный конструктор `Vue` +const localVue = createLocalVue() + +// устанавливаем плагины как обычно +localVue.use(MyPlugin) + +// передаём `localVue` в настройки монтирования +mount(Component, { + localVue +}) +``` + +## Создание моков инъекций + +Другая стратегия для инъекции входных параметров — просто создание их моков. Вы можете это сделать с помощью опции `mocks`: + +```js +import { mount } from 'vue-test-utils' + +const $route = { + path: '/', + hash: '', + params: { id: '123' }, + query: { q: 'hello' } +} + +mount(Component, { + mocks: { + $route // добавление мока объекта `$route` в экземпляр Vue перед монтированием компонента + } +}) +``` + +## Работа с маршрутизацией + +Поскольку маршрутизация по определению имеет отношение к общей структуре приложения и включает в себя несколько компонентов, её лучше всего тестировать с помощью интеграционных или end-to-end тестов. Для отдельных компонентов, которые используют возможности `vue-router`, вы можете создать моки с использованием упомянутых выше методов. diff --git a/docs/ru/guides/dom-events.md b/docs/ru/guides/dom-events.md new file mode 100644 index 000000000..039f248c1 --- /dev/null +++ b/docs/ru/guides/dom-events.md @@ -0,0 +1,196 @@ +# Тестирование нажатий клавиш, мыши и других событий DOM + +## Генерация событий + +`Wrapper` предоставляет метод `trigger`. Его можно использовать для генерации событий DOM. + +```js +const wrapper = mount(MyButton) + +wrapper.trigger('click') +``` + +Вы должны помнить, что метод `find` также возвращает `Wrapper`. Предполагается, что `MyComponent` содержит кнопку, а следующий код нажимает эту кнопку. + +```js +const wrapper = mount(MyComponent) + +wrapper.find('button').trigger('click') +``` + +## Опции + +Метод `trigger` также может опционально принимать объект `options`. Свойства объекта `options` добавятся к Event. + +Вы можете запустить `preventDefault` для события, передав `preventDefault: true` в `options`. + +```js +const wrapper = mount(MyButton) + +wrapper.trigger('click', { preventDefault: true }) +``` + + +## Пример тестирования кнопки мыши + +**Тестируемый компонент** + +```html + + + +``` + +**Тест** + +```js +import YesNoComponent from '@/components/YesNoComponent' +import { mount } from 'vue-test-utils' +import sinon from 'sinon' + +describe('Click event', () => { + it('Нажатие на кнопке yes вызывает наш метод с аргументом "yes"', () => { + const spy = sinon.spy() + const wrapper = mount(YesNoComponent, { + propsData: { + callMe: spy + } + }) + wrapper.find('button.yes').trigger('click') + + spy.should.have.been.calledWith('yes') + }) +}) +``` + +## Пример тестирования клавиши + +**Тестируемый компонент** + +Этот компонент позволяет увеличивать/уменьшать количество с помощью различных клавиш. + +```html + + + +``` + +**Тест** + +```js +import QuantityComponent from '@/components/QuantityComponent' +import { mount } from 'vue-test-utils' + +describe('Тестирование событий клавиш', () => { + it('Quantity по умолчанию равно нулю', () => { + const wrapper = mount(QuantityComponent) + expect(wrapper.vm.quantity).to.equal(0) + }) + + it('Клавиша вверх устанавливает quantity равным 1', () => { + const wrapper = mount(QuantityComponent) + wrapper.trigger('keydown.up') + expect(wrapper.vm.quantity).to.equal(1) + }) + + it('Клавиша вниз уменьшает quantity на 1', () => { + const wrapper = mount(QuantityComponent) + wrapper.vm.quantity = 5 + wrapper.trigger('keydown.down') + expect(wrapper.vm.quantity).to.equal(4) + }) + + it('Escape устанавливает quantity равным 0', () => { + const wrapper = mount(QuantityComponent) + wrapper.vm.quantity = 5 + wrapper.trigger('keydown.esc') + expect(wrapper.vm.quantity).to.equal(0) + }) + + it('Магический символ "a" устанавливает quantity равным 13', () => { + const wrapper = mount(QuantityComponent) + wrapper.trigger('keydown', { + which: 65 + }) + expect(wrapper.vm.quantity).to.equal(13) + }) +}) + +``` + +**Ограничения** + +Имя модификатора после точки `keydown.up` преобразуется в `keyCode`. Это поддерживается для следующих имён: + +* `enter`, `tab`, `delete`, `esc`, `space`, `up`, `down`, `left`, `right` + +## Важно + +`vue-test-utils` генерирует событие синхронно. Следовательно, `Vue.nextTick` не требуется. \ No newline at end of file diff --git a/docs/ru/guides/getting-started.md b/docs/ru/guides/getting-started.md new file mode 100644 index 000000000..4f9c736bf --- /dev/null +++ b/docs/ru/guides/getting-started.md @@ -0,0 +1,116 @@ +# Введение + +## Настройка + +Для быстрого старта работы с `vue-test-utils`, клонируйте наш демонстрационный репозиторий с базовыми настройками и установите зависимости: + +``` bash +git clone https://github.com/vuejs/vue-test-utils-getting-started +cd vue-test-utils-getting-started +npm install +``` + +Вы увидите, что проект содержит простой компонент `counter.js`: + +```js +// counter.js + +export default { + template: ` +
+ {{ count }} + +
+ `, + + data () { + return { + count: 0 + } + }, + + methods: { + increment () { + this.count++ + } + } +} +``` + +### Монтирование компонентов + +`vue-test-utils` тестирует компоненты Vue монтируя их изолированно, создавая моки необходимых входных данных (входные параметры, инъекции и пользовательские события) и выполняя проверки над результатом (результат рендеринга, вызванные пользовательские события). + +Примонтированные компоненты возвращаются внутри [Wrapper](./api/wrapper.md), который предоставляет множество удобных методов для манипулирования, перемещения и различных запросов для экземпляра компонента Vue. + +Вы можете создавать wrapper с помощью метода `mount`. Давайте создадим файл `test.js`: + +```js +// test.js + +// Импортируем метод `mount()` из `vue-test-utils` +// и компонент, который хотим протестировать +import { mount } from 'vue-test-utils' +import Counter from './counter' + +// Теперь монтируем компонент и у нас появляется wrapper +const wrapper = mount(Counter) + +// Вы можете получить доступ к экземпляру Vue через `wrapper.vm` +const vm = wrapper.vm + +// Чтобы изучить wrapper подробнее, просто выведите его в консоль +// и ваши приключения с `vue-test-utils` начнутся +console.log(wrapper) +``` + +### Тестирование отрендеренного HTML компонента + +Теперь, когда у нас есть wrapper, первой вещью, которую мы можем захотеть проверить что отрендеренный HTML компонента соответствует нашим ожиданиям. + +```js +import { mount } from 'vue-test-utils' +import Counter from './counter' + +describe('Компонент Counter', () => { + // Теперь монтируем компонент и получаем wrapper + const wrapper = mount(Counter) + + it('отображает корректную разметку', () => { + expect(wrapper.html()).toContain('0') + }) + + // также легко проверить наличие других элементов + it('имеет кнопку', () => { + expect(wrapper.contains('button')).toBe(true) + }) +}) +``` + +Теперь запустите тесты командой `npm test`. Вы должны увидеть, что все тесты проходят успешно. + +### Симуляция пользовательских действий + +Наш счётчик должен увеличивать значение, когда пользователь нажимает кнопку. Чтобы симулировать это поведение, нам необходимо сначала получить кнопку с помощью `wrapper.find()`, который возвращает **wrapper для элемента кнопки**. Мы можем симулировать клик с помощью вызова `.trigger()` на wrapper кнопки: + +```js +it('нажатие кнопки должно увеличивать счётчик', () => { + expect(wrapper.vm.count).toBe(0) + const button = wrapper.find('button') + button.trigger('click') + expect(wrapper.vm.count).toBe(1) +}) +``` + +### Что делать с `nextTick`? + +Vue собирает пачку предстоящих обновлений DOM и применяет их асинхронно для избежания ненужных повторных рендерингов, вызываемых множественными изменениями данных. Вот почему на практике на часто приходится использовать `Vue.nextTick` для ожидания, пока Vue не выполнит фактическое обновление DOM после того, как мы инициируем некоторое изменение состояния. + +Для упрощения работы, `vue-test-utils` применяет все обновления синхронно, поэтому вам не потребуется использовать `Vue.nextTick` для ожидания обновления DOM в ваших тестах. + +*Примечание: `nextTick` по-прежнему необходим, когда вам нужно явно форсировать цикл событий, для таких операций как асинхронные обратные вызовы или разрешение промисов.* + +## Что дальше + +- Интегрируйте `vue-test-utils` в ваш проект выбрав [программу для запуска тестов](./choosing-a-test-runner.md). +- Прочитайте больше об [общих техниках и советах при написании тестов](./common-tips.md). diff --git a/docs/ru/guides/testing-SFCs-with-jest.md b/docs/ru/guides/testing-SFCs-with-jest.md new file mode 100644 index 000000000..604d062a5 --- /dev/null +++ b/docs/ru/guides/testing-SFCs-with-jest.md @@ -0,0 +1,179 @@ +# Тестирование однофайловых компонентов с Jest + +> Пример проекта для этой конфигурации доступен на [GitHub](https://github.com/vuejs/vue-test-utils-jest-example). + +Jest — это программа для запуска тестов, разработанная Facebook, направленная на предоставление функционального решения для модульного тестирования. Вы можете узнать больше о Jest в [официальной документации](https://facebook.github.io/jest/). + +## Установка Jest + +Предположим, что вы начинаете с конфигурации, где правильно настроены webpack, vue-loader и Babel — например, развёрнутый шаблон `webpack-simple` с помощью `vue-cli`. + +Первым делом нам необходимо установить Jest и `vue-test-utils`: + +```bash +$ npm install --save-dev jest vue-test-utils +``` + +Затем, необходимо указать псевдоним для запуска тестов в нашем `package.json`. + +```json +// package.json +{ + "scripts": { + "test": "jest" + } +} +``` + +## Обработка однофайловых компонентов с Jest + +Чтобы научить Jest как обрабатывать `*.vue` файлы, нам необходимо установить и настроить пре-процессор `vue-jest`: + +``` bash +npm install --save-dev vue-jest +``` + +Теперь, создадим секцию `jest` в файле `package.json`: + +``` json +{ + // ... + "jest": { + "moduleFileExtensions": [ + "js", + "json", + // сообщаем Jest что необходимо обрабатывать `*.vue` файлы + "vue" + ], + "transform": { + // обрабатываем `*.vue` файлы с помощью `jest-vue` + ".*\\.(vue)$": "/node_modules/jest-vue" + }, + "mapCoverage": true + } +} +``` + +> **Примечание:** `vue-jest` в настоящее время не поддерживает все возможности `vue-loader`, например пользовательские блоки и загрузку стилей. Кроме того, некоторые функции, специфичные для webpack, такие как code-splitting, также не поддерживаются. Чтобы использовать их прочитайте руководство по [тестированию однофайловых компонентов с Mocha + webpack](./testing-SFCs-with-mocha-webpack.md). + +## Обработка псевдонимов webpack + +Если вы используете псевдонимы в конфигурации webpack, например когда `@` ссылается на путь `/src`, вам также нужно добавить соответствующую конфигурацию для Jest, используя опцию `moduleNameMapper`: + +``` json +{ + // ... + "jest": { + // ... + // добавление поддержки псевдонима @ -> src в исходном коде + "moduleNameMapper": { + "^@/(.*)$": "/src/$1" + } + } +} +``` + +## Конфигурация Babel для Jest + + +Хотя последние версии Node уже поддерживают большинство функций ES2015, вы всё равно можете использовать синтаксис ES-модулей и stage-x функции в ваших тестах. Для этого нужно установить `babel-jest`: + +``` bash +npm install --save-dev babel-jest +``` + +Затем мы должны сообщить Jest обрабатывать файлы тестов с JavaScript с помощью `babel-jest`, добавив запись `jest.transform` в `package.json`: + +``` json +{ + // ... + "jest": { + // ... + "transform": { + // ... + // обрабатывать js с помощью `babel-jest` + "^.+\\.js$": "/node_modules/babel-jest" + }, + // ... + } +} +``` + +> По умолчанию `babel-jest` автоматически настраивается по установке. Однако, поскольку мы явно добавили преобразование файлов `*.vue`, нам теперь нужно также настроить `babel-jest`. + +Предполагая использование `babel-preset-env` с webpack, конфигурация Babel по умолчанию отключает транспиляцию ES-модулей, потому что webpack уже знает как обрабатывать ES-модули. Однако нам нужно включить его для наших тестов, потому что тесты Jest запускаются непосредственно в Node. + +Кроме того, мы можем указать `babel-preset-env` в качестве цели используемую нами версию Node. Это пропустит транспиляцию ненужных функций и ускорит загрузку тестов. + +Чтобы применить эти параметры только для тестов, поместите их в отдельную конфигурацию в `env.test` (это будет автоматически обработано `babel-jest`). + +Пример `.babelrc`: + +``` json +{ + "presets": [ + ["env", { "modules": false }] + ], + "env": { + "test": { + "presets": [ + ["env", { "targets": { "node": "current" }}] + ] + } + } +} +``` + +### Тестирование моментальными снимками + +Вы можете использовать [`vue-server-renderer`](https://github.com/vuejs/vue/tree/dev/packages/vue-server-renderer) для рендеринга компонента в строку, чтобы его можно было сохранить в качестве снимка для [тестирования моментальными снимками в Jest](https://facebook.github.io/jest/docs/en/snapshot-testing.html). + +Результат рендеринга `vue-server-renderer` включает в себя несколько атрибутов, специфичных для SSR, и игнорирует пробелы, что затрудняет сравнивать diff. Мы можем улучшить сохранённый снимок с помощью специального сериализатора: + +``` bash +npm install --save-dev jest-serializer-vue +``` + +Затем добавьте конфигурацию в `package.json`: + +``` json +{ + // ... + "jest": { + // ... + // сериализатор для снимков + "snapshotSerializers": [ + "/node_modules/jest-serializer-vue" + ] + } +} +``` + +### Расположение файлов тестов + +По умолчанию Jest будет рекурсивно выбирать все файлы с расширением `.spec.js` или `.test.js` во всём проекте. Если это поведение не соответствует вашим потребностям, то возможно [изменить `testRegex`](https://facebook.github.io/jest/docs/en/configuration.html#testregex-string) в секции конфигурации в файле `package.json`. + +Jest рекомендует создать каталог `__tests__` рядом с тестируемым кодом, но не стесняйтесь структурировать ваши тесты по своему усмотрению. Просто остерегайтесь того, что Jest создаст каталог `__snapshots__` рядом с тестовыми файлами, который необходим для тестирования с помощью моментальных снимков. + +### Пример спецификации + +Если вы знакомы с Jasmine, то вы должны чувствовать себя как дома с [проверочным API](https://facebook.github.io/jest/docs/en/expect.html#content) Jest: + +```js +import { mount } from 'vue-test-utils' +import Component from './component' + +describe('Component', () => { + test('является экземпляром Vue', () => { + const wrapper = mount(Component) + expect(wrapper.isVueInstance()).toBeTruthy() + }) +}) +``` + +### Ресурсы + +- [Пример проекта для этой конфигурации](https://github.com/vuejs/vue-test-utils-jest-example) +- [Примеры и слайды с Vue Conf 2017](https://github.com/codebryo/vue-testing-with-jest-conf17) +- [Jest](https://facebook.github.io/jest/) +- [Babel preset env](https://github.com/babel/babel-preset-env) diff --git a/docs/ru/guides/testing-SFCs-with-mocha-webpack.md b/docs/ru/guides/testing-SFCs-with-mocha-webpack.md new file mode 100644 index 000000000..2efb19b86 --- /dev/null +++ b/docs/ru/guides/testing-SFCs-with-mocha-webpack.md @@ -0,0 +1,180 @@ +# Тестирование однофайловых компонентов с Mocha + webpack + +> Пример проекта для этой конфигурации доступен на [GitHub](https://github.com/vuejs/vue-test-utils-mocha-webpack-example). + +Другая стратегия тестирования однофайловых компонентов заключается в компиляции всех наших тестов с помощью webpack, а затем программой для запуска тестов. Преимущество такого подхода заключается в том, что он даёт нам полную поддержку всех функций webpack и `vue-loader`, поэтому нам не нужно идти на компромиссы в нашем исходном коде. + +Технически, вы можете использовать любую программу для запуска тестов, которая вам нравится, и вручную соединять вещи, но мы нашли [`mocha-webpack`](https://github.com/zinserjan/mocha-webpack) как очень удобный способ для реализации этой задачи. + +## Настройка `mocha-webpack` + +Мы предположим, что вы начинаете с настройки, когда уже есть правильно настроенные webpack, vue-loader и Babel — например используя шаблон `webpack-simple`, развёрнутый с помощью `vue-cli`. + +Первое, что нужно сделать, это установить тестовые зависимости: + +``` bash +npm install --save-dev vue-test-utils mocha mocha-webpack +``` + +Затем мы должны указать скрипт test в нашем `package.json`. + +```json +// package.json +{ + "scripts": { + "test": "mocha-webpack --webpack-config webpack.config.js --require test/setup.js test/**/*.spec.js" + } +} +``` + +Несколько вещей, о том что мы сделали: + +- Флаг `--webpack-config` указывает конфигурационный файл webpack для использования в тестах. В большинстве случаев это будут идентичная конфигурация, используемой в проекте, с одной небольшой доработкой. Мы поговорим об этом позднее. + +- Флаг `--require` гарантирует, что файл `test/setup.js` будет запущен перед любыми тестами, в котором мы можем настроить для наших тестов глобальное окружение, в котором они будут запускаться. + +- Последний аргумент — это шаблон для тестовых файлов, которые будут включены в тестовую сборку. + +### Дополнительная конфигурация webpack + +#### Вынесение внешних NPM-зависимостей + +В наших тестах мы, скорее всего, импортируем ряд NPM-зависимостей — некоторые из этих модулей могут быть написаны не для использования в браузере и просто не смогут быть корректно добавлены в сборку webpack. Другой плюс в том, что извлечение внешних зависимостей значительно улучшит скорость загрузки тестов. Мы можем вынести все NPM-зависимости с помощью `webpack-node-externals`: + +```js +// webpack.config.js +const nodeExternals = require('webpack-node-externals') + +module.exports = { + // ... + externals: [nodeExternals()] +} +``` + +#### Source Maps + +Source maps должны быть встроены для использования в `mocha-webpack`. Рекомендуемая конфигурация: + +``` js +module.exports = { + // ... + devtool: 'inline-cheap-module-source-map' +} +``` + +При отладке через IDE рекомендуется также добавить следующее: + +``` js +module.exports = { + // ... + output: { + // ... + // использовать абсолютные пути в sourcemaps (важно для отладки через IDE) + devtoolModuleFilenameTemplate: '[absolute-resource-path]', + devtoolFallbackModuleFilenameTemplate: '[absolute-resource-path]?[hash]' + } +} +``` + +### Настройка браузерного окружения + +`vue-test-utils` требует браузерного окружения для запуска. Мы можем симулировать его в Node используя `jsdom-global`: + +```bash +npm install --save-dev jsdom jsdom-global +``` + +Затем в `test/setup.js`: + +``` js +require('jsdom-global')() +``` + +Это добавит браузерное окружение в Node, таким образом `vue-test-utils` сможет корректно запуститься. + +### Выбор библиотеки утверждений + +[Chai](http://chaijs.com/) — популярная библиотека утверждений, которая обычно используется вместе с Mocha. Вы также можете воспользоваться [Sinon](http://sinonjs.org/) для создания шпионов и заглушек. + +В качестве альтернативы вы можете использовать `expect`, который является частью Jest и реализует [точно такой же API](http://facebook.github.io/jest/docs/en/expect.html#content) в документации Jest. + +Мы будем использовать `expect` здесь и сделаем его глобально доступным, чтобы нам не приходилось импортировать его в каждом тесте: + +``` bash +npm install --save-dev expect +``` + +Затем в `test/setup.js`: + +``` js +require('jsdom-global')() + +global.expect = require('expect') +``` + +### Оптимизация Babel для тестов + +Обратите внимание, что мы используем `babel-loader` для обработки JavaScript. У вас уже должен быть настроен Babel, если вы используете его в своём приложении, через файл `.babelrc`. Здесь `babel-loader` будет автоматически использовать тот же файл конфигурации. + +Следует отметить, что если вы используете Node 6+, которая уже поддерживает большинство функций ES2015, вы можете настроить отдельную [опцию env](https://babeljs.io/docs/usage/babelrc/#env-option) Babel, которая будет транспилировать только те функции, которые ещё не поддерживаются в используемой версии Node (например, `stage-2` или поддержку синтаксиса flow, и т.п.). + +### Добавление теста + +Создайте файл в `src` названный `Counter.vue`: + +``` html + + + +``` + +И создайте файл теста, названный `test/Counter.spec.js` со следующим кодом: + +```js +import { shallow } from 'vue-test-utils' +import Counter from '../src/Counter.vue' + +describe('Counter.vue', () => { + it('увеличивает счётчик по нажатию кнопки', () => { + const wrapper = shallow(Counter) + wrapper.find('button').trigger('click') + expect(wrapper.find('div').text()).toMatch('1') + }) +}) +``` + +И теперь мы можем запустить тест: + +``` +npm run unit +``` + +Ура, мы запустили наши тесты! + +### Ресурсы + +- [Пример проекта для этой конфигурации](https://github.com/vuejs/vue-test-utils-mocha-webpack-example) +- [Mocha](https://mochajs.org/) +- [mocha-webpack](http://zinserjan.github.io/mocha-webpack/) +- [Chai](http://chaijs.com/) +- [Sinon](http://sinonjs.org/) +- [jest/expect](http://facebook.github.io/jest/docs/en/expect.html#content) diff --git a/docs/ru/guides/using-with-vue-router.md b/docs/ru/guides/using-with-vue-router.md new file mode 100644 index 000000000..c52bcb447 --- /dev/null +++ b/docs/ru/guides/using-with-vue-router.md @@ -0,0 +1,71 @@ +# Использование с Vue Router + +## Установка Vue Router в тестах + +Вы никогда не должны устанавливать Vue Router в базовый конструктор Vue в тестах. Установка Vue Router добавляет `$route` и `$router` как свойства только для чтения на прототипе Vue. + +Чтобы этого избежать, мы можем создать localVue и установить Vue Router на него. + +```js +import VueRouter from 'vue-router' +const localVue = createLocalVue() + +localVue.use(VueRouter) + +shallow(Component, { + localVue +}) +``` + +## Тестирование компонентов использующих `router-link` или `router-view` + +Когда вы устанавливаете Vue Router, регистрируются глобальные компоненты `router-link` и `router-view`. Это означает, что мы можем использовать их в любом месте нашего приложения без необходимости импортировать их. + +Когда мы запускаем тесты, нам нужно сделать эти компоненты vue-router доступными для компонента, который мы монтируем. Есть два способа сделать это. + +### Использование заглушек (stubs) + +```js +shallow(Component, { + stubs: ['router-link', 'router-view'] +}) +``` + +### Установка Vue Router с помощью localVue + +```js +import VueRouter from 'vue-router' +const localVue = createLocalVue() + +localVue.use(VueRouter) + +shallow(Component, { + localVue +}) +``` + +## Создание моков `$route` и `$router` + +Иногда вам может потребоваться протестировать, что компонент что-то делает с параметрами объектов `$route` и `$router`. Для этого вы можете передавать пользовательские моки в экземпляр Vue. + +```js +const $route = { + path: '/some/path' +} + +const wrapper = shallow(Component, { + mocks: { + $route + } +}) + +wrapper.vm.$router // /some/path +``` + +## Общие ошибки + +Установка Vue Router добавляет `$route` и `$router` в качестве свойств только для чтения на прототипе Vue. + +Это означет, что любые будущие тесты, которые попытаются сделать мок `$route` или `$router` потерпят неудачу. + +Для избежания этого никогда не устанавливайте Vue Router при запуске тестов. diff --git a/docs/ru/guides/using-with-vuex.md b/docs/ru/guides/using-with-vuex.md new file mode 100644 index 000000000..e882b2042 --- /dev/null +++ b/docs/ru/guides/using-with-vuex.md @@ -0,0 +1,265 @@ +# Использование с Vuex + +В этом руководстве мы рассмотрим как тестировать Vuex в компонентах с `vue-test-utils`. + +## Создание моков для действий + +Давайте посмотрим на часть кода. + +Это компонент который мы хотим протестировать. Он вызывает действие Vuex. + +``` html + + + +``` + +Для целей этого теста нам всё равно, что делает действие или как выглядит структура хранилища. Мы должны просто узнать, что это действие вызывается когда должно, и что оно вызывается с ожидаемым значением. + +Чтобы протестировать это, нам нужно передать мок хранилища в Vue, когда мы отделяем наш компонент. + +Вместо передачи хранилища в базовый конструктор Vue, мы можем передать его в [localVue](../api/options.md#localvue). localVue — это изолированный конструктор Vue, в который мы можем вносить изменения без влияния на глобальный конструктор Vue. + +Давайте посмотрим, как это выглядит: + +``` js +import { shallow, createLocalVue } from 'vue-test-utils' +import Vuex from 'vuex' +import Actions from '../../../src/components/Actions' + +const localVue = createLocalVue() + +localVue.use(Vuex) + +describe('Actions.vue', () => { + let actions + let store + + beforeEach(() => { + actions = { + actionClick: jest.fn(), + actionInput: jest.fn() + } + store = new Vuex.Store({ + state: {}, + actions + }) + }) + + it('вызывает действие хранилища "actionInput" когда значение поля "input" и было событие "input"', () => { + const wrapper = shallow(Actions, { store, localVue }) + const input = wrapper.find('input') + input.element.value = 'input' + input.trigger('input') + expect(actions.actionInput).toHaveBeenCalled() + }) + + it('не вызывает действие хранилища "actionInput" когда значение поля отлично от "input" и было событие "input"', () => { + const wrapper = shallow(Actions, { store, localVue }) + const input = wrapper.find('input') + input.element.value = 'not input' + input.trigger('input') + expect(actions.actionInput).not.toHaveBeenCalled() + }) + + it('вызывает действие хранилища "actionClick" по нажатию кнопки', () => { + const wrapper = shallow(Actions, { store, localVue }) + wrapper.find('button').trigger('click') + expect(actions.actionClick).toHaveBeenCalled() + }) +}) +``` + +Что тут происходит? Сначала мы говорим Vue использовать Vuex с помощью метода `Vue.use`. Это всего лишь обёртка вокруг `Vue.use`. + +Затем мы создаём мок хранилища вызовом `Vuex.store` с нашими заготовленными значениями. Мы передаём ему только дейсвтия, так как это всё что нам необходимо. + +Действия реализуются с помощью [mock-функций jest](https://facebook.github.io/jest/docs/en/mock-functions.html). Эти mock-функции предоставляют нам методы для проверки, вызывались ли действия или нет. + +Затем мы можем проверить в наших тестах, что заглушка действия была вызвана когда ожидалось. + +Теперь способ, которым мы определяем наше хранилище выглядит немного необычным для вас. + +Мы используем `beforeEach`, чтобы убедиться, что у нас есть чистое хранилище перед каждым тестом. `beforeEach` — это хук в mocha, который вызывается перед каждым тестом. В нашем тесте мы переназначаем значения переменных хранилища. Если бы мы этого не сделали, mock-функции нужно было бы автоматически сбрасывать. Это также позволяет нам изменять состояние в наших тестах, не влияя на последующие тесты. + +Самое важно, что следует отметить в этом тесте — то что **мы создаём мок хранилища Vuex и затем передаём его в `vue-test-utils`**. + +Отлично, теперь мы можем создавать моки действий, давайте посмотрим на создание моков для геттеров. + +## Создание моков для геттеров + +``` html + + + +``` + +Это довольно простой компонент. Он отображает результат геттеров `clicks` и `inputValue`. Опять же, нас не волнует что возвращают эти геттеры — только то, что их результат будет корректно отображён. + +Давайте посмотрим на тест: + +``` js +import { shallow, createLocalVue } from 'vue-test-utils' +import Vuex from 'vuex' +import Actions from '../../../src/components/Getters' + +const localVue = createLocalVue() + +localVue.use(Vuex) + +describe('Getters.vue', () => { + let getters + let store + + beforeEach(() => { + getters = { + clicks: () => 2, + inputValue: () => 'input' + } + + store = new Vuex.Store({ + getters + }) + }) + + it('Отображает "state.inputValue" в первом теге p', () => { + const wrapper = shallow(Actions, { store, localVue }) + const p = wrapper.find('p') + expect(p.text()).toBe(getters.inputValue()) + }) + + it('Отображает "state.clicks" во втором теге p', () => { + const wrapper = shallow(Actions, { store, localVue }) + const p = wrapper.findAll('p').at(1) + expect(p.text()).toBe(getters.clicks().toString()) + }) +}) +``` + +Этот тест очень похож на тест действий. Мы создаём мок хранилища перед каждым тестом, передаём его в качестве опции когда вызываем `shallow`, и проверяем что значение вернувшееся из мока-геттера отображается. + +Это здорово, но что, если мы хотим проверить, что наши геттеры возвращают правильную часть нашего состояния? + +## Создание моков с модулями + +[Модули](https://vuex.vuejs.org/ru/modules.html) полезны для разделения нашего хранилища на управляемые части. Они также экспортируют геттеры. Мы можем использовать их в наших тестах. + +Давайте взглянем на наш компонент: + +``` html + + + +``` + +Простой компонент, который содержит одно действие и один геттер. + +И тест: + +``` js +import { shallow, createLocalVue } from 'vue-test-utils' +import Vuex from 'vuex' +import Modules from '../../../src/components/Modules' +import module from '../../../src/store/module' + +const localVue = createLocalVue() + +localVue.use(Vuex) + +describe('Modules.vue', () => { + let actions + let state + let store + + beforeEach(() => { + state = { + module: { + clicks: 2 + } + } + + actions = { + moduleActionClick: jest.fn() + } + + store = new Vuex.Store({ + state, + actions, + getters: module.getters + }) + }) + + it('вызывает действие "moduleActionClick" при нажатии кнопки', () => { + const wrapper = shallow(Modules, { store, localVue }) + const button = wrapper.find('button') + button.trigger('click') + expect(actions.moduleActionClick).toHaveBeenCalled() + }) + + it('отображает "state.inputValue" в первом теге p', () => { + const wrapper = shallow(Modules, { store, localVue }) + const p = wrapper.find('p') + expect(p.text()).toBe(state.module.clicks.toString()) + }) +}) +``` + +### Ресурсы + +- [Пример проекта для этого руководства](https://github.com/eddyerburgh/vue-test-utils-vuex-example) +- [`localVue`](../api/options.md#localvue) +- [`createLocalVue`](../api/createLocalVue.md) From 13cd08eb085dc8ac3300d54340ef9f786d9ea3e6 Mon Sep 17 00:00:00 2001 From: eddyerburgh Date: Sun, 3 Dec 2017 08:56:31 +0000 Subject: [PATCH 0113/1136] docs: fix linting errors --- docs/ru/api/selectors.md | 6 +++--- docs/ru/api/wrapper/setComputed.md | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/ru/api/selectors.md b/docs/ru/api/selectors.md index 64b66a968..c37c8c1da 100644 --- a/docs/ru/api/selectors.md +++ b/docs/ru/api/selectors.md @@ -49,6 +49,6 @@ expect(wrapper.is(Foo)).toBe(true) Использование опции поиска объекта позволяет `vue-test-utils` выбирать элементы по `$ref` на компонентах обёрток. ```js -const buttonWrapper = wrapper.find({ ref: 'myButton' }); -buttonWrapper.trigger('click'); -``` \ No newline at end of file +const buttonWrapper = wrapper.find({ ref: 'myButton' }) +buttonWrapper.trigger('click') +``` diff --git a/docs/ru/api/wrapper/setComputed.md b/docs/ru/api/wrapper/setComputed.md index 975630fe1..70d0c44f6 100644 --- a/docs/ru/api/wrapper/setComputed.md +++ b/docs/ru/api/wrapper/setComputed.md @@ -20,15 +20,15 @@ const wrapper = mount({ data () { return { initial: 'initial' - }; + } }, computed: { - computed1 () { - return this.initial - }, - computed2 () { - return this.initial + computed1 () { + return this.initial }, + computed2 () { + return this.initial + } } }) @@ -40,4 +40,4 @@ wrapper.setComputed({ }) expect(wrapper.html()).toBe('
new-computed1 new-computed2
') -``` \ No newline at end of file +``` From 920a536fabec0bcf061014ed1426bfe5505f32cf Mon Sep 17 00:00:00 2001 From: eddyerburgh Date: Sun, 3 Dec 2017 15:20:09 +0000 Subject: [PATCH 0114/1136] docs: add provide to docs --- docs/en/api/options.md | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/docs/en/api/options.md b/docs/en/api/options.md index 9aa8c2c20..c2e0f3da6 100644 --- a/docs/en/api/options.md +++ b/docs/en/api/options.md @@ -13,8 +13,11 @@ Vue options are passed to the component when a new instance is created. , e.g. ` - [`localVue`](#localvue) - [`attachToDocument`](#attachtodocument) - [`attrs`](#attrs) +- [`provide`](#provide) - [`listeners`](#listeners) - [`clone`](#clone) +- [`mocks`](#mocks) +- [`localVue`](#localVue) ### `context` @@ -160,6 +163,20 @@ Set the component instance's `$listeners` object. Clones component before mounting if `true`, which avoids mutating the original component definition. -`options.mocks` (`Object`): Add globals to Vue instance. +### `provide` (`Object`) + +- type: `Object` + +Pass properties for components to use in injection. See [provide/inject](https://vuejs.org/v2/api/#provide-inject) + +### `mocks` (`Object`) + +- type: `Object` + +Adds globals to Vue instance + +### localVue + +- type: `localVue` -`options.localVue` (`Object`): `Vue` class to use in `mount`. See [`createLocalVue`](createLocalVue.md). +`Vue` class to use in `mount`. See [`createLocalVue`](createLocalVue.md). From d7534f7b591d4e882402b0683e6fc5999bf95453 Mon Sep 17 00:00:00 2001 From: eddyerburgh Date: Sun, 3 Dec 2017 15:30:55 +0000 Subject: [PATCH 0115/1136] docs: remove duplicate localVue in mounting options --- docs/en/api/options.md | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/docs/en/api/options.md b/docs/en/api/options.md index c2e0f3da6..0c3c7421f 100644 --- a/docs/en/api/options.md +++ b/docs/en/api/options.md @@ -16,8 +16,6 @@ Vue options are passed to the component when a new instance is created. , e.g. ` - [`provide`](#provide) - [`listeners`](#listeners) - [`clone`](#clone) -- [`mocks`](#mocks) -- [`localVue`](#localVue) ### `context` @@ -163,20 +161,8 @@ Set the component instance's `$listeners` object. Clones component before mounting if `true`, which avoids mutating the original component definition. -### `provide` (`Object`) +### `provide` - type: `Object` Pass properties for components to use in injection. See [provide/inject](https://vuejs.org/v2/api/#provide-inject) - -### `mocks` (`Object`) - -- type: `Object` - -Adds globals to Vue instance - -### localVue - -- type: `localVue` - -`Vue` class to use in `mount`. See [`createLocalVue`](createLocalVue.md). From f48a913f592328009b6ee7710912251d3f5a035c Mon Sep 17 00:00:00 2001 From: eddyerburgh Date: Sun, 3 Dec 2017 20:09:12 +0000 Subject: [PATCH 0116/1136] fix: add object assign polyfill --- src/lib/{ => polyfills}/matches-polyfill.js | 0 src/lib/polyfills/object-assign-polyfill.js | 23 +++++++++++++++++++++ src/mount.js | 3 ++- 3 files changed, 25 insertions(+), 1 deletion(-) rename src/lib/{ => polyfills}/matches-polyfill.js (100%) create mode 100644 src/lib/polyfills/object-assign-polyfill.js diff --git a/src/lib/matches-polyfill.js b/src/lib/polyfills/matches-polyfill.js similarity index 100% rename from src/lib/matches-polyfill.js rename to src/lib/polyfills/matches-polyfill.js diff --git a/src/lib/polyfills/object-assign-polyfill.js b/src/lib/polyfills/object-assign-polyfill.js new file mode 100644 index 000000000..16851a00d --- /dev/null +++ b/src/lib/polyfills/object-assign-polyfill.js @@ -0,0 +1,23 @@ +if (typeof Object.assign !== 'function') { + (function () { + Object.assign = function (target) { + 'use strict' + if (target === undefined || target === null) { + throw new TypeError('Cannot convert undefined or null to object') + } + + var output = Object(target) + for (var index = 1; index < arguments.length; index++) { + var source = arguments[index] + if (source !== undefined && source !== null) { + for (var nextKey in source) { + if (source.hasOwnProperty(nextKey)) { + output[nextKey] = source[nextKey] + } + } + } + } + return output + } + })() +} diff --git a/src/mount.js b/src/mount.js index 603fca128..9d7e884f2 100644 --- a/src/mount.js +++ b/src/mount.js @@ -6,7 +6,8 @@ import VueWrapper from './wrappers/vue-wrapper' import createInstance from './lib/create-instance' import cloneDeep from 'lodash/cloneDeep' import createElement from './lib/create-element' -import './lib/matches-polyfill' +import './lib/polyfills/matches-polyfill' +import './lib/polyfills/object-assign-polyfill' import errorHandler from './lib/error-handler' Vue.config.productionTip = false From 8224bd35c2b6def288e99ad3ce2884c858d93fab Mon Sep 17 00:00:00 2001 From: eddyerburgh Date: Sun, 3 Dec 2017 20:49:40 +0000 Subject: [PATCH 0117/1136] refactor: create test-utils file --- .../resources/components/component-with-mixin.vue | 2 +- test/resources/test-mixin.js | 4 ++++ test/resources/test-utils.js | 15 +++++++++++++++ test/resources/testMixin.js | 0 test/unit/specs/mount.spec.js | 11 ++--------- test/unit/specs/mount/options/attrs.spec.js | 9 ++------- test/unit/specs/mount/options/context.spec.js | 9 ++------- test/unit/specs/mount/options/listeners.spec.js | 9 ++------- test/unit/specs/mount/options/provide.spec.js | 13 ++++--------- 9 files changed, 32 insertions(+), 40 deletions(-) create mode 100644 test/resources/test-mixin.js create mode 100644 test/resources/test-utils.js delete mode 100644 test/resources/testMixin.js diff --git a/test/resources/components/component-with-mixin.vue b/test/resources/components/component-with-mixin.vue index 7f6403e1a..0ed5cbeaa 100644 --- a/test/resources/components/component-with-mixin.vue +++ b/test/resources/components/component-with-mixin.vue @@ -3,7 +3,7 @@ + +``` + +**테스트** + +```js +import YesNoComponent from '@/components/YesNoComponent' +import { mount } from 'vue-test-utils' +import sinon from 'sinon' // sinon.js + +describe('Click event', () => { + it('Click on yes button calls our method with argument "yes"', () => { + const spy = sinon.spy() + const wrapper = mount(YesNoComponent, { + propsData: { + callMe: spy + } + }) + wrapper.find('button.yes').trigger('click') + + spy.should.have.been.calledWith('yes') + }) +}) +``` + +## 키보드 예제 + +**테스트할 컴포넌트** + +이 컴포넌트는 다양한 키조작으로 증감을 처리합니다. + +```html + + + +``` + +**Test** + +```js +import QuantityComponent from '@/components/QuantityComponent' +import { mount } from 'vue-test-utils' + +describe('Key event tests', () => { + it('Quantity is zero by default', () => { + const wrapper = mount(QuantityComponent) + expect(wrapper.vm.quantity).to.equal(0) + }) + + it('Cursor up sets quantity to 1', () => { + const wrapper = mount(QuantityComponent) + wrapper.trigger('keydown.up') + expect(wrapper.vm.quantity).to.equal(1) + }) + + it('Cursor down reduce quantity by 1', () => { + const wrapper = mount(QuantityComponent) + wrapper.vm.quantity = 5 + wrapper.trigger('keydown.down') + expect(wrapper.vm.quantity).to.equal(4) + }) + + it('Escape sets quantity to 0', () => { + const wrapper = mount(QuantityComponent) + wrapper.vm.quantity = 5 + wrapper.trigger('keydown.esc') + expect(wrapper.vm.quantity).to.equal(0) + }) + + it('Magic character "a" sets quantity to 13', () => { + const wrapper = mount(QuantityComponent) + wrapper.trigger('keydown', { + which: 65 + }) + expect(wrapper.vm.quantity).to.equal(13) + }) +}) + +``` + +**제한 사항** + +`keydown.up` 처럼, 점 뒤에 있는 키 이름은 `keyCode`로 반환됩니다. 현재 다음 이름들만 지원하고 있습니다. + +* enter, tab, delete, esc, space, up, down, left, right + +## 중요 + +vue-test-utils는 이벤트를 동기적으로 트리거 합니다. 따라서, `Vue.nextTick`은 필요하지 않습니다. diff --git a/docs/kr/guides/getting-started.md b/docs/kr/guides/getting-started.md new file mode 100644 index 000000000..c8aef2df7 --- /dev/null +++ b/docs/kr/guides/getting-started.md @@ -0,0 +1,149 @@ +# 시작하기 + +## 설치 + +`vue-test-utils`를 빠르게 맛보려면 제공하는 기본적인 설정이 되어있는 데모 저장소를 클론하세요. + + +``` bash +git clone https://github.com/vuejs/vue-test-utils-getting-started +cd vue-test-utils-getting-started +npm install +``` + +프로젝트에 포함된 간단한 예제 컴포넌트인 `counter.js`를 볼 수 있습니다. + +```js +// counter.js + +export default { + template: ` +
+ {{ count }} + +
+ `, + + data () { + return { + count: 0 + } + }, + + methods: { + increment () { + this.count++ + } + } +} +``` + +### 컴포넌트 마운팅 + +`vue-test-utils`는 Vue Component를 독립적으로 마운팅하고, 필요한 것들(props, injections, 사용자 이벤트)을 목킹(mocking)할 수 있고 결과(렌더링된 결과, 방출된 커스텀 이벤트)를 검증할 수 있습니다. + +마운트된 컴포넌트는 [Wrapper](./api/wrapper.md) 내부에 반환됩니다. 래퍼는 컴포넌트 내부를 조회, 탐색, 조작 등을 할 수 있는 여러가지 유용한 메소드 지원합니다. + +래퍼를 만들기 위해 `mount` 메소드를 사용합니다. 이제, `test.js` 파일을 만드세요 + +```js +// test.js + +// mount() 메소드를 테스트 유틸에서 임포트합니다. +// 그리고 테스트할 컴포넌트를 가져옵니다. + +import { mount } from 'vue-test-utils' +import Counter from './counter' + +// 컴포넌트를 마운트하여 래퍼를 얻습니다. +const wrapper = mount(Counter) + +// 실제 Vue 인스턴스는 wrapper.vm로 접근할 수 있습니다. +const vm = wrapper.vm + +// 콘솔 로그를 사용해 래퍼의 내부를 살펴볼 수 있습니다. +// 이제 vue-test-utils와 함께 모험을 시작하세요 +console.log(wrapper) +``` + +### 컴포넌트에서 렌더링 된 HTML 출력 테스트 + +래퍼를 얻었으니 처음으로 할 일은 컴포넌트가 렌더링한 HTML이 예상하는 것과 같은지 살펴봅니다. + +```js +import { mount } from 'vue-test-utils' +import Counter from './counter' + +describe('Counter', () => { + // 컴포넌트를 마운트하여 래퍼를 얻습니다. + const wrapper = mount(Counter) + + it('renders the correct markup', () => { + expect(wrapper.html()).toContain('0') + }) + + // 엘리먼트 유무를 확인하는 것도 쉽습니다. + it('has a button', () => { + expect(wrapper.contains('button')).toBe(true) + }) +}) +``` + +`npm test`를 실행하면 테스트를 통과한 것을 볼 수 있습니다. + +### 사용자 조작 시뮬레이션 + +우리의 카운터는 사용자가 버튼을 누르면 증가해야합니다. 이 행동을 시뮬레이션 하려면, 가장 먼저 `wrapper.find()`를 사용 해 버튼의 위치를 찾아야 합니다. 그러면 **버튼 엘리먼트의 래퍼**를 반환합니다. `.trigger()`를 이용하여 버튼 클릭을 시뮬레이션 합니다. + +```js +it('button click should increment the count', () => { + expect(wrapper.vm.count).toBe(0) + const button = wrapper.find('button') + button.trigger('click') + expect(wrapper.vm.count).toBe(1) +}) +``` + +### `nextTick`은 어떻게 할까요? + +Vue는 DOM 업데이트를 미루고 비동기적으로 한번에 배치 처리하기 때문에, 여러번의 데이터 변이로 발생하는 불필요한 재 렌더링을 방지합니다. 실제로 상태가 변경된 후, Vue는 실제 DOM을 갱신을 미루고 기다리고 있습니다. 따라서 `Vue.nextTick`을 사용하지 않으면 처리 순서에 따라 업데이트 된 데이터를 바로 볼 수 없습니다. + +하지만 테스트를 쉽게 하기 위해서 `vue-test-utils`는 모든 업데이트를 동기적으로 반영하므로, DOM 업데이트가 반영되는것을 기다리기 위해 `Vue.nextTick`을 사용할 필요가 없습니다. + +*주의: 비동기 콜백이나 Promise같은 작업을 위해, 명시적으로 이벤트 루프를 이용해야할 경우, 아직은 `nextTick`이 필요합니다.* + +만약 당신이 테스트 파일에서 `nextTick`를 사용하는 하는 경우, 프로미스 내부에서 발생하는 오류는 테스트 러너가 잡아낼 수 없다는 것을 알아둬야 합니다. + +이걸 피하려면, 두가지 접근방법이 있습니다. 테스트를 시작할 때, 테스트를 시작할 때 `done`콜백을 전역 에러 핸들러로 감싸거나, 인자가 없는 `nextTick`을 호출해서 프로미스로 리턴받을 수 있습니다.: + +```js +// 테스트에 잡히지 않는 경우. +it('잡아내지 못하고 지나갑니다.', (done) => { + Vue.nextTick(() => { + expect(true).toBe(false) + done() + }) +}) + +// 테스트가 되는 두가지 케이스. +it('done을 이용해서 에러를 잡을 것 입니다.', (done) => { + Vue.config.errorHandler = done + Vue.nextTick(() => { + expect(true).toBe(false) + done() + }) +}) + +it('프로미스를 이용해서 오류를 잡을 것 입니다.', () => { + return Vue.nextTick() + .then(function () { + expect(true).toBe(false) + }) +}) +``` + + +## 앞으로 해야할 일은 + +- [테스트 러너 선택]하고 `vue-test-utils`을 프로젝트에 추가하세요 +- [테스트를 작성하는데 필요한 기본적인 테크닉](./common-tips.md)을 살펴보세요 diff --git a/docs/kr/guides/testing-SFCs-with-jest.md b/docs/kr/guides/testing-SFCs-with-jest.md new file mode 100644 index 000000000..985d53ac5 --- /dev/null +++ b/docs/kr/guides/testing-SFCs-with-jest.md @@ -0,0 +1,179 @@ +# Jest로 싱글 파일 컴포넌트 테스트 + +> 이 설정의 예제는 [GitHub](https://github.com/vuejs/vue-test-utils-jest-example)에 있습니다. + +Jest는 페이스북이 개발한 테스트 러너입니다. 잘 갖춰진 유닛 테스팅 솔루션을 목표로 합니다. 자세한 내용은 Jest의 [공식 문서](https://facebook.github.io/jest/)를 살펴보세요. + +## Jest 설정 + +이미 webpack, vue-loader 및 Babel이 올바르게 설정했다고 가정합니다. `vue-cli`로 스캐폴딩된 `webpack-simple` 템플릿을 사용합니다. + +가장 먼저 할 일은 Jest와 `vue-test-utils`를 설치하는 것 입니다.: + +```bash +$ npm install --save-dev jest vue-test-utils +``` + +다음은 `package.json`에서 스크립트 하나를 추가합니다. + +```json +// package.json +{ + "scripts": { + "test": "jest" + } +} +``` + +## Jest로 싱글 파일 컴포넌트 처리 + +Jest에게 `*.vue` 파일을 처리하는 방법을 알려주기 위해, `vue-jest` 프리프로세서를 설치하고 설정해야합니다.: + +``` bash +npm install --save-dev vue-jest +``` + +다음으로 `jest` 블럭을 `package.json`에 추가합니다.: + +``` json +{ + // ... + "jest": { + "moduleFileExtensions": [ + "js", + "json", + // Jest가 *.vue 파일을 처리할 수 있도록 합니다. + "vue" + ], + "transform": { + // vue-jest로 *.vue 파일을 처리합니다. + ".*\\.(vue)$": "/node_modules/vue-jest" + }, + "mapCoverage": true + } +} +``` + +> **참고:** `vue-jest`는 현재 사용자 정의 블럭 및 스타일 로딩과 같은 `vue-loader`의 모든 기능을 지원하지는 않습니다. 또한, 코드 분할과 같은 일부 webpack 관련 기능은 지원하지 않습니다. 이를 사용하려면, [Mocha + webpack로 싱글 파일 컴포넌트 테스트](./testing-SFCs-with-mocha-webpack.md)를 읽어보세요. + +## webpack 알리아스 다루기 + +webpack 설정에서 `@`를 `/src`로 지정한 알리아스(별칭)를 사용하는 경우, Jest가 알 수 있도록 `moduleNameMapper` 옵션을 사용하여 맞추는 설정을 추가해야 합니다. + +``` json +{ + // ... + "jest": { + // ... + // @를 src로 매핑합니다. + "moduleNameMapper": { + "^@/(.*)$": "/src/$1" + } + } +} +``` + +## Jest를 위한 Babel 설정 + +Node의 최신 버전이 이미 대부분의 ES2015 기능을 지원하지만 테스트에서 ES 모듈 문법 및 stage-x 기능을 사용하길 원할 수 있습니다. 이를 위해 `babel-jest`를 설치해야합니다.: + +``` bash +npm install --save-dev babel-jest +``` + +다음으로 Jest에게 `babel-jest`로 JavaScript 테스트 파일을 처리하도록 `package.json`에 `jest.transform` 아래에 엔트리를 추가하여 알려줄 필요가 있습니다.: + +``` json +{ + // ... + "jest": { + // ... + "transform": { + // ... +      // babel-jest로 js를 처리한다. +      "^.+\\.js$": "/node_modules/babel-jest" + }, + // ... + } +} +``` + +> 기본적으로 `babel-jest`는 설치 되어있는 한 자동으로 설정됩니다. 그러나 앞서 `*.vue` 파일에 대한 변환을 명시적으로 추가했으므로 이제 `babel-jest`도 명시적으로 구성해야 합니다. + +webpack에서 `babel-preset-env`를 사용한다고 가정하면, webpack은 이미 ES 모듈을 처리하는 방법을 알고 있기 때문에 Babel 설정은 ES 모듈 트랜스파일을 비활성화합니다. 그러나 Jest 테스트는 Node에서 직접 실행되기 때문에, 테스트를 위해 이것을 활성해야 합니다. + +또한 `babel-preset-env`에 현재 사용하고 있는 Node 버전을 알려줄 수도 있습니다. 이렇게 하면 불필요한 기능을 건너뛰고 테스트가 더 빨리 시작됩니다. + +이 옵션을 테스트에만 적용하려면, `env.test` (`babel-jest`를 통해 자동으로 선택)를 아래 설정에 넣으세요. + +`.babelrc` 예제: + +``` json +{ + "presets": [ + ["env", { "modules": false }] + ], + "env": { + "test": { + "presets": [ + ["env", { "targets": { "node": "current" }}] + ] + } + } +} +``` + +### 스냅샷 테스트 + +[`vue-server-renderer`](https://github.com/vuejs/vue/tree/dev/packages/vue-server-renderer)를 사용해 컴포넌트를 문자열로 렌더링하여 [Jest 스냅샷 테스트](https://facebook.github.io/jest/docs/en/snapshot-testing.html)를 위한 스냅샷으로 제공할 수 있습니다. + +`vue-server-renderer`의 렌더링 결과 만으로는 몇개의 SSR 특정 속성을 포함하여 공백 같은 문자열을 무시하므로, diff 부분을 찾아내기가 좀 어렵습니다. 사용자 정의 시리얼라저(custom serializer)를 사용해, 저장된 스냅샷을 개선할 수 있습니다.: + +``` bash +npm install --save-dev jest-serializer-vue +``` + +`package.json` 설정: + +``` json +{ + // ... + "jest": { + // ... + // 스냅샷을 위한 시리얼라이저 + "snapshotSerializers": [ + "/node_modules/jest-serializer-vue" + ] + } +} +``` + +### 테스트 파일 배치 + +기본적으로 Jest는 전체 프로젝트에서 `.spec.js` 또는 `.test.js` 확장자를 갖는 모든 파일을 재귀적으로 선택합니다. 이것이 상황에 맞지 않으면 `package.json` 파일의 config 섹션에서 [testRegex를 변경할 수 있습니다](https://facebook.github.io/jest/docs/en/configuration.html#testregex-string). + + +Jest는 테스트중인 코드 바로 다음에 `__tests__` 디렉터리를 만들 것을 권장 하지만, 테스트 결과를 적절히 마음대로 구조화할 수 있습니다. Jest는 스냅샷 테스트를 수행하는 파일 옆에 `__snapshots__` 디렉터리를 만듭니다. + +### 스펙 예제 + +Jasmine에 익숙하면 Jest의 [assertion API](https://facebook.github.io/jest/docs/en/expect.html#content)를 사용해 편하게 사용할 수 있습니다. + +```js +import { mount } from 'vue-test-utils' +import Component from './component' + +describe('Component', () => { + test('is a Vue instance', () => { + const wrapper = mount(Component) + expect(wrapper.isVueInstance()).toBeTruthy() + }) +}) +``` + +### 리소스 + +- [위 설정의 예제](https://github.com/vuejs/vue-test-utils-jest-example) +- [Vue Conf 2017의 예제 및 슬라이드](https://github.com/codebryo/vue-testing-with-jest-conf17) +- [Jest](https://facebook.github.io/jest/) +- [Babel preset env](https://github.com/babel/babel-preset-env) diff --git a/docs/kr/guides/testing-SFCs-with-mocha-webpack.md b/docs/kr/guides/testing-SFCs-with-mocha-webpack.md new file mode 100644 index 000000000..fde72afab --- /dev/null +++ b/docs/kr/guides/testing-SFCs-with-mocha-webpack.md @@ -0,0 +1,180 @@ +# Mocha + webpack로 싱글 파일 컴포넌트 테스트 + +> 이 설정의 예제는 [GitHub](https://github.com/vuejs/vue-test-utils-mocha-webpack-example)에 있습니다. + +싱글 파일 컴포넌트를 테스트하기 위한 또 다른 전략은 webpack을 통해 모든 테스트를 컴파일한 다음 테스트 러너에서 실행하는 것 입니다. 이 접근 방식의 장점은 모든 webpack 및 `vue-loader` 기능을 완벽히 지원하므로 소스코드를 테스트에 타협하지 않아도 된다는 점 입니다. + +기술적으로 테스트 러너를 사용해 수동으로 연결 할 수 있으나, 특정 작업에 매우 유용한 [`mocha-webpack`](https://github.com/zinserjan/mocha-webpack)이 있습니다. + +## `mocha-webpack` 설정 + +이미 webpack, vue-loader 및 Babel이 올바르게 구성되어 있는 것으로 시작한다고 가정합니다. `vue-cli`에 의해 스캐폴딩된 `webpack-simple`템플릿을 사용합니다. + +가장 먼저 할 일은 테스트 종속성을 설치하는 것 입니다.: + +``` bash +npm install --save-dev vue-test-utils mocha mocha-webpack +``` + +다음은 `package.json`에서 스크립트 하나를 추가합니다.: + +```json +// package.json +{ + "scripts": { + "test": "mocha-webpack --webpack-config webpack.config.js --require test/setup.js test/**/*.spec.js" + } +} +``` + +몇가지 참고해야 할 주의사항이 있습니다.: + +- `--webpack-config` 플래그는 테스트에 사용할 webpack 설정을 지정합니다. 대부분의 경우 이 설정은 실제 프로젝트에 사용하는 설정과 동일하지만, 작은 수정을 하려합니다. 나중에 이것을 다룰 것입니다. + +- `--require` 플래그는 테스트 전에 `test/setup.js`가 실행되도록 합니다. 테스트가 실행될 글로벌 환경을 설정할 수 있습니다. + +- 마지막 전달 인자는 모든 테스트 파일의 집합(glob) 입니다. + +### 추가 webpack 설정 + +#### NPM 의존성 외부화 + +우리는 테스트를 하면서 다양한 NPM 의존성을 가져오게 되는 경우가 많이 있습니다. - 이 모듈 중 일부는 브라우저 사용을 염두하고 작성되어 있는 것 도 있기에, 단순하게 webpack으로 패키지 하기엔 적합하지 않습니다. 또 다른 고려사항으로는 의존성을 외부화 하게 되면, 테스트 시작 속도를 크게 향상시킬 수 있습니다. `webpack-node-externals`을 사용하여 모든 NPM 의존성을 외부화할 수 있습니다.: + +```js +// webpack.config.js +const nodeExternals = require('webpack-node-externals') + +module.exports = { + // ... + externals: [nodeExternals()] +} +``` + +#### 소스맵 + +소스맵은 `mocha-webpack`에 의해서 포착 될 수 있게 하기 위해, in-line화 할 필요가 있습니다. 권장 설정은 다음과 같습니다.: + +``` js +module.exports = { + // ... + devtool: 'inline-cheap-module-source-map' +} +``` + +IDE를 통해 디버깅하는 경우, 다음을 추가하는 것이 좋습니다.: + +``` js +module.exports = { + // ... + output: { + // ... + // 소스맵에 절대 경로 사용 (IDE를 통한 디버깅에서 중요함) + devtoolModuleFilenameTemplate: '[absolute-resource-path]', + devtoolFallbackModuleFilenameTemplate: '[absolute-resource-path]?[hash]' + } +} +``` + +### 브라우저 환경 설정 + +`vue-test-utils`를 실행하려면, 브라우저 환경이 필요합니다. Node.js에서 `jsdom-global`를 이용해 시뮬레이션합니다.: + +```bash +npm install --save-dev jsdom jsdom-global +``` + +`test/setup.js`에 추가합니다.: + +``` js +require('jsdom-global')() +``` + +이는 `vue-test-utils`가 올바르게 동작할 수 있도록 브라우저 환경을 노드에 추가합니다. + +### 검증(Assertion) 라이브러리 선택 + +[Chai](http://chaijs.com/)는 Mocha와 함께 사용하는 인기있는 검증 라이브러리입니다. 스파이와 스텁을 만드는 방법에 대해 [Sinon](http://sinonjs.org/)에서 확인하세요. + +대안으로 Jest의 일부인 `expect`를 사용할 수 있으며, Jest 문서에서 [정확히 같은 API](http://facebook.github.io/jest/docs/en/expect.html#content)를 노출합니다. + +여기서 `expect`를 사용하여 글로벌로 사용할 수 있도록 만들어, 모든 테스트에서 임포트할 필요는 없습니다.: + +``` bash +npm install --save-dev expect +``` + +`test/setup.js`입니다. + +``` js +require('jsdom-global')() + +global.expect = require('expect') +``` + +### 테스트를 위한 Babel 최적화 + +JavaScript를 처리하기 위해 `babel-loader`를 사용하고 있습니다. 앱에서 Babel을 쓰시는 경우, `.babelrc` 파일이 이미 구성 되어 있을 것입니다. 만약 당신이 별도로 구성하지 않는다면, `babel-loader`는 자동으로 같은 설정 파일을 가져오게 됩니다. + +주의 해야 할 한가지는, 이미 ES2015 기능을 대부분 지원하는 Node 6+를 사용하는 경우에 별도의 Babel [env 옵션](https://babeljs.io/docs/usage/babelrc/#env-option)을 설정할 수 있습니다. (예: stage-2 또는 미구현 된 flow 구문을 사용할 수 있도록 지원 등) + +### 테스트 추가 + +`src`에 `Counter.vue` 파일을 만듭니다.: + +``` html + + + +``` + +다음 `test/Counter.spec.js` 파일을 만들고 아래 내용으로 테스트를 작성하세요.: + +```js +import { shallow } from 'vue-test-utils' +import Counter from '../src/Counter.vue' + +describe('Counter.vue', () => { + it('increments count when button is clicked', () => { + const wrapper = shallow(Counter) + wrapper.find('button').trigger('click') + expect(wrapper.find('div').text()).toMatch('1') + }) +}) +``` + +이제 테스트를 할 수 있습니다.: + +``` +npm run unit +``` + +테스트가 실행됩니다! + +### 리소스 + +- [위 설정의 예제](https://github.com/vuejs/vue-test-utils-mocha-webpack-example) +- [Mocha](https://mochajs.org/) +- [mocha-webpack](http://zinserjan.github.io/mocha-webpack/) +- [Chai](http://chaijs.com/) +- [Sinon](http://sinonjs.org/) +- [jest/expect](http://facebook.github.io/jest/docs/en/expect.html#content) diff --git a/docs/kr/guides/using-with-vue-router.md b/docs/kr/guides/using-with-vue-router.md new file mode 100644 index 000000000..883e3ae4c --- /dev/null +++ b/docs/kr/guides/using-with-vue-router.md @@ -0,0 +1,71 @@ +# Vue Router 사용하기 + +## 테스트에 Vue Router 설치 + +테스트 할때 Vue 기반 생성자에 Vue Router를 설치하면 안됩니다. Vue Router를 설치하면 Vue prototype에 읽기 전용 속성으로 `$route`, `$router`가 추가됩니다. + +이를 피하기 위해, localVue를 만들고 여기에 Vue Router를 설치합니다. + +```js +import VueRouter from 'vue-router' +const localVue = createLocalVue() + +localVue.use(VueRouter) + +shallow(Component, { + localVue +}) +``` + +## `router-link` 또는`router-view`를 사용하는 테스트 컴포넌트 + +Vue Router를 설치하면 `router-link`와 `router-view` 컴포넌트가 등록됩니다. 즉, 임포트할 필요 없이 앱 어디서나 사용할 수 있습니다. + +테스트를 실행할 때 컴포넌트에서 vue-router 관련 컴포넌트를 사용할 수 있도록 해야합니다. 이 방법에는 두가지가 있습니다. + +### 스텁 사용하기 + +```js +shallow(Component, { + stubs: ['router-link', 'router-view'] +}) +``` + +### localVue에 Vue Router 설치 + +```js +import VueRouter from 'vue-router' +const localVue = createLocalVue() + +localVue.use(VueRouter) + +shallow(Component, { + localVue +}) +``` + +## `$route`와 `$router` 목킹 + +때로는 컴포넌트가 `$route`와 `$router` 객체의 매개변수로 무언가를 수행하고 있는지 테스트를 할 필요가 있습니다. 이를 위해 사용자 정의 목(mocks-가짜데이터)을 Vue 인스턴스에 전달해야합니다. + +```js +const $route = { + path: '/some/path' +} + +const wrapper = shallow(Component, { + mocks: { + $route + } +}) + +wrapper.vm.$router // /some/path +``` + +## 공통적으로 확인 할 주의사항 + +Vue Router를 설치하면 Vue prototype에 읽기 전용 속성으로 `$route`, `$router`가 추가됩니다. + +이는 `$route` 또는 `$router`를 목킹하려고 시도하는 모든 테스트가 실패하는 것을 의미합니다. + +이를 피하려면, 테스트를 실행하는 동안에는 Vue Router를 설치하지 마십시오. diff --git a/docs/kr/guides/using-with-vuex.md b/docs/kr/guides/using-with-vuex.md new file mode 100644 index 000000000..9fa42d233 --- /dev/null +++ b/docs/kr/guides/using-with-vuex.md @@ -0,0 +1,265 @@ +# Vuex 사용하기 + +이번에는 `vue-test-utils`와 함께, 컴포넌트에서 Vuex를 테스트하는 방법을 알아봅니다. + +## 액션 목킹하기 + +약간의 코드를 살펴보겠습니다. + +이 컴포넌트는 테스트가 필요합니다. Vuex의 액션을 호출하고 있습니다. + +``` html + + + +``` + +이 테스트의 목적을 위해서 액션(action)이 무엇인지, 또는 스토어(store)가 어떻게 구성 되어 있는지 신경 쓸 필요가 없습니다. 우리는 단지 액션이 필요할 때 호출되고 있으며, 기대한 값으로 호출된 것을 확인해야합니다. + +이 테스트에서 얕은(shallow) 스토어에 목킹된 스토어를 전달해야합니다. + +저장소를 베이스 Vue 생성자에 전달하는 대신 [localVue](../api/options.md#localvue)에 전달할 수 있습니다. localVue는 글로벌 Vue 생성자에 영향을 미치지 않고, 변경할 수 있는 범위가 지정된 Vue 생성자입니다. + +어떻게 구성되었는지 보겠습니다. + +``` js +import { shallow, createLocalVue } from 'vue-test-utils' +import Vuex from 'vuex' +import Actions from '../../../src/components/Actions' + +const localVue = createLocalVue() + +localVue.use(Vuex) + +describe('Actions.vue', () => { + let actions + let store + + beforeEach(() => { + actions = { + actionClick: jest.fn(), + actionInput: jest.fn() + } + store = new Vuex.Store({ + state: {}, + actions + }) + }) + + it('calls store action actionInput when input value is input and an input event is fired', () => { + const wrapper = shallow(Actions, { store, localVue }) + const input = wrapper.find('input') + input.element.value = 'input' + input.trigger('input') + expect(actions.actionInput).toHaveBeenCalled() + }) + + it('does not call store action actionInput when input value is not input and an input event is fired', () => { + const wrapper = shallow(Actions, { store, localVue }) + const input = wrapper.find('input') + input.element.value = 'not input' + input.trigger('input') + expect(actions.actionInput).not.toHaveBeenCalled() + }) + + it('calls store action actionClick when button is clicked', () => { + const wrapper = shallow(Actions, { store, localVue }) + wrapper.find('button').trigger('click') + expect(actions.actionClick).toHaveBeenCalled() + }) +}) +``` + +여기서 무슨일이 일어나고 있을까요? 첫째로 Vue에게 `Vue.use` 메소드로 Vuex를 사용하도록 지시합니다. 이는 `Vue.use`에 대한 래퍼일 뿐입니다. + +다음 가짜 값과 함께 `Vuex.store`를 호출하여 목킹 스토어를 만듭니다. 우리가 지금 신경써야 할 액션만 전달합니다. + +액션은 [jest 목킹 함수](https://facebook.github.io/jest/docs/en/mock-functions.html)입니다. 목킹 함수는 액션이 호출되었는지 아닌지를 검증하는 메소드를 제공합니다. + +그런 다음, 우리는 테스트에서 액션 스텁이 예상한 시점에 호출되었는지 검증할 수 있습니다. + +여기에서 이러한 스토어를 정의하는 방식이 조금 어색할 수 있습니다. + +`beforeEach`를 사용하여, 각 테스트 전에 깨끗한 스토어를 보장 하도록 합니다. `beforeEach`는 각 테스트 전에 호출되는 Mocha 훅입니다. 이 테스트에서는 스토어 변수에 값을 다시 지정합니다. 이렇게 하지 않으면, 목킹 함수를 자동으로 재설정해 주어야 합니다. 물론 테스트에서 상태(state)를 바꿀 수 있습니다만, 이 방법이 다음에 진행되는 테스트들에 영향을 주지 않는 방법입니다. + +테스트에서 가장 주의깊게 봐야할 부분은 **가짜 Vuex 스토어를 만든 다음, 이를 vue-test-utils**에 전달하는 것 입니다. + +이제 액션을 목킹할 수 있습니다. getters를 목킹해보겠습니다. + +## 게터 목킹하기 + + +``` html + + + +``` + +아주 간단한 컴포넌트입니다. 게터는 `clicks`와 `inputValue`의 결과를 렌더링합니다. 다시 말하지만, 우리는 단지 결과가 올바르게 렌더링 되는 것 외에 게터가 반환하는 것을 신경쓰지 않습니다. + +테스트를 봅니다. + +``` js +import { shallow, createLocalVue } from 'vue-test-utils' +import Vuex from 'vuex' +import Actions from '../../../src/components/Getters' + +const localVue = createLocalVue() + +localVue.use(Vuex) + +describe('Getters.vue', () => { + let getters + let store + + beforeEach(() => { + getters = { + clicks: () => 2, + inputValue: () => 'input' + } + + store = new Vuex.Store({ + getters + }) + }) + + it('Renders state.inputValue in first p tag', () => { + const wrapper = shallow(Actions, { store, localVue }) + const p = wrapper.find('p') + expect(p.text()).toBe(getters.inputValue()) + }) + + it('Renders state.clicks in second p tag', () => { + const wrapper = shallow(Actions, { store, localVue }) + const p = wrapper.findAll('p').at(1) + expect(p.text()).toBe(getters.clicks().toString()) + }) +}) +``` +이 테스트는 액션 테스트와 비슷합니다. 각 테스트 전에 가짜 스토어를 만들고 `shallow`를 호출 할 때 옵션을 넘겨주고, 목킹 게터에 의해 반환된 값이 렌더링 되는 것을 검증합니다. + +이는 훌륭하지만, getter가 멀쩡한 상태(state)를 리턴하고 있는지 확인 하려면 어떻게 해야할까요? + +## 모듈 목킹하기 + +[모듈](https://vuex.vuejs.org/en/modules.html)은 스토어를 관리 가능한 덩어리로 분리하는데 유용합니다. 또한 게터를 내보냅니다. 테스트에서 이 것을 사용할 수 있습니다. + +컴포넌트를 살펴봅니다. + +``` html + + + +``` + +하나의 액션과 게터를 포함하는 간단한 컴포넌트입니다. + +아래는 테스트입니다. + +``` js +import { shallow, createLocalVue } from 'vue-test-utils' +import Vuex from 'vuex' +import Modules from '../../../src/components/Modules' +import module from '../../../src/store/module' + +const localVue = createLocalVue() + +localVue.use(Vuex) + +describe('Modules.vue', () => { + let actions + let state + let store + + beforeEach(() => { + state = { + module: { + clicks: 2 + } + } + + actions = { + moduleActionClick: jest.fn() + } + + store = new Vuex.Store({ + state, + actions, + getters: module.getters + }) + }) + + it('calls store action moduleActionClick when button is clicked', () => { + const wrapper = shallow(Modules, { store, localVue }) + const button = wrapper.find('button') + button.trigger('click') + expect(actions.moduleActionClick).toHaveBeenCalled() + }) + + it('Renders state.inputValue in first p tag', () => { + const wrapper = shallow(Modules, { store, localVue }) + const p = wrapper.find('p') + expect(p.text()).toBe(state.module.clicks.toString()) + }) +}) +``` + +### 리소스 + +- [이 가이드의 예제](https://github.com/eddyerburgh/vue-test-utils-vuex-example) +- [localVue](../api/options.md#localvue) +- [createLocalVue](../api/createLocalVue.md) From d1dd99c2344fb37a38580404d8b4bdfedd1df72f Mon Sep 17 00:00:00 2001 From: Paul Salaets Date: Fri, 8 Dec 2017 02:02:40 -0500 Subject: [PATCH 0131/1136] docs: createLocalVue should be a named import (#241) --- docs/en/guides/common-tips.md | 2 +- docs/fr/guides/common-tips.md | 2 +- docs/ja/guides/common-tips.md | 2 +- docs/kr/guides/common-tips.md | 2 +- docs/pt-br/guides/common-tips.md | 2 +- docs/ru/guides/common-tips.md | 2 +- docs/zh-cn/guides/common-tips.md | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/en/guides/common-tips.md b/docs/en/guides/common-tips.md index 0ff8b4c15..12668cfe6 100644 --- a/docs/en/guides/common-tips.md +++ b/docs/en/guides/common-tips.md @@ -95,7 +95,7 @@ Some of the components may rely on features injected by a global plugin or mixin If you are writing tests for components in a specific app, you can setup the same global plugins and mixins once in the entry of your tests. But in some cases, for example testing a generic component suite that may get shared across different apps, it's better to test your components in a more isolated setup, without polluting the global `Vue` constructor. We can use the [`createLocalVue`](../api/createLocalVue.md) method to achieve that: ``` js -import createLocalVue from 'vue-test-utils' +import { createLocalVue } from 'vue-test-utils' // create an extended `Vue` constructor const localVue = createLocalVue() diff --git a/docs/fr/guides/common-tips.md b/docs/fr/guides/common-tips.md index 5be769188..fd10e53f0 100644 --- a/docs/fr/guides/common-tips.md +++ b/docs/fr/guides/common-tips.md @@ -94,7 +94,7 @@ Des composants pourraient se fier à des fonctionnalités injectées par un plug Si vous écrivez des tests pour des composants dans une application spécifique, vous pouvez mettre en place les mêmes plugins globaux et mixins en une seule fois dans vos tests. Dans certains cas, comme tester un composant générique utilisé par des applications différentes, il est favorable de tester ces composants dans une installation plus isolée, sans avoir à polluer le constructeur global `Vue`. On peut utiliser la méthode [`createLocalVue`](../api/createLocalVue.md) pour faire cela : ``` js -import createLocalVue from 'vue-test-utils' +import { createLocalVue } from 'vue-test-utils' // créer un constructeur local de `Vue` const localVue = createLocalVue() diff --git a/docs/ja/guides/common-tips.md b/docs/ja/guides/common-tips.md index aab57634f..d8629af45 100644 --- a/docs/ja/guides/common-tips.md +++ b/docs/ja/guides/common-tips.md @@ -95,7 +95,7 @@ mount(Component, { 特定のアプリケーションでコンポーネントのテストを作成している場合は、同じグローバルプラグインとミックスインをテストのエントリに設定できます。しかし、異なるアプリケーション間で共有される可能性のあるジェネリックコンポーネントスイートをテストする場合など、グローバルな `Vue` コンストラクタを汚染することなく、より孤立した設定でコンポーネントをテストする方が良い場合もあります。[createLocalVue](../api/createLocalVue.md) メソッドを使用すると、次のことができます: ``` js -import createLocalVue from 'vue-test-utils' +import { createLocalVue } from 'vue-test-utils' // 拡張された Vue コンストラクタを作成する const localVue = createLocalVue() diff --git a/docs/kr/guides/common-tips.md b/docs/kr/guides/common-tips.md index a1b3ca8b9..8c5678f9a 100644 --- a/docs/kr/guides/common-tips.md +++ b/docs/kr/guides/common-tips.md @@ -95,7 +95,7 @@ mount(Component, { 특정 앱의 컴포넌트에 대한 테스트를 작성하는 경우 동일한 글로벌 플러그인과 믹스인을 테스트 항목에 한번만 설정할 수 있습니다. 그러나 일부 앱에서 공유할 수 있는 일반적인 컴포넌트들을 테스트하는 경우에는 글로벌 `Vue` 생성자를 오염시키지 않고 격리시켜 컴포넌트를 테스트하는 것이 좋습니다. [createLocalVue](../api/createLocalVue.md) 메소드를 사용해 다음과 같이 할 수 있습니다. ``` js -import createLocalVue from 'vue-test-utils' +import { createLocalVue } from 'vue-test-utils' // 확장된 Vue 생성자를 만듭니다. const localVue = createLocalVue() diff --git a/docs/pt-br/guides/common-tips.md b/docs/pt-br/guides/common-tips.md index 798245c80..4d0780e61 100644 --- a/docs/pt-br/guides/common-tips.md +++ b/docs/pt-br/guides/common-tips.md @@ -96,7 +96,7 @@ Alguns dos seus componentes podem ter características injetadas por um plugin o Se você está escrevendo testes para componentes de uma aplicação específica, você pode configurar os plugins e mixins globais uma vez na entrada dos seus testes. Mas, em alguns casos, por exemplo, testando um pacote de componentes genéricos que podem ser compartilhados em diferentes aplicações, será melhor testar seus componentes com uma configuração mais isolada, sem popular o construtor global do Vue. Nós podemos usar o método [createLocalVue](../api/createLocalVue.md) para conseguir isso: ``` js -import createLocalVue from 'vue-test-utils' +import { createLocalVue } from 'vue-test-utils' // criando um construtor ampliado do Vue const localVue = createLocalVue() diff --git a/docs/ru/guides/common-tips.md b/docs/ru/guides/common-tips.md index 039711e82..5b148e6d4 100644 --- a/docs/ru/guides/common-tips.md +++ b/docs/ru/guides/common-tips.md @@ -95,7 +95,7 @@ mount(Component, { Если вы пишете тесты для компонентов определённого приложения, вы можете настроить одни и те же глобальные плагины и примеси один раз перед началом ваших тестов. Но в некоторых случаях, например при тестировании набора общих компонентов, которые могут использоваться в разных приложениях, гораздо лучше протестировать ваши компоненты в более изолированной конфигурации, без загрязнения глобального конструктора `Vue`. Мы можем использовать метод [`createLocalVue`](../api/createLocalVue.md) для достижения этого: ``` js -import createLocalVue from 'vue-test-utils' +import { createLocalVue } from 'vue-test-utils' // создаём расширенный конструктор `Vue` const localVue = createLocalVue() diff --git a/docs/zh-cn/guides/common-tips.md b/docs/zh-cn/guides/common-tips.md index ee4f9fb05..9b506fca4 100644 --- a/docs/zh-cn/guides/common-tips.md +++ b/docs/zh-cn/guides/common-tips.md @@ -95,7 +95,7 @@ mount(Component, { 如果你在为一个特定的应用撰写组件,你可以在你的测试入口处一次性设置相同的全局插件和混入。但是有些情况下,比如测试一个可能会跨越不同应用共享的普通的组件套件的时候,最好还是在一个更加隔离的设置中测试你的组件,不对全局的 `Vue` 构造函数注入任何东西。我们可以使用 [`createLocalVue`](../api/createLocalVue.md) 方法来存档它们: ``` js -import createLocalVue from 'vue-test-utils' +import { createLocalVue } from 'vue-test-utils' // 创建一个扩展的 `Vue` 构造函数 const localVue = createLocalVue() From 87900a4b9eaff67a83a77ef254ecd1fedbb051e7 Mon Sep 17 00:00:00 2001 From: eddyerburgh Date: Fri, 8 Dec 2017 07:06:44 +0000 Subject: [PATCH 0132/1136] test: fix flow error --- src/wrappers/wrapper.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/wrappers/wrapper.js b/src/wrappers/wrapper.js index 26db15a47..223ecf443 100644 --- a/src/wrappers/wrapper.js +++ b/src/wrappers/wrapper.js @@ -413,6 +413,7 @@ export default class Wrapper implements BaseWrapper { } }) } + // $FlowIgnore this.vm._watchers.forEach((watcher) => { if (watcher.expression === key) { watcher.run() } }) From 5da682152959d0ed644cfeaa3e9e50ddb06ecb14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8B=BE=E4=B8=89=E8=82=A1=E5=9B=9B?= Date: Fri, 8 Dec 2017 19:52:52 +0800 Subject: [PATCH 0133/1136] docs(en): typos (#242) * [docs][en] small fix * [en] typos --- docs/en/api/README.md | 2 +- docs/en/api/options.md | 2 +- docs/en/guides/using-with-vuex.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/en/api/README.md b/docs/en/api/README.md index 0404e1e93..28993ec27 100644 --- a/docs/en/api/README.md +++ b/docs/en/api/README.md @@ -57,6 +57,6 @@ * [components](./components/README.md) * [TransitionStub](./components/TransitionStub.md) * [TransitionGroupStub](./components/TransitionGroupStub.md) -* [createLocalVue](./createLocalVue.md) * [Selectors](./selectors.md) +* [createLocalVue](./createLocalVue.md) * [config](./config.md) diff --git a/docs/en/api/options.md b/docs/en/api/options.md index 0c3c7421f..88c41b2be 100644 --- a/docs/en/api/options.md +++ b/docs/en/api/options.md @@ -165,4 +165,4 @@ Clones component before mounting if `true`, which avoids mutating the original c - type: `Object` -Pass properties for components to use in injection. See [provide/inject](https://vuejs.org/v2/api/#provide-inject) +Pass properties for components to use in injection. See [provide/inject](https://vuejs.org/v2/api/#provide-inject). diff --git a/docs/en/guides/using-with-vuex.md b/docs/en/guides/using-with-vuex.md index dacdc253a..462e964db 100644 --- a/docs/en/guides/using-with-vuex.md +++ b/docs/en/guides/using-with-vuex.md @@ -93,7 +93,7 @@ describe('Actions.vue', () => { What’s happening here? First we tell Vue to use Vuex with the `localVue.use` method. This is just a wrapper around `Vue.use`. -We then make a mock store by calling new `Vuex.store` with our mock values. We only pass it the actions, since that’s all we care about. +We then make a mock store by calling `new Vuex.store` with our mock values. We only pass it the actions, since that’s all we care about. The actions are [jest mock functions](https://facebook.github.io/jest/docs/en/mock-functions.html). These mock functions give us methods to assert whether the actions were called or not. From 790a949cbff94e98829df05a32f3c1ce801a2aab Mon Sep 17 00:00:00 2001 From: 38elements <38elements@users.noreply.github.com> Date: Sat, 9 Dec 2017 16:16:47 +0900 Subject: [PATCH 0134/1136] feat(slots): enable passing text to slots (#233) * Passing text to slots * Add VNode * Fix message * Use vueVersion * Change vueVersion * Change vueVersion * Add vueVersion * Improve test --- .gitignore | 3 ++ src/lib/add-slots.js | 35 ++++++++++++--------- test/unit/specs/mount.spec.js | 3 +- test/unit/specs/mount/options/slots.spec.js | 23 ++++++++++++++ 4 files changed, 48 insertions(+), 16 deletions(-) diff --git a/.gitignore b/.gitignore index 4c30d01be..994308194 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,7 @@ _book .tmp tmp* +# Vim +*.sw[po] + yarn.lock diff --git a/src/lib/add-slots.js b/src/lib/add-slots.js index a2425a76f..536b8de02 100644 --- a/src/lib/add-slots.js +++ b/src/lib/add-slots.js @@ -1,5 +1,6 @@ // @flow +import Vue from 'vue' import { compileToFunctions } from 'vue-template-compiler' import { throwError } from './util' @@ -8,24 +9,28 @@ function isValidSlot (slot: any): boolean { } function addSlotToVm (vm: Component, slotName: string, slotValue: Component | string | Array | Array): void { - if (Array.isArray(vm.$slots[slotName])) { - if (typeof slotValue === 'string') { - if (!compileToFunctions) { - throwError('vueTemplateCompiler is undefined, you must pass components explicitly if vue-template-compiler is undefined') - } - vm.$slots[slotName].push(vm.$createElement(compileToFunctions(slotValue))) - } else { - vm.$slots[slotName].push(vm.$createElement(slotValue)) + let elem + const vueVersion = Number(`${Vue.version.split('.')[0]}.${Vue.version.split('.')[1]}`) + if (typeof slotValue === 'string') { + if (!compileToFunctions) { + throwError('vueTemplateCompiler is undefined, you must pass components explicitly if vue-template-compiler is undefined') } - } else { - if (typeof slotValue === 'string') { - if (!compileToFunctions) { - throwError('vueTemplateCompiler is undefined, you must pass components explicitly if vue-template-compiler is undefined') - } - vm.$slots[slotName] = [vm.$createElement(compileToFunctions(slotValue))] + if (slotValue.trim()[0] === '<') { + elem = vm.$createElement(compileToFunctions(slotValue)) } else { - vm.$slots[slotName] = [vm.$createElement(slotValue)] // eslint-disable-line no-param-reassign + if (vueVersion >= 2.2) { + elem = vm._v(slotValue) + } else { + throwError('vue-test-utils support for passing text to slots at vue@2.2+') + } } + } else { + elem = vm.$createElement(slotValue) + } + if (Array.isArray(vm.$slots[slotName])) { + vm.$slots[slotName].push(elem) + } else { + vm.$slots[slotName] = [elem] } } diff --git a/test/unit/specs/mount.spec.js b/test/unit/specs/mount.spec.js index 0a9968ee2..b507cc304 100644 --- a/test/unit/specs/mount.spec.js +++ b/test/unit/specs/mount.spec.js @@ -1,6 +1,7 @@ import Vue from 'vue' import { compileToFunctions } from 'vue-template-compiler' import mount from '~src/mount' +import Component from '~resources/components/component.vue' import ComponentWithProps from '~resources/components/component-with-props.vue' import ComponentWithMixin from '~resources/components/component-with-mixin.vue' import createLocalVue from '~src/create-local-vue' @@ -101,7 +102,7 @@ describe('mount', () => { 'prop': 'val' }, slots: { - 'prop': 'val' + 'prop': Component }, localVue: createLocalVue(), stubs: { diff --git a/test/unit/specs/mount/options/slots.spec.js b/test/unit/specs/mount/options/slots.spec.js index 6458c665a..bbe28354a 100644 --- a/test/unit/specs/mount/options/slots.spec.js +++ b/test/unit/specs/mount/options/slots.spec.js @@ -2,6 +2,7 @@ import { compileToFunctions } from 'vue-template-compiler' import mount from '~src/mount' import Component from '~resources/components/component.vue' import ComponentWithSlots from '~resources/components/component-with-slots.vue' +import { vueVersion } from '~resources/test-utils' describe('mount.slots', () => { it('mounts component with default slot if passed component in slot object', () => { @@ -25,6 +26,17 @@ describe('mount.slots', () => { expect(wrapper.contains('span')).to.equal(true) }) + it('mounts component with default slot if passed string in slot object', () => { + if (vueVersion >= 2.2) { + const wrapper = mount(ComponentWithSlots, { slots: { default: 'foo' }}) + expect(wrapper.find('main').text()).to.equal('foo') + } else { + const message = '[vue-test-utils]: vue-test-utils support for passing text to slots at vue@2.2+' + const fn = () => mount(ComponentWithSlots, { slots: { default: 'foo' }}) + expect(fn).to.throw().with.property('message', message) + } + }) + it('throws error if passed string in default slot object and vue-template-compiler is undefined', () => { const compilerSave = require.cache[require.resolve('vue-template-compiler')].exports.compileToFunctions require.cache[require.resolve('vue-template-compiler')].exports.compileToFunctions = undefined @@ -46,6 +58,17 @@ describe('mount.slots', () => { expect(wrapper.contains('span')).to.equal(true) }) + it('mounts component with default slot if passed string in slot text array object', () => { + if (vueVersion >= 2.2) { + const wrapper = mount(ComponentWithSlots, { slots: { default: ['foo', 'bar'] }}) + expect(wrapper.find('main').text()).to.equal('foobar') + } else { + const message = '[vue-test-utils]: vue-test-utils support for passing text to slots at vue@2.2+' + const fn = () => mount(ComponentWithSlots, { slots: { default: ['foo', 'bar'] }}) + expect(fn).to.throw().with.property('message', message) + } + }) + it('throws error if passed string in default slot array vue-template-compiler is undefined', () => { const compilerSave = require.cache[require.resolve('vue-template-compiler')].exports.compileToFunctions require.cache[require.resolve('vue-template-compiler')].exports.compileToFunctions = undefined From 7149c0523510c53b60c66e89e97474c6b6c09070 Mon Sep 17 00:00:00 2001 From: Tho Date: Sat, 9 Dec 2017 12:25:33 +0100 Subject: [PATCH 0135/1136] fix: event capturing in beforeCreate lifecycle hook (#243) * Fix event capture by installing captor via mixin The event capture was not working when events were emitted in methods during the component creation, like in the lifecycle hook beforeCreate. This fix installs the captor using a mixin before the beforeCreate event gets emitted. The captor is available to the wrapper, when the wrapper accesses the vue component. Two tests were written, in emitted.spec, and emittedByOrder, respectively. * Adjust emitted tests to pass tested Vue versions * Use component field for event capturing Instead of using an external object, this.__emitted and this.__emittedByOrder is now used to capture events. This allows the wrapper to access the event logger of the component. The installed event logger is never removed and therefore will exist even after the wrapper was created. --- src/lib/create-instance.js | 3 + src/lib/log-events.js | 14 ++++- src/wrappers/vue-wrapper.js | 7 +-- test/unit/specs/mount/Wrapper/emitted.spec.js | 36 ++++++++++++ .../mount/Wrapper/emittedByOrder.spec.js | 57 +++++++++++++++++-- 5 files changed, 106 insertions(+), 11 deletions(-) diff --git a/src/lib/create-instance.js b/src/lib/create-instance.js index 7295be72b..993b7b921 100644 --- a/src/lib/create-instance.js +++ b/src/lib/create-instance.js @@ -5,6 +5,7 @@ import addMocks from './add-mocks' import addAttrs from './add-attrs' import addListeners from './add-listeners' import addProvide from './add-provide' +import { addEventLogger } from './log-events' import { stubComponents } from './stub-components' import { throwError } from './util' import { compileTemplate } from './compile-template' @@ -45,6 +46,8 @@ export default function createConstructor ( compileTemplate(component) } + addEventLogger(vue) + const Constructor = vue.extend(component) const instanceOptions = { ...options } diff --git a/src/lib/log-events.js b/src/lib/log-events.js index 62e4634a1..fffcb783e 100644 --- a/src/lib/log-events.js +++ b/src/lib/log-events.js @@ -1,4 +1,6 @@ -export function logEvents (vm, emitted, emittedByOrder) { +// @flow + +export function logEvents (vm: Component, emitted: Object, emittedByOrder: Array) { const emit = vm.$emit vm.$emit = (name, ...args) => { (emitted[name] || (emitted[name] = [])).push(args) @@ -6,3 +8,13 @@ export function logEvents (vm, emitted, emittedByOrder) { return emit.call(vm, name, ...args) } } + +export function addEventLogger (vue: Component) { + vue.mixin({ + beforeCreate: function () { + this.__emitted = Object.create(null) + this.__emittedByOrder = [] + logEvents(this, this.__emitted, this.__emittedByOrder) + } + }) +} diff --git a/src/wrappers/vue-wrapper.js b/src/wrappers/vue-wrapper.js index 09c4b19ce..8e853bf4b 100644 --- a/src/wrappers/vue-wrapper.js +++ b/src/wrappers/vue-wrapper.js @@ -1,7 +1,6 @@ // @flow import Wrapper from './wrapper' -import { logEvents } from '../lib/log-events' function update () { this._update(this._render()) @@ -24,9 +23,7 @@ export default class VueWrapper extends Wrapper implements BaseWrapper { })) this.vm = vm this.isVueComponent = true - this._emitted = Object.create(null) - this._emittedByOrder = [] - - logEvents(vm, this._emitted, this._emittedByOrder) + this._emitted = vm.__emitted + this._emittedByOrder = vm.__emittedByOrder } } diff --git a/test/unit/specs/mount/Wrapper/emitted.spec.js b/test/unit/specs/mount/Wrapper/emitted.spec.js index 449a62270..7188c3ded 100644 --- a/test/unit/specs/mount/Wrapper/emitted.spec.js +++ b/test/unit/specs/mount/Wrapper/emitted.spec.js @@ -1,4 +1,5 @@ import mount from '~src/mount' +import createLocalVue from '~src/create-local-vue' describe('emitted', () => { it('captures emitted events with a different api', () => { @@ -54,4 +55,39 @@ describe('emitted', () => { const fn = () => wrapper.find('p').emitted() expect(fn).to.throw().with.property('message', message) }) + + it('captures all events thrown after beforeCreate lifecycle hook', () => { + const wrapper = mount({ + beforeCreate () { + this.$emit('foo') + }, + mounted () { + this.$emit('bar', 1, 2) + } + }) + + expect(wrapper.emitted().foo).to.eql([[]]) + expect(wrapper.emitted().bar).to.eql([[1, 2]]) + }) + + it('captures only events from its component without side effects on localVue', () => { + const localVue = createLocalVue() + + const wrapper1 = mount({ + beforeCreate () { + this.$emit('foo') + } + }, { localVue }) + + const wrapper2 = mount({ + mounted () { + this.$emit('bar') + } + }, { localVue }) + + expect(wrapper1.emitted().foo).to.eql([[]]) + expect(wrapper1.emitted().bar).to.eql(undefined) + expect(wrapper2.emitted().foo).to.eql(undefined) + expect(wrapper2.emitted().bar).to.eql([[]]) + }) }) diff --git a/test/unit/specs/mount/Wrapper/emittedByOrder.spec.js b/test/unit/specs/mount/Wrapper/emittedByOrder.spec.js index 99b0013c8..dd88384eb 100644 --- a/test/unit/specs/mount/Wrapper/emittedByOrder.spec.js +++ b/test/unit/specs/mount/Wrapper/emittedByOrder.spec.js @@ -1,4 +1,5 @@ import mount from '~src/mount' +import Vue from 'vue' describe('emittedByOrder', () => { it('captures emitted events in order', () => { @@ -9,11 +10,24 @@ describe('emittedByOrder', () => { wrapper.vm.$emit('foo') wrapper.vm.$emit('bar', 1, 2, 3) wrapper.vm.$emit('foo', 2, 3, 4) - expect(wrapper.emittedByOrder()).to.eql([ - { name: 'foo', args: [] }, - { name: 'bar', args: [1, 2, 3] }, - { name: 'foo', args: [2, 3, 4] } - ]) + + if (Vue.version === '2.0.8') { + expect(wrapper.emittedByOrder()).to.eql([ + { name: 'hook:beforeCreate', args: [] }, + { name: 'hook:created', args: [] }, + { name: 'hook:beforeMount', args: [] }, + { name: 'hook:mounted', args: [] }, + { name: 'foo', args: [] }, + { name: 'bar', args: [1, 2, 3] }, + { name: 'foo', args: [2, 3, 4] } + ]) + } else { + expect(wrapper.emittedByOrder()).to.eql([ + { name: 'foo', args: [] }, + { name: 'bar', args: [1, 2, 3] }, + { name: 'foo', args: [2, 3, 4] } + ]) + } }) it('throws error when called on non VueWrapper', () => { @@ -25,4 +39,37 @@ describe('emittedByOrder', () => { const fn = () => wrapper.find('p').emittedByOrder() expect(fn).to.throw().with.property('message', message) }) + + it('captures in lifecycle hooks emitted events in order', () => { + const wrapper = mount({ + render: h => h('div'), + beforeCreate: function () { + this.$emit('foo') + }, + created: function () { + this.$emit('bar', 1, 2, 3) + }, + mounted: function () { + this.$emit('foo', 2, 3, 4) + } + }) + + if (Vue.version === '2.0.8') { + expect(wrapper.emittedByOrder()).to.eql([ + { name: 'foo', args: [] }, + { name: 'hook:beforeCreate', args: [] }, + { name: 'bar', args: [1, 2, 3] }, + { name: 'hook:created', args: [] }, + { name: 'hook:beforeMount', args: [] }, + { name: 'foo', args: [2, 3, 4] }, + { name: 'hook:mounted', args: [] } + ]) + } else { + expect(wrapper.emittedByOrder()).to.eql([ + { name: 'foo', args: [] }, + { name: 'bar', args: [1, 2, 3] }, + { name: 'foo', args: [2, 3, 4] } + ]) + } + }) }) From 71d2c6b73e7738e99654f731207b79a1fd1c207f Mon Sep 17 00:00:00 2001 From: eddyerburgh Date: Sat, 9 Dec 2017 11:29:00 +0000 Subject: [PATCH 0136/1136] build: 1.0.0-beta.7 --- dist/vue-test-utils.amd.js | 219 +++++++++++++++++++++++++----------- dist/vue-test-utils.iife.js | 219 +++++++++++++++++++++++++----------- dist/vue-test-utils.js | 219 +++++++++++++++++++++++++----------- dist/vue-test-utils.umd.js | 219 +++++++++++++++++++++++++----------- 4 files changed, 612 insertions(+), 264 deletions(-) diff --git a/dist/vue-test-utils.amd.js b/dist/vue-test-utils.amd.js index a4dbc757e..ae8074217 100644 --- a/dist/vue-test-utils.amd.js +++ b/dist/vue-test-utils.amd.js @@ -2802,26 +2802,42 @@ function getSelectorTypeOrThrow (selector, methodName) { // -function findAllVueComponents (vm, components) { +function findAllVueComponentsFromVm (vm, components) { if ( components === void 0 ) components = []; components.push(vm); - vm.$children.forEach(function (child) { - findAllVueComponents(child, components); + findAllVueComponentsFromVm(child, components); }); return components } +function findAllVueComponentsFromVnode (vnode, components) { + if ( components === void 0 ) components = []; + + debugger + if (vnode.child) { + components.push(vnode.child); + } + if (vnode.children) { + vnode.children.forEach(function (child) { + findAllVueComponentsFromVnode(child, components); + }); + } + + return components +} + function vmCtorMatchesName (vm, name) { return (vm.$vnode && vm.$vnode.componentOptions && vm.$vnode.componentOptions.Ctor.options.name === name) || (vm._vnode && vm._vnode.functionalOptions && vm._vnode.functionalOptions.name === name) || vm.$options && vm.$options.name === name } -function findVueComponents (vm, componentName) { - var components = findAllVueComponents(vm); +function findVueComponents (root, componentName) { + debugger + var components = root._isVue ? findAllVueComponentsFromVm(root) : findAllVueComponentsFromVnode(root); return components.filter(function (component) { if (!component.$vnode) { return false @@ -2920,8 +2936,9 @@ WrapperArray.prototype.contains = function contains (selector) { return this.wrappers.every(function (wrapper) { return wrapper.contains(selector); }) }; + WrapperArray.prototype.exists = function exists () { - return this.wrappers.length > 0 + return this.length > 0 && this.wrappers.every(function (wrapper) { return wrapper.exists(); }) }; WrapperArray.prototype.emitted = function emitted () { @@ -3277,6 +3294,9 @@ Wrapper.prototype.emittedByOrder = function emittedByOrder () { * Utility to check wrapper exists. Returns true as Wrapper always exists */ Wrapper.prototype.exists = function exists () { + if (this.isVueComponent) { + return !!this.vm && !this.vm._isDestroyed + } return true }; @@ -3379,13 +3399,12 @@ Wrapper.prototype.hasStyle = function hasStyle (style, value) { */ Wrapper.prototype.find = function find (selector) { var selectorType = getSelectorTypeOrThrow(selector, 'find'); - if (selectorType === selectorTypes.VUE_COMPONENT) { if (!selector.name) { throwError('.find() requires component to have a name property'); } - var vm = this.vm || this.vnode.context.$root; - var components = findVueComponents(vm, selector.name); + var root = this.vm || this.vnode; + var components = findVueComponents(root, selector.name); if (components.length === 0) { return new ErrorWrapper('Component') } @@ -3423,8 +3442,8 @@ Wrapper.prototype.findAll = function findAll (selector) { if (!selector.name) { throwError('.findAll() requires component to have a name property'); } - var vm = this.vm || this.vnode.context.$root; - var components = findVueComponents(vm, selector.name); + var root = this.vm || this.vnode; + var components = findVueComponents(root, selector.name); return new WrapperArray(components.map(function (component) { return new VueWrapper(component, this$1.options); })) } @@ -3577,6 +3596,10 @@ Wrapper.prototype.setComputed = function setComputed (computed) { } }); } + // $FlowIgnore + this$1.vm._watchers.forEach(function (watcher) { + if (watcher.expression === key) { watcher.run(); } + }); }); this.update(); }; @@ -3679,15 +3702,29 @@ Wrapper.prototype.trigger = function trigger (type, options) { up: 38, down: 40, left: 37, - right: 39 + right: 39, + end: 35, + home: 36, + backspace: 8, + insert: 45, + pageup: 33, + pagedown: 34 }; var event = type.split('.'); - var eventObject = new window.Event(event[0], { - bubbles: true, - cancelable: true - }); + var eventObject; + + // Fallback for IE10,11 - https://stackoverflow.com/questions/26596123 + if (typeof (window.Event) === 'function') { + eventObject = new window.Event(event[0], { + bubbles: true, + cancelable: true + }); + } else { + eventObject = document.createEvent('Event'); + eventObject.initEvent(event[0], true, true); + } if (options && options.preventDefault) { eventObject.preventDefault(); @@ -3695,11 +3732,13 @@ Wrapper.prototype.trigger = function trigger (type, options) { if (options) { Object.keys(options).forEach(function (key) { + // $FlowIgnore eventObject[key] = options[key]; }); } if (event.length === 2) { + // $FlowIgnore eventObject.keyCode = modifiers[event[1]]; } @@ -3707,18 +3746,6 @@ Wrapper.prototype.trigger = function trigger (type, options) { this.update(); }; -function logEvents (vm, emitted, emittedByOrder) { - var emit = vm.$emit; - vm.$emit = function (name) { - var args = [], len = arguments.length - 1; - while ( len-- > 0 ) args[ len ] = arguments[ len + 1 ]; - - (emitted[name] || (emitted[name] = [])).push(args); - emittedByOrder.push({ name: name, args: args }); - return emit.call.apply(emit, [ vm, name ].concat( args )) - }; -} - // function update () { @@ -3742,10 +3769,8 @@ var VueWrapper = (function (Wrapper$$1) { })); this.vm = vm; this.isVueComponent = true; - this._emitted = Object.create(null); - this._emittedByOrder = []; - - logEvents(vm, this._emitted, this._emittedByOrder); + this._emitted = vm.__emitted; + this._emittedByOrder = vm.__emittedByOrder; } if ( Wrapper$$1 ) VueWrapper.__proto__ = Wrapper$$1; @@ -3757,35 +3782,39 @@ var VueWrapper = (function (Wrapper$$1) { // -function isValidSlot$1 (slot) { +function isValidSlot (slot) { return Array.isArray(slot) || (slot !== null && typeof slot === 'object') || typeof slot === 'string' } function addSlotToVm (vm, slotName, slotValue) { - if (Array.isArray(vm.$slots[slotName])) { - if (typeof slotValue === 'string') { - if (!vueTemplateCompiler.compileToFunctions) { - throwError('vueTemplateCompiler is undefined, you must pass components explicitly if vue-template-compiler is undefined'); - } - vm.$slots[slotName].push(vm.$createElement(vueTemplateCompiler.compileToFunctions(slotValue))); - } else { - vm.$slots[slotName].push(vm.$createElement(slotValue)); + var elem; + var vueVersion = Number(((Vue.version.split('.')[0]) + "." + (Vue.version.split('.')[1]))); + if (typeof slotValue === 'string') { + if (!vueTemplateCompiler.compileToFunctions) { + throwError('vueTemplateCompiler is undefined, you must pass components explicitly if vue-template-compiler is undefined'); } - } else { - if (typeof slotValue === 'string') { - if (!vueTemplateCompiler.compileToFunctions) { - throwError('vueTemplateCompiler is undefined, you must pass components explicitly if vue-template-compiler is undefined'); - } - vm.$slots[slotName] = [vm.$createElement(vueTemplateCompiler.compileToFunctions(slotValue))]; + if (slotValue.trim()[0] === '<') { + elem = vm.$createElement(vueTemplateCompiler.compileToFunctions(slotValue)); } else { - vm.$slots[slotName] = [vm.$createElement(slotValue)]; // eslint-disable-line no-param-reassign + if (vueVersion >= 2.2) { + elem = vm._v(slotValue); + } else { + throwError('vue-test-utils support for passing text to slots at vue@2.2+'); + } } + } else { + elem = vm.$createElement(slotValue); + } + if (Array.isArray(vm.$slots[slotName])) { + vm.$slots[slotName].push(elem); + } else { + vm.$slots[slotName] = [elem]; } } function addSlots (vm, slots) { Object.keys(slots).forEach(function (key) { - if (!isValidSlot$1(slots[key])) { + if (!isValidSlot(slots[key])) { throwError('slots[key] must be a Component, string or an array of Components'); } @@ -3843,6 +3872,30 @@ function addProvide (component, optionProvide, options) { // +function logEvents (vm, emitted, emittedByOrder) { + var emit = vm.$emit; + vm.$emit = function (name) { + var args = [], len = arguments.length - 1; + while ( len-- > 0 ) args[ len ] = arguments[ len + 1 ]; + + (emitted[name] || (emitted[name] = [])).push(args); + emittedByOrder.push({ name: name, args: args }); + return emit.call.apply(emit, [ vm, name ].concat( args )) + }; +} + +function addEventLogger (vue) { + vue.mixin({ + beforeCreate: function () { + this.__emitted = Object.create(null); + this.__emittedByOrder = []; + logEvents(this, this.__emitted, this.__emittedByOrder); + } + }); +} + +// + function compileTemplate (component) { Object.assign(component, vueTemplateCompiler.compileToFunctions(component.template)); } @@ -4077,7 +4130,7 @@ function deleteMountingOptions (options) { // -function isValidSlot (slot) { +function isValidSlot$1 (slot) { return Array.isArray(slot) || (slot !== null && typeof slot === 'object') || typeof slot === 'string' } @@ -4095,7 +4148,7 @@ function createFunctionalSlots (slots, h) { Object.keys(slots).forEach(function (slotType) { if (Array.isArray(slots[slotType])) { slots[slotType].forEach(function (slot) { - if (!isValidSlot(slot)) { + if (!isValidSlot$1(slot)) { throwError('slots[key] must be a Component, string or an array of Components'); } var component = typeof slot === 'string' ? vueTemplateCompiler.compileToFunctions(slot) : slot; @@ -4104,7 +4157,7 @@ function createFunctionalSlots (slots, h) { children.push(newSlot); }); } else { - if (!isValidSlot(slots[slotType])) { + if (!isValidSlot$1(slots[slotType])) { throwError('slots[key] must be a Component, string or an array of Components'); } var component = typeof slots[slotType] === 'string' ? vueTemplateCompiler.compileToFunctions(slots[slotType]) : slots[slotType]; @@ -4116,6 +4169,25 @@ function createFunctionalSlots (slots, h) { return children } +function createFunctionalComponent (component, mountingOptions) { + if (mountingOptions.context && typeof mountingOptions.context !== 'object') { + throwError('mount.context must be an object'); + } + + var clonedComponent = cloneDeep_1(component); + return { + render: function render (h) { + return h( + clonedComponent, + mountingOptions.context || component.FunctionalRenderContext, + (mountingOptions.context && mountingOptions.context.children && mountingOptions.context.children.map(function (x) { return typeof x === 'function' ? x(h) : x; })) || createFunctionalSlots(mountingOptions.slots, h) + ) + } + } +} + +// + function createConstructor ( component, options @@ -4129,20 +4201,7 @@ function createConstructor ( } if (component.functional) { - if (mountingOptions.context && typeof mountingOptions.context !== 'object') { - throwError('mount.context must be an object'); - } - - var clonedComponent = cloneDeep_1(component); - component = { - render: function render (h) { - return h( - clonedComponent, - mountingOptions.context || component.FunctionalRenderContext, - (mountingOptions.context && mountingOptions.context.children) || createFunctionalSlots(mountingOptions.slots, h) - ) - } - }; + component = createFunctionalComponent(component, mountingOptions); } else if (mountingOptions.context) { throwError( 'mount.context can only be used when mounting a functional component' @@ -4161,6 +4220,8 @@ function createConstructor ( compileTemplate(component); } + addEventLogger(vue); + var Constructor = vue.extend(component); var instanceOptions = Object.assign({}, options); @@ -4206,6 +4267,32 @@ if (!Element.prototype.matches) { }; } +if (typeof Object.assign !== 'function') { + (function () { + Object.assign = function (target) { + 'use strict'; + var arguments$1 = arguments; + + if (target === undefined || target === null) { + throw new TypeError('Cannot convert undefined or null to object') + } + + var output = Object(target); + for (var index = 1; index < arguments.length; index++) { + var source = arguments$1[index]; + if (source !== undefined && source !== null) { + for (var nextKey in source) { + if (source.hasOwnProperty(nextKey)) { + output[nextKey] = source[nextKey]; + } + } + } + } + return output + }; + })(); +} + // Vue.config.productionTip = false; diff --git a/dist/vue-test-utils.iife.js b/dist/vue-test-utils.iife.js index 4c635fe0f..de152f9b2 100644 --- a/dist/vue-test-utils.iife.js +++ b/dist/vue-test-utils.iife.js @@ -2803,26 +2803,42 @@ function getSelectorTypeOrThrow (selector, methodName) { // -function findAllVueComponents (vm, components) { +function findAllVueComponentsFromVm (vm, components) { if ( components === void 0 ) components = []; components.push(vm); - vm.$children.forEach(function (child) { - findAllVueComponents(child, components); + findAllVueComponentsFromVm(child, components); }); return components } +function findAllVueComponentsFromVnode (vnode, components) { + if ( components === void 0 ) components = []; + + debugger + if (vnode.child) { + components.push(vnode.child); + } + if (vnode.children) { + vnode.children.forEach(function (child) { + findAllVueComponentsFromVnode(child, components); + }); + } + + return components +} + function vmCtorMatchesName (vm, name) { return (vm.$vnode && vm.$vnode.componentOptions && vm.$vnode.componentOptions.Ctor.options.name === name) || (vm._vnode && vm._vnode.functionalOptions && vm._vnode.functionalOptions.name === name) || vm.$options && vm.$options.name === name } -function findVueComponents (vm, componentName) { - var components = findAllVueComponents(vm); +function findVueComponents (root, componentName) { + debugger + var components = root._isVue ? findAllVueComponentsFromVm(root) : findAllVueComponentsFromVnode(root); return components.filter(function (component) { if (!component.$vnode) { return false @@ -2921,8 +2937,9 @@ WrapperArray.prototype.contains = function contains (selector) { return this.wrappers.every(function (wrapper) { return wrapper.contains(selector); }) }; + WrapperArray.prototype.exists = function exists () { - return this.wrappers.length > 0 + return this.length > 0 && this.wrappers.every(function (wrapper) { return wrapper.exists(); }) }; WrapperArray.prototype.emitted = function emitted () { @@ -3278,6 +3295,9 @@ Wrapper.prototype.emittedByOrder = function emittedByOrder () { * Utility to check wrapper exists. Returns true as Wrapper always exists */ Wrapper.prototype.exists = function exists () { + if (this.isVueComponent) { + return !!this.vm && !this.vm._isDestroyed + } return true }; @@ -3380,13 +3400,12 @@ Wrapper.prototype.hasStyle = function hasStyle (style, value) { */ Wrapper.prototype.find = function find (selector) { var selectorType = getSelectorTypeOrThrow(selector, 'find'); - if (selectorType === selectorTypes.VUE_COMPONENT) { if (!selector.name) { throwError('.find() requires component to have a name property'); } - var vm = this.vm || this.vnode.context.$root; - var components = findVueComponents(vm, selector.name); + var root = this.vm || this.vnode; + var components = findVueComponents(root, selector.name); if (components.length === 0) { return new ErrorWrapper('Component') } @@ -3424,8 +3443,8 @@ Wrapper.prototype.findAll = function findAll (selector) { if (!selector.name) { throwError('.findAll() requires component to have a name property'); } - var vm = this.vm || this.vnode.context.$root; - var components = findVueComponents(vm, selector.name); + var root = this.vm || this.vnode; + var components = findVueComponents(root, selector.name); return new WrapperArray(components.map(function (component) { return new VueWrapper(component, this$1.options); })) } @@ -3578,6 +3597,10 @@ Wrapper.prototype.setComputed = function setComputed (computed) { } }); } + // $FlowIgnore + this$1.vm._watchers.forEach(function (watcher) { + if (watcher.expression === key) { watcher.run(); } + }); }); this.update(); }; @@ -3680,15 +3703,29 @@ Wrapper.prototype.trigger = function trigger (type, options) { up: 38, down: 40, left: 37, - right: 39 + right: 39, + end: 35, + home: 36, + backspace: 8, + insert: 45, + pageup: 33, + pagedown: 34 }; var event = type.split('.'); - var eventObject = new window.Event(event[0], { - bubbles: true, - cancelable: true - }); + var eventObject; + + // Fallback for IE10,11 - https://stackoverflow.com/questions/26596123 + if (typeof (window.Event) === 'function') { + eventObject = new window.Event(event[0], { + bubbles: true, + cancelable: true + }); + } else { + eventObject = document.createEvent('Event'); + eventObject.initEvent(event[0], true, true); + } if (options && options.preventDefault) { eventObject.preventDefault(); @@ -3696,11 +3733,13 @@ Wrapper.prototype.trigger = function trigger (type, options) { if (options) { Object.keys(options).forEach(function (key) { + // $FlowIgnore eventObject[key] = options[key]; }); } if (event.length === 2) { + // $FlowIgnore eventObject.keyCode = modifiers[event[1]]; } @@ -3708,18 +3747,6 @@ Wrapper.prototype.trigger = function trigger (type, options) { this.update(); }; -function logEvents (vm, emitted, emittedByOrder) { - var emit = vm.$emit; - vm.$emit = function (name) { - var args = [], len = arguments.length - 1; - while ( len-- > 0 ) args[ len ] = arguments[ len + 1 ]; - - (emitted[name] || (emitted[name] = [])).push(args); - emittedByOrder.push({ name: name, args: args }); - return emit.call.apply(emit, [ vm, name ].concat( args )) - }; -} - // function update () { @@ -3743,10 +3770,8 @@ var VueWrapper = (function (Wrapper$$1) { })); this.vm = vm; this.isVueComponent = true; - this._emitted = Object.create(null); - this._emittedByOrder = []; - - logEvents(vm, this._emitted, this._emittedByOrder); + this._emitted = vm.__emitted; + this._emittedByOrder = vm.__emittedByOrder; } if ( Wrapper$$1 ) VueWrapper.__proto__ = Wrapper$$1; @@ -3758,35 +3783,39 @@ var VueWrapper = (function (Wrapper$$1) { // -function isValidSlot$1 (slot) { +function isValidSlot (slot) { return Array.isArray(slot) || (slot !== null && typeof slot === 'object') || typeof slot === 'string' } function addSlotToVm (vm, slotName, slotValue) { - if (Array.isArray(vm.$slots[slotName])) { - if (typeof slotValue === 'string') { - if (!vueTemplateCompiler.compileToFunctions) { - throwError('vueTemplateCompiler is undefined, you must pass components explicitly if vue-template-compiler is undefined'); - } - vm.$slots[slotName].push(vm.$createElement(vueTemplateCompiler.compileToFunctions(slotValue))); - } else { - vm.$slots[slotName].push(vm.$createElement(slotValue)); + var elem; + var vueVersion = Number(((Vue.version.split('.')[0]) + "." + (Vue.version.split('.')[1]))); + if (typeof slotValue === 'string') { + if (!vueTemplateCompiler.compileToFunctions) { + throwError('vueTemplateCompiler is undefined, you must pass components explicitly if vue-template-compiler is undefined'); } - } else { - if (typeof slotValue === 'string') { - if (!vueTemplateCompiler.compileToFunctions) { - throwError('vueTemplateCompiler is undefined, you must pass components explicitly if vue-template-compiler is undefined'); - } - vm.$slots[slotName] = [vm.$createElement(vueTemplateCompiler.compileToFunctions(slotValue))]; + if (slotValue.trim()[0] === '<') { + elem = vm.$createElement(vueTemplateCompiler.compileToFunctions(slotValue)); } else { - vm.$slots[slotName] = [vm.$createElement(slotValue)]; // eslint-disable-line no-param-reassign + if (vueVersion >= 2.2) { + elem = vm._v(slotValue); + } else { + throwError('vue-test-utils support for passing text to slots at vue@2.2+'); + } } + } else { + elem = vm.$createElement(slotValue); + } + if (Array.isArray(vm.$slots[slotName])) { + vm.$slots[slotName].push(elem); + } else { + vm.$slots[slotName] = [elem]; } } function addSlots (vm, slots) { Object.keys(slots).forEach(function (key) { - if (!isValidSlot$1(slots[key])) { + if (!isValidSlot(slots[key])) { throwError('slots[key] must be a Component, string or an array of Components'); } @@ -3844,6 +3873,30 @@ function addProvide (component, optionProvide, options) { // +function logEvents (vm, emitted, emittedByOrder) { + var emit = vm.$emit; + vm.$emit = function (name) { + var args = [], len = arguments.length - 1; + while ( len-- > 0 ) args[ len ] = arguments[ len + 1 ]; + + (emitted[name] || (emitted[name] = [])).push(args); + emittedByOrder.push({ name: name, args: args }); + return emit.call.apply(emit, [ vm, name ].concat( args )) + }; +} + +function addEventLogger (vue) { + vue.mixin({ + beforeCreate: function () { + this.__emitted = Object.create(null); + this.__emittedByOrder = []; + logEvents(this, this.__emitted, this.__emittedByOrder); + } + }); +} + +// + function compileTemplate (component) { Object.assign(component, vueTemplateCompiler.compileToFunctions(component.template)); } @@ -4078,7 +4131,7 @@ function deleteMountingOptions (options) { // -function isValidSlot (slot) { +function isValidSlot$1 (slot) { return Array.isArray(slot) || (slot !== null && typeof slot === 'object') || typeof slot === 'string' } @@ -4096,7 +4149,7 @@ function createFunctionalSlots (slots, h) { Object.keys(slots).forEach(function (slotType) { if (Array.isArray(slots[slotType])) { slots[slotType].forEach(function (slot) { - if (!isValidSlot(slot)) { + if (!isValidSlot$1(slot)) { throwError('slots[key] must be a Component, string or an array of Components'); } var component = typeof slot === 'string' ? vueTemplateCompiler.compileToFunctions(slot) : slot; @@ -4105,7 +4158,7 @@ function createFunctionalSlots (slots, h) { children.push(newSlot); }); } else { - if (!isValidSlot(slots[slotType])) { + if (!isValidSlot$1(slots[slotType])) { throwError('slots[key] must be a Component, string or an array of Components'); } var component = typeof slots[slotType] === 'string' ? vueTemplateCompiler.compileToFunctions(slots[slotType]) : slots[slotType]; @@ -4117,6 +4170,25 @@ function createFunctionalSlots (slots, h) { return children } +function createFunctionalComponent (component, mountingOptions) { + if (mountingOptions.context && typeof mountingOptions.context !== 'object') { + throwError('mount.context must be an object'); + } + + var clonedComponent = cloneDeep_1(component); + return { + render: function render (h) { + return h( + clonedComponent, + mountingOptions.context || component.FunctionalRenderContext, + (mountingOptions.context && mountingOptions.context.children && mountingOptions.context.children.map(function (x) { return typeof x === 'function' ? x(h) : x; })) || createFunctionalSlots(mountingOptions.slots, h) + ) + } + } +} + +// + function createConstructor ( component, options @@ -4130,20 +4202,7 @@ function createConstructor ( } if (component.functional) { - if (mountingOptions.context && typeof mountingOptions.context !== 'object') { - throwError('mount.context must be an object'); - } - - var clonedComponent = cloneDeep_1(component); - component = { - render: function render (h) { - return h( - clonedComponent, - mountingOptions.context || component.FunctionalRenderContext, - (mountingOptions.context && mountingOptions.context.children) || createFunctionalSlots(mountingOptions.slots, h) - ) - } - }; + component = createFunctionalComponent(component, mountingOptions); } else if (mountingOptions.context) { throwError( 'mount.context can only be used when mounting a functional component' @@ -4162,6 +4221,8 @@ function createConstructor ( compileTemplate(component); } + addEventLogger(vue); + var Constructor = vue.extend(component); var instanceOptions = Object.assign({}, options); @@ -4207,6 +4268,32 @@ if (!Element.prototype.matches) { }; } +if (typeof Object.assign !== 'function') { + (function () { + Object.assign = function (target) { + 'use strict'; + var arguments$1 = arguments; + + if (target === undefined || target === null) { + throw new TypeError('Cannot convert undefined or null to object') + } + + var output = Object(target); + for (var index = 1; index < arguments.length; index++) { + var source = arguments$1[index]; + if (source !== undefined && source !== null) { + for (var nextKey in source) { + if (source.hasOwnProperty(nextKey)) { + output[nextKey] = source[nextKey]; + } + } + } + } + return output + }; + })(); +} + // Vue.config.productionTip = false; diff --git a/dist/vue-test-utils.js b/dist/vue-test-utils.js index dda5e0ecc..bdf29b4d3 100644 --- a/dist/vue-test-utils.js +++ b/dist/vue-test-utils.js @@ -273,26 +273,42 @@ function getSelectorTypeOrThrow (selector, methodName) { // -function findAllVueComponents (vm, components) { +function findAllVueComponentsFromVm (vm, components) { if ( components === void 0 ) components = []; components.push(vm); - vm.$children.forEach(function (child) { - findAllVueComponents(child, components); + findAllVueComponentsFromVm(child, components); }); return components } +function findAllVueComponentsFromVnode (vnode, components) { + if ( components === void 0 ) components = []; + + debugger + if (vnode.child) { + components.push(vnode.child); + } + if (vnode.children) { + vnode.children.forEach(function (child) { + findAllVueComponentsFromVnode(child, components); + }); + } + + return components +} + function vmCtorMatchesName (vm, name) { return (vm.$vnode && vm.$vnode.componentOptions && vm.$vnode.componentOptions.Ctor.options.name === name) || (vm._vnode && vm._vnode.functionalOptions && vm._vnode.functionalOptions.name === name) || vm.$options && vm.$options.name === name } -function findVueComponents (vm, componentName) { - var components = findAllVueComponents(vm); +function findVueComponents (root, componentName) { + debugger + var components = root._isVue ? findAllVueComponentsFromVm(root) : findAllVueComponentsFromVnode(root); return components.filter(function (component) { if (!component.$vnode) { return false @@ -391,8 +407,9 @@ WrapperArray.prototype.contains = function contains (selector) { return this.wrappers.every(function (wrapper) { return wrapper.contains(selector); }) }; + WrapperArray.prototype.exists = function exists () { - return this.wrappers.length > 0 + return this.length > 0 && this.wrappers.every(function (wrapper) { return wrapper.exists(); }) }; WrapperArray.prototype.emitted = function emitted () { @@ -748,6 +765,9 @@ Wrapper.prototype.emittedByOrder = function emittedByOrder () { * Utility to check wrapper exists. Returns true as Wrapper always exists */ Wrapper.prototype.exists = function exists () { + if (this.isVueComponent) { + return !!this.vm && !this.vm._isDestroyed + } return true }; @@ -850,13 +870,12 @@ Wrapper.prototype.hasStyle = function hasStyle (style, value) { */ Wrapper.prototype.find = function find (selector) { var selectorType = getSelectorTypeOrThrow(selector, 'find'); - if (selectorType === selectorTypes.VUE_COMPONENT) { if (!selector.name) { throwError('.find() requires component to have a name property'); } - var vm = this.vm || this.vnode.context.$root; - var components = findVueComponents(vm, selector.name); + var root = this.vm || this.vnode; + var components = findVueComponents(root, selector.name); if (components.length === 0) { return new ErrorWrapper('Component') } @@ -894,8 +913,8 @@ Wrapper.prototype.findAll = function findAll (selector) { if (!selector.name) { throwError('.findAll() requires component to have a name property'); } - var vm = this.vm || this.vnode.context.$root; - var components = findVueComponents(vm, selector.name); + var root = this.vm || this.vnode; + var components = findVueComponents(root, selector.name); return new WrapperArray(components.map(function (component) { return new VueWrapper(component, this$1.options); })) } @@ -1048,6 +1067,10 @@ Wrapper.prototype.setComputed = function setComputed (computed) { } }); } + // $FlowIgnore + this$1.vm._watchers.forEach(function (watcher) { + if (watcher.expression === key) { watcher.run(); } + }); }); this.update(); }; @@ -1150,15 +1173,29 @@ Wrapper.prototype.trigger = function trigger (type, options) { up: 38, down: 40, left: 37, - right: 39 + right: 39, + end: 35, + home: 36, + backspace: 8, + insert: 45, + pageup: 33, + pagedown: 34 }; var event = type.split('.'); - var eventObject = new window.Event(event[0], { - bubbles: true, - cancelable: true - }); + var eventObject; + + // Fallback for IE10,11 - https://stackoverflow.com/questions/26596123 + if (typeof (window.Event) === 'function') { + eventObject = new window.Event(event[0], { + bubbles: true, + cancelable: true + }); + } else { + eventObject = document.createEvent('Event'); + eventObject.initEvent(event[0], true, true); + } if (options && options.preventDefault) { eventObject.preventDefault(); @@ -1166,11 +1203,13 @@ Wrapper.prototype.trigger = function trigger (type, options) { if (options) { Object.keys(options).forEach(function (key) { + // $FlowIgnore eventObject[key] = options[key]; }); } if (event.length === 2) { + // $FlowIgnore eventObject.keyCode = modifiers[event[1]]; } @@ -1178,18 +1217,6 @@ Wrapper.prototype.trigger = function trigger (type, options) { this.update(); }; -function logEvents (vm, emitted, emittedByOrder) { - var emit = vm.$emit; - vm.$emit = function (name) { - var args = [], len = arguments.length - 1; - while ( len-- > 0 ) args[ len ] = arguments[ len + 1 ]; - - (emitted[name] || (emitted[name] = [])).push(args); - emittedByOrder.push({ name: name, args: args }); - return emit.call.apply(emit, [ vm, name ].concat( args )) - }; -} - // function update () { @@ -1213,10 +1240,8 @@ var VueWrapper = (function (Wrapper$$1) { })); this.vm = vm; this.isVueComponent = true; - this._emitted = Object.create(null); - this._emittedByOrder = []; - - logEvents(vm, this._emitted, this._emittedByOrder); + this._emitted = vm.__emitted; + this._emittedByOrder = vm.__emittedByOrder; } if ( Wrapper$$1 ) VueWrapper.__proto__ = Wrapper$$1; @@ -1228,35 +1253,39 @@ var VueWrapper = (function (Wrapper$$1) { // -function isValidSlot$1 (slot) { +function isValidSlot (slot) { return Array.isArray(slot) || (slot !== null && typeof slot === 'object') || typeof slot === 'string' } function addSlotToVm (vm, slotName, slotValue) { - if (Array.isArray(vm.$slots[slotName])) { - if (typeof slotValue === 'string') { - if (!vueTemplateCompiler.compileToFunctions) { - throwError('vueTemplateCompiler is undefined, you must pass components explicitly if vue-template-compiler is undefined'); - } - vm.$slots[slotName].push(vm.$createElement(vueTemplateCompiler.compileToFunctions(slotValue))); - } else { - vm.$slots[slotName].push(vm.$createElement(slotValue)); + var elem; + var vueVersion = Number(((Vue.version.split('.')[0]) + "." + (Vue.version.split('.')[1]))); + if (typeof slotValue === 'string') { + if (!vueTemplateCompiler.compileToFunctions) { + throwError('vueTemplateCompiler is undefined, you must pass components explicitly if vue-template-compiler is undefined'); } - } else { - if (typeof slotValue === 'string') { - if (!vueTemplateCompiler.compileToFunctions) { - throwError('vueTemplateCompiler is undefined, you must pass components explicitly if vue-template-compiler is undefined'); - } - vm.$slots[slotName] = [vm.$createElement(vueTemplateCompiler.compileToFunctions(slotValue))]; + if (slotValue.trim()[0] === '<') { + elem = vm.$createElement(vueTemplateCompiler.compileToFunctions(slotValue)); } else { - vm.$slots[slotName] = [vm.$createElement(slotValue)]; // eslint-disable-line no-param-reassign + if (vueVersion >= 2.2) { + elem = vm._v(slotValue); + } else { + throwError('vue-test-utils support for passing text to slots at vue@2.2+'); + } } + } else { + elem = vm.$createElement(slotValue); + } + if (Array.isArray(vm.$slots[slotName])) { + vm.$slots[slotName].push(elem); + } else { + vm.$slots[slotName] = [elem]; } } function addSlots (vm, slots) { Object.keys(slots).forEach(function (key) { - if (!isValidSlot$1(slots[key])) { + if (!isValidSlot(slots[key])) { throwError('slots[key] must be a Component, string or an array of Components'); } @@ -1314,6 +1343,30 @@ function addProvide (component, optionProvide, options) { // +function logEvents (vm, emitted, emittedByOrder) { + var emit = vm.$emit; + vm.$emit = function (name) { + var args = [], len = arguments.length - 1; + while ( len-- > 0 ) args[ len ] = arguments[ len + 1 ]; + + (emitted[name] || (emitted[name] = [])).push(args); + emittedByOrder.push({ name: name, args: args }); + return emit.call.apply(emit, [ vm, name ].concat( args )) + }; +} + +function addEventLogger (vue) { + vue.mixin({ + beforeCreate: function () { + this.__emitted = Object.create(null); + this.__emittedByOrder = []; + logEvents(this, this.__emitted, this.__emittedByOrder); + } + }); +} + +// + function compileTemplate (component) { Object.assign(component, vueTemplateCompiler.compileToFunctions(component.template)); } @@ -1548,7 +1601,7 @@ function deleteMountingOptions (options) { // -function isValidSlot (slot) { +function isValidSlot$1 (slot) { return Array.isArray(slot) || (slot !== null && typeof slot === 'object') || typeof slot === 'string' } @@ -1566,7 +1619,7 @@ function createFunctionalSlots (slots, h) { Object.keys(slots).forEach(function (slotType) { if (Array.isArray(slots[slotType])) { slots[slotType].forEach(function (slot) { - if (!isValidSlot(slot)) { + if (!isValidSlot$1(slot)) { throwError('slots[key] must be a Component, string or an array of Components'); } var component = typeof slot === 'string' ? vueTemplateCompiler.compileToFunctions(slot) : slot; @@ -1575,7 +1628,7 @@ function createFunctionalSlots (slots, h) { children.push(newSlot); }); } else { - if (!isValidSlot(slots[slotType])) { + if (!isValidSlot$1(slots[slotType])) { throwError('slots[key] must be a Component, string or an array of Components'); } var component = typeof slots[slotType] === 'string' ? vueTemplateCompiler.compileToFunctions(slots[slotType]) : slots[slotType]; @@ -1587,6 +1640,25 @@ function createFunctionalSlots (slots, h) { return children } +function createFunctionalComponent (component, mountingOptions) { + if (mountingOptions.context && typeof mountingOptions.context !== 'object') { + throwError('mount.context must be an object'); + } + + var clonedComponent = cloneDeep(component); + return { + render: function render (h) { + return h( + clonedComponent, + mountingOptions.context || component.FunctionalRenderContext, + (mountingOptions.context && mountingOptions.context.children && mountingOptions.context.children.map(function (x) { return typeof x === 'function' ? x(h) : x; })) || createFunctionalSlots(mountingOptions.slots, h) + ) + } + } +} + +// + function createConstructor ( component, options @@ -1600,20 +1672,7 @@ function createConstructor ( } if (component.functional) { - if (mountingOptions.context && typeof mountingOptions.context !== 'object') { - throwError('mount.context must be an object'); - } - - var clonedComponent = cloneDeep(component); - component = { - render: function render (h) { - return h( - clonedComponent, - mountingOptions.context || component.FunctionalRenderContext, - (mountingOptions.context && mountingOptions.context.children) || createFunctionalSlots(mountingOptions.slots, h) - ) - } - }; + component = createFunctionalComponent(component, mountingOptions); } else if (mountingOptions.context) { throwError( 'mount.context can only be used when mounting a functional component' @@ -1632,6 +1691,8 @@ function createConstructor ( compileTemplate(component); } + addEventLogger(vue); + var Constructor = vue.extend(component); var instanceOptions = Object.assign({}, options); @@ -1677,6 +1738,32 @@ if (!Element.prototype.matches) { }; } +if (typeof Object.assign !== 'function') { + (function () { + Object.assign = function (target) { + 'use strict'; + var arguments$1 = arguments; + + if (target === undefined || target === null) { + throw new TypeError('Cannot convert undefined or null to object') + } + + var output = Object(target); + for (var index = 1; index < arguments.length; index++) { + var source = arguments$1[index]; + if (source !== undefined && source !== null) { + for (var nextKey in source) { + if (source.hasOwnProperty(nextKey)) { + output[nextKey] = source[nextKey]; + } + } + } + } + return output + }; + })(); +} + // Vue.config.productionTip = false; diff --git a/dist/vue-test-utils.umd.js b/dist/vue-test-utils.umd.js index d56183fa1..a184ddea6 100644 --- a/dist/vue-test-utils.umd.js +++ b/dist/vue-test-utils.umd.js @@ -2806,26 +2806,42 @@ function getSelectorTypeOrThrow (selector, methodName) { // -function findAllVueComponents (vm, components) { +function findAllVueComponentsFromVm (vm, components) { if ( components === void 0 ) components = []; components.push(vm); - vm.$children.forEach(function (child) { - findAllVueComponents(child, components); + findAllVueComponentsFromVm(child, components); }); return components } +function findAllVueComponentsFromVnode (vnode, components) { + if ( components === void 0 ) components = []; + + debugger + if (vnode.child) { + components.push(vnode.child); + } + if (vnode.children) { + vnode.children.forEach(function (child) { + findAllVueComponentsFromVnode(child, components); + }); + } + + return components +} + function vmCtorMatchesName (vm, name) { return (vm.$vnode && vm.$vnode.componentOptions && vm.$vnode.componentOptions.Ctor.options.name === name) || (vm._vnode && vm._vnode.functionalOptions && vm._vnode.functionalOptions.name === name) || vm.$options && vm.$options.name === name } -function findVueComponents (vm, componentName) { - var components = findAllVueComponents(vm); +function findVueComponents (root, componentName) { + debugger + var components = root._isVue ? findAllVueComponentsFromVm(root) : findAllVueComponentsFromVnode(root); return components.filter(function (component) { if (!component.$vnode) { return false @@ -2924,8 +2940,9 @@ WrapperArray.prototype.contains = function contains (selector) { return this.wrappers.every(function (wrapper) { return wrapper.contains(selector); }) }; + WrapperArray.prototype.exists = function exists () { - return this.wrappers.length > 0 + return this.length > 0 && this.wrappers.every(function (wrapper) { return wrapper.exists(); }) }; WrapperArray.prototype.emitted = function emitted () { @@ -3281,6 +3298,9 @@ Wrapper.prototype.emittedByOrder = function emittedByOrder () { * Utility to check wrapper exists. Returns true as Wrapper always exists */ Wrapper.prototype.exists = function exists () { + if (this.isVueComponent) { + return !!this.vm && !this.vm._isDestroyed + } return true }; @@ -3383,13 +3403,12 @@ Wrapper.prototype.hasStyle = function hasStyle (style, value) { */ Wrapper.prototype.find = function find (selector) { var selectorType = getSelectorTypeOrThrow(selector, 'find'); - if (selectorType === selectorTypes.VUE_COMPONENT) { if (!selector.name) { throwError('.find() requires component to have a name property'); } - var vm = this.vm || this.vnode.context.$root; - var components = findVueComponents(vm, selector.name); + var root = this.vm || this.vnode; + var components = findVueComponents(root, selector.name); if (components.length === 0) { return new ErrorWrapper('Component') } @@ -3427,8 +3446,8 @@ Wrapper.prototype.findAll = function findAll (selector) { if (!selector.name) { throwError('.findAll() requires component to have a name property'); } - var vm = this.vm || this.vnode.context.$root; - var components = findVueComponents(vm, selector.name); + var root = this.vm || this.vnode; + var components = findVueComponents(root, selector.name); return new WrapperArray(components.map(function (component) { return new VueWrapper(component, this$1.options); })) } @@ -3581,6 +3600,10 @@ Wrapper.prototype.setComputed = function setComputed (computed) { } }); } + // $FlowIgnore + this$1.vm._watchers.forEach(function (watcher) { + if (watcher.expression === key) { watcher.run(); } + }); }); this.update(); }; @@ -3683,15 +3706,29 @@ Wrapper.prototype.trigger = function trigger (type, options) { up: 38, down: 40, left: 37, - right: 39 + right: 39, + end: 35, + home: 36, + backspace: 8, + insert: 45, + pageup: 33, + pagedown: 34 }; var event = type.split('.'); - var eventObject = new window.Event(event[0], { - bubbles: true, - cancelable: true - }); + var eventObject; + + // Fallback for IE10,11 - https://stackoverflow.com/questions/26596123 + if (typeof (window.Event) === 'function') { + eventObject = new window.Event(event[0], { + bubbles: true, + cancelable: true + }); + } else { + eventObject = document.createEvent('Event'); + eventObject.initEvent(event[0], true, true); + } if (options && options.preventDefault) { eventObject.preventDefault(); @@ -3699,11 +3736,13 @@ Wrapper.prototype.trigger = function trigger (type, options) { if (options) { Object.keys(options).forEach(function (key) { + // $FlowIgnore eventObject[key] = options[key]; }); } if (event.length === 2) { + // $FlowIgnore eventObject.keyCode = modifiers[event[1]]; } @@ -3711,18 +3750,6 @@ Wrapper.prototype.trigger = function trigger (type, options) { this.update(); }; -function logEvents (vm, emitted, emittedByOrder) { - var emit = vm.$emit; - vm.$emit = function (name) { - var args = [], len = arguments.length - 1; - while ( len-- > 0 ) args[ len ] = arguments[ len + 1 ]; - - (emitted[name] || (emitted[name] = [])).push(args); - emittedByOrder.push({ name: name, args: args }); - return emit.call.apply(emit, [ vm, name ].concat( args )) - }; -} - // function update () { @@ -3746,10 +3773,8 @@ var VueWrapper = (function (Wrapper$$1) { })); this.vm = vm; this.isVueComponent = true; - this._emitted = Object.create(null); - this._emittedByOrder = []; - - logEvents(vm, this._emitted, this._emittedByOrder); + this._emitted = vm.__emitted; + this._emittedByOrder = vm.__emittedByOrder; } if ( Wrapper$$1 ) VueWrapper.__proto__ = Wrapper$$1; @@ -3761,35 +3786,39 @@ var VueWrapper = (function (Wrapper$$1) { // -function isValidSlot$1 (slot) { +function isValidSlot (slot) { return Array.isArray(slot) || (slot !== null && typeof slot === 'object') || typeof slot === 'string' } function addSlotToVm (vm, slotName, slotValue) { - if (Array.isArray(vm.$slots[slotName])) { - if (typeof slotValue === 'string') { - if (!vueTemplateCompiler.compileToFunctions) { - throwError('vueTemplateCompiler is undefined, you must pass components explicitly if vue-template-compiler is undefined'); - } - vm.$slots[slotName].push(vm.$createElement(vueTemplateCompiler.compileToFunctions(slotValue))); - } else { - vm.$slots[slotName].push(vm.$createElement(slotValue)); + var elem; + var vueVersion = Number(((Vue.version.split('.')[0]) + "." + (Vue.version.split('.')[1]))); + if (typeof slotValue === 'string') { + if (!vueTemplateCompiler.compileToFunctions) { + throwError('vueTemplateCompiler is undefined, you must pass components explicitly if vue-template-compiler is undefined'); } - } else { - if (typeof slotValue === 'string') { - if (!vueTemplateCompiler.compileToFunctions) { - throwError('vueTemplateCompiler is undefined, you must pass components explicitly if vue-template-compiler is undefined'); - } - vm.$slots[slotName] = [vm.$createElement(vueTemplateCompiler.compileToFunctions(slotValue))]; + if (slotValue.trim()[0] === '<') { + elem = vm.$createElement(vueTemplateCompiler.compileToFunctions(slotValue)); } else { - vm.$slots[slotName] = [vm.$createElement(slotValue)]; // eslint-disable-line no-param-reassign + if (vueVersion >= 2.2) { + elem = vm._v(slotValue); + } else { + throwError('vue-test-utils support for passing text to slots at vue@2.2+'); + } } + } else { + elem = vm.$createElement(slotValue); + } + if (Array.isArray(vm.$slots[slotName])) { + vm.$slots[slotName].push(elem); + } else { + vm.$slots[slotName] = [elem]; } } function addSlots (vm, slots) { Object.keys(slots).forEach(function (key) { - if (!isValidSlot$1(slots[key])) { + if (!isValidSlot(slots[key])) { throwError('slots[key] must be a Component, string or an array of Components'); } @@ -3847,6 +3876,30 @@ function addProvide (component, optionProvide, options) { // +function logEvents (vm, emitted, emittedByOrder) { + var emit = vm.$emit; + vm.$emit = function (name) { + var args = [], len = arguments.length - 1; + while ( len-- > 0 ) args[ len ] = arguments[ len + 1 ]; + + (emitted[name] || (emitted[name] = [])).push(args); + emittedByOrder.push({ name: name, args: args }); + return emit.call.apply(emit, [ vm, name ].concat( args )) + }; +} + +function addEventLogger (vue) { + vue.mixin({ + beforeCreate: function () { + this.__emitted = Object.create(null); + this.__emittedByOrder = []; + logEvents(this, this.__emitted, this.__emittedByOrder); + } + }); +} + +// + function compileTemplate (component) { Object.assign(component, vueTemplateCompiler.compileToFunctions(component.template)); } @@ -4081,7 +4134,7 @@ function deleteMountingOptions (options) { // -function isValidSlot (slot) { +function isValidSlot$1 (slot) { return Array.isArray(slot) || (slot !== null && typeof slot === 'object') || typeof slot === 'string' } @@ -4099,7 +4152,7 @@ function createFunctionalSlots (slots, h) { Object.keys(slots).forEach(function (slotType) { if (Array.isArray(slots[slotType])) { slots[slotType].forEach(function (slot) { - if (!isValidSlot(slot)) { + if (!isValidSlot$1(slot)) { throwError('slots[key] must be a Component, string or an array of Components'); } var component = typeof slot === 'string' ? vueTemplateCompiler.compileToFunctions(slot) : slot; @@ -4108,7 +4161,7 @@ function createFunctionalSlots (slots, h) { children.push(newSlot); }); } else { - if (!isValidSlot(slots[slotType])) { + if (!isValidSlot$1(slots[slotType])) { throwError('slots[key] must be a Component, string or an array of Components'); } var component = typeof slots[slotType] === 'string' ? vueTemplateCompiler.compileToFunctions(slots[slotType]) : slots[slotType]; @@ -4120,6 +4173,25 @@ function createFunctionalSlots (slots, h) { return children } +function createFunctionalComponent (component, mountingOptions) { + if (mountingOptions.context && typeof mountingOptions.context !== 'object') { + throwError('mount.context must be an object'); + } + + var clonedComponent = cloneDeep_1(component); + return { + render: function render (h) { + return h( + clonedComponent, + mountingOptions.context || component.FunctionalRenderContext, + (mountingOptions.context && mountingOptions.context.children && mountingOptions.context.children.map(function (x) { return typeof x === 'function' ? x(h) : x; })) || createFunctionalSlots(mountingOptions.slots, h) + ) + } + } +} + +// + function createConstructor ( component, options @@ -4133,20 +4205,7 @@ function createConstructor ( } if (component.functional) { - if (mountingOptions.context && typeof mountingOptions.context !== 'object') { - throwError('mount.context must be an object'); - } - - var clonedComponent = cloneDeep_1(component); - component = { - render: function render (h) { - return h( - clonedComponent, - mountingOptions.context || component.FunctionalRenderContext, - (mountingOptions.context && mountingOptions.context.children) || createFunctionalSlots(mountingOptions.slots, h) - ) - } - }; + component = createFunctionalComponent(component, mountingOptions); } else if (mountingOptions.context) { throwError( 'mount.context can only be used when mounting a functional component' @@ -4165,6 +4224,8 @@ function createConstructor ( compileTemplate(component); } + addEventLogger(vue); + var Constructor = vue.extend(component); var instanceOptions = Object.assign({}, options); @@ -4210,6 +4271,32 @@ if (!Element.prototype.matches) { }; } +if (typeof Object.assign !== 'function') { + (function () { + Object.assign = function (target) { + 'use strict'; + var arguments$1 = arguments; + + if (target === undefined || target === null) { + throw new TypeError('Cannot convert undefined or null to object') + } + + var output = Object(target); + for (var index = 1; index < arguments.length; index++) { + var source = arguments$1[index]; + if (source !== undefined && source !== null) { + for (var nextKey in source) { + if (source.hasOwnProperty(nextKey)) { + output[nextKey] = source[nextKey]; + } + } + } + } + return output + }; + })(); +} + // Vue.config.productionTip = false; From b263b603f8e75ee1f7513729d23e945ad3a42ecd Mon Sep 17 00:00:00 2001 From: eddyerburgh Date: Sat, 9 Dec 2017 11:29:00 +0000 Subject: [PATCH 0137/1136] release: 1.0.0-beta.7 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c52cfaaaa..3e134d580 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vue-test-utils", - "version": "1.0.0-beta.6", + "version": "1.0.0-beta.7", "description": "Utilities for testing Vue components.", "main": "dist/vue-test-utils.js", "types": "types/index.d.ts", From fa4d8c7c0f06989d5aa7001790a79466ccf0f802 Mon Sep 17 00:00:00 2001 From: eddyerburgh Date: Sat, 9 Dec 2017 16:06:00 +0000 Subject: [PATCH 0138/1136] fix: return array of classes from classes method --- package.json | 2 +- src/lib/create-functional-component.js | 3 ++- src/wrappers/wrapper.js | 2 +- test/unit/specs/mount/Wrapper/classes.spec.js | 6 +++--- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 3e134d580..0f209a008 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "gitbook-plugin-edit-link": "^2.0.2", "gitbook-plugin-github": "^3.0.0", "gitbook-plugin-theme-vuejs": "^1.1.0", - "jsdom": "^11.0.0", + "jsdom": "^11.5.1", "jsdom-global": "^3.0.2", "karma": "^1.7.0", "karma-chrome-launcher": "^2.2.0", diff --git a/src/lib/create-functional-component.js b/src/lib/create-functional-component.js index c80a15255..14738f2f5 100644 --- a/src/lib/create-functional-component.js +++ b/src/lib/create-functional-component.js @@ -54,6 +54,7 @@ export default function createFunctionalComponent (component: Component, mountin mountingOptions.context || component.FunctionalRenderContext, (mountingOptions.context && mountingOptions.context.children && mountingOptions.context.children.map(x => typeof x === 'function' ? x(h) : x)) || createFunctionalSlots(mountingOptions.slots, h) ) - } + }, + name: component.name } } diff --git a/src/wrappers/wrapper.js b/src/wrappers/wrapper.js index 223ecf443..45be68ea4 100644 --- a/src/wrappers/wrapper.js +++ b/src/wrappers/wrapper.js @@ -49,7 +49,7 @@ export default class Wrapper implements BaseWrapper { * Returns an Array containing all the classes on the element */ classes (): Array { - let classes = [...this.element.classList] + let classes = this.element.className ? this.element.className.split(' ') : [] // Handle converting cssmodules identifiers back to the original class name if (this.vm && this.vm.$style) { const cssModuleIdentifiers = {} diff --git a/test/unit/specs/mount/Wrapper/classes.spec.js b/test/unit/specs/mount/Wrapper/classes.spec.js index 2c7a4529a..2bd6f4e59 100644 --- a/test/unit/specs/mount/Wrapper/classes.spec.js +++ b/test/unit/specs/mount/Wrapper/classes.spec.js @@ -7,18 +7,18 @@ describe('classes', () => { it('returns array of class names if wrapper has class names', () => { const compiled = compileToFunctions('
') const wrapper = mount(compiled) - expect(wrapper.classes()).to.eql(['a-class', 'b-class']) + expect(wrapper.classes()).to.contain('a-class') + expect(wrapper.classes()).to.contain('b-class') }) it('returns empty array if wrapper has no classes', () => { const compiled = compileToFunctions('
') const wrapper = mount(compiled) - expect(wrapper.classes()).to.eql([]) + expect(wrapper.classes().length).to.equal(0) }) it('returns original class names when element mapped in css modules', () => { const wrapper = mount(ComponentWithCssModules) - expect(wrapper.classes()).to.eql(['extension', 'color-red']) }) }) From d8feb2688a9540ab867e9f88f5fc26423eaefeb2 Mon Sep 17 00:00:00 2001 From: eddyerburgh Date: Sat, 9 Dec 2017 17:17:32 +0000 Subject: [PATCH 0139/1136] docs: add class, props, and attributes --- docs/en/api/wrapper-array/hasAttribute.md | 21 ------------- docs/en/api/wrapper-array/hasClass.md | 20 ------------ docs/en/api/wrapper-array/hasProp.md | 24 --------------- docs/en/api/wrapper/attributes.md | 16 ++++++++++ docs/en/api/wrapper/classes.md | 18 +++++++++++ docs/en/api/wrapper/hasAttribute.md | 37 ----------------------- docs/en/api/wrapper/hasClass.md | 21 ------------- docs/en/api/wrapper/hasProp.md | 25 --------------- docs/en/api/wrapper/props.md | 20 ++++++++++++ 9 files changed, 54 insertions(+), 148 deletions(-) delete mode 100644 docs/en/api/wrapper-array/hasAttribute.md delete mode 100644 docs/en/api/wrapper-array/hasClass.md delete mode 100644 docs/en/api/wrapper-array/hasProp.md create mode 100644 docs/en/api/wrapper/attributes.md create mode 100644 docs/en/api/wrapper/classes.md delete mode 100644 docs/en/api/wrapper/hasAttribute.md delete mode 100644 docs/en/api/wrapper/hasClass.md delete mode 100644 docs/en/api/wrapper/hasProp.md create mode 100644 docs/en/api/wrapper/props.md diff --git a/docs/en/api/wrapper-array/hasAttribute.md b/docs/en/api/wrapper-array/hasAttribute.md deleted file mode 100644 index b3436edc9..000000000 --- a/docs/en/api/wrapper-array/hasAttribute.md +++ /dev/null @@ -1,21 +0,0 @@ -# hasAttribute(attribute, value) - -Assert every `Wrapper` in `WrapperArray` DOM node has `attribute` matching `value`. - -- **Arguments:** - - `{string} attribute` - - `{string} value` - -- **Returns:** `{boolean}` - -- **Example:** - -```js -import { mount } from 'vue-test-utils' -import { expect } from 'chai' -import Foo from './Foo.vue' - -const wrapper = mount(Foo) -const divArray = wrapper.findAll('div') -expect(divArray.hasAttribute('id', 'foo')).toBe(true) -``` diff --git a/docs/en/api/wrapper-array/hasClass.md b/docs/en/api/wrapper-array/hasClass.md deleted file mode 100644 index b4c48b397..000000000 --- a/docs/en/api/wrapper-array/hasClass.md +++ /dev/null @@ -1,20 +0,0 @@ -# hasClass(className) - -Assert every `Wrapper` in `WrapperArray` DOM node has class containing `className`. - -- **Arguments:** - - `{string} className` - -- **Returns:** `{boolean}` - -- **Example:** - -```js -import { mount } from 'vue-test-utils' -import { expect } from 'chai' -import Foo from './Foo.vue' - -const wrapper = mount(Foo) -const divArray = wrapper.findAll('div') -expect(divArray.hasClass('bar')).toBe(true) -``` diff --git a/docs/en/api/wrapper-array/hasProp.md b/docs/en/api/wrapper-array/hasProp.md deleted file mode 100644 index 6ce255fd7..000000000 --- a/docs/en/api/wrapper-array/hasProp.md +++ /dev/null @@ -1,24 +0,0 @@ -# hasProp(prop, value) - -Assert every `Wrapper` in `WrapperArray` `vm` has `prop` matching `value`. - -**Note the Wrapper must contain a Vue instance.** - -- **Arguments:** - - `{string} prop` - - `{any} value` - -- **Returns:** `{boolean}` - -- **Example:** - -```js -import { mount } from 'vue-test-utils' -import { expect } from 'chai' -import Foo from './Foo.vue' -import Bar from './Bar.vue' - -const wrapper = mount(Foo) -const barArray = wrapper.findAll(Bar) -expect(barArray.hasProp('bar', 10)).toBe(true) -``` diff --git a/docs/en/api/wrapper/attributes.md b/docs/en/api/wrapper/attributes.md new file mode 100644 index 000000000..08a65bf17 --- /dev/null +++ b/docs/en/api/wrapper/attributes.md @@ -0,0 +1,16 @@ +# attributes() + +Returns `Wrapper` DOM node attribute object. + +- **Returns:** `{[attribute: string]: any}` + +- **Example:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +expect(wrapper.attributes().id).toBe('foo') +``` diff --git a/docs/en/api/wrapper/classes.md b/docs/en/api/wrapper/classes.md new file mode 100644 index 000000000..24d7cf4ed --- /dev/null +++ b/docs/en/api/wrapper/classes.md @@ -0,0 +1,18 @@ +# classes() + +Return `Wrapper` DOM node classes. + +Returns Array of class names. + +- **Returns:** `Array<{string}>` + +- **Example:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo) +expect(wrapper.classes()).toContain('bar') +``` diff --git a/docs/en/api/wrapper/hasAttribute.md b/docs/en/api/wrapper/hasAttribute.md deleted file mode 100644 index da9a893b5..000000000 --- a/docs/en/api/wrapper/hasAttribute.md +++ /dev/null @@ -1,37 +0,0 @@ -# hasAttribute(attribute, value) - -Assert `Wrapper` DOM node has attribute matching value. - -Returns `true` if `Wrapper` DOM node contains attribute with matching value. - -- **Arguments:** - - `{string} attribute` - - `{string} value` - -- **Returns:** `{boolean}` - -- **Example:** - -```js -import { mount } from 'vue-test-utils' -import { expect } from 'chai' -import Foo from './Foo.vue' - -const wrapper = mount(Foo) -expect(wrapper.hasAttribute('id', 'foo')).toBe(true) -``` - -- **Alternative:** - -You could get the attribute from the `Wrapper.element` to have a value based assertion: - -```js -import { mount } from 'vue-test-utils' -import { expect } from 'chai' -import Foo from './Foo.vue' - -const wrapper = mount(Foo) -expect(wrapper.element.getAttribute('id')).toBe('foo') -``` - -This makes for a more informative assertion error. diff --git a/docs/en/api/wrapper/hasClass.md b/docs/en/api/wrapper/hasClass.md deleted file mode 100644 index 8f578bd24..000000000 --- a/docs/en/api/wrapper/hasClass.md +++ /dev/null @@ -1,21 +0,0 @@ -# hasClass(className) - -Assert `Wrapper` DOM node has class contains `className`. - -Returns `true` if `Wrapper` DOM node contains the class. - -- **Arguments:** - - `{string} className` - -- **Returns:** `{boolean}` - -- **Example:** - -```js -import { mount } from 'vue-test-utils' -import { expect } from 'chai' -import Foo from './Foo.vue' - -const wrapper = mount(Foo) -expect(wrapper.hasClass('bar')).toBe(true) -``` diff --git a/docs/en/api/wrapper/hasProp.md b/docs/en/api/wrapper/hasProp.md deleted file mode 100644 index 66e64b41b..000000000 --- a/docs/en/api/wrapper/hasProp.md +++ /dev/null @@ -1,25 +0,0 @@ -# hasProp(prop, value) - -Assert `Wrapper` `vm` has `prop` matching `value`. - -Returns `true` if `Wrapper` `vm` has `prop` matching `value`. - - -**Note: the Wrapper must contain a Vue instance.** - -- **Arguments:** - - `{string} prop` - - `{any} value` - -- **Returns:** `{boolean}` - -- **Example:** - -```js -import { mount } from 'vue-test-utils' -import { expect } from 'chai' -import Foo from './Foo.vue' - -const wrapper = mount(Foo) -expect(wrapper.hasProp('bar', 10)).toBe(true) -``` diff --git a/docs/en/api/wrapper/props.md b/docs/en/api/wrapper/props.md new file mode 100644 index 000000000..63cc0955c --- /dev/null +++ b/docs/en/api/wrapper/props.md @@ -0,0 +1,20 @@ +# props() + +Return `Wrapper` `vm` props object. + +**Note the Wrapper must contain a Vue instance.** + +- **Returns:** `{[prop: string]: any}` + +- **Example:** + +```js +import { mount } from 'vue-test-utils' +import { expect } from 'chai' +import Foo from './Foo.vue' + +const wrapper = mount(Foo, { + propsData: 'baz' +}) +expect(wrapper.props().bar).toBe('baz') +``` From bc41361bc78300ff11d82d70eb4e3a72f9fe7105 Mon Sep 17 00:00:00 2001 From: eddyerburgh Date: Sat, 9 Dec 2017 17:28:05 +0000 Subject: [PATCH 0140/1136] docs: remove hasClass, hasProps, and hasAttributes from nav --- docs/en/SUMMARY.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/docs/en/SUMMARY.md b/docs/en/SUMMARY.md index f8e5b356c..3f046fa48 100644 --- a/docs/en/SUMMARY.md +++ b/docs/en/SUMMARY.md @@ -23,6 +23,8 @@ - [listeners](api/options.md#listeners) - [clone](api/options.md#clone) * [Wrapper](api/wrapper/README.md) + * [attributes](api/wrapper/attributes.md) + * [classes](api/wrapper/classes.md) * [contains](api/wrapper/contains.md) * [emitted](api/wrapper/emitted.md) * [emittedByOrder](api/wrapper/emittedByOrder.md) @@ -30,15 +32,13 @@ * [destroy](api/wrapper/destroy.md) * [find](api/wrapper/find.md) * [findAll](api/wrapper/findAll.md) - * [hasAttribute](api/wrapper/hasAttribute.md) - * [hasClass](api/wrapper/hasClass.md) - * [hasProp](api/wrapper/hasProp.md) * [hasStyle](api/wrapper/hasStyle.md) * [html](api/wrapper/html.md) * [is](api/wrapper/is.md) * [isEmpty](api/wrapper/isEmpty.md) * [isVueInstance](api/wrapper/isVueInstance.md) * [name](api/wrapper/name.md) + * [props](api/wrapper/props.md) * [setComputed](api/wrapper/setComputed.md) * [setData](api/wrapper/setData.md) * [setMethods](api/wrapper/setMethods.md) @@ -51,9 +51,6 @@ * [contains](api/wrapper-array/contains.md) * [exists](api/wrapper/exists.md) * [destroy](api/wrapper-array/destroy.md) - * [hasAttribute](api/wrapper-array/hasAttribute.md) - * [hasClass](api/wrapper-array/hasClass.md) - * [hasProp](api/wrapper-array/hasProp.md) * [hasStyle](api/wrapper-array/hasStyle.md) * [is](api/wrapper-array/is.md) * [isEmpty](api/wrapper-array/isEmpty.md) From 0e0c94d4f87a32208144e0c3097add3da6d93af4 Mon Sep 17 00:00:00 2001 From: eddyerburgh Date: Sat, 9 Dec 2017 17:37:27 +0000 Subject: [PATCH 0141/1136] refactor: add deprecation warnings to hasClass, hasAttribute, and hasProp --- src/wrappers/wrapper.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/wrappers/wrapper.js b/src/wrappers/wrapper.js index 45be68ea4..0286abcac 100644 --- a/src/wrappers/wrapper.js +++ b/src/wrappers/wrapper.js @@ -8,7 +8,7 @@ import findVNodesByRef from '../lib/find-vnodes-by-ref' import VueWrapper from './vue-wrapper' import WrapperArray from './wrapper-array' import ErrorWrapper from './error-wrapper' -import { throwError } from '../lib/util' +import { throwError, warn } from '../lib/util' export default class Wrapper implements BaseWrapper { vnode: VNode; @@ -129,6 +129,8 @@ export default class Wrapper implements BaseWrapper { * Checks if wrapper has an attribute with matching value */ hasAttribute (attribute: string, value: string) { + warn('hasAttribute() has been deprecated and will be removed in version 1.0.0. Use attributes() instead—https://vue-test-utils.vuejs.org/en/api/wrapper/attributes') + if (typeof attribute !== 'string') { throwError('wrapper.hasAttribute() must be passed attribute as a string') } @@ -144,6 +146,7 @@ export default class Wrapper implements BaseWrapper { * Asserts wrapper has a class name */ hasClass (className: string) { + warn('hasClass() has been deprecated and will be removed in version 1.0.0. Use classes() instead—https://vue-test-utils.vuejs.org/en/api/wrapper/classes') let targetClass = className if (typeof targetClass !== 'string') { @@ -166,6 +169,8 @@ export default class Wrapper implements BaseWrapper { * Asserts wrapper has a prop name */ hasProp (prop: string, value: string) { + warn('hasProp() has been deprecated and will be removed in version 1.0.0. Use props() instead—https://vue-test-utils.vuejs.org/en/api/wrapper/props') + if (!this.isVueComponent) { throwError('wrapper.hasProp() must be called on a Vue instance') } From 2df74c77641e2bc1f350bd74176b50eb2e2fb10e Mon Sep 17 00:00:00 2001 From: 38elements <38elements@users.noreply.github.com> Date: Sun, 10 Dec 2017 18:17:47 +0900 Subject: [PATCH 0142/1136] docs: update docs/en/guides/dom-events.md (#247) * Add test for key code * Update dom-events.md * Fix trigger.spec.js * Update README.md * Update README.md --- docs/en/README.md | 10 +++---- docs/en/SUMMARY.md | 1 + docs/en/api/README.md | 7 +++-- docs/en/api/options.md | 2 +- docs/en/guides/dom-events.md | 18 +++++++++++- src/lib/find-vue-components.js | 2 -- .../components/component-with-events.vue | 6 +++- test/unit/specs/mount/Wrapper/trigger.spec.js | 29 +++++++++++++++++++ 8 files changed, 61 insertions(+), 14 deletions(-) diff --git a/docs/en/README.md b/docs/en/README.md index 4a3bc81c5..586453c27 100644 --- a/docs/en/README.md +++ b/docs/en/README.md @@ -26,7 +26,10 @@ - [attrs](api/options.md#attrs) - [listeners](api/options.md#listeners) - [clone](api/options.md#clone) + - [provide](api/options.md#provide) * [Wrapper](api/wrapper/README.md) + * [attributes](api/wrapper/attributes.md) + * [classes](api/wrapper/classes.md) * [contains](api/wrapper/contains.md) * [emitted](api/wrapper/emitted.md) * [emittedByOrder](api/wrapper/emittedByOrder.md) @@ -34,15 +37,13 @@ * [destroy](api/wrapper/destroy.md) * [find](api/wrapper/find.md) * [findAll](api/wrapper/findAll.md) - * [hasAttribute](api/wrapper/hasAttribute.md) - * [hasClass](api/wrapper/hasClass.md) - * [hasProp](api/wrapper/hasProp.md) * [hasStyle](api/wrapper/hasStyle.md) * [html](api/wrapper/html.md) * [is](api/wrapper/is.md) * [isEmpty](api/wrapper/isEmpty.md) * [isVueInstance](api/wrapper/isVueInstance.md) * [name](api/wrapper/name.md) + * [props](api/wrapper/props.md) * [setComputed](api/wrapper/setComputed.md) * [setData](api/wrapper/setData.md) * [setMethods](api/wrapper/setMethods.md) @@ -55,9 +56,6 @@ * [contains](api/wrapper-array/contains.md) * [exists](api/wrapper/exists.md) * [destroy](api/wrapper-array/destroy.md) - * [hasAttribute](api/wrapper-array/hasAttribute.md) - * [hasClass](api/wrapper-array/hasClass.md) - * [hasProp](api/wrapper-array/hasProp.md) * [hasStyle](api/wrapper-array/hasStyle.md) * [is](api/wrapper-array/is.md) * [isEmpty](api/wrapper-array/isEmpty.md) diff --git a/docs/en/SUMMARY.md b/docs/en/SUMMARY.md index 3f046fa48..89aedfafa 100644 --- a/docs/en/SUMMARY.md +++ b/docs/en/SUMMARY.md @@ -22,6 +22,7 @@ - [attrs](api/options.md#attrs) - [listeners](api/options.md#listeners) - [clone](api/options.md#clone) + - [provide](api/options.md#provide) * [Wrapper](api/wrapper/README.md) * [attributes](api/wrapper/attributes.md) * [classes](api/wrapper/classes.md) diff --git a/docs/en/api/README.md b/docs/en/api/README.md index 28993ec27..3a2bdc783 100644 --- a/docs/en/api/README.md +++ b/docs/en/api/README.md @@ -12,7 +12,10 @@ - [attrs](./options.md#attrs) - [listeners](./options.md#listeners) - [clone](./options.md#clone) + - [provide](./options.md#provide) * [Wrapper](./wrapper/README.md) + * [attributes](./wrapper/attributes.md) + * [classes](./wrapper/classes.md) * [contains](./wrapper/contains.md) * [emitted](./wrapper/emitted.md) * [emittedByOrder](./wrapper/emittedByOrder.md) @@ -29,6 +32,7 @@ * [isEmpty](./wrapper/isEmpty.md) * [isVueInstance](./wrapper/isVueInstance.md) * [name](./wrapper/name.md) + * [props](./wrapper/props.md) * [setComputed](./wrapper/setComputed.md) * [setData](./wrapper/setData.md) * [setMethods](./wrapper/setMethods.md) @@ -41,9 +45,6 @@ * [contains](./wrapper-array/contains.md) * [exists](./wrapper/exists.md) * [destroy](./wrapper-array/destroy.md) - * [hasAttribute](./wrapper-array/hasAttribute.md) - * [hasClass](./wrapper-array/hasClass.md) - * [hasProp](./wrapper-array/hasProp.md) * [hasStyle](./wrapper-array/hasStyle.md) * [is](./wrapper-array/is.md) * [isEmpty](./wrapper-array/isEmpty.md) diff --git a/docs/en/api/options.md b/docs/en/api/options.md index 88c41b2be..4d83e572e 100644 --- a/docs/en/api/options.md +++ b/docs/en/api/options.md @@ -13,9 +13,9 @@ Vue options are passed to the component when a new instance is created. , e.g. ` - [`localVue`](#localvue) - [`attachToDocument`](#attachtodocument) - [`attrs`](#attrs) -- [`provide`](#provide) - [`listeners`](#listeners) - [`clone`](#clone) +- [`provide`](#provide) ### `context` diff --git a/docs/en/guides/dom-events.md b/docs/en/guides/dom-events.md index bf92955be..17b4a98e4 100644 --- a/docs/en/guides/dom-events.md +++ b/docs/en/guides/dom-events.md @@ -189,7 +189,23 @@ describe('Key event tests', () => { A key name after the dot `keydown.up` is translated to a `keyCode`. This is supported for the following names: -* `enter`, `tab`, `delete`, `esc`, `space`, `up`, `down`, `left`, `right` +| key name | key code | +| --- | --- | +| enter | 13 | +| esc | 27 | +| tab | 9 | +| space | 32 | +| delete | 46 | +| backspace | 8 | +| insert | 45 | +| up | 38 | +| down | 40 | +| left | 37 | +| right | 39 | +| end | 35 | +| home | 36 | +| pageup | 33 | +| pagedown | 34 | ## Important diff --git a/src/lib/find-vue-components.js b/src/lib/find-vue-components.js index 381a3e7d5..dd74fbc5d 100644 --- a/src/lib/find-vue-components.js +++ b/src/lib/find-vue-components.js @@ -10,7 +10,6 @@ function findAllVueComponentsFromVm (vm: Component, components: Array } function findAllVueComponentsFromVnode (vnode: Component, components: Array = []): Array { - debugger if (vnode.child) { components.push(vnode.child) } @@ -30,7 +29,6 @@ export function vmCtorMatchesName (vm: Component, name: string): boolean { } export default function findVueComponents (root: Component, componentName: string): Array { - debugger const components = root._isVue ? findAllVueComponentsFromVm(root) : findAllVueComponentsFromVnode(root) return components.filter((component) => { if (!component.$vnode) { diff --git a/test/resources/components/component-with-events.vue b/test/resources/components/component-with-events.vue index 2883af0d8..eb09dc42a 100644 --- a/test/resources/components/component-with-events.vue +++ b/test/resources/components/component-with-events.vue @@ -3,7 +3,7 @@