Skip to content

Commit

Permalink
fix: improve slots option (#813)
Browse files Browse the repository at this point in the history
  • Loading branch information
38elements authored and eddyerburgh committed Jul 22, 2018
1 parent 7b28af6 commit 5fecbd2
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 44 deletions.
39 changes: 0 additions & 39 deletions packages/create-instance/add-slots.js

This file was deleted.

4 changes: 2 additions & 2 deletions packages/create-instance/create-functional-component.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { throwError } from 'shared/util'
import { validateSlots } from './validate-slots'
import { createSlotVNodes } from './add-slots'
import { createSlotVNodes } from './create-slot-vnodes'

export default function createFunctionalComponent (
component: Component,
Expand All @@ -25,7 +25,7 @@ export default function createFunctionalComponent (
mountingOptions.context.children.map(
x => (typeof x === 'function' ? x(h) : x)
)) ||
createSlotVNodes(h, mountingOptions.slots || {})
createSlotVNodes(this, mountingOptions.slots || {})
)
},
name: component.name,
Expand Down
17 changes: 15 additions & 2 deletions packages/create-instance/create-instance.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @flow

import { createSlotVNodes } from './add-slots'
import { createSlotVNodes } from './create-slot-vnodes'
import addMocks from './add-mocks'
import { addEventLogger } from './log-events'
import { createComponentStubs } from 'shared/stub-components'
Expand All @@ -12,6 +12,17 @@ import { componentNeedsCompiling } from 'shared/validators'
import { validateSlots } from './validate-slots'
import createScopedSlots from './create-scoped-slots'

function compileTemplateForSlots (slots: Object): void {
Object.keys(slots).forEach(key => {
const slot = Array.isArray(slots[key]) ? slots[key] : [slots[key]]
slot.forEach(slotValue => {
if (componentNeedsCompiling(slotValue)) {
compileTemplate(slotValue)
}
})
})
}

export default function createInstance (
component: Component,
options: Options,
Expand Down Expand Up @@ -109,6 +120,8 @@ export default function createInstance (
})

if (options.slots) {
compileTemplateForSlots(options.slots)
// $FlowIgnore
validateSlots(options.slots)
}

