Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add createStubs plugin hook #1241

Merged
merged 3 commits into from
Feb 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 50 additions & 4 deletions docs/guide/extending-vtu/plugins.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
})
})
})