Skip to content

Allow find to work with component tag #897

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 4 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
26 changes: 26 additions & 0 deletions packages/shared/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,32 @@ export const camelize = (str: string): string => {
return camelizedStr.charAt(0).toLowerCase() + camelizedStr.slice(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'
]

/**
* Capitalize a string.
*/
Expand Down
23 changes: 22 additions & 1 deletion packages/shared/validators.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
// @flow
import { throwError, capitalize, camelize, hyphenate } from './util'
import {
camelize,
capitalize,
htmlTags,
hyphenate,
svgElements,
throwError
} from './util'

export function isDomSelector (selector: any): boolean {
if (typeof selector !== 'string') {
Expand Down Expand Up @@ -76,6 +83,20 @@ export function isNameSelector (nameOptionsObject: any): boolean {
return !!nameOptionsObject.name
}

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))) {
Copy link
Member

Choose a reason for hiding this comment

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

I think we should throw an error if the user is using a component selector with a pseudo selector, because they might expect this to work and be confused by find returning false.

return false
} else {
return true
}
}

export function templateContainsComponent (
template: string,
name: string
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 @@ -3,6 +3,7 @@ import Vue from 'vue'
export const NAME_SELECTOR = 'NAME_SELECTOR'
export const COMPONENT_SELECTOR = 'COMPONENT_SELECTOR'
export const REF_SELECTOR = 'REF_SELECTOR'
export const TAG_SELECTOR = 'TAG_SELECTOR'
export const DOM_SELECTOR = 'DOM_SELECTOR'
export const VUE_VERSION = Number(
`${Vue.version.split('.')[0]}.${Vue.version.split('.')[1]}`
Expand Down
29 changes: 23 additions & 6 deletions packages/test-utils/src/find-vue-components.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ export function vmCtorMatchesName (vm: Component, name: string): boolean {
return !!(
name && (
(vm._vnode &&
vm._vnode.functionalOptions &&
vm._vnode.functionalOptions.name === name) ||
(vm.$options && vm.$options.name === name) ||
(vm.options && vm.options.name === name)
vm._vnode.functionalOptions &&
vm._vnode.functionalOptions.name === name) ||
(vm.$options && vm.$options.name === name) ||
(vm.options && vm.options.name === name)
))
}

Expand Down Expand Up @@ -91,6 +91,15 @@ export function vmFunctionalCtorMatchesSelector (
return Ctors.some(c => Ctor[c] === component[FUNCTIONAL_OPTIONS]._Ctor[c])
}

function findComponentsByTag (
selector: string,
components: Array<Component> = []
): Array<Component> {
return components.filter(component =>
component.$options._componentTag === selector
)
}

export default function findVueComponents (
root: Component,
selectorType: ?string,
Expand All @@ -103,14 +112,22 @@ export default function findVueComponents (
return nodes.filter(
node =>
vmFunctionalCtorMatchesSelector(node, selector._Ctor) ||
node[FUNCTIONAL_OPTIONS].name === selector.name
node[FUNCTIONAL_OPTIONS].name === selector.name
)
}
const nameSelector =
typeof selector === 'function' ? selector.extendOptions.name : selector.name
typeof selector === 'function' ? selector.extendOptions.name : selector.name
const components = root._isVue
? findAllVueComponentsFromVm(root)
: findAllVueComponentsFromVnode(root)

if (typeof selector === 'string') {
const componentsWithMatchingTag = findComponentsByTag(selector, components)
if (componentsWithMatchingTag.length > 0) {
return componentsWithMatchingTag
}
}

return components.filter(component => {
if (!component.$vnode && !component.$options.extends) {
return false
Expand Down
11 changes: 9 additions & 2 deletions packages/test-utils/src/find.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@
import findVnodes from './find-vnodes'
import findVueComponents from './find-vue-components'
import findDOMNodes from './find-dom-nodes'
import { COMPONENT_SELECTOR, NAME_SELECTOR, DOM_SELECTOR } from './consts'
import {
COMPONENT_SELECTOR,
DOM_SELECTOR,
NAME_SELECTOR,
TAG_SELECTOR
} from './consts'
import Vue from 'vue'
import getSelectorTypeOrThrow from './get-selector-type'
import { throwError } from 'shared/util'
Expand All @@ -24,7 +29,9 @@ export default function find (
)
}

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,20 +4,23 @@ import {
isDomSelector,
isNameSelector,
isRefSelector,
isTagSelector,
isVueComponent
} from 'shared/validators'
import { throwError } from 'shared/util'
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
5 changes: 5 additions & 0 deletions test/specs/wrapper/find.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ describeWithShallowAndMount('find', mountingMethod => {
expect(wrapper.find('p').vm).to.equal(undefined)
})

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', () => {
const compiled = compileToFunctions('<div><div class="foo" /></div>')
const wrapper = mountingMethod(compiled)
Expand Down