Skip to content

Update find and findAll to be more robust #692

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions packages/shared/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,26 @@ const hyphenateRE = /\B([A-Z])/g
export const hyphenate = (str: string) => str.replace(hyphenateRE, '-$1').toLowerCase()

export const vueVersion = Number(`${Vue.version.split('.')[0]}.${Vue.version.split('.')[1]}`)

export const htmlTags = [
'html', 'body', 'base', 'head', 'link', 'meta', 'style', 'title',
'address', 'article', 'aside', 'footer', 'header', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
'hgroup', 'nav', 'section', 'div', 'dd', 'dl', 'dt', 'figcaption', 'figure', 'picture', 'hr',
'img', 'li', 'main', 'ol', 'p', 'pre', 'ul',
'a', 'b', 'abbr', 'bdi', 'bdo', 'br', 'cite', 'code', 'data', 'dfn',
'em', 'i', 'kbd', 'mark', 'q', 'rp', 'rt', 'rtc', 'ruby',
's', 'samp', 'small', 'span', 'strong', 'sub', 'sup', 'time',
'u', 'var', 'wbr', 'area', 'audio', 'map', 'track', 'video',
'embed', 'object', 'param', 'source', 'canvas', 'script',
'noscript', 'del', 'ins', 'caption', 'col', 'colgroup', 'table',
'thead', 'tbody', 'td', 'th', 'tr', 'button', 'datalist', 'fieldset',
'form', 'input', 'label', 'legend', 'meter', 'optgroup', 'option',
'output', 'progress', 'select', 'textarea', 'details', 'dialog', 'menu',
'menuitem', 'summary', 'content', 'element', 'shadow', 'template', 'blockquote', 'iframe', 'tfoot'
]

export const svgElements = [
'svg', 'animate', 'circle', 'clippath', 'cursor', 'defs', 'desc', 'ellipse', 'filter', 'font-face',
'foreignObject', 'g', 'glyph', 'image', 'line', 'marker', 'mask', 'missing-glyph', 'path', 'pattern',
'polygon', 'polyline', 'rect', 'switch', 'symbol', 'text', 'textpath', 'tspan', 'use', 'view'
]
20 changes: 19 additions & 1 deletion packages/shared/validators.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import {
throwError,
capitalize,
camelize,
hyphenate
hyphenate,
htmlTags,
svgElements
} from './util'

export function isDomSelector (selector: any) {
Expand All @@ -27,6 +29,22 @@ export function isDomSelector (selector: any) {
}
}

export function isTagSelector (selector: any) {
if (typeof selector !== 'string') {
return false
}

const pseudoSelectors = ['.', '#', '[', ':', '>', ' ']

if (htmlTags.includes(selector) ||
svgElements.includes(selector) ||
selector.split('').some(char => pseudoSelectors.includes(char))) {
return false
} else {
return true
}
}

