diff --git a/docs/api/wrapper/findAllComponents.md b/docs/api/wrapper/findAllComponents.md
index 6b3256e16..041f4ff68 100644
--- a/docs/api/wrapper/findAllComponents.md
+++ b/docs/api/wrapper/findAllComponents.md
@@ -4,7 +4,7 @@ Returns a [`WrapperArray`](../wrapper-array/) of all matching Vue components.
- **Arguments:**
- - `{Component|ref|name} selector`
+ - `selector` Use any valid [selector](../selectors.md)
- **Returns:** `{WrapperArray}`
@@ -21,3 +21,7 @@ expect(bar.exists()).toBeTruthy()
const bars = wrapper.findAllComponents(Bar)
expect(bars).toHaveLength(1)
```
+
+::: warning Usage with CSS selectors
+Using `findAllComponents` with CSS selector is subject to same limitations as [findComponent](api/wrapper/findComponent.md)
+:::
diff --git a/docs/api/wrapper/findComponent.md b/docs/api/wrapper/findComponent.md
index cfe4a4fe8..febbb41e3 100644
--- a/docs/api/wrapper/findComponent.md
+++ b/docs/api/wrapper/findComponent.md
@@ -4,7 +4,7 @@ Returns `Wrapper` of first matching Vue component.
- **Arguments:**
- - `{Component|ref|name} selector`
+ - `{Component|ref|string} selector`
- **Returns:** `{Wrapper}`
@@ -24,3 +24,31 @@ expect(barByName.exists()).toBe(true)
const barRef = wrapper.findComponent({ ref: 'bar' }) // => finds Bar by `ref`
expect(barRef.exists()).toBe(true)
```
+
+::: warning Usage with CSS selectors
+Using `findAllComponents` with CSS selector might have confusing behavior
+
+Consider this example:
+
+```js
+const ChildComponent = {
+ name: 'Child',
+ template: '
'
+}
+
+const RootComponent = {
+ name: 'Root',
+ components: { ChildComponent },
+ template: ''
+}
+
+const wrapper = mount(RootComponent)
+
+const rootByCss = wrapper.findComponent('.root') // => finds Root
+expect(rootByCss.vm.$options.name).toBe('Root')
+const childByCss = wrapper.findComponent('.child')
+expect(childByCss.vm.$options.name).toBe('Root') // => still Root
+```
+
+The reason for such behavior is that `RootComponent` and `ChildComponent` are sharing same DOM node and only first matching component is included for each unique DOM node
+:::
diff --git a/packages/shared/util.js b/packages/shared/util.js
index 679315f63..19ede4cf6 100644
--- a/packages/shared/util.js
+++ b/packages/shared/util.js
@@ -111,3 +111,7 @@ export function warnDeprecated(method: string, fallback: string = '') {
warn(msg)
}
}
+
+export function isVueWrapper(wrapper: Object) {
+ return wrapper.vm || wrapper.isFunctionalComponent
+}
diff --git a/packages/test-utils/src/wrapper.js b/packages/test-utils/src/wrapper.js
index 82f98cce4..7d5e2c724 100644
--- a/packages/test-utils/src/wrapper.js
+++ b/packages/test-utils/src/wrapper.js
@@ -16,7 +16,8 @@ import {
isPhantomJS,
nextTick,
warn,
- warnDeprecated
+ warnDeprecated,
+ isVueWrapper
} from 'shared/util'
import { isElementVisible } from 'shared/is-visible'
import find from './find'
@@ -275,17 +276,6 @@ export default class Wrapper implements BaseWrapper {
this.__warnIfDestroyed()
const selector = getSelector(rawSelector, 'findComponent')
- if (!this.vm && !this.isFunctionalComponent) {
- throwError(
- 'You cannot chain findComponent off a DOM element. It can only be used on Vue Components.'
- )
- }
-
- if (selector.type === DOM_SELECTOR) {
- throwError(
- 'findComponent requires a Vue constructor or valid find object. If you are searching for DOM nodes, use `find` instead'
- )
- }
return this.__find(rawSelector, selector)
}
@@ -327,28 +317,25 @@ export default class Wrapper implements BaseWrapper {
this.__warnIfDestroyed()
const selector = getSelector(rawSelector, 'findAll')
- if (!this.vm) {
- throwError(
- 'You cannot chain findAllComponents off a DOM element. It can only be used on Vue Components.'
- )
- }
- if (selector.type === DOM_SELECTOR) {
- throwError(
- 'findAllComponents requires a Vue constructor or valid find object. If you are searching for DOM nodes, use `find` instead'
- )
- }
- return this.__findAll(rawSelector, selector)
+
+ return this.__findAll(rawSelector, selector, isVueWrapper)
}
- __findAll(rawSelector: Selector, selector: Object): WrapperArray {
+ __findAll(
+ rawSelector: Selector,
+ selector: Object,
+ filterFn: Function = () => true
+ ): WrapperArray {
const nodes = find(this.rootNode, this.vm, selector)
- const wrappers = nodes.map(node => {
- // Using CSS Selector, returns a VueWrapper instance if the root element
- // binds a Vue instance.
- const wrapper = createWrapper(node, this.options)
- wrapper.selector = rawSelector
- return wrapper
- })
+ const wrappers = nodes
+ .map(node => {
+ // Using CSS Selector, returns a VueWrapper instance if the root element
+ // binds a Vue instance.
+ const wrapper = createWrapper(node, this.options)
+ wrapper.selector = rawSelector
+ return wrapper
+ })
+ .filter(filterFn)
const wrapperArray = new WrapperArray(wrappers)
wrapperArray.selector = rawSelector
diff --git a/test/specs/wrapper/find.spec.js b/test/specs/wrapper/find.spec.js
index 2e53577ee..861551317 100644
--- a/test/specs/wrapper/find.spec.js
+++ b/test/specs/wrapper/find.spec.js
@@ -194,20 +194,33 @@ describeWithShallowAndMount('find', mountingMethod => {
expect(wrapper.findComponent(Component).vnode).toBeTruthy()
})
- it('throws an error if findComponent selector is a CSS selector', () => {
- const wrapper = mountingMethod(Component)
- const message =
- '[vue-test-utils]: findComponent requires a Vue constructor or valid find object. If you are searching for DOM nodes, use `find` instead'
- const fn = () => wrapper.findComponent('#foo')
- expect(fn).toThrow(message)
- })
+ it('findComponent returns top-level component when multiple components are matching', () => {
+ const DeepNestedChild = {
+ name: 'DeepNestedChild',
+ template: 'I am deeply nested
'
+ }
+ const NestedChild = {
+ name: 'NestedChild',
+ components: { DeepNestedChild },
+ template: ''
+ }
+ const RootComponent = {
+ name: 'RootComponent',
+ components: { NestedChild },
+ template: '
'
+ }
- it('throws an error if findComponent is chained off a DOM element', () => {
- const wrapper = mountingMethod(ComponentWithChild)
- const message =
- '[vue-test-utils]: You cannot chain findComponent off a DOM element. It can only be used on Vue Components.'
- const fn = () => wrapper.find('span').findComponent('#foo')
- expect(fn).toThrow(message)
+ const wrapper = mountingMethod(RootComponent, { stubs: { NestedChild } })
+
+ expect(wrapper.findComponent('.in-root').vm.$options.name).toEqual(
+ 'NestedChild'
+ )
+
+ // someone might expect DeepNestedChild here, but
+ // we always return TOP component matching DOM element
+ expect(wrapper.findComponent('.in-child').vm.$options.name).toEqual(
+ 'NestedChild'
+ )
})
it('allows using findComponent on functional component', () => {
diff --git a/test/specs/wrapper/findAll.spec.js b/test/specs/wrapper/findAll.spec.js
index b74c095d7..94aa783d7 100644
--- a/test/specs/wrapper/findAll.spec.js
+++ b/test/specs/wrapper/findAll.spec.js
@@ -149,20 +149,51 @@ describeWithShallowAndMount('findAll', mountingMethod => {
expect(componentArr.length).toEqual(1)
})
- it('throws an error if findAllComponents selector is a CSS selector', () => {
- const wrapper = mountingMethod(Component)
- const message =
- '[vue-test-utils]: findAllComponents requires a Vue constructor or valid find object. If you are searching for DOM nodes, use `find` instead'
- const fn = () => wrapper.findAllComponents('#foo')
- expect(fn).toThrow(message)
- })
+ it('findAllComponents ignores DOM nodes matching same CSS selector', () => {
+ const RootComponent = {
+ components: { Component },
+ template: ''
+ }
+ const wrapper = mountingMethod(RootComponent)
+ expect(wrapper.findAllComponents('.foo')).toHaveLength(1)
+ expect(
+ wrapper
+ .findAllComponents('.foo')
+ .at(0)
+ .is(Component)
+ ).toBe(true)
+ })
+
+ it('findAllComponents returns top-level components when components are nested', () => {
+ const DeepNestedChild = {
+ name: 'DeepNestedChild',
+ template: 'I am deeply nested
'
+ }
+ const NestedChild = {
+ name: 'NestedChild',
+ components: { DeepNestedChild },
+ template: ''
+ }
+ const RootComponent = {
+ name: 'RootComponent',
+ components: { NestedChild },
+ template: '
'
+ }
- it('throws an error if chaining findAllComponents off a DOM element', () => {
- const wrapper = mountingMethod(ComponentWithChild)
- const message =
- '[vue-test-utils]: You cannot chain findAllComponents off a DOM element. It can only be used on Vue Components.'
- const fn = () => wrapper.find('span').findAllComponents('#foo')
- expect(fn).toThrow(message)
+ const wrapper = mountingMethod(RootComponent, { stubs: { NestedChild } })
+
+ expect(wrapper.findAllComponents('.in-root')).toHaveLength(1)
+ expect(
+ wrapper.findAllComponents('.in-root').at(0).vm.$options.name
+ ).toEqual('NestedChild')
+
+ expect(wrapper.findAllComponents('.in-child')).toHaveLength(1)
+
+ // someone might expect DeepNestedChild here, but
+ // we always return TOP component matching DOM element
+ expect(
+ wrapper.findAllComponents('.in-child').at(0).vm.$options.name
+ ).toEqual('NestedChild')
})
it('returns correct number of Vue Wrapper when component has a v-for', () => {
diff --git a/test/specs/wrapper/setValue.spec.js b/test/specs/wrapper/setValue.spec.js
index dc5d9efdd..a7bb86c01 100644
--- a/test/specs/wrapper/setValue.spec.js
+++ b/test/specs/wrapper/setValue.spec.js
@@ -66,7 +66,7 @@ describeWithShallowAndMount('setValue', mountingMethod => {
})
if (process.env.TEST_ENV !== 'browser') {
- it.only('sets element of multiselect value', async () => {
+ it('sets element of multiselect value', async () => {
const wrapper = mountingMethod(ComponentWithInput)
const select = wrapper.find('select.multiselect')
await select.setValue(['selectA', 'selectC'])