diff --git a/src/index.ts b/src/index.ts index 9d4e9b950..85b75d0f1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ -import { mount } from './mount' +import { mount, shallowMount } from './mount' import { RouterLinkStub } from './components/RouterLinkStub' import { VueWrapper } from './vue-wrapper' import { DOMWrapper } from './dom-wrapper' import { config } from './config' -export { mount, RouterLinkStub, VueWrapper, DOMWrapper, config } +export { mount, shallowMount, RouterLinkStub, VueWrapper, DOMWrapper, config } diff --git a/src/mount.ts b/src/mount.ts index c521874c0..ba0467f41 100644 --- a/src/mount.ts +++ b/src/mount.ts @@ -39,6 +39,7 @@ interface MountingOptions { } global?: GlobalMountOptions attachTo?: HTMLElement | string + shallow?: boolean } // TODO improve the typings of the overloads @@ -189,8 +190,8 @@ export function mount( app.mixin(attachEmitListener()) // stubs - if (options?.global?.stubs) { - stubComponents(options.global.stubs) + if (options?.global?.stubs || options?.shallow) { + stubComponents(options?.global?.stubs, options?.shallow) } else { transformVNodeArgs() } @@ -201,3 +202,10 @@ export function mount( const App = vm.$refs[MOUNT_COMPONENT_REF] as ComponentPublicInstance return createWrapper(app, App, setProps) } + +export function shallowMount( + originalComponent, + options?: MountingOptions +) { + return mount(originalComponent, { ...options, shallow: true }) +} diff --git a/src/stubs.ts b/src/stubs.ts index c392138a9..02ba87352 100644 --- a/src/stubs.ts +++ b/src/stubs.ts @@ -1,5 +1,6 @@ import { transformVNodeArgs, h, createVNode } from 'vue' import { hyphenate } from '@vue/shared' +import { MOUNT_COMPONENT_REF, MOUNT_PARENT_NAME } from './constants' import { matchName } from './utils/matchName' interface IStubOptions { @@ -41,36 +42,53 @@ const isHTMLElement = (args: VNodeArgs) => typeof args[0] === 'string' const isCommentOrFragment = (args: VNodeArgs) => typeof args[0] === 'symbol' const isParent = (args: VNodeArgs) => - isComponent(args) && args[0]['name'] === 'VTU_COMPONENT' + isComponent(args) && args[0]['name'] === MOUNT_PARENT_NAME + +const isMountedComponent = (args: VNodeArgs) => + isComponent(args) && args[1] && args[1]['ref'] === MOUNT_COMPONENT_REF const isComponent = (args: VNodeArgs) => typeof args[0] === 'object' const isFunctionalComponent = ([type]: VNodeArgs) => typeof type === 'function' && ('name' in type || 'displayName' in type) -export function stubComponents(stubs: Record) { +export function stubComponents( + stubs: Record = {}, + shallow: boolean = false +) { transformVNodeArgs((args) => { // args[0] can either be: // 1. a HTML tag (div, span...) // 2. An object of component options, such as { name: 'foo', render: [Function], props: {...} } // Depending what it is, we do different things. - if (isHTMLElement(args) || isCommentOrFragment(args) || isParent(args)) { + if ( + isHTMLElement(args) || + isCommentOrFragment(args) || + isParent(args) || + isMountedComponent(args) + ) { return args } if (isComponent(args) || isFunctionalComponent(args)) { const [type, props, children, patchFlag, dynamicProps] = args const name = type['name'] || type['displayName'] - if (!name) { + if (!name && !shallow) { return args } const stub = resolveComponentStubByName(name, stubs) + // case 2: custom implementation + if (typeof stub === 'object') { + // pass the props and children, for advanced stubbing + return [stubs[name], props, children, patchFlag, dynamicProps] + } + // we return a stub by matching Vue's `h` function // where the signature is h(Component, props, slots) // case 1: default stub - if (stub === true) { + if (stub === true || shallow) { // @ts-ignore const propsDeclaration = type?.props || {} return [ @@ -81,12 +99,6 @@ export function stubComponents(stubs: Record) { dynamicProps ] } - - // case 2: custom implementation - if (typeof stub === 'object') { - // pass the props and children, for advanced stubbing - return [stubs[name], props, children, patchFlag, dynamicProps] - } } return args diff --git a/tests/components/ComponentWithChildren.vue b/tests/components/ComponentWithChildren.vue new file mode 100644 index 000000000..b245995b4 --- /dev/null +++ b/tests/components/ComponentWithChildren.vue @@ -0,0 +1,28 @@ + + + diff --git a/tests/components/WithProps.vue b/tests/components/WithProps.vue index 719a80d2e..d0763f957 100644 --- a/tests/components/WithProps.vue +++ b/tests/components/WithProps.vue @@ -8,7 +8,7 @@ import { defineComponent } from 'vue' export default defineComponent({ - name: 'Hello', + name: 'WithProps', props: { msg: { @@ -17,4 +17,4 @@ export default defineComponent({ } } }) - \ No newline at end of file + diff --git a/tests/shallowMount.spec.ts b/tests/shallowMount.spec.ts new file mode 100644 index 000000000..6fd3f1896 --- /dev/null +++ b/tests/shallowMount.spec.ts @@ -0,0 +1,47 @@ +import { mount, shallowMount } from '../src' +import ComponentWithChildren from './components/ComponentWithChildren.vue' + +describe('shallowMount', () => { + it('stubs all components automatically using { shallow: true }', () => { + const wrapper = mount(ComponentWithChildren, { shallow: true }) + expect(wrapper.html()).toEqual( + '
' + + '' + + '' + + '' + + '' + + '
' + ) + }) + + it('stubs all components automatically using shallowMount', () => { + const wrapper = shallowMount(ComponentWithChildren) + expect(wrapper.html()).toEqual( + '
' + + '' + + '' + + '' + + '' + + '
' + ) + }) + + it('stubs all components, but allows providing custom stub', () => { + const wrapper = mount(ComponentWithChildren, { + shallow: true, + global: { + stubs: { + Hello: { template: '
Override
' } + } + } + }) + expect(wrapper.html()).toEqual( + '
' + + '
Override
' + + '' + + '' + + '' + + '
' + ) + }) +})