From 6fcacfa7bfcf2c85e5a340f63262b9260202e19d Mon Sep 17 00:00:00 2001 From: matt-oconnell Date: Mon, 2 Oct 2017 14:25:28 -0400 Subject: [PATCH] Selector: add refs option --- 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