Skip to content

Commit 5fecbd2

Browse files
38elementseddyerburgh
authored andcommitted
fix: improve slots option (#813)
1 parent 7b28af6 commit 5fecbd2

File tree

6 files changed

+176
-44
lines changed

6 files changed

+176
-44
lines changed

packages/create-instance/add-slots.js

-39
This file was deleted.

packages/create-instance/create-functional-component.js

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

33
import { throwError } from 'shared/util'
44
import { validateSlots } from './validate-slots'
5-
import { createSlotVNodes } from './add-slots'
5+
import { createSlotVNodes } from './create-slot-vnodes'
66

77
export default function createFunctionalComponent (
88
component: Component,
@@ -25,7 +25,7 @@ export default function createFunctionalComponent (
2525
mountingOptions.context.children.map(
2626
x => (typeof x === 'function' ? x(h) : x)
2727
)) ||
28-
createSlotVNodes(h, mountingOptions.slots || {})
28+
createSlotVNodes(this, mountingOptions.slots || {})
2929
)
3030
},
3131
name: component.name,

packages/create-instance/create-instance.js

+15-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// @flow
22

3-
import { createSlotVNodes } from './add-slots'
3+
import { createSlotVNodes } from './create-slot-vnodes'
44
import addMocks from './add-mocks'
55
import { addEventLogger } from './log-events'
66
import { createComponentStubs } from 'shared/stub-components'
@@ -12,6 +12,17 @@ import { componentNeedsCompiling } from 'shared/validators'
1212
import { validateSlots } from './validate-slots'
1313
import createScopedSlots from './create-scoped-slots'
1414

15+
function compileTemplateForSlots (slots: Object): void {
16+
Object.keys(slots).forEach(key => {
17+
const slot = Array.isArray(slots[key]) ? slots[key] : [slots[key]]
18+
slot.forEach(slotValue => {
19+
if (componentNeedsCompiling(slotValue)) {
20+
compileTemplate(slotValue)
21+
}
22+
})
23+
})
24+
}
25+
1526
export default function createInstance (
1627
component: Component,
1728
options: Options,
@@ -109,6 +120,8 @@ export default function createInstance (
109120
})
110121

111122
if (options.slots) {
123+
compileTemplateForSlots(options.slots)
124+
// $FlowIgnore
112125
validateSlots(options.slots)
113126
}
114127