Expand All @@ -129,7 +142,7 @@ export default function createInstance (
provide: options.provide,
render (h) {
const slots = options.slots
? createSlotVNodes(h, options.slots)
? createSlotVNodes(this, options.slots)
: undefined
return h(
Constructor,
Expand Down
56 changes: 56 additions & 0 deletions packages/create-instance/create-slot-vnodes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// @flow

import { compileToFunctions } from 'vue-template-compiler'

function createVNodes (
vm: Component,
slotValue: string
): Array<VNode> {
const el = compileToFunctions(`<div>${slotValue}</div>`)
const _staticRenderFns = vm._renderProxy.$options.staticRenderFns
// version < 2.5
if (!vm._renderProxy._staticTrees) {
vm._renderProxy._staticTrees = []
}
vm._renderProxy.$options.staticRenderFns = el.staticRenderFns
const vnode = el.render.call(vm._renderProxy, vm.$createElement)
vm._renderProxy.$options.staticRenderFns = _staticRenderFns
return vnode.children
}

function createVNodesForSlot (
vm: Component,
slotValue: SlotValue,
name: string,
): VNode | string {
let vnode
if (typeof slotValue === 'string') {
const vnodes = createVNodes(vm, slotValue)
vnode = vnodes[0]
} else {
vnode = vm.$createElement(slotValue)
}
if (vnode.data) {
vnode.data.slot = name
} else {
vnode.data = { slot: name }
}
return vnode
}

export function createSlotVNodes (
vm: Component,
slots: SlotsObject
): Array<VNode | string> {
return Object.keys(slots).reduce((acc, key) => {
const content = slots[key]
if (Array.isArray(content)) {
const nodes = content.map(
slotDef => createVNodesForSlot(vm, slotDef, key)
)
return acc.concat(nodes)
}

return acc.concat(createVNodesForSlot(vm, content, key))
}, [])
}
18 changes: 18 additions & 0 deletions test/resources/components/component-with-parent-name.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<template>
<div><span baz="qux">{{ fromLocalVue }},{{ bar }}</span></div>
</template>

<script>
export default{
name: 'component-with-parent-name',
props: ['fromLocalVue'],
data () {
return {
bar: 'quux'
}
},
mounted () {
this.$parent.childComponentName = this.$options.name
}
}
</script>
86 changes: 85 additions & 1 deletion test/specs/mounting-options/slots.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import { compileToFunctions } from 'vue-template-compiler'
import Component from '~resources/components/component.vue'
import ComponentWithSlots from '~resources/components/component-with-slots.vue'
import ComponentAsAClass from '~resources/components/component-as-a-class.vue'
import ComponentWithParentName from '~resources/components/component-with-parent-name.vue'
import { describeWithMountingMethods, vueVersion } from '~resources/utils'
import { itSkipIf, itDoNotRunIf } from 'conditional-specs'
import { mount, createLocalVue } from '~vue/test-utils'

describeWithMountingMethods('options.slots', mountingMethod => {
it('mounts component with default slot if passed component in slot object', () => {
Expand Down Expand Up @@ -221,7 +223,7 @@ describeWithMountingMethods('options.slots', mountingMethod => {
}
})

it('mounts component with text slot', () => {
it('mounts component with default and named text slot', () => {
const wrapper = mountingMethod(ComponentWithSlots, {
slots: {
default: 'hello,',
Expand All @@ -235,6 +237,24 @@ describeWithMountingMethods('options.slots', mountingMethod => {
}
})

it('mounts functional component with only named text slot', () => {
const TestComponent = {
name: 'component-with-slots',
functional: true,
render: (h, ctx) => h('div', ctx.data, [ctx.slots().default, ctx.slots().footer])
}
const wrapper = mountingMethod(TestComponent, {
slots: {
footer: 'foo'
}
})
if (mountingMethod.name === 'renderToString') {
expect(wrapper).contains('foo')
} else {
expect(wrapper.text()).to.equal('foo')
}
})

it('mounts functional component with text slot', () => {
const TestComponent = {
name: 'component-with-slots',
Expand Down Expand Up @@ -568,4 +588,68 @@ describeWithMountingMethods('options.slots', mountingMethod => {
expect(wrapper.contains(ComponentAsAClass)).to.equal(true)
}
})

itDoNotRunIf(
mountingMethod.name === 'renderToString',
'sets a component which can access the parent component and the child component',
() => {
const childComponentName = 'component-with-parent-name'
const localVue = createLocalVue()
localVue.prototype.bar = 'FOO'
let ParentComponent = mount(
{
name: 'parentComponent',
template: '<div><slot /></div>',
data () {
return {
childComponentName: ''
}
}
},
{
components: {
ComponentWithParentName
},
slots: {
default: [
'<component-with-parent-name :fromLocalVue="bar" />',
'<component-with-parent-name :fromLocalVue="bar" />'
]
},
localVue
}
)
expect(ParentComponent.vm.childComponentName).to.equal(childComponentName)
expect(ParentComponent.vm.$children.length).to.equal(2)
expect(ParentComponent.vm.$children.every(c => c.$options.name === childComponentName)).to.equal(true)
expect(ParentComponent.html()).to.equal('<div><div><span baz="qux">FOO,quux</span></div><div><span baz="qux">FOO,quux</span></div></div>')

ParentComponent = mount(
{
name: 'parentComponent',
template: '<div><slot /></div>',
data () {
return {
childComponentName: ''
}
}
},
{
slots: {
default: {
name: childComponentName,
template: '<p>1234</p>',
mounted () {
this.$parent.childComponentName = this.$options.name
}
}
}
}
)
expect(ParentComponent.vm.childComponentName).to.equal(childComponentName)
expect(ParentComponent.vm.$children.length).to.equal(1)
expect(ParentComponent.vm.$children.every(c => c.$options.name === childComponentName)).to.equal(true)
expect(ParentComponent.html()).to.equal('<div><p>1234</p></div>')
}
)
})

0 comments on commit 5fecbd2

Please sign in to comment.