Skip to content

Commit 2abdbb8

Browse files
authored
feat: Add createStubs plugin hook (#1241)
* feat(plugins): Add createStubs plugin hook * Fix lint errors * docs(plugins): Add docs for createStubs plugin hook
1 parent 29e0975 commit 2abdbb8

File tree

4 files changed

+184
-10
lines changed

4 files changed

+184
-10
lines changed

docs/guide/extending-vtu/plugins.md

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ Some use cases for plugins:
1010
1. Attaching matchers to the Wrapper instance
1111
1. Attaching functionality to the Wrapper
1212

13-
## Using a Plugin
13+
## Wrapper Plugin
14+
15+
### Using a Plugin
1416

1517
Install plugins by calling the `config.plugins.VueWrapper.install()` method
1618
. This has to be done before you call `mount`.
@@ -43,12 +45,12 @@ once. Follow the instructions of the plugin you're installing.
4345

4446
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.
4547

46-
## Writing a Plugin
48+
### Writing a Plugin
4749

4850
A Vue Test Utils plugin is simply a function that receives the mounted
4951
`VueWrapper` or `DOMWrapper` instance and can modify it.
5052

51-
### Basic Plugin
53+
#### Basic Plugin
5254

5355
Below is a simple plugin to add a convenient alias to map `wrapper.element` to `wrapper.$el`
5456

@@ -75,7 +77,7 @@ const wrapper = mount({ template: `<h1>🔌 Plugin</h1>` })
7577
console.log(wrapper.$el.innerHTML) // 🔌 Plugin
7678
```
7779

78-
### Data Test ID Plugin
80+
#### Data Test ID Plugin
7981

8082
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.
8183

@@ -122,6 +124,50 @@ const DataTestIdPlugin = (wrapper) => {
122124
config.plugins.VueWrapper.install(DataTestIdPlugin)
123125
```
124126

127+
## Stubs Plugin
128+
129+
The `config.plugins.createStubs` allows to overwrite the default stub creation provided by VTU.
130+
131+
Some use cases are:
132+
* You want to add more logic into the stubs (for example named slots)
133+
* You want to use different stubs for multiple components (for example stub components from a library)
134+
135+
### Usage
136+
137+
```typescript
138+
config.plugins.createStubs = ({ name, component }) => {
139+
return defineComponent({
140+
render: () => h(`custom-${name}-stub`)
141+
})
142+
}
143+
```
144+
145+
This function will be called everytime VTU generates a stub either from
146+
```typescript
147+
const wrapper = mount(Component, {
148+
global: {
149+
stubs: {
150+
ChildComponent: true
151+
}
152+
}
153+
})
154+
```
155+
or
156+
```typescript
157+
const wrapper = shallowMount(Component)
158+
```
159+
160+
But will not be called, when you explicit set a stub
161+
```typescript
162+
const wrapper = mount(Component, {
163+
global: {
164+
stubs: {
165+
ChildComponent: { template: '<child-stub/>' }
166+
}
167+
}
168+
})
169+
```
170+
125171
## Featuring Your Plugin
126172

127173
If you're missing functionality, consider writing a plugin to extend Vue Test

src/config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import { GlobalMountOptions } from './types'
22
import { VueWrapper } from './vueWrapper'
33
import { DOMWrapper } from './domWrapper'
4+
import { CustomCreateStub } from './stubs'
45

56
export interface GlobalConfigOptions {
67
global: Required<GlobalMountOptions>
78
plugins: {
89
VueWrapper: Pluggable<VueWrapper>
910
DOMWrapper: Pluggable<DOMWrapper<Node>>
11+
createStubs?: CustomCreateStub
1012
}
1113
renderStubDefaultSlot: boolean
1214
}

src/stubs.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ import {
2020
getComponentName,
2121
getComponentRegisteredName
2222
} from './utils/componentName'
23+
import { config } from './config'
24+
25+
export type CustomCreateStub = (params: {
26+
name: string
27+
component: ConcreteComponent
28+
}) => ConcreteComponent
2329

2430
interface StubOptions {
2531
name: string
@@ -259,11 +265,16 @@ export function stubComponents(
259265
}
260266

261267
const newStub = createStubOnce(type, () =>
262-
createStub({
263-
name: stubName,
264-
type,
265-
renderStubDefaultSlot
266-
})
268+
config.plugins.createStubs
269+
? config.plugins.createStubs({
270+
name: stubName,
271+
component: type
272+
})
273+
: createStub({
274+
name: stubName,
275+
type,
276+
renderStubDefaultSlot
277+
})
267278
)
268279
registerStub({ source: type, stub: newStub })
269280
return [newStub, props, children, patchFlag, dynamicProps]

tests/features/plugins.spec.ts

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ComponentPublicInstance } from 'vue'
1+
import { ComponentPublicInstance, h } from 'vue'
22

33
import { mount, config, VueWrapper } from '../../src'
44

@@ -92,3 +92,118 @@ describe('Plugin#install', () => {
9292
)
9393
})
9494
})
95+
96+
describe('createStubs', () => {
97+
const Child1 = {
98+
name: 'child1',
99+
render: () => h('div', 'real child 1')
100+
}
101+
const Child2 = {
102+
name: 'child2',
103+
render: () => h('div', 'real child 2')
104+
}
105+
106+
const Parent = {
107+
render: () => h('div', [h(Child1), h(Child1), h(Child2)])
108+
}
109+
110+
const customCreateStub = jest.fn(({ name }) => h(`${name}-custom-stub`))
111+
beforeAll(() => {
112+
config.plugins.createStubs = customCreateStub
113+
})
114+
115+
afterAll(() => {
116+
config.plugins.createStubs = undefined
117+
})
118+
119+
beforeEach(() => {
120+
customCreateStub.mockClear()
121+
})
122+
123+
it('should be called for every stub once', () => {
124+
const wrapper = mount(Parent, {
125+
shallow: true
126+
})
127+
128+
expect(wrapper.html()).toBe(
129+
'<div>\n' +
130+
' <child1-custom-stub></child1-custom-stub>\n' +
131+
' <child1-custom-stub></child1-custom-stub>\n' +
132+
' <child2-custom-stub></child2-custom-stub>\n' +
133+
'</div>'
134+
)
135+
136+
expect(customCreateStub).toHaveBeenCalledTimes(2)
137+
expect(customCreateStub).toHaveBeenCalledWith({
138+
name: 'child1',
139+
component: Child1
140+
})
141+
expect(customCreateStub).toHaveBeenCalledWith({
142+
name: 'child2',
143+
component: Child2
144+
})
145+
})
146+
147+
it('should be called only for stubbed components', () => {
148+
const wrapper = mount(Parent, {
149+
global: {
150+
stubs: {
151+
child2: true
152+
}
153+
}
154+
})
155+
156+
expect(wrapper.html()).toBe(
157+
'<div>\n' +
158+
' <div>real child 1</div>\n' +
159+
' <div>real child 1</div>\n' +
160+
' <child2-custom-stub></child2-custom-stub>\n' +
161+
'</div>'
162+
)
163+
164+
expect(customCreateStub).toHaveBeenCalledTimes(1)
165+
expect(customCreateStub).toHaveBeenCalledWith({
166+
name: 'child2',
167+
component: Child2
168+
})
169+
})
170+
171+
it('should not be called for no stubs', () => {
172+
const wrapper = mount(Parent)
173+
174+
expect(wrapper.html()).toBe(
175+
'<div>\n' +
176+
' <div>real child 1</div>\n' +
177+
' <div>real child 1</div>\n' +
178+
' <div>real child 2</div>\n' +
179+
'</div>'
180+
)
181+
182+
expect(customCreateStub).not.toHaveBeenCalled()
183+
})
184+
185+
it('should not be called for manual stubs', () => {
186+
const wrapper = mount(Parent, {
187+
shallow: true,
188+
global: {
189+
stubs: {
190+
child2: () => h('div', 'Child 2 stub')
191+
}
192+
}
193+
})
194+
195+
expect(wrapper.html()).toBe(
196+
'<div>\n' +
197+
' <child1-custom-stub></child1-custom-stub>\n' +
198+
' <child1-custom-stub></child1-custom-stub>\n' +
199+
' <div>Child 2 stub</div>\n' +
200+
'</div>'
201+
)
202+
203+
expect(customCreateStub).toHaveBeenCalledTimes(1)
204+
expect(customCreateStub).toHaveBeenCalledWith({
205+
name: 'child1',
206+
component: Child1
207+
})
208+
})
209+
})

0 commit comments

Comments
 (0)