export function isVueComponent (component: any) {
if (typeof component === 'function' && component.options) {
return true
Expand Down
1 change: 1 addition & 0 deletions packages/test-utils/src/consts.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ export const NAME_SELECTOR = 'NAME_SELECTOR'
export const COMPONENT_SELECTOR = 'COMPONENT_SELECTOR'
export const REF_SELECTOR = 'REF_SELECTOR'
export const DOM_SELECTOR = 'DOM_SELECTOR'
export const TAG_SELECTOR = 'TAG_SELECTOR'
export const VUE_VERSION = Number(`${Vue.version.split('.')[0]}.${Vue.version.split('.')[1]}`)
export const FUNCTIONAL_OPTIONS = VUE_VERSION >= 2.5 ? 'fnOptions' : 'functionalOptions'
23 changes: 21 additions & 2 deletions packages/test-utils/src/find-vue-components.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,18 @@ function findAllFunctionalComponentsFromVnode (
return components
}

export function vmCtorMatchesTag (vm: Component, tag: string): boolean {
if (vm.$vnode.componentOptions.tag === tag) {
return true
}

return false
}

export function vmCtorMatchesName (vm: Component, name: string): boolean {
if (!name) {
return false
}
return !!((vm.$vnode && vm.$vnode.componentOptions &&
vm.$vnode.componentOptions.Ctor.options.name === name) ||
(vm._vnode &&
Expand Down Expand Up @@ -102,14 +113,22 @@ export default function findVueComponents (
node[FUNCTIONAL_OPTIONS].name === selector.name
)
}
const nameSelector = typeof selector === 'function' ? selector.options.name : selector.name

const nameSelector = typeof selector === 'function'
? selector.options.name
: typeof selector === 'object' ? selector.name : selector

const components = root._isVue
? findAllVueComponentsFromVm(root)
: findAllVueComponentsFromVnode(root)

return components.filter((component) => {
if (!component.$vnode && !component.$options.extends) {
return false
}
return vmCtorMatchesSelector(component, selector) || vmCtorMatchesName(component, nameSelector)

return vmCtorMatchesSelector(component, selector) ||
vmCtorMatchesName(component, nameSelector) ||
vmCtorMatchesTag(component, nameSelector)
})
}
5 changes: 3 additions & 2 deletions packages/test-utils/src/find.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import findDOMNodes from './find-dom-nodes'
import {
COMPONENT_SELECTOR,
NAME_SELECTOR,
DOM_SELECTOR
DOM_SELECTOR,
TAG_SELECTOR
} from './consts'
import Vue from 'vue'
import getSelectorTypeOrThrow from './get-selector-type'
Expand All @@ -24,7 +25,7 @@ export default function find (
throwError('cannot find a Vue instance on a DOM node. The node you are calling find on does not exist in the VDom. Are you adding the node as innerHTML?')
}

if (selectorType === COMPONENT_SELECTOR || selectorType === NAME_SELECTOR) {
if (selectorType === COMPONENT_SELECTOR || selectorType === NAME_SELECTOR || selectorType === TAG_SELECTOR) {
const root = vm || vnode
if (!root) {
return []
Expand Down
5 changes: 4 additions & 1 deletion packages/test-utils/src/get-selector-type.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
isDomSelector,
isNameSelector,
isRefSelector,
isTagSelector,
isVueComponent
} from 'shared/validators'
import {
Expand All @@ -13,10 +14,12 @@ import {
REF_SELECTOR,
COMPONENT_SELECTOR,
NAME_SELECTOR,
DOM_SELECTOR
DOM_SELECTOR,
TAG_SELECTOR
} from './consts'

export default function getSelectorTypeOrThrow (selector: Selector, methodName: string): string | void {
if (isTagSelector(selector)) return TAG_SELECTOR
if (isDomSelector(selector)) return DOM_SELECTOR
if (isNameSelector(selector)) return NAME_SELECTOR
if (isVueComponent(selector)) return COMPONENT_SELECTOR
Expand Down
8 changes: 1 addition & 7 deletions test/specs/wrapper/contains.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,7 @@ describeWithShallowAndMount('contains', (mountingMethod) => {
expect(fn).to.throw().with.property('message', message)
})

it('returns true when wrapper contains root element', () => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this exact tests appeared twice (duplicated by accident I guess) and also description was wrong so updated for this PR (it was a failing test so I noticed it).

const compiled = compileToFunctions('<div><input /></div>')
const wrapper = mountingMethod(compiled)
expect(wrapper.contains('doesntexist')).to.equal(false)
})

it('returns true if wrapper root element matches contains', () => {
it('returns false when wrapper does not contain the element', () => {
const compiled = compileToFunctions('<div><input /></div>')
const wrapper = mountingMethod(compiled)
expect(wrapper.contains('doesntexist')).to.equal(false)
Expand Down
5 changes: 5 additions & 0 deletions test/specs/wrapper/find.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ describeWithShallowAndMount('find', (mountingMethod) => {
expect(wrapper.find('.foo').vnode).to.be.an('object')
})

it('returns Wrapper matching component tag passed', () => {
const wrapper = mountingMethod(ComponentWithChild)
expect(wrapper.find('child-component').vnode).to.be.an('object')
})

it('returns Wrapper matching class selector passed if nested in a transition', () => {
const compiled = compileToFunctions('<transition><div /></transition>')
const wrapper = mountingMethod(compiled)
Expand Down