Skip to content

Commit 27e9d79

Browse files
committed
feat(find): improve support for functional components
1 parent d6bf1b8 commit 27e9d79

File tree

11 files changed

+143
-10
lines changed

11 files changed

+143
-10
lines changed

.babelrc

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
22
"presets": ["env", "stage-2", "flow-vue"],
3+
"plugins": ["transform-decorators-legacy"],
34
"comments": false
45
}

package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
"setup": "node build/install-hooks.js",
2323
"test": "npm run lint && npm run lint:docs && npm run flow && npm run test:types && npm run test:unit && npm run test:unit:karma",
2424
"test:compatibility": "test/test.sh",
25-
"test:unit": "npm run build:test && mocha-webpack --webpack-config build/webpack.test.config.js test/unit/specs --recursive --require test/unit/setup/mocha.setup.js",
25+
"test:unit": "npm run build:test && cross-env BABEL_ENV=test && mocha-webpack --webpack-config build/webpack.test.config.js test/unit/specs --recursive --require test/unit/setup/mocha.setup.js",
2626
"test:unit:karma": "npm run build:test && cross-env BABEL_ENV=test TARGET=browser karma start test/unit/setup/karma.conf.js --single-run",
2727
"test:types": "tsc -p types",
2828
"release": "bash build/release.sh",
@@ -42,6 +42,7 @@
4242
"babel-core": "^6.26.0",
4343
"babel-eslint": "^7.2.3",
4444
"babel-loader": "^7.0.0",
45+
"babel-plugin-transform-decorators-legacy": "^1.3.4",
4546
"babel-polyfill": "^6.23.0",
4647
"babel-preset-env": "^1.6.0",
4748
"babel-preset-flow-vue": "^1.0.0",
@@ -86,6 +87,7 @@
8687
"sinon-chai": "^2.10.0",
8788
"typescript": "^2.4.1",
8889
"vue": "^2.4.2",
90+
"vue-class-component": "^6.1.2",
8991
"vue-loader": "^13.0.5",
9092
"vue-router": "^2.7.0",
9193
"vue-template-compiler": "^2.4.2",

src/lib/create-functional-component.js

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// @flow
22

3-
import cloneDeep from 'lodash/cloneDeep'
43
import { compileToFunctions } from 'vue-template-compiler'
54
import { throwError } from './util'
65

@@ -46,11 +45,10 @@ export default function createFunctionalComponent (component: Component, mountin
4645
throwError('mount.context must be an object')
4746
}
4847

49-
const clonedComponent = cloneDeep(component)
5048
return {
5149
render (h: Function) {
5250
return h(
53-
clonedComponent,
51+
component,
5452
mountingOptions.context || component.FunctionalRenderContext,
5553
(mountingOptions.context && mountingOptions.context.children && mountingOptions.context.children.map(x => typeof x === 'function' ? x(h) : x)) || createFunctionalSlots(mountingOptions.slots, h)
5654
)

src/lib/find-vue-components.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,16 @@ export function vmCtorMatchesName (vm: Component, name: string): boolean {
5454
vm.$options && vm.$options.name === name
5555
}
5656

57-
export function vmCtorMatchesSelector (component: Component, Ctor: Object) {
57+
export function vmCtorMatchesSelector (component: Component, selector: Object) {
58+
const Ctor = selector._Ctor || selector.options && selector.options._Ctor
5859
const Ctors = Object.keys(Ctor)
5960
return Ctors.some(c => Ctor[c] === component.__proto__.constructor)
6061
}
6162

6263
export function vmFunctionalCtorMatchesSelector (component: VNode, Ctor: Object) {
64+
if (!component.fnOptions) {
65+
return false
66+
}
6367
const Ctors = Object.keys(component.fnOptions._Ctor)
6468
return Ctors.some(c => Ctor[c] === component.fnOptions._Ctor[c])
6569
}
@@ -83,7 +87,7 @@ export default function findVueComponents (
8387
return false
8488
}
8589
return selectorType === COMPONENT_SELECTOR
86-
? vmCtorMatchesSelector(component, selector._Ctor)
90+
? vmCtorMatchesSelector(component, selector)
8791
: vmCtorMatchesName(component, selector.name)
8892
})
8993
}

