Skip to content

Commit

Permalink
feat: Add createStubs plugin hook (#1241)
Browse files Browse the repository at this point in the history
* feat(plugins): Add createStubs plugin hook

* Fix lint errors

* docs(plugins): Add docs for createStubs plugin hook
  • Loading branch information
freakzlike committed Feb 7, 2022
1 parent 29e0975 commit 2abdbb8
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 10 deletions.
54 changes: 50 additions & 4 deletions docs/guide/extending-vtu/plugins.md
Expand Up @@ -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`.
Expand Down Expand Up @@ -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`

Expand All @@ -75,7 +77,7 @@ const wrapper = mount({ template: `<h1>🔌 Plugin</h1>` })
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.

Expand Down Expand Up @@ -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: '<child-stub/>' }
}
}
})
```

## Featuring Your Plugin

If you're missing functionality, consider writing a plugin to extend Vue Test
Expand Down
2 changes: 2 additions & 0 deletions 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<GlobalMountOptions>
plugins: {
VueWrapper: Pluggable<VueWrapper>
DOMWrapper: Pluggable<DOMWrapper<Node>>
createStubs?: CustomCreateStub
}
renderStubDefaultSlot: boolean
}
Expand Down
21 changes: 16 additions & 5 deletions src/stubs.ts
Expand Up @@ -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
Expand Down Expand Up @@ -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]
Expand Down
117 changes: 116 additions & 1 deletion tests/features/plugins.spec.ts
@@ -1,4 +1,4 @@
import { ComponentPublicInstance } from 'vue'
import { ComponentPublicInstance, h } from 'vue'

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

Expand Down Expand Up @@ -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(
'<div>\n' +
' <child1-custom-stub></child1-custom-stub>\n' +
' <child1-custom-stub></child1-custom-stub>\n' +
' <child2-custom-stub></child2-custom-stub>\n' +
'</div>'
)

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(
'<div>\n' +
' <div>real child 1</div>\n' +
' <div>real child 1</div>\n' +
' <child2-custom-stub></child2-custom-stub>\n' +
'</div>'
)

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(
'<div>\n' +
' <div>real child 1</div>\n' +
' <div>real child 1</div>\n' +
' <div>real child 2</div>\n' +
'</div>'
)

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(
'<div>\n' +
' <child1-custom-stub></child1-custom-stub>\n' +
' <child1-custom-stub></child1-custom-stub>\n' +
' <div>Child 2 stub</div>\n' +
'</div>'
)

expect(customCreateStub).toHaveBeenCalledTimes(1)
expect(customCreateStub).toHaveBeenCalledWith({
name: 'child1',
component: Child1
})
})
})

0 comments on commit 2abdbb8

Please sign in to comment.