diff --git a/docs/en/README.md b/docs/en/README.md index 99c68fa6f..75f8c6751 100644 --- a/docs/en/README.md +++ b/docs/en/README.md @@ -52,6 +52,7 @@ * [text](api/wrapper/text.md) * [trigger](api/wrapper/trigger.md) * [update](api/wrapper/update.md) + * [visible](api/wrapper/visible.md) * [WrapperArray](api/wrapper-array/README.md) * [at](api/wrapper-array/at.md) * [contains](api/wrapper-array/contains.md) @@ -67,6 +68,7 @@ * [setProps](api/wrapper-array/setProps.md) * [trigger](api/wrapper-array/trigger.md) * [update](api/wrapper-array/update.md) + * [visible](api/wrapper-array/visible.md) * [components](api/components/README.md) * [TransitionStub](api/components/TransitionStub.md) * [TransitionGroupStub](api/components/TransitionGroupStub.md) diff --git a/docs/en/SUMMARY.md b/docs/en/SUMMARY.md index 0ba2554f3..ed65e91dd 100644 --- a/docs/en/SUMMARY.md +++ b/docs/en/SUMMARY.md @@ -48,6 +48,7 @@ * [text](api/wrapper/text.md) * [trigger](api/wrapper/trigger.md) * [update](api/wrapper/update.md) + * [visible](api/wrapper/visible.md) * [WrapperArray](api/wrapper-array/README.md) * [at](api/wrapper-array/at.md) * [contains](api/wrapper-array/contains.md) @@ -63,6 +64,7 @@ * [setProps](api/wrapper-array/setProps.md) * [trigger](api/wrapper-array/trigger.md) * [update](api/wrapper-array/update.md) + * [visible](api/wrapper-array/visible.md) * [components](api/components/README.md) * [TransitionStub](api/components/TransitionStub.md) * [TransitionGroupStub](api/components/TransitionGroupStub.md) diff --git a/docs/en/api/wrapper-array/visible.md b/docs/en/api/wrapper-array/visible.md new file mode 100644 index 000000000..aa2574e5c --- /dev/null +++ b/docs/en/api/wrapper-array/visible.md @@ -0,0 +1,22 @@ +# visible() + +Assert every `Wrapper` in `WrapperArray` is visible. + +Returns false if at least one ancestor element has `display: none` or `visibility: hidden` style. + +This can be used to assert that a component is hidden by `v-show`. + +- **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.visible()).toBe(true) +expect(wrapper.findAll('.is-not-visible').visible()).toBe(false) +expect(wrapper.findAll('.is-visible').visible()).toBe(true) +``` diff --git a/docs/en/api/wrapper/visible.md b/docs/en/api/wrapper/visible.md new file mode 100644 index 000000000..5063fc38d --- /dev/null +++ b/docs/en/api/wrapper/visible.md @@ -0,0 +1,21 @@ +# visible() + +Assert `Wrapper` or `WrapperArray` is visible. + +Returns false if an ancestor element has `display: none` or `visibility: hidden` style. + +This can be used to assert that a component is hidden by `v-show`. + +- **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.visible()).toBe(true) +expect(wrapper.find('.is-not-visible').visible()).toBe(false) +``` diff --git a/flow/wrapper.flow.js b/flow/wrapper.flow.js index 2286dbe4d..786e44334 100644 --- a/flow/wrapper.flow.js +++ b/flow/wrapper.flow.js @@ -13,6 +13,7 @@ declare interface BaseWrapper { // eslint-disable-line no-undef emitted(event?: string): { [name: string]: Array> } | Array> | void, emittedByOrder(): Array<{ name: string; args: Array }> | void, exists(): boolean, + visible(): boolean | void, hasAttribute(attribute: string, value: string): boolean | void, hasClass(className: string): boolean | void, hasProp(prop: string, value: string): boolean | void, diff --git a/package.json b/package.json index 1dce391c3..7391e5a75 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ ], "scripts": { "build": "node build/build.js", - "build:test": "NODE_ENV=test node build/build.js", + "build:test": "cross-env NODE_ENV=test node build/build.js", "coverage": "cross-env NODE_ENV=coverage nyc --reporter=lcov --reporter=text npm run test:unit", "docs": "cd docs && gitbook install && gitbook serve", "docs:deploy": "build/update-docs.sh", diff --git a/src/wrappers/error-wrapper.js b/src/wrappers/error-wrapper.js index a75cc1f14..535faf27f 100644 --- a/src/wrappers/error-wrapper.js +++ b/src/wrappers/error-wrapper.js @@ -36,6 +36,10 @@ export default class ErrorWrapper implements BaseWrapper { return false } + visible (): void { + throwError(`find did not return ${this.selector}, cannot call visible() on empty Wrapper`) + } + hasAttribute (): void { throwError(`find did not return ${this.selector}, cannot call hasAttribute() on empty Wrapper`) } diff --git a/src/wrappers/wrapper-array.js b/src/wrappers/wrapper-array.js index 4e7250fca..5a2f26ba1 100644 --- a/src/wrappers/wrapper-array.js +++ b/src/wrappers/wrapper-array.js @@ -42,6 +42,12 @@ export default class WrapperArray implements BaseWrapper { return this.length > 0 && this.wrappers.every(wrapper => wrapper.exists()) } + visible (): boolean { + this.throwErrorIfWrappersIsEmpty('visible') + + return this.length > 0 && this.wrappers.every(wrapper => wrapper.visible()) + } + emitted (): void { this.throwErrorIfWrappersIsEmpty('emitted') diff --git a/src/wrappers/wrapper.js b/src/wrappers/wrapper.js index 87632e128..fc7e34329 100644 --- a/src/wrappers/wrapper.js +++ b/src/wrappers/wrapper.js @@ -120,6 +120,26 @@ export default class Wrapper implements BaseWrapper { return true } + /** + * Utility to check wrapper is visible. Returns false if a parent element has display: none or visibility: hidden style. + */ + visible (): boolean { + let element = this.element + + if (!element) { + return false + } + + while (element) { + if (element.style && (element.style.visibility === 'hidden' || element.style.display === 'none')) { + return false + } + element = element.parentElement + } + + return true + } + /** * Checks if wrapper has an attribute with matching value */ diff --git a/test/resources/components/component-with-v-show.vue b/test/resources/components/component-with-v-show.vue new file mode 100644 index 000000000..a42692a38 --- /dev/null +++ b/test/resources/components/component-with-v-show.vue @@ -0,0 +1,19 @@ + + + diff --git a/test/unit/specs/mount/Wrapper/visible.spec.js b/test/unit/specs/mount/Wrapper/visible.spec.js new file mode 100644 index 000000000..e76d96c57 --- /dev/null +++ b/test/unit/specs/mount/Wrapper/visible.spec.js @@ -0,0 +1,137 @@ +import { compileToFunctions } from 'vue-template-compiler' +import { mount } from '~vue-test-utils' +import ComponentWithVShow from '~resources/components/component-with-v-show.vue' +import ComponentWithVIf from '~resources/components/component-with-v-if.vue' + +describe('visible', () => { + it('returns true if element has no inline style', () => { + const compiled = compileToFunctions('
') + const wrapper = mount(compiled) + const element = wrapper.find('.visible') + expect(element.visible()).to.equal(true) + }) + + it('returns false if element has inline style display: none', () => { + const compiled = compileToFunctions('
') + const wrapper = mount(compiled) + const element = wrapper.find('.visible') + expect(element.visible()).to.equal(false) + }) + + it('returns false if element has inline style visibility: hidden', () => { + const compiled = compileToFunctions('
') + const wrapper = mount(compiled) + const element = wrapper.find('.visible') + expect(element.visible()).to.equal(false) + }) + + it('returns true if element has v-show true', () => { + const wrapper = mount(ComponentWithVShow) + wrapper.vm.$set(wrapper.vm, 'ready', true) + wrapper.update() + + const notReadyElement = wrapper.find('.not-ready') + expect(notReadyElement.visible()).to.equal(false) + + const readyElement = wrapper.find('.parent.ready') + expect(readyElement.visible()).to.equal(true) + }) + + it('returns false if element has v-show true', () => { + const wrapper = mount(ComponentWithVShow) + wrapper.vm.$set(wrapper.vm, 'ready', true) + wrapper.update() + + const notReadyElement = wrapper.find('.not-ready') + expect(notReadyElement.visible()).to.equal(false) + + const readyElement = wrapper.find('.parent.ready') + expect(readyElement.visible()).to.equal(true) + }) + + it('returns true if parent element has v-show true', () => { + const wrapper = mount(ComponentWithVShow) + wrapper.vm.$set(wrapper.vm, 'ready', true) + wrapper.update() + + const notReadyElement = wrapper.find('.not-ready') + expect(notReadyElement.visible()).to.equal(false) + + const readyChildElement = wrapper.find('.child.ready') + expect(readyChildElement.visible()).to.equal(true) + }) + + it('returns false if parent element has v-show false', () => { + const wrapper = mount(ComponentWithVShow) + wrapper.vm.$set(wrapper.vm, 'ready', true) + wrapper.update() + + const notReadyElement = wrapper.find('.not-ready') + expect(notReadyElement.visible()).to.equal(false) + + const readyChildElement = wrapper.find('.child.ready') + expect(readyChildElement.visible()).to.equal(true) + }) + + it('returns false if root element has v-show false and parent has v-show true', () => { + const wrapper = mount(ComponentWithVShow) + wrapper.vm.$set(wrapper.vm, 'ready', true) + wrapper.vm.$set(wrapper.vm, 'rootReady', false) + wrapper.update() + + const notReadyElement = wrapper.find('.not-ready') + expect(notReadyElement.visible()).to.equal(false) + + const readyChildElement = wrapper.find('.child.ready') + expect(readyChildElement.visible()).to.equal(false) + }) + + it('returns false if root element has v-show true and parent has v-show false', () => { + const wrapper = mount(ComponentWithVShow) + wrapper.vm.$set(wrapper.vm, 'ready', false) + wrapper.vm.$set(wrapper.vm, 'rootReady', true) + wrapper.update() + + const notReadyElement = wrapper.find('.not-ready') + expect(notReadyElement.visible()).to.equal(true) + + const readyChildElement = wrapper.find('.child.ready') + expect(readyChildElement.visible()).to.equal(false) + }) + + it('returns true if all elements are visible', () => { + const wrapper = mount(ComponentWithVShow) + wrapper.vm.$set(wrapper.vm, 'ready', true) + wrapper.vm.$set(wrapper.vm, 'rootReady', true) + wrapper.update() + + const readyChildElement = wrapper.find('.ready') + expect(readyChildElement.visible()).to.equal(true) + }) + + it('returns false if one element is not visible', () => { + const wrapper = mount(ComponentWithVShow) + wrapper.vm.$set(wrapper.vm, 'ready', true) + wrapper.vm.$set(wrapper.vm, 'rootReady', true) + wrapper.update() + + const readyChildElement = wrapper.find('.ready, .not-ready') + expect(readyChildElement.visible()).to.equal(false) + }) + + it('fails if one element is absent', () => { + const wrapper = mount(ComponentWithVIf) + wrapper.vm.$set(wrapper.vm, 'ready', false) + wrapper.update() + + const fn = () => wrapper.find('.child.ready').visible() + expect(fn).to.throw() + }) + + it('returns true if one element is present', () => { + const wrapper = mount(ComponentWithVIf) + wrapper.vm.$set(wrapper.vm, 'ready', true) + wrapper.update() + expect(wrapper.find('.child.ready').visible()).to.equal(true) + }) +}) diff --git a/types/index.d.ts b/types/index.d.ts index f2bf36bef..1e8b51d01 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -45,6 +45,7 @@ type RefSelector = { interface BaseWrapper { contains (selector: Selector): boolean exists (): boolean + visible (): boolean attributes(): { [name: string]: string } | void classes(): Array | void