Skip to content

Commit d6dbc67

Browse files
committed
add ref search to is/contains
1 parent 48fdb64 commit d6dbc67

File tree

8 files changed

+101
-41
lines changed

8 files changed

+101
-41
lines changed

flow/wrapper.flow.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@
33
import type Wrapper from '~src/Wrapper'
44
import type WrapperArray from '~src/WrapperArray'
55

6-
declare type Selector = string | Component
6+
// declare type FindOptions = {
7+
// ref: string,
8+
// }
9+
10+
// declare type Selector = string | Component | FindOptions
11+
// Component is not getting imported
12+
declare type Selector = any
713

814
declare interface BaseWrapper { // eslint-disable-line no-undef
915
at(index: number): Wrapper | void,
@@ -15,8 +21,8 @@ declare interface BaseWrapper { // eslint-disable-line no-undef
1521
hasClass(className: string): boolean | void,
1622
hasProp(prop: string, value: string): boolean | void,
1723
hasStyle(style: string, value: string): boolean | void,
18-
find(selector: Selector | FindOptions): Wrapper | void,
19-
findAll(selector: Selector | FindOptions): WrapperArray | void,
24+
find(selector: Selector): Wrapper | void,
25+
findAll(selector: Selector): WrapperArray | void,
2026
html(): string | void,
2127
is(selector: Selector): boolean | void,
2228
isEmpty(): boolean | void,
@@ -36,7 +42,3 @@ declare type WrapperOptions = { // eslint-disable-line no-undef
3642
attachedToDocument: boolean,
3743
error?: string
3844
}
39-
40-
declare type FindOptions = { // eslint-disable-line no-undef
41-
ref: string,
42-
}

src/lib/get-selector-type.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// @flow
2+
3+
import { isDomSelector, isVueComponent, isFindOption } from './validators.js'
4+
import { throwError } from '../lib/util'
5+
6+
export const selectorTypes = {
7+
DOM_SELECTOR: 'DOM_SELECTOR',
8+
VUE_COMPONENT: 'VUE_COMPONENT',
9+
OPTIONS_OBJECT: 'OPTIONS_OBJECT'
10+
}
11+
12+
function getSelectorType (selector: Selector): string | void {
13+
if (isDomSelector(selector)) {
14+
return selectorTypes.DOM_SELECTOR
15+
}
16+
17+
if (isVueComponent(selector)) {
18+
return selectorTypes.VUE_COMPONENT
19+
}
20+
21+
if (isFindOption(selector)) {
22+
return selectorTypes.OPTIONS_OBJECT
23+
}
24+
}
25+
26+
export default function getSelectorTypeOrThrow (selector: Selector, methodName: string): string | void {
27+
const selectorType = getSelectorType(selector)
28+
if (!selectorType) {
29+
throwError(`wrapper.${methodName}() must be passed a valid CSS selector, Vue constructor, or valid find option object`)
30+
}
31+
return selectorType
32+
}

src/lib/validators.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,14 @@ export function isValidSelector (selector: any): boolean {
4343
return true
4444
}
4545

46-
return isVueComponent(selector)
46+
if (isVueComponent(selector)) {
47+
return true
48+
}
49+
50+
return isFindOption(selector)
4751
}
4852

49-
export function isValidFindOption (findOptions: any) {
53+
export function isFindOption (findOptions: any) {
5054
if (typeof findOptions !== 'object') {
5155
return false
5256
}

src/wrappers/wrapper.js

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// @flow
22

33
import Vue from 'vue'
4-
import { isValidSelector, isVueComponent, isValidFindOption } from '../lib/validators'
4+
import getSelectorTypeOrThrow, { selectorTypes } from '../lib/get-selector-type'
55
import findVueComponents, { vmCtorMatchesName } from '../lib/find-vue-components'
66
import findVNodesBySelector from '../lib/find-vnodes-by-selector'
77
import findVNodesByRef from '../lib/find-vnodes-by-ref'
@@ -37,16 +37,19 @@ export default class Wrapper implements BaseWrapper {
3737
* Checks if wrapper contains provided selector.
3838
*/
3939
contains (selector: Selector) {
40-
if (!isValidSelector(selector)) {
41-
throwError('wrapper.contains() must be passed a valid CSS selector or a Vue constructor')
42-
}
40+
const selectorType = getSelectorTypeOrThrow(selector, 'contains')
4341

44-
if (typeof selector === 'object') {
42+
if (selectorType === selectorTypes.VUE_COMPONENT) {
4543
const vm = this.vm || this.vnode.context.$root
4644
return findVueComponents(vm, selector.name).length > 0
4745
}
4846

49-
if (typeof selector === 'string' && this.element instanceof HTMLElement) {
47+
if (selectorType === selectorTypes.OPTIONS_OBJECT) {
48+
const nodes = findVNodesByRef(this.vnode, selector.ref)
49+
return nodes.length > 0
50+
}
51+
52+
if (selectorType === selectorTypes.DOM_SELECTOR && this.element instanceof HTMLElement) {
5053
return this.element.querySelectorAll(selector).length > 0
5154
}
5255

@@ -175,13 +178,10 @@ export default class Wrapper implements BaseWrapper {
175178
/**
176179
* Finds first node in tree of the current wrapper that matches the provided selector.
177180
*/
178-
find (selector: Selector | FindOptions): Wrapper | ErrorWrapper | VueWrapper {
179-
const isValidOptionObject = isValidFindOption(selector)
180-
if (!isValidSelector(selector) && !isValidOptionObject) {
181-
throwError('wrapper.find() must be passed a valid CSS selector, Vue constructor, or valid find option object')
182-
}
181+
find (selector: Selector): Wrapper | ErrorWrapper | VueWrapper {
182+
const selectorType = getSelectorTypeOrThrow(selector, 'find')
183183

184-
if (typeof selector === 'object' && isVueComponent(selector)) {
184+
if (selectorType === selectorTypes.VUE_COMPONENT) {
185185
if (!selector.name) {
186186
throwError('.find() requires component to have a name property')
187187
}
@@ -193,7 +193,7 @@ export default class Wrapper implements BaseWrapper {
193193
return new VueWrapper(components[0], this.options)
194194
}
195195

196-
if (typeof selector === 'object' && isValidOptionObject) {
196+
if (selectorType === selectorTypes.OPTIONS_OBJECT) {
197197
if (!this.isVueComponent) {
198198
throwError('$ref selectors can only be used on Vue component wrappers')
199199
}
@@ -215,12 +215,10 @@ export default class Wrapper implements BaseWrapper {
215215
/**
216216
* Finds node in tree of the current wrapper that matches the provided selector.
217217
*/
218-
findAll (selector: Selector | FindOptions): WrapperArray {
219-
if (!isValidSelector(selector) && !isValidFindOption(selector)) {
220-
throwError('wrapper.findAll() must be passed a valid CSS selector, Vue constructor, or valid find option object')
221-
}
218+
findAll (selector: Selector): WrapperArray {
219+
const selectorType = getSelectorTypeOrThrow(selector, 'findAll')
222220

223-
if (typeof selector === 'object' && isVueComponent(selector)) {
221+
if (selectorType === selectorTypes.VUE_COMPONENT) {
224222
if (!selector.name) {
225223
throwError('.findAll() requires component to have a name property')
226224
}
@@ -229,7 +227,7 @@ export default class Wrapper implements BaseWrapper {
229227
return new WrapperArray(components.map(component => new VueWrapper(component, this.options)))
230228
}
231229

232-
if (typeof selector === 'object' && isValidFindOption(selector)) {
230+
if (selectorType === selectorTypes.OPTIONS_OBJECT) {
233231
if (!this.isVueComponent) {
234232
throwError('$ref selectors can only be used on Vue component wrappers')
235233
}
@@ -258,20 +256,23 @@ export default class Wrapper implements BaseWrapper {
258256
* Checks if node matches selector
259257
*/
260258
is (selector: Selector): boolean {
261-
if (!isValidSelector(selector)) {
262-
throwError('wrapper.is() must be passed a valid CSS selector or a Vue constructor')
263-
}
259+
const selectorType = getSelectorTypeOrThrow(selector, 'is')
264260

265-
if (typeof selector === 'object') {
266-
if (!this.isVueComponent) {
267-
return false
268-
}
261+
if (selectorType === selectorTypes.VUE_COMPONENT && this.isVueComponent) {
269262
if (typeof selector.name !== 'string') {
270263
throwError('a Component used as a selector must have a name property')
271264
}
272265
return vmCtorMatchesName(this.vm, selector.name)
273266
}
274267

268+
if (selectorType === selectorTypes.OPTIONS_OBJECT) {
269+
throwError('$ref selectors can not be used with wrapper.is()')
270+
}
271+
272+
if (typeof selector === 'object') {
273+
return false
274+
}
275+
275276
return !!(this.element &&
276277
this.element.getAttribute &&
277278
this.element.matches(selector))

test/unit/specs/mount/Wrapper/contains.spec.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,31 @@ describe('contains', () => {
1515
expect(wrapper.contains(Component)).to.equal(true)
1616
})
1717

18+
it('returns true if wrapper contains element specified by ref selector', () => {
19+
const compiled = compileToFunctions('<div><input ref="foo" /></div>')
20+
const wrapper = mount(compiled)
21+
expect(wrapper.contains({ ref: 'foo' })).to.equal(true)
22+
})
23+
1824
it('returns false if wrapper does not contain element', () => {
1925
const compiled = compileToFunctions('<div><input /></div>')
2026
const wrapper = mount(compiled)
2127
expect(wrapper.contains('doesntexist')).to.equal(false)
2228
})
2329

30+
it('returns false if wrapper does not contain element specified by ref selector', () => {
31+
const compiled = compileToFunctions('<div><input ref="bar" /></div>')
32+
const wrapper = mount(compiled)
33+
expect(wrapper.contains({ ref: 'foo' })).to.equal(false)
34+
})
35+
2436
it('throws an error if selector is not a valid selector', () => {
2537
const wrapper = mount(Component)
2638
const invalidSelectors = [
27-
undefined, null, NaN, 0, 2, true, false, () => {}, {}, { name: undefined }, []
39+
undefined, null, NaN, 0, 2, true, false, () => {}, {}, { name: undefined }, { ref: 'foo', nope: true }, []
2840
]
2941
invalidSelectors.forEach((invalidSelector) => {
30-
const message = '[vue-test-utils]: wrapper.contains() must be passed a valid CSS selector or a Vue constructor'
42+
const message = '[vue-test-utils]: wrapper.contains() must be passed a valid CSS selector, Vue constructor, or valid find option object'
3143
const fn = () => wrapper.contains(invalidSelector)
3244
expect(fn).to.throw().with.property('message', message)
3345
})

test/unit/specs/mount/Wrapper/is.spec.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,15 @@ describe('is', () => {
6363
expect(wrapper.is('#p')).to.equal(false)
6464
})
6565

66+
it('throws error if ref options object is passed', () => {
67+
const compiled = compileToFunctions('<div />')
68+
const wrapper = mount(compiled)
69+
70+
const message = '[vue-test-utils]: $ref selectors can not be used with wrapper.is()'
71+
const fn = () => wrapper.is({ ref: 'foo' })
72+
expect(fn).to.throw().with.property('message', message)
73+
})
74+
6675
it('throws error if component passed to use as identifier does not have a name', () => {
6776
const compiled = compileToFunctions('<div />')
6877
const wrapper = mount(compiled)
@@ -76,10 +85,10 @@ describe('is', () => {
7685
const compiled = compileToFunctions('<div />')
7786
const wrapper = mount(compiled)
7887
const invalidSelectors = [
79-
undefined, null, NaN, 0, 2, true, false, () => {}, {}, { name: undefined }, []
88+
undefined, null, NaN, 0, 2, true, false, () => {}, {}, { name: undefined }, { ref: 'foo', nope: true }, []
8089
]
8190
invalidSelectors.forEach((invalidSelector) => {
82-
const message = '[vue-test-utils]: wrapper.is() must be passed a valid CSS selector or a Vue constructor'
91+
const message = '[vue-test-utils]: wrapper.is() must be passed a valid CSS selector, Vue constructor, or valid find option object'
8392
const fn = () => wrapper.is(invalidSelector)
8493
expect(fn).to.throw().with.property('message', message)
8594
})

test/unit/specs/mount/WrapperArray/contains.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ describe('contains', () => {
3030
undefined, null, NaN, 0, 2, true, false, () => {}, {}, { name: undefined }, []
3131
]
3232
invalidSelectors.forEach((invalidSelector) => {
33-
const message = '[vue-test-utils]: wrapper.contains() must be passed a valid CSS selector or a Vue constructor'
33+
const message = '[vue-test-utils]: wrapper.contains() must be passed a valid CSS selector, Vue constructor, or valid find option object'
3434
expect(() => pArr.contains(invalidSelector)).to.throw().with.property('message', message)
3535
})
3636
})

test/unit/specs/mount/WrapperArray/is.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ describe('is', () => {
4242
undefined, null, NaN, 0, 2, true, false, () => {}, {}, { name: undefined }, []
4343
]
4444
invalidSelectors.forEach((invalidSelector) => {
45-
const message = '[vue-test-utils]: wrapper.is() must be passed a valid CSS selector or a Vue constructor'
45+
const message = '[vue-test-utils]: wrapper.is() must be passed a valid CSS selector, Vue constructor, or valid find option object'
4646
const fn = () => wrapper.findAll('div').is(invalidSelector)
4747
expect(fn).to.throw().with.property('message', message)
4848
})

0 commit comments

Comments
 (0)