diff --git a/src/components/RouterLinkStub.ts b/src/components/RouterLinkStub.ts index 255253a91..bf7900f89 100644 --- a/src/components/RouterLinkStub.ts +++ b/src/components/RouterLinkStub.ts @@ -12,6 +12,6 @@ export const RouterLinkStub = defineComponent({ }, render() { - return h('a', undefined, this.$slots.default()) + return h('a', undefined, this.$slots?.default?.()) } }) diff --git a/src/mount.ts b/src/mount.ts index 6ce0d2618..3f9dd5bf6 100644 --- a/src/mount.ts +++ b/src/mount.ts @@ -34,7 +34,8 @@ import { createWrapper, VueWrapper } from './vueWrapper' import { attachEmitListener } from './emitMixin' import { createDataMixin } from './dataMixin' import { MOUNT_COMPONENT_REF, MOUNT_PARENT_NAME } from './constants' -import { stubComponents } from './stubs' +import { createStub, stubComponents } from './stubs' +import { hyphenate } from './utils/vueShared' // NOTE this should come from `vue` type PublicProps = VNodeProps & AllowedComponentProps & ComponentCustomProps @@ -405,6 +406,23 @@ export function mount( // stub out Transition and Transition Group by default. stubComponents(global.stubs, options?.shallow) + // users expect stubs to work with globally registered + // compnents, too, such as and + // so we register those globally. + // https://github.com/vuejs/vue-test-utils-next/issues/249 + if (global?.stubs) { + for (const [name, stub] of Object.entries(global.stubs)) { + const tag = hyphenate(name) + if (stub === true) { + // default stub. + app.component(tag, createStub({ name, props: {} })) + } else { + // user has provided a custom implementation. + app.component(tag, stub) + } + } + } + // mount the app! const vm = app.mount(el) diff --git a/src/stubs.ts b/src/stubs.ts index 42ad49217..ed562c168 100644 --- a/src/stubs.ts +++ b/src/stubs.ts @@ -25,7 +25,7 @@ function getSlots(ctx: ComponentPublicInstance): Slots | undefined { return !config.renderStubDefaultSlot ? undefined : ctx.$slots } -const createStub = ({ name, props }: StubOptions): ComponentOptions => { +export const createStub = ({ name, props }: StubOptions): ComponentOptions => { const anonName = 'anonymous-stub' const tag = name ? `${hyphenate(name)}-stub` : anonName diff --git a/tests/mountingOptions/stubs.global.spec.ts b/tests/mountingOptions/stubs.global.spec.ts index 1fe7280fb..2ee6d86e1 100644 --- a/tests/mountingOptions/stubs.global.spec.ts +++ b/tests/mountingOptions/stubs.global.spec.ts @@ -1,6 +1,6 @@ -import { h, ComponentOptions } from 'vue' +import { h, ComponentOptions, defineComponent } from 'vue' -import { config, mount } from '../../src' +import { config, mount, RouterLinkStub } from '../../src' import Hello from '../components/Hello.vue' import ComponentWithoutName from '../components/ComponentWithoutName.vue' import ComponentWithSlots from '../components/ComponentWithSlots.vue' @@ -37,6 +37,31 @@ describe('mounting options: stubs', () => { expect(wrapper.html()).toBe('
') }) + // https://github.com/vuejs/vue-test-utils-next/issues/249 + it('applies stubs globally', () => { + const Comp = defineComponent({ + template: '
' + }) + const wrapper = mount(Comp, { + global: { + stubs: { + Foo: true, + RouterLink: RouterLinkStub, + RouterView: defineComponent({ + render() { + return h('span') + } + }) + } + } + }) + + expect(wrapper.html()).toBe( + '
' + ) + expect(wrapper.getComponent(RouterLinkStub).vm.to).toBe('/foo') + }) + it('stubs a functional component by its variable declaration name', () => { const FunctionalFoo = (props) => h('p', props, 'Foo Text')