diff --git a/docs/guide/extending-vtu/plugins.md b/docs/guide/extending-vtu/plugins.md index a1ae01f77..1d32a97d9 100644 --- a/docs/guide/extending-vtu/plugins.md +++ b/docs/guide/extending-vtu/plugins.md @@ -10,7 +10,9 @@ Some use cases for plugins: 1. Attaching matchers to the Wrapper instance 1. Attaching functionality to the Wrapper -## Using a Plugin +## Wrapper Plugin + +### Using a Plugin Install plugins by calling the `config.plugins.VueWrapper.install()` method . This has to be done before you call `mount`. @@ -43,12 +45,12 @@ once. Follow the instructions of the plugin you're installing. Check out the [Vue Community Guide](https://vue-community.org/v2/guide/ecosystem/testing.html) or [awesome-vue](https://github.com/vuejs/awesome-vue#test) for a collection of community-contributed plugins and libraries. -## Writing a Plugin +### Writing a Plugin A Vue Test Utils plugin is simply a function that receives the mounted `VueWrapper` or `DOMWrapper` instance and can modify it. -### Basic Plugin +#### Basic Plugin Below is a simple plugin to add a convenient alias to map `wrapper.element` to `wrapper.$el` @@ -75,7 +77,7 @@ const wrapper = mount({ template: `

🔌 Plugin

` }) console.log(wrapper.$el.innerHTML) // 🔌 Plugin ``` -### Data Test ID Plugin +#### Data Test ID Plugin The below plugin adds a method `findByTestId` to the `VueWrapper` instance. This encourages using a selector strategy relying on test-only attributes on your Vue Components. @@ -122,6 +124,50 @@ const DataTestIdPlugin = (wrapper) => { config.plugins.VueWrapper.install(DataTestIdPlugin) ``` +## Stubs Plugin + +The `config.plugins.createStubs` allows to overwrite the default stub creation provided by VTU. + +Some use cases are: +* You want to add more logic into the stubs (for example named slots) +* You want to use different stubs for multiple components (for example stub components from a library) + +### Usage + +```typescript +config.plugins.createStubs = ({ name, component }) => { + return defineComponent({ + render: () => h(`custom-${name}-stub`) + }) +} +``` + +This function will be called everytime VTU generates a stub either from +```typescript +const wrapper = mount(Component, { + global: { + stubs: { + ChildComponent: true + } + } +}) +``` +or +```typescript +const wrapper = shallowMount(Component) +``` + +But will not be called, when you explicit set a stub +```typescript +const wrapper = mount(Component, { + global: { + stubs: { + ChildComponent: { template: '' } + } + } +}) +``` + ## Featuring Your Plugin If you're missing functionality, consider writing a plugin to extend Vue Test diff --git a/src/config.ts b/src/config.ts index b921e1717..2771ba84b 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,12 +1,14 @@ import { GlobalMountOptions } from './types' import { VueWrapper } from './vueWrapper' import { DOMWrapper } from './domWrapper' +import { CustomCreateStub } from './stubs' export interface GlobalConfigOptions { global: Required plugins: { VueWrapper: Pluggable DOMWrapper: Pluggable> + createStubs?: CustomCreateStub } renderStubDefaultSlot: boolean } diff --git a/src/stubs.ts b/src/stubs.ts index 6da5757d1..75a36b98f 100644 --- a/src/stubs.ts +++ b/src/stubs.ts @@ -20,6 +20,12 @@ import { getComponentName, getComponentRegisteredName } from './utils/componentName' +import { config } from './config' + +export type CustomCreateStub = (params: { + name: string + component: ConcreteComponent +}) => ConcreteComponent interface StubOptions { name: string @@ -259,11 +265,16 @@ export function stubComponents( } const newStub = createStubOnce(type, () => - createStub({ - name: stubName, - type, - renderStubDefaultSlot - }) + config.plugins.createStubs + ? config.plugins.createStubs({ + name: stubName, + component: type + }) + : createStub({ + name: stubName, + type, + renderStubDefaultSlot + }) ) registerStub({ source: type, stub: newStub }) return [newStub, props, children, patchFlag, dynamicProps] diff --git a/tests/features/plugins.spec.ts b/tests/features/plugins.spec.ts index 64b33ec1a..78d51701d 100644 --- a/tests/features/plugins.spec.ts +++ b/tests/features/plugins.spec.ts @@ -1,4 +1,4 @@ -import { ComponentPublicInstance } from 'vue' +import { ComponentPublicInstance, h } from 'vue' import { mount, config, VueWrapper } from '../../src' @@ -92,3 +92,118 @@ describe('Plugin#install', () => { ) }) }) + +describe('createStubs', () => { + const Child1 = { + name: 'child1', + render: () => h('div', 'real child 1') + } + const Child2 = { + name: 'child2', + render: () => h('div', 'real child 2') + } + + const Parent = { + render: () => h('div', [h(Child1), h(Child1), h(Child2)]) + } + + const customCreateStub = jest.fn(({ name }) => h(`${name}-custom-stub`)) + beforeAll(() => { + config.plugins.createStubs = customCreateStub + }) + + afterAll(() => { + config.plugins.createStubs = undefined + }) + + beforeEach(() => { + customCreateStub.mockClear() + }) + + it('should be called for every stub once', () => { + const wrapper = mount(Parent, { + shallow: true + }) + + expect(wrapper.html()).toBe( + '
\n' + + ' \n' + + ' \n' + + ' \n' + + '
' + ) + + expect(customCreateStub).toHaveBeenCalledTimes(2) + expect(customCreateStub).toHaveBeenCalledWith({ + name: 'child1', + component: Child1 + }) + expect(customCreateStub).toHaveBeenCalledWith({ + name: 'child2', + component: Child2 + }) + }) + + it('should be called only for stubbed components', () => { + const wrapper = mount(Parent, { + global: { + stubs: { + child2: true + } + } + }) + + expect(wrapper.html()).toBe( + '
\n' + + '
real child 1
\n' + + '
real child 1
\n' + + ' \n' + + '
' + ) + + expect(customCreateStub).toHaveBeenCalledTimes(1) + expect(customCreateStub).toHaveBeenCalledWith({ + name: 'child2', + component: Child2 + }) + }) + + it('should not be called for no stubs', () => { + const wrapper = mount(Parent) + + expect(wrapper.html()).toBe( + '
\n' + + '
real child 1
\n' + + '
real child 1
\n' + + '
real child 2
\n' + + '
' + ) + + expect(customCreateStub).not.toHaveBeenCalled() + }) + + it('should not be called for manual stubs', () => { + const wrapper = mount(Parent, { + shallow: true, + global: { + stubs: { + child2: () => h('div', 'Child 2 stub') + } + } + }) + + expect(wrapper.html()).toBe( + '
\n' + + ' \n' + + ' \n' + + '
Child 2 stub
\n' + + '
' + ) + + expect(customCreateStub).toHaveBeenCalledTimes(1) + expect(customCreateStub).toHaveBeenCalledWith({ + name: 'child1', + component: Child1 + }) + }) +})