Skip to content

Commit 31a3d0b

Browse files
eddyerburghTheAlexLichter
authored andcommitted
fix: stub child components (#723)
1 parent 7302e05 commit 31a3d0b

File tree

11 files changed

+118
-71
lines changed

11 files changed

+118
-71
lines changed

Diff for: packages/create-instance/add-slots.js

+8-6
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ function startsWithTag (str) {
66
return str && str.trim()[0] === '<'
77
}
88

9-
function createVNodesForSlot (
9+
export function createVNodesForSlot (
1010
h: Function,
1111
slotValue: SlotValue,
1212
name: string
@@ -30,12 +30,14 @@ export function createSlotVNodes (
3030
return Object.keys(slots).reduce((acc, key) => {
3131
const content = slots[key]
3232
if (Array.isArray(content)) {
33-
const nodes = content.reduce((accInner, slotDef) => {
34-
return accInner.concat(createVNodesForSlot(h, slotDef, key))
35-
}, [])
33+
const nodes = content.map(slotDef => createVNodesForSlot(h, slotDef, key))
3634
return acc.concat(nodes)
37-
} else {
38-
return acc.concat(createVNodesForSlot(h, content, key))
3935
}
36+
37+
return acc.concat(createVNodesForSlot(h, content, key))
4038
}, [])
4139
}
40+
41+
export function createFunctionalSlotVNodes (h, slots = {}) {
42+
return createSlotVNodes(h, slots)
43+
}

Diff for: packages/create-instance/create-functional-component.js

+2-33
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,8 @@
11
// @flow
22

3-
import { compileToFunctions } from 'vue-template-compiler'
43
import { throwError } from 'shared/util'
54
import { validateSlots } from './validate-slots'
6-
7-
function createFunctionalSlots (slots = {}, h) {
8-
if (Array.isArray(slots.default)) {
9-
return slots.default.map(h)
10-
}
11-
12-
if (typeof slots.default === 'string') {
13-
return [h(compileToFunctions(slots.default))]
14-
}
15-
const children = []
16-
Object.keys(slots).forEach(slotType => {
17-
if (Array.isArray(slots[slotType])) {
18-
slots[slotType].forEach(slot => {
19-
const component =
20-
typeof slot === 'string' ? compileToFunctions(slot) : slot
21-
const newSlot = h(component)
22-
newSlot.data.slot = slotType
23-
children.push(newSlot)
24-
})
25-
} else {
26-
const component =
27-
typeof slots[slotType] === 'string'
28-
? compileToFunctions(slots[slotType])
29-
: slots[slotType]
30-
const slot = h(component)
31-
slot.data.slot = slotType
32-
children.push(slot)
33-
}
34-
})
35-
return children
36-
}
5+
import { createFunctionalSlotVNodes } from './add-slots'
376

387
export default function createFunctionalComponent (
398
component: Component,
@@ -56,7 +25,7 @@ export default function createFunctionalComponent (
5625
mountingOptions.context.children.map(
5726
x => (typeof x === 'function' ? x(h) : x)
5827
)) ||
59-
createFunctionalSlots(mountingOptions.slots, h)
28+
createFunctionalSlotVNodes(h, mountingOptions.slots)
6029
)
6130
},
6231
name: component.name,

Diff for: packages/create-instance/create-instance.js

+14-8
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,14 @@ export default function createInstance (
5959
...stubComponents
6060
}
6161
}
62-
62+
_Vue.mixin({
63+
created () {
64+
Object.assign(
65+
this.$options.components,
66+
stubComponents
67+
)
68+
}
69+
})
6370
Object.keys(component.components || {}).forEach(c => {
6471
if (
6572
component.components[c].extendOptions &&
@@ -79,14 +86,13 @@ export default function createInstance (
7986
}
8087
})
8188

82-
Object.keys(stubComponents).forEach(c => {
83-
_Vue.component(c, stubComponents[c])
84-
})
89+
if (component.options) {
90+
component.options._base = _Vue
91+
}
8592

86-
const Constructor =
87-
vueVersion < 2.3 && typeof component === 'function'
88-
? component.extend(instanceOptions)
89-
: _Vue.extend(component).extend(instanceOptions)
93+
const Constructor = vueVersion < 2.3 && typeof component === 'function'
94+
? component.extend(instanceOptions)
95+
: _Vue.extend(component).extend(instanceOptions)
9096

9197
Object.keys(instanceOptions.components || {}).forEach(key => {
9298
Constructor.component(key, instanceOptions.components[key])

Diff for: packages/server-test-utils/src/renderToString.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,12 @@ export default function renderToString (
2828
if (options.attachToDocument) {
2929
throwError(`you cannot use attachToDocument with ` + `renderToString`)
3030
}
31-
const vueClass = options.localVue || testUtils.createLocalVue()
32-
const vm = createInstance(component, mergeOptions(options, config), vueClass)
31+
const vueConstructor = testUtils.createLocalVue(options.localVue)
32+
const vm = createInstance(
33+
component,
34+
mergeOptions(options, config),
35+
vueConstructor
36+
)
3337
let renderedString = ''
3438

3539
// $FlowIgnore

Diff for: packages/test-utils/src/create-local-vue.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ import Vue from 'vue'
44
import cloneDeep from 'lodash/cloneDeep'
55
import errorHandler from './error-handler'
66

7-
function createLocalVue (): Component {
8-
const instance = Vue.extend()
7+
function createLocalVue (_Vue: Component = Vue): Component {
8+
const instance = _Vue.extend()
99

1010
// clone global APIs
11-
Object.keys(Vue).forEach(key => {
11+
Object.keys(_Vue).forEach(key => {
1212
if (!instance.hasOwnProperty(key)) {
13-
const original = Vue[key]
13+
const original = _Vue[key]
1414
instance[key] =
1515
typeof original === 'object' ? cloneDeep(original) : original
1616
}

Diff for: packages/test-utils/src/mount.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ export default function mount (
2828

2929
// Remove cached constructor
3030
delete component._Ctor
31-
32-
const vueConstructor = options.localVue || createLocalVue()
31+
const vueConstructor = createLocalVue(options.localVue)
3332

3433
const elm = options.attachToDocument ? createElement() : undefined
3534

Diff for: test/specs/mount.spec.js

+3-8
Original file line numberDiff line numberDiff line change
@@ -58,20 +58,15 @@ describeRunIf(process.env.TEST_ENV !== 'node', 'mount', () => {
5858
}
5959
})
6060

61-
it('returns new VueWrapper with mounted Vue instance initialized with Vue.extend with props, if passed as propsData', () => {
62-
const prop1 = { test: 'TEST' }
61+
it('handles propsData for extended components', () => {
62+
const prop1 = 'test'
6363
const TestComponent = Vue.extend(ComponentWithProps)
6464
const wrapper = mount(TestComponent, {
6565
propsData: {
6666
prop1
6767
}
6868
})
69-
expect(wrapper.vm).to.be.an('object')
70-
if (wrapper.vm.$props) {
71-
expect(wrapper.vm.$props.prop1).to.equal(prop1)
72-
} else {
73-
expect(wrapper.vm.$options.propsData.prop1).to.equal(prop1)
74-
}
69+
expect(wrapper.text()).to.contain(prop1)
7570
})
7671

7772
it('handles uncompiled extended Vue component', () => {

Diff for: test/specs/mounting-options/localVue.spec.js

+8
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,12 @@ describeWithMountingMethods('options.localVue', mountingMethod => {
6969
expect(HTML).to.contain('2')
7070
}
7171
})
72+
73+
it('does not add created mixin to localVue', () => {
74+
const localVue = createLocalVue()
75+
mountingMethod({ render: () => {} }, {
76+
localVue
77+
})
78+
expect(localVue.options.created).to.equal(undefined)
79+
})
7280
})

Diff for: test/specs/mounting-options/slots.spec.js

+19
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,25 @@ describeWithMountingMethods('options.slots', mountingMethod => {
235235
}
236236
})
237237

238+
it('mounts functional component with text slot', () => {
239+
const TestComponent = {
240+
name: 'component-with-slots',
241+
functional: true,
242+
render: (h, ctx) => h('div', ctx.data, [ctx.slots().default, ctx.slots().header])
243+
}
244+
const wrapper = mountingMethod(TestComponent, {
245+
slots: {
246+
default: 'hello,',
247+
header: 'world'
248+
}
249+
})
250+
if (mountingMethod.name === 'renderToString') {
251+
expect(wrapper).contains('hello,world')
252+
} else {
253+
expect(wrapper.text()).to.contain('hello,world')
254+
}
255+
})
256+
238257
it('mounts component with named slot if passed component in slot object', () => {
239258
const wrapper = mountingMethod(ComponentWithSlots, {
240259
slots: {

Diff for: test/specs/mounting-options/stubs.spec.js

+46
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,52 @@ describeWithMountingMethods('options.stub', mountingMethod => {
128128
expect(HTML).to.contain('<registered-component-stub>')
129129
})
130130

131+
itDoNotRunIf(
132+
mountingMethod.name === 'shallowMount',
133+
'stubs nested components', () => {
134+
const GrandchildComponent = {
135+
template: '<span />'
136+
}
137+
const ChildComponent = {
138+
template: '<grandchild-component />',
139+
components: { GrandchildComponent }
140+
}
141+
const TestComponent = {
142+
template: '<child-component />',
143+
components: { ChildComponent }
144+
}
145+
const wrapper = mountingMethod(TestComponent, {
146+
stubs: ['grandchild-component']
147+
})
148+
const HTML = mountingMethod.name === 'renderToString'
149+
? wrapper
150+
: wrapper.html()
151+
expect(HTML).not.to.contain('<span>')
152+
})
153+
154+
itDoNotRunIf(
155+
mountingMethod.name === 'shallowMount',
156+
'stubs nested components on extended components', () => {
157+
const GrandchildComponent = {
158+
template: '<span />'
159+
}
160+
const ChildComponent = {
161+
template: '<grandchild-component />',
162+
components: { GrandchildComponent }
163+
}
164+
const TestComponent = {
165+
template: '<div><child-component /></div>',
166+
components: { ChildComponent }
167+
}
168+
const wrapper = mountingMethod(Vue.extend(TestComponent), {
169+
stubs: ['grandchild-component']
170+
})
171+
const HTML = mountingMethod.name === 'renderToString'
172+
? wrapper
173+
: wrapper.html()
174+
expect(HTML).not.to.contain('<span>')
175+
})
176+
131177
it('stubs components with dummy when passed a boolean', () => {
132178
const ComponentWithGlobalComponent = {
133179
render: h => h('div', [h('registered-component')])

Diff for: test/specs/shallow-mount.spec.js

+7-8
Original file line numberDiff line numberDiff line change
@@ -171,14 +171,13 @@ describeRunIf(process.env.TEST_ENV !== 'node', 'shallowMount', () => {
171171
expect(wrapper.find('p').exists()).to.equal(false)
172172
})
173173

174-
it('stubs Vue class component children', () => {
175-
if (vueVersion < 2.3) {
176-
return
177-
}
178-
const wrapper = shallowMount(ComponentAsAClassWithChild)
179-
expect(wrapper.find(Component).exists()).to.equal(true)
180-
expect(wrapper.findAll('div').length).to.equal(1)
181-
})
174+
itDoNotRunIf(
175+
vueVersion < 2.3,
176+
'stubs Vue class component children', () => {
177+
const wrapper = shallowMount(ComponentAsAClassWithChild)
178+
expect(wrapper.find(Component).exists()).to.equal(true)
179+
expect(wrapper.findAll('div').length).to.equal(1)
180+
})
182181

183182
it('works correctly with find, contains, findAll, and is on unnamed components', () => {
184183
const TestComponent = {

0 commit comments

Comments
 (0)