From 0f854b116f99d49233d5971d89b322a95c98cb54 Mon Sep 17 00:00:00 2001 From: lmiller1990 Date: Sat, 4 Aug 2018 15:25:56 +0900 Subject: [PATCH 1/4] First implementation --- packages/shared/util.js | 26 +++++++++++++++++ packages/shared/validators.js | 23 ++++++++++++++- packages/test-utils/src/consts.js | 1 + .../test-utils/src/find-vue-components.js | 29 +++++++++++++++---- packages/test-utils/src/find.js | 11 +++++-- packages/test-utils/src/get-selector-type.js | 5 +++- test/specs/wrapper/find.spec.js | 5 ++++ 7 files changed, 90 insertions(+), 10 deletions(-) diff --git a/packages/shared/util.js b/packages/shared/util.js index 1b78c8506..e4df811e8 100644 --- a/packages/shared/util.js +++ b/packages/shared/util.js @@ -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. */ diff --git a/packages/shared/validators.js b/packages/shared/validators.js index 8259e753f..de2f1e010 100644 --- a/packages/shared/validators.js +++ b/packages/shared/validators.js @@ -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') { @@ -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))) { + return false + } else { + return true + } +} + export function templateContainsComponent ( template: string, name: string diff --git a/packages/test-utils/src/consts.js b/packages/test-utils/src/consts.js index 276c020f8..6da066747 100644 --- a/packages/test-utils/src/consts.js +++ b/packages/test-utils/src/consts.js @@ -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]}` diff --git a/packages/test-utils/src/find-vue-components.js b/packages/test-utils/src/find-vue-components.js index d9858c49b..4f4b4a0dd 100644 --- a/packages/test-utils/src/find-vue-components.js +++ b/packages/test-utils/src/find-vue-components.js @@ -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) )) } @@ -91,6 +91,15 @@ export function vmFunctionalCtorMatchesSelector ( return Ctors.some(c => Ctor[c] === component[FUNCTIONAL_OPTIONS]._Ctor[c]) } +function findComponentsByTag ( + selector: string, + components: Array = [] +): Array { + return components.filter(component => + component.$options._componentTag === selector + ) +} + export default function findVueComponents ( root: Component, selectorType: ?string, @@ -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 diff --git a/packages/test-utils/src/find.js b/packages/test-utils/src/find.js index 1217343d4..bafdfa038 100644 --- a/packages/test-utils/src/find.js +++ b/packages/test-utils/src/find.js @@ -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' @@ -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 [] diff --git a/packages/test-utils/src/get-selector-type.js b/packages/test-utils/src/get-selector-type.js index a4b25473c..cddb91173 100644 --- a/packages/test-utils/src/get-selector-type.js +++ b/packages/test-utils/src/get-selector-type.js @@ -4,6 +4,7 @@ import { isDomSelector, isNameSelector, isRefSelector, + isTagSelector, isVueComponent } from 'shared/validators' import { throwError } from 'shared/util' @@ -11,13 +12,15 @@ 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 diff --git a/test/specs/wrapper/find.spec.js b/test/specs/wrapper/find.spec.js index a45e8fc53..bb925a7ae 100644 --- a/test/specs/wrapper/find.spec.js +++ b/test/specs/wrapper/find.spec.js @@ -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('
') const wrapper = mountingMethod(compiled) From eff7f5c3622457d5debd8dbb4fc8f1b0cdfd9e36 Mon Sep 17 00:00:00 2001 From: lmiller1990 Date: Sat, 4 Aug 2018 15:34:04 +0900 Subject: [PATCH 2/4] Remove unneeded changes --- packages/test-utils/src/find-vue-components.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/test-utils/src/find-vue-components.js b/packages/test-utils/src/find-vue-components.js index 4f4b4a0dd..63c2cd3dc 100644 --- a/packages/test-utils/src/find-vue-components.js +++ b/packages/test-utils/src/find-vue-components.js @@ -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) )) } @@ -112,11 +112,11 @@ 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) From c2b23938c55b45915f7a79ec3ce1f5ffe5c5c706 Mon Sep 17 00:00:00 2001 From: lmiller1990 Date: Sat, 4 Aug 2018 17:58:08 +0900 Subject: [PATCH 3/4] work on validations and add tests for extended components --- packages/shared/validators.js | 11 ++++++ test/specs/wrapper/find.spec.js | 70 +++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/packages/shared/validators.js b/packages/shared/validators.js index de2f1e010..3a47c6841 100644 --- a/packages/shared/validators.js +++ b/packages/shared/validators.js @@ -1,4 +1,6 @@ // @flow +import intersection from 'lodash/intersection' +import concat from 'lodash/concat' import { camelize, capitalize, @@ -88,9 +90,18 @@ export function isTagSelector (selector: any) { return false } const pseudoSelectors = ['.', '#', '[', ':', '>', ' '] + if (htmlTags.includes(selector) || svgElements.includes(selector) || selector.split('').some(char => pseudoSelectors.includes(char))) { + + const tags = selector.split(/\.|\[|\s|>|#|:/).filter(tag => tag.length > 0) + const componentTags = + tags.filter(tag => { + return !concat(htmlTags, pseudoSelectors, svgElements).includes(tag) + }) + + // throwError('Pseudo selectors cannot be used with component tag selectors') return false } else { return true diff --git a/test/specs/wrapper/find.spec.js b/test/specs/wrapper/find.spec.js index bb925a7ae..562152ec0 100644 --- a/test/specs/wrapper/find.spec.js +++ b/test/specs/wrapper/find.spec.js @@ -29,7 +29,77 @@ describeWithShallowAndMount('find', mountingMethod => { expect(wrapper.find('child-component').vnode).to.be.an('object') }) + it('returns Wrapper matching sub class component tag passed', () => { + const ChildComponent = Vue.extend({ + template: '
' + }) + const TestComponent = { + template: '', + components: { ChildComponent } + } + const wrapper = mountingMethod(TestComponent) + expect(wrapper.find('child-component').vnode).to.be.an('object') + }) + + it('returns Wrapper matching extended component tag passed', () => { + const BaseComponent = { + template: '
', + components: { + AComponent: Component + } + } + const TestComponent = { + extends: BaseComponent, + name: 'test-component' + } + const wrapper = mountingMethod(TestComponent) + expect(wrapper.find('a-component').exists()).to.equal(true) + expect(wrapper.find('a-component').isVueInstance()).to.equal(true) + }) + + xit('returns Wrapper matching functional component tag passed', () => { + if (!functionalSFCsSupported) { + return + } + const TestComponent = { + template: ` +
+ +
+ `, + components: { + FunctionalComponent + } + } + + const wrapper = mountingMethod(TestComponent) + console.log(wrapper.html()) + expect(wrapper.find('functional-component').vnode).to.be.an('object') + expect(wrapper.find('functional-component').vm).to.equal(undefined) + }) + + it('throws an error when passed a hierarchy pseudo selector and component tag', () => { + const wrapper = mountingMethod(ComponentWithChild) + const message = + '[vue-test-utils]: Pseudo selectors cannot be used with component tag selectors' + const fn = () => wrapper.find('a > child-component') + expect(fn) + .to.throw() + .with.property('message', message) + }) + + it('throws an error when passed a class pseudo selector and component tag', () => { + const wrapper = mountingMethod(ComponentWithChild) + const message = + '[vue-test-utils]: Pseudo selectors cannot be used with component tag selectors' + const fn = () => wrapper.find('child-component.class-name') + expect(fn) + .to.throw() + .with.property('message', message) + }) + it('returns Wrapper matching class selector passed', () => { + const compiled = compileToFunctions('
') const wrapper = mountingMethod(compiled) expect(wrapper.find('.foo').vnode).to.be.an('object') From fae5ceddd5e2b8ffb30ab9654f62442adcee6c4e Mon Sep 17 00:00:00 2001 From: lmiller1990 Date: Sat, 4 Aug 2018 20:12:10 +0900 Subject: [PATCH 4/4] Update --- packages/shared/validators.js | 23 ++++++++++++++++++----- test/specs/wrapper/find.spec.js | 3 +-- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/packages/shared/validators.js b/packages/shared/validators.js index 3a47c6841..bac7519d5 100644 --- a/packages/shared/validators.js +++ b/packages/shared/validators.js @@ -1,6 +1,5 @@ // @flow -import intersection from 'lodash/intersection' -import concat from 'lodash/concat' +// import concat from 'lodash/concat' import { camelize, capitalize, @@ -94,14 +93,28 @@ export function isTagSelector (selector: any) { if (htmlTags.includes(selector) || svgElements.includes(selector) || selector.split('').some(char => pseudoSelectors.includes(char))) { - + /* TODO: Throw error for cases such as find("a > my-component") const tags = selector.split(/\.|\[|\s|>|#|:/).filter(tag => tag.length > 0) - const componentTags = + const componentTags = tags.filter(tag => { return !concat(htmlTags, pseudoSelectors, svgElements).includes(tag) }) - // throwError('Pseudo selectors cannot be used with component tag selectors') + componentTags.forEach(tag => { + const selectorWithoutWhitespace = selector.replace(/\s/g, '') + const tagIndex = selectorWithoutWhitespace.indexOf(tag) + if ( + pseudoSelectors.includes( + selectorWithoutWhitespace[tagIndex + tag.length]) || + (tagIndex !== 0 && + pseudoSelectors.includes( + selectorWithoutWhitespace[tagIndex - 1])) + ) { + throwError( + 'Pseudo selectors cannot be used with component tag selectors') + } + }) + */ return false } else { return true diff --git a/test/specs/wrapper/find.spec.js b/test/specs/wrapper/find.spec.js index 562152ec0..4487356fc 100644 --- a/test/specs/wrapper/find.spec.js +++ b/test/specs/wrapper/find.spec.js @@ -31,7 +31,7 @@ describeWithShallowAndMount('find', mountingMethod => { it('returns Wrapper matching sub class component tag passed', () => { const ChildComponent = Vue.extend({ - template: '
' + template: '
' }) const TestComponent = { template: '', @@ -99,7 +99,6 @@ describeWithShallowAndMount('find', mountingMethod => { }) it('returns Wrapper matching class selector passed', () => { - const compiled = compileToFunctions('
') const wrapper = mountingMethod(compiled) expect(wrapper.find('.foo').vnode).to.be.an('object')