src/lib/validators.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ export function isDomSelector (selector: any): boolean {
2323
}
2424

2525
export function isVueComponent (component: any): boolean {
26-
if (typeof component === 'function') {
27-
return false
26+
if (typeof component === 'function' && component.options) {
27+
return true
2828
}
2929

3030
if (component === null) {

src/wrappers/wrapper.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import {
99
} from '../lib/consts'
1010
import {
1111
vmCtorMatchesName,
12-
vmCtorMatchesSelector
12+
vmCtorMatchesSelector,
13+
vmFunctionalCtorMatchesSelector
1314
} from '../lib/find-vue-components'
1415
import VueWrapper from './vue-wrapper'
1516
import WrapperArray from './wrapper-array'
@@ -268,7 +269,10 @@ export default class Wrapper implements BaseWrapper {
268269
if (!this.vm) {
269270
return false
270271
}
271-
return vmCtorMatchesSelector(this.vm, selector._Ctor)
272+
if (selector.functional) {
273+
return vmFunctionalCtorMatchesSelector(this.vm._vnode, selector._Ctor)
274+
}
275+
return vmCtorMatchesSelector(this.vm, selector)
272276
}
273277

274278
if (selectorType === REF_SELECTOR) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<template>
2+
<div />
3+
</template>
4+
5+
<script>
6+
import Vue from 'vue'
7+
import Component from 'vue-class-component'
8+
9+
@Component({
10+
props: {
11+
propMessage: String
12+
}
13+
})
14+
export default class App extends Vue {
15+
msg = 123
16+
}
17+
</script>

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

+36
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import { compileToFunctions } from 'vue-template-compiler'
22
import { mount } from '~vue-test-utils'
33
import ComponentWithChild from '~resources/components/component-with-child.vue'
44
import Component from '~resources/components/component.vue'
5+
import FunctionalComponent from '~resources/components/functional-component.vue'
6+
import ComponentAsAClass from '~resources/components/component-as-a-class.vue'
7+
import { functionalSFCsSupported } from '~resources/test-utils'
58

69
describe('contains', () => {
710
it('returns true if wrapper contains element', () => {
@@ -15,6 +18,39 @@ describe('contains', () => {
1518
expect(wrapper.contains(Component)).to.equal(true)
1619
})
1720

21+
it('returns true if wrapper contains functional Vue component', () => {
22+
if (!functionalSFCsSupported()) {
23+
return false
24+
}
25+
const TestComponent = {
26+
template: `
27+
<div>
28+
<functional-component />
29+
</div>
30+
`,
31+
components: {
32+
FunctionalComponent
33+
}
34+
}
35+
const wrapper = mount(TestComponent)
36+
expect(wrapper.contains(FunctionalComponent)).to.equal(true)
37+
})
38+
39+
it('returns true if wrapper contains Vue class component', () => {
40+
const TestComponent = {
41+
template: `
42+
<div>
43+
<component-as-a-class />
44+
</div>
45+
`,
46+
components: {
47+
ComponentAsAClass
48+
}
49+
}
50+
const wrapper = mount(TestComponent)
51+
expect(wrapper.contains(ComponentAsAClass)).to.equal(true)
52+
})
53+
1854
it('returns true if wrapper contains element specified by ref selector', () => {
1955
const compiled = compileToFunctions('<div><input ref="foo" /></div>')
2056
const wrapper = mount(compiled)

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

+17
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import ComponentWithSlots from '~resources/components/component-with-slots.vue'
77
import ComponentWithVFor from '~resources/components/component-with-v-for.vue'
88
import Component from '~resources/components/component.vue'
99
import FunctionalComponent from '~resources/components/functional-component.vue'
10+
import ComponentAsAClass from '~resources/components/component-as-a-class.vue'
1011
import { functionalSFCsSupported } from '~resources/test-utils'
1112

1213
describe('find', () => {
@@ -104,6 +105,22 @@ describe('find', () => {
104105
expect(wrapper.find(Component).vnode).to.be.an('object')
105106
})
106107

108+
it('returns Wrapper of class component', () => {
109+
const TestComponent = {
110+
template: `
111+
<div>
112+
<component-as-a-class />
113+
</div>
114+
`,
115+
components: {
116+
ComponentAsAClass
117+
}
118+
}
119+
120+
const wrapper = mount(TestComponent)
121+
expect(wrapper.find(ComponentAsAClass).vnode).to.be.an('object')
122+
})
123+
107124
it('returns Wrapper of Vue Component matching functional component', () => {
108125
if (!functionalSFCsSupported()) {
109126
return

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

+38
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import ComponentWithoutName from '~resources/components/component-without-name.v
66
import ComponentWithSlots from '~resources/components/component-with-slots.vue'
77
import ComponentWithVFor from '~resources/components/component-with-v-for.vue'
88
import Component from '~resources/components/component.vue'
9+
import FunctionalComponent from '~resources/components/functional-component.vue'
10+
import ComponentAsAClass from '~resources/components/component-as-a-class.vue'
11+
import { functionalSFCsSupported } from '~resources/test-utils'
912

1013
describe('findAll', () => {
1114
it('returns an WrapperArray of elements matching tag selector passed', () => {
@@ -175,6 +178,41 @@ describe('findAll', () => {
175178
expect(wrapper.findAll(ComponentWithoutName).length).to.equal(3)
176179
})
177180

181+
it('returns Wrapper of class component', () => {
182+
const TestComponent = {
183+
template: `
184+
<div>
185+
<component-as-a-class />
186+
</div>
187+
`,
188+
components: {
189+
ComponentAsAClass
190+
}
191+
}
192+
193+
const wrapper = mount(TestComponent)
194+
expect(wrapper.findAll(ComponentAsAClass).length).to.equal(1)
195+
})
196+
197+
it('returns Wrapper of Vue Component matching functional component', () => {
198+
if (!functionalSFCsSupported()) {
199+
return
200+
}
201+
const TestComponent = {
202+
template: `
203+
<div>
204+
<functional-component />
205+
</div>
206+
`,
207+
components: {
208+
FunctionalComponent
209+
}
210+
}
211+
212+
const wrapper = mount(TestComponent)
213+
expect(wrapper.findAll(FunctionalComponent).length).to.equal(1)
214+
})
215+
178216
it('returns VueWrapper with length 0 if no nodes matching selector are found', () => {
179217
const wrapper = mount(Component)
180218
const preArray = wrapper.findAll('pre')

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

+16
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import { mount } from '~vue-test-utils'
33
import ComponentWithChild from '~resources/components/component-with-child.vue'
44
import Component from '~resources/components/component.vue'
55
import ComponentWithoutName from '~resources/components/component-without-name.vue'
6+
import FunctionalComponent from '~resources/components/functional-component.vue'
7+
import ComponentAsAClass from '~resources/components/component-as-a-class.vue'
8+
import { functionalSFCsSupported } from '~resources/test-utils'
69

710
describe('is', () => {
811
it('returns true if root node matches tag selector', () => {
@@ -45,6 +48,19 @@ describe('is', () => {
4548
expect(wrapper.is(ComponentWithoutName)).to.equal(true)
4649
})
4750

51+
it('returns true if root node matches functional Component', () => {
52+
if (!functionalSFCsSupported()) {
53+
return
54+
}
55+
const wrapper = mount(FunctionalComponent)
56+
expect(wrapper.is(FunctionalComponent)).to.equal(true)
57+
})
58+
59+
it('returns true if root node matches Component extending class component', () => {
60+
const wrapper = mount(ComponentAsAClass)
61+
expect(wrapper.is(ComponentAsAClass)).to.equal(true)
62+
})
63+
4864
it('returns false if root node is not a Vue Component', () => {
4965
const wrapper = mount(ComponentWithChild)
5066
const input = wrapper.findAll('span').at(0)

0 commit comments

Comments
 (0)