diff --git a/src/config.ts b/src/config.ts index 54c0ab8f7..c06f6a4b1 100644 --- a/src/config.ts +++ b/src/config.ts @@ -6,6 +6,7 @@ interface GlobalConfigOptions { VueWrapper: Pluggable DOMWrapper: Pluggable } + renderStubDefaultSlot: boolean } class Pluggable { @@ -47,5 +48,6 @@ export const config: GlobalConfigOptions = { plugins: { VueWrapper: new Pluggable(), DOMWrapper: new Pluggable() - } + }, + renderStubDefaultSlot: false } diff --git a/src/stubs.ts b/src/stubs.ts index 02ba87352..05e75b370 100644 --- a/src/stubs.ts +++ b/src/stubs.ts @@ -1,6 +1,7 @@ import { transformVNodeArgs, h, createVNode } from 'vue' import { hyphenate } from '@vue/shared' import { MOUNT_COMPONENT_REF, MOUNT_PARENT_NAME } from './constants' +import { config } from './index' import { matchName } from './utils/matchName' interface IStubOptions { @@ -10,10 +11,17 @@ interface IStubOptions { type VNodeArgs = Parameters +function getSlots(ctx) { + return !config.renderStubDefaultSlot ? undefined : ctx.$slots +} + const createStub = ({ name, props }: IStubOptions) => { const anonName = 'anonymous-stub' const tag = name ? `${hyphenate(name)}-stub` : anonName - const render = () => h(tag) + + const render = (ctx) => { + return h(tag, {}, getSlots(ctx)) + } return { name: name || anonName, render, props } } @@ -94,7 +102,7 @@ export function stubComponents( return [ createStub({ name, props: propsDeclaration }), props, - {}, + children, patchFlag, dynamicProps ] diff --git a/tests/components/ComponentWithSlots.vue b/tests/components/ComponentWithSlots.vue new file mode 100644 index 000000000..d2c01a434 --- /dev/null +++ b/tests/components/ComponentWithSlots.vue @@ -0,0 +1,21 @@ + + + diff --git a/tests/mountingOptions/stubs.global.spec.ts b/tests/mountingOptions/stubs.global.spec.ts index e8720c95f..fa9c420b1 100644 --- a/tests/mountingOptions/stubs.global.spec.ts +++ b/tests/mountingOptions/stubs.global.spec.ts @@ -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', () => { @@ -298,4 +299,86 @@ describe('mounting options: stubs', () => { expect(wrapper.html()).toBe('') }) + + describe('stub slots', () => { + const Component = { + name: 'Parent', + template: ` +
+ + + + + + +
`, + components: { ComponentWithSlots } + } + + afterEach(() => { + config.renderStubDefaultSlot = false + }) + + it('renders only the default stub slot only behind flag', () => { + config.renderStubDefaultSlot = true + + const wrapper = mount(Component, { + global: { + stubs: ['ComponentWithSlots'] + } + }) + expect(wrapper.html()).toBe( + `
Default
` + ) + }) + + it('renders none of the slots on a stub', () => { + config.renderStubDefaultSlot = false + const wrapper = mount(Component, { + global: { + stubs: ['ComponentWithSlots'] + } + }) + expect(wrapper.html()).toBe( + '
' + ) + }) + + it('renders the default slot of deeply nested stubs when renderStubDefaultSlot=true', () => { + config.renderStubDefaultSlot = true + + const SimpleSlot = { + name: 'SimpleSlot', + template: '
' + } + const WrappingComponent = { + template: ` + + + + + + + + `, + components: { + ComponentWithSlots, + SimpleSlot + } + } + const wrapper = mount(WrappingComponent, { + global: { + stubs: ['component-with-slots', 'simple-slot'] + } + }) + + expect(wrapper.html()).toEqual( + '' + + '' + + 'nested content' + + '' + + '' + ) + }) + }) })