Skip to content

Commit eda39df

Browse files
committed
feat(context option): add context option to mount
Add option to pass context to functional components
1 parent e50e5e9 commit eda39df

File tree

5 files changed

+99
-17
lines changed

5 files changed

+99
-17
lines changed

docs/en/api/mount.md

+30-5
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,40 @@
11
# mount(component,{,options}])
22

3-
Create a fully rendered Vue component. Returns a Wrapper that includes methods to test the component renders and reacts as expected.
3+
- **Arguments:**
4+
5+
- `{Component} component`
6+
- `{Object} options`
7+
- `{boolean} attachToDocument`
8+
- `{Object} context`
9+
- `{Object} slots`
10+
- `{Array<Componet|Object>|Component|String} default`
11+
- `{Array<Componet|Object>|Component|String} named`
12+
- `{Object} globals`
13+
- `{Object} instance`
14+
- `{Object} stub`
15+
16+
- **Arguments:**
17+
18+
- **Returns:** `{Wrapper}`
19+
20+
21+
Returns [`Wrapper`](/api/wrapper/README.md) of first DOM node or Vue component matching selector.
422

5-
### Arguments
23+
Use any valid [selector](/api/selectors.md).
624

7-
`component` (`Component`): A vue component
825

9-
`options` (`Object`) [optional]: a Vue options object. Vue options are passed to the component when a new instance is created. , e.g. `store`, `propsData`. For full list, see the [Vue API](https://vuejs.org/v2/api/). Also takes vue-test-utils options:
26+
- **See also:** [Wrapper](/api/wrapper/README.md)
27+
28+
Create a fully rendered Vue component. Returns a Wrapper that includes methods to test the component renders and reacts as expected.
29+
30+
- **Options:**
31+
32+
`options` (`Object`): a Vue options object. Vue options are passed to the component when a new instance is created. , e.g. `store`, `propsData`. For full list, see the [Vue API](https://vuejs.org/v2/api/). Also takes vue-test-utils options:
1033

1134
`options.attachToDocument` (`Boolean`): Component will attach to DOM when rendered. This can be used with [`hasStyle`](/api/wrapper/hasStyle.md) to check multi element CSS selectors
1235

36+
`options.context` (`Object`): Passes context to functional component. Can only be used with functional components
37+
1338
`options.slots` (`Object`): Render component with slots.
1439

1540
`options.slots.default` (`Array[Component]|Component|String`): Default slot object to render, can be a Vue component or array of Vue components
@@ -22,7 +47,7 @@ Create a fully rendered Vue component. Returns a Wrapper that includes methods t
2247

2348
`options.stub` ('Object): Stubs components matchng the name passed with a string
2449

25-
### Examples
50+
- **Usage:**
2651

2752
#### Without options
2853

src/lib/find-vue-components.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ function findAllVueComponents (vm: Component, components: Array<Component> = [])
1010
return components
1111
}
1212

13-
function vmCtorMatchesName (vm: Component, name: string): boolean {
14-
return vm.$vnode.componentOptions.Ctor.options.name === name
13+
export function vmCtorMatchesName (vm: Component, name: string): boolean {
14+
return (vm.$vnode && vm.$vnode.componentOptions && vm.$vnode.componentOptions.Ctor.options.name === name) ||
15+
(vm._vnode && vm._vnode.functionalOptions && vm._vnode.functionalOptions.name === name)
1516
}
1617

1718
export default function findVueComponents (vm: Component, componentName: string): Array<Component> {

src/mount.js

+24-7
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import addSlots from './lib/add-slots'
66
import addGlobals from './lib/add-globals'
77
import addProvide from './lib/add-provide'
88
import { stubComponents } from './lib/stub-components'
9+
import { cloneDeep } from 'lodash'
910

1011
Vue.config.productionTip = false
1112

@@ -25,7 +26,8 @@ type MountOptions = {
2526
intercept?: Object,
2627
slots?: Object,
2728
instance?: Component,
28-
stub?: Object
29+
stub?: Object,
30+
context?: Object
2931
}
3032

3133
export default function mount (component: Component, options: MountOptions = {}): VueWrapper {
@@ -35,15 +37,27 @@ export default function mount (component: Component, options: MountOptions = {})
3537

3638
if (attachToDocument) {
3739
elem = createElem()
38-
delete options.attachToDocument // eslint-disable-line no-param-reassign
39-
}
40-
41-
if (options.stub) {
42-
stubComponents(component, options.stub)
40+
delete options.attachToDocument
4341
}
4442

4543
// Remove cached constructor
46-
delete component._Ctor // eslint-disable-line no-param-reassign
44+
delete component._Ctor
45+
46+
if (options.context) {
47+
if (!component.functional) {
48+
throw new Error('mount.context can only be used when mounting a functional component')
49+
}
50+
51+
if (typeof options.context !== 'object') {
52+
throw new Error('mount.context must be an object')
53+
}
54+
const clonedComponent = cloneDeep(component)
55+
component = {
56+
render (h) {
57+
return h(clonedComponent, options.context)
58+
}
59+
}
60+
}
4761

4862
if (options.provide) {
4963
addProvide(component, options)
@@ -54,6 +68,9 @@ export default function mount (component: Component, options: MountOptions = {})
5468
if (options.instance) {
5569
Constructor = options.instance.extend(component)
5670
} else {
71+
if (options.stub) {
72+
stubComponents(component, options.stub)
73+
}
5774
Constructor = Vue.extend(component)
5875
}
5976

src/wrappers/wrapper.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { matchesSelector } from 'sizzle'
44
import { isValidSelector } from '../lib/validators'
5-
import findVueComponents from '../lib/find-vue-components'
5+
import findVueComponents, { vmCtorMatchesName } from '../lib/find-vue-components'
66
import findMatchingVNodes from '../lib/find-matching-vnodes'
77
import VueWrapper from './vue-wrapper'
88
import WrapperArray from './wrapper-array'
@@ -199,8 +199,8 @@ export default class Wrapper implements BaseWrapper {
199199
if (!this.isVueComponent) {
200200
return false
201201
}
202-
// TODO: Throw error if component does not have name
203-
return !!this.vm && this.vm.$vnode.componentOptions.Ctor.options.name === selector.name
202+
// TODO: Throw error if component does not have name
203+
return vmCtorMatchesName(this.vm, selector.name)
204204
}
205205
return this.element.getAttribute && matchesSelector(this.element, selector)
206206
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import mount from '~src/mount'
2+
3+
describe('context', () => {
4+
it('mounts functional component when passed context object', () => {
5+
const Component = {
6+
functional: true,
7+
render (h, { props }) {
8+
return h('div')
9+
},
10+
name: 'common'
11+
}
12+
const context = {
13+
data: { hellpo: true },
14+
props: { show: true }
15+
}
16+
17+
const wrapper = mount(Component, { context })
18+
expect(wrapper.is(Component)).to.equal(true)
19+
})
20+
21+
it('throws error if non functional component is passed with context option', () => {
22+
const Component = {
23+
render: h => h('div')
24+
}
25+
const context = {}
26+
const message = 'mount.context can only be used when mounting a functional component'
27+
expect(() => mount(Component, { context })).to.throw(Error, message)
28+
})
29+
30+
it('throws error if context option is not an object', () => {
31+
const Component = {
32+
functional: true,
33+
render: h => h('div')
34+
}
35+
const context = 'string'
36+
const message = 'mount.context must be an object'
37+
expect(() => mount(Component, { context })).to.throw(Error, message)
38+
})
39+
})

0 commit comments

Comments
 (0)