@@ -129,7 +142,7 @@ export default function createInstance (
129142
provide: options.provide,
130143
render (h) {
131144
const slots = options.slots
132-
? createSlotVNodes(h, options.slots)
145+
? createSlotVNodes(this, options.slots)
133146
: undefined
134147
return h(
135148
Constructor,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// @flow
2+
3+
import { compileToFunctions } from 'vue-template-compiler'
4+
5+
function createVNodes (
6+
vm: Component,
7+
slotValue: string
8+
): Array<VNode> {
9+
const el = compileToFunctions(`<div>${slotValue}</div>`)
10+
const _staticRenderFns = vm._renderProxy.$options.staticRenderFns
11+
// version < 2.5
12+
if (!vm._renderProxy._staticTrees) {
13+
vm._renderProxy._staticTrees = []
14+
}
15+
vm._renderProxy.$options.staticRenderFns = el.staticRenderFns
16+
const vnode = el.render.call(vm._renderProxy, vm.$createElement)
17+
vm._renderProxy.$options.staticRenderFns = _staticRenderFns
18+
return vnode.children
19+
}
20+
21+
function createVNodesForSlot (
22+
vm: Component,
23+
slotValue: SlotValue,
24+
name: string,
25+
): VNode | string {
26+
let vnode
27+
if (typeof slotValue === 'string') {
28+
const vnodes = createVNodes(vm, slotValue)
29+
vnode = vnodes[0]
30+
} else {
31+
vnode = vm.$createElement(slotValue)
32+
}
33+
if (vnode.data) {
34+
vnode.data.slot = name
35+
} else {
36+
vnode.data = { slot: name }
37+
}
38+
return vnode
39+
}
40+
41+
export function createSlotVNodes (
42+
vm: Component,
43+
slots: SlotsObject
44+
): Array<VNode | string> {
45+
return Object.keys(slots).reduce((acc, key) => {
46+
const content = slots[key]
47+
if (Array.isArray(content)) {
48+
const nodes = content.map(
49+
slotDef => createVNodesForSlot(vm, slotDef, key)
50+
)
51+
return acc.concat(nodes)
52+
}
53+
54+
return acc.concat(createVNodesForSlot(vm, content, key))
55+
}, [])
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<template>
2+
<div><span baz="qux">{{ fromLocalVue }},{{ bar }}</span></div>
3+
</template>
4+
5+
<script>
6+
export default{
7+
name: 'component-with-parent-name',
8+
props: ['fromLocalVue'],
9+
data () {
10+
return {
11+
bar: 'quux'
12+
}
13+
},
14+
mounted () {
15+
this.$parent.childComponentName = this.$options.name
16+
}
17+
}
18+
</script>

test/specs/mounting-options/slots.spec.js

+85-1
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ import { compileToFunctions } from 'vue-template-compiler'
22
import Component from '~resources/components/component.vue'
33
import ComponentWithSlots from '~resources/components/component-with-slots.vue'
44
import ComponentAsAClass from '~resources/components/component-as-a-class.vue'
5+
import ComponentWithParentName from '~resources/components/component-with-parent-name.vue'
56
import { describeWithMountingMethods, vueVersion } from '~resources/utils'
67
import { itSkipIf, itDoNotRunIf } from 'conditional-specs'
8+
import { mount, createLocalVue } from '~vue/test-utils'
79

810
describeWithMountingMethods('options.slots', mountingMethod => {
911
it('mounts component with default slot if passed component in slot object', () => {
@@ -221,7 +223,7 @@ describeWithMountingMethods('options.slots', mountingMethod => {
221223
}
222224
})
223225

224-
it('mounts component with text slot', () => {
226+
it('mounts component with default and named text slot', () => {
225227
const wrapper = mountingMethod(ComponentWithSlots, {
226228
slots: {
227229
default: 'hello,',
@@ -235,6 +237,24 @@ describeWithMountingMethods('options.slots', mountingMethod => {
235237
}
236238
})
237239

240+
it('mounts functional component with only named text slot', () => {
241+
const TestComponent = {
242+
name: 'component-with-slots',
243+
functional: true,
244+
render: (h, ctx) => h('div', ctx.data, [ctx.slots().default, ctx.slots().footer])
245+
}
246+
const wrapper = mountingMethod(TestComponent, {
247+
slots: {
248+
footer: 'foo'
249+
}
250+
})
251+
if (mountingMethod.name === 'renderToString') {
252+
expect(wrapper).contains('foo')
253+
} else {
254+
expect(wrapper.text()).to.equal('foo')
255+
}
256+
})
257+
238258
it('mounts functional component with text slot', () => {
239259
const TestComponent = {
240260
name: 'component-with-slots',
@@ -568,4 +588,68 @@ describeWithMountingMethods('options.slots', mountingMethod => {
568588
expect(wrapper.contains(ComponentAsAClass)).to.equal(true)
569589
}
570590
})
591+
592+
itDoNotRunIf(
593+
mountingMethod.name === 'renderToString',
594+
'sets a component which can access the parent component and the child component',
595+
() => {
596+
const childComponentName = 'component-with-parent-name'
597+
const localVue = createLocalVue()
598+
localVue.prototype.bar = 'FOO'
599+
let ParentComponent = mount(
600+
{
601+
name: 'parentComponent',
602+
template: '<div><slot /></div>',
603+
data () {
604+
return {
605+
childComponentName: ''
606+
}
607+
}
608+
},
609+
{
610+
components: {
611+
ComponentWithParentName
612+
},
613+
slots: {
614+
default: [
615+
'<component-with-parent-name :fromLocalVue="bar" />',
616+
'<component-with-parent-name :fromLocalVue="bar" />'
617+
]
618+
},
619+
localVue
620+
}
621+
)
622+
expect(ParentComponent.vm.childComponentName).to.equal(childComponentName)
623+
expect(ParentComponent.vm.$children.length).to.equal(2)
624+
expect(ParentComponent.vm.$children.every(c => c.$options.name === childComponentName)).to.equal(true)
625+
expect(ParentComponent.html()).to.equal('<div><div><span baz="qux">FOO,quux</span></div><div><span baz="qux">FOO,quux</span></div></div>')
626+
627+
ParentComponent = mount(
628+
{
629+
name: 'parentComponent',
630+
template: '<div><slot /></div>',
631+
data () {
632+
return {
633+
childComponentName: ''
634+
}
635+
}
636+
},
637+
{
638+
slots: {
639+
default: {
640+
name: childComponentName,
641+
template: '<p>1234</p>',
642+
mounted () {
643+
this.$parent.childComponentName = this.$options.name
644+
}
645+
}
646+
}
647+
}
648+
)
649+
expect(ParentComponent.vm.childComponentName).to.equal(childComponentName)
650+
expect(ParentComponent.vm.$children.length).to.equal(1)
651+
expect(ParentComponent.vm.$children.every(c => c.$options.name === childComponentName)).to.equal(true)
652+
expect(ParentComponent.html()).to.equal('<div><p>1234</p></div>')
653+
}
654+
)
571655
})

0 commit comments

Comments
 (0)