Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { GlobalMountOptions } from './types'

export const config: { global: GlobalMountOptions } = {
global: {}
type Config = { global: GlobalMountOptions; renderAllStubSlots: boolean }

export const config: Config = {
global: {},
renderAllStubSlots: false
}
18 changes: 16 additions & 2 deletions src/stubs.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { transformVNodeArgs, h } from 'vue'
import { hyphenate } from '@vue/shared'
import { config } from './index'
import { matchName } from './utils/matchName'

interface IStubOptions {
Expand All @@ -10,10 +11,23 @@ interface IStubOptions {
// TODO: figure out how to type this
type VNodeArgs = any[]

function getSlots(ctx) {
return config.renderAllStubSlots
? Object.entries(ctx.$slots)
.filter(([name]) => name !== '_')
// @ts-ignore
.map(([name, slot]) => slot.call(ctx, {}))
: ctx.$slots
}

export const createStub = ({ name, props }: IStubOptions) => {
const anonName = 'anonymous-stub'
const tag = name ? `${hyphenate(name)}-stub` : anonName
const render = () => h(tag)

const render = (ctx) => {
const slots = getSlots(ctx)
return h(tag, {}, slots)
}

return { name: name || anonName, render, props }
}
Expand Down Expand Up @@ -77,7 +91,7 @@ export function stubComponents(stubs: Record<any, any>) {
return [
createStub({ name, props: propsDeclaration }),
props,
{},
children,
patchFlag,
dynamicProps
]
Expand Down
21 changes: 21 additions & 0 deletions tests/components/ComponentWithSlots.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<template>
<div class="ComponentWithSlots">
<slot />
<slot name="named" />
<slot name="withDefault">With Default Content</slot>
<slot name="scoped" v-bind="{ boolean, string, object }" />
</div>
</template>

<script>
export default {
name: 'ComponentWithSlots',
data () {
return {
boolean: true,
string: 'string',
object: { foo: 'foo' }
}
}
}
</script>
46 changes: 45 additions & 1 deletion tests/mountingOptions/stubs.global.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { h, ComponentOptions } from 'vue'

import { mount } from '../../src'
import { config, mount } from '../../src'
import Hello from '../components/Hello.vue'
import ComponentWithoutName from '../components/ComponentWithoutName.vue'
import ComponentWithSlots from '../components/ComponentWithSlots.vue'

describe('mounting options: stubs', () => {
it('handles Array syntax', () => {
Expand Down Expand Up @@ -300,4 +301,47 @@ describe('mounting options: stubs', () => {

expect(wrapper.html()).toBe('<foo-bar-stub></foo-bar-stub>')
})

describe('stub slots', () => {
const Component = {
name: 'Parent',
template: `
<div>
<component-with-slots>
<template #default>Default</template>
<template #named>A named</template>
<template #noop>A not existing one</template>
<template #scoped="{ boolean, string }">{{ boolean }} {{ string }}</template>
</component-with-slots>
</div>`,
components: { ComponentWithSlots }
}

afterEach(() => {
config.renderAllStubSlots = false
})

it('renders only the default stub slot by default', () => {
const wrapper = mount(Component, {
global: {
stubs: ['ComponentWithSlots']
}
})
expect(wrapper.html()).toBe(
`<div><component-with-slots-stub>Default</component-with-slots-stub></div>`
)
})

it('renders all provided stub slots', () => {
config.renderAllStubSlots = true
const wrapper = mount(Component, {
global: {
stubs: ['ComponentWithSlots']
}
})
expect(wrapper.html()).toBe(
'<div><component-with-slots-stub>DefaultA namedA not existing one </component-with-slots-stub></div>'
)
})
})
})