Skip to content

Commit

Permalink
feat: Remove Router/Vuex for Vue 3 (#206)
Browse files Browse the repository at this point in the history
* Remove handling of router/vuex

* Fix existing tests

* Mark types as deprecated

* Add simple vuex example

* Improve messaging

* Mark store as optional
  • Loading branch information
afontcu authored Feb 23, 2021
1 parent aa00f27 commit 3c33dc3
Show file tree
Hide file tree
Showing 9 changed files with 168 additions and 131 deletions.
44 changes: 20 additions & 24 deletions src/__tests__/components/Store/VuexTest.vue
Original file line number Diff line number Diff line change
@@ -1,24 +1,20 @@
<template>
<div>
<h2>Counter</h2>
<div>
<button data-testid="decrementer" @click="decrement">-</button>
<span data-testid="count-value">{{ count }}</span>
<button data-testid="incrementer" @click="increment">+</button>
</div>
</div>
</template>

<script>
import { mapActions, mapState } from 'vuex'
export default {
computed: {
...mapState(['count'])
},
methods: {
...mapActions(['decrement', 'increment'])
}
}
</script>
<template>
<h2>Counter</h2>
<button data-testid="decrementer" @click="decrement">-</button>
<span data-testid="count-value">{{ count }}</span>
<button data-testid="incrementer" @click="increment">+</button>
</template>

<script>
import {mapActions, mapState} from 'vuex'
export default {
computed: {
...mapState(['count']),
},
methods: {
...mapActions(['decrement', 'increment']),
},
}
</script>
39 changes: 39 additions & 0 deletions src/__tests__/deprecated-options.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {render} from '..'

beforeEach(() => {
jest.spyOn(console, 'warn').mockImplementation(() => {})
})

afterEach(() => {
console.warn.mockRestore()
})

test('warns on deprecated store option', () => {
const Component = {template: `<div></div>`}

render(Component, {
store: 'anything',
})

expect(console.warn).toHaveBeenCalledTimes(1)
expect(console.warn).toHaveBeenCalledWith(
expect.stringContaining(
`Providing 'store' or 'routes' options is no longer available`,
),
)
})

test('warns on deprecated routes option', () => {
const Component = {template: `<div></div>`}

render(Component, {
routes: 'anything',
})

expect(console.warn).toHaveBeenCalledTimes(1)
expect(console.warn).toHaveBeenCalledWith(
expect.stringContaining(
`Providing 'store' or 'routes' options is no longer available`,
),
)
})
49 changes: 0 additions & 49 deletions src/__tests__/plugins.js

This file was deleted.

14 changes: 0 additions & 14 deletions src/__tests__/vue-router-mocha.js

This file was deleted.

65 changes: 55 additions & 10 deletions src/__tests__/vue-router.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,38 @@
// Please notice that this example is a draft example on how to test
// the router.
// Related issue on Vue Test Utils: https://github.com/vuejs/vue-test-utils-next/issues/152
/* eslint-disable vue/require-prop-types */
/* eslint-disable vue/one-component-per-file */

import '@testing-library/jest-dom'
import {waitFor} from '@testing-library/dom'
import {defineComponent} from 'vue'
import {createRouter, createWebHistory} from 'vue-router'
import {render, fireEvent} from '..'
import App from './components/Router/App.vue'
import Home from './components/Router/Home.vue'
import About from './components/Router/About.vue'

const routes = [
{path: '/', component: Home},
{path: '/about', component: About},
]
test('full app rendering/navigating from base URL', async () => {
// Create a Router instance
// https://next.router.vuejs.org/api/#createrouter
// using a HTML5 history.
// https://next.router.vuejs.org/api/#createwebhistory
const router = createRouter({
history: createWebHistory(),
routes: [
{path: '/', component: Home},
{path: '/about', component: About},
],
})

test('full app rendering/navigating', async () => {
// Notice how we pass a `routes` object to our render function.
const {findByText, getByText, getByTestId} = render(App, {routes})
const {findByText, getByText, getByTestId} = render(App, {
global: {
plugins: [router],
},
})

// Vue Router navigation is async, so we need to wait until the
// initial render happens
expect(await findByText('You are home')).toBeInTheDocument()
expect(getByTestId('location-display')).toHaveTextContent('/')

await fireEvent.click(getByTestId('about-link'))

Expand All @@ -32,3 +44,36 @@ test('full app rendering/navigating', async () => {

expect(getByText('You are on the about page')).toBeInTheDocument()
})

test('sets router initial state', async () => {
const Component = defineComponent({
props: ['to'],
template: `<router-link :to="to">Learn More</router-link>`,
})

const route = {
name: 'routeName',
path: '/',
component: defineComponent({template: `<div></div>`}),
}

const router = createRouter({
history: createWebHistory(),
routes: [route],
})

const to = {name: route.name}

const {getByText} = render(Component, {
props: {to},
global: {
plugins: [router],
},
})

// We need to wait for router to complete the initial navigation.
await router.isReady()

const button = getByText('Learn More')
expect(button).toHaveAttribute('href', route.path)
})
49 changes: 40 additions & 9 deletions src/__tests__/vuex.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,46 @@
import '@testing-library/jest-dom'
import {createStore} from 'vuex'
import {render, fireEvent} from '..'
import VuexTest from './components/Store/VuexTest'
import {store} from './components/Store/store'

test('basic test with Vuex store', async () => {
const storeInstance = createStore(store)

const {getByTestId, getByText} = render(VuexTest, {
global: {
plugins: [storeInstance],
},
})

expect(getByTestId('count-value')).toHaveTextContent('0')

await fireEvent.click(getByText('+'))
expect(getByTestId('count-value')).toHaveTextContent('1')

await fireEvent.click(getByText('+'))
expect(getByTestId('count-value')).toHaveTextContent('2')

await fireEvent.click(getByText('-'))
expect(getByTestId('count-value')).toHaveTextContent('1')
})

// A common testing pattern is to create a custom renderer for a specific test
// file. This way, common operations such as registering a Vuex store can be
// abstracted out while avoiding sharing mutable state.
//
// Tests should be completely isolated from one another.
// Read this for additional context: https://kentcdodds.com/blog/test-isolation-with-react
function renderVuexTestComponent(customStore) {
// Render the component and merge the original store and the custom one
// provided as a parameter. This way, we can alter some behaviors of the
// initial implementation.
return render(VuexTest, {store: {...store, ...customStore}})
// Create a custom store with the original one and the one coming as a
// parameter. This way we can alter some of its values.
const mergedStoreInstance = createStore({...store, ...customStore})

return render(VuexTest, {
global: {
plugins: [mergedStoreInstance],
},
})
}

test('can render with vuex with defaults', async () => {
Expand All @@ -26,7 +53,7 @@ test('can render with vuex with defaults', async () => {

test('can render with vuex with custom initial state', async () => {
const {getByTestId, getByText} = renderVuexTestComponent({
state: {count: 3},
state: () => ({count: 3}),
})

await fireEvent.click(getByText('-'))
Expand All @@ -37,17 +64,21 @@ test('can render with vuex with custom initial state', async () => {
test('can render with vuex with custom store', async () => {
// This is a silly store that can never be changed.
// eslint-disable-next-line no-shadow
const store = {
state: {count: 1000},
const store = createStore({
state: () => ({count: 1000}),
actions: {
increment: () => jest.fn(),
decrement: () => jest.fn(),
},
}
})

// Notice how here we are not using the helper rendering method, because
// there's no need to do that here. We're passing a whole store.
const {getByTestId, getByText} = render(VuexTest, {store})
const {getByTestId, getByText} = render(VuexTest, {
global: {
plugins: [store],
},
})

await fireEvent.click(getByText('+'))
expect(getByTestId('count-value')).toHaveTextContent('1000')
Expand Down
19 changes: 4 additions & 15 deletions src/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,15 @@ function render(
const baseElement = customBaseElement || customContainer || document.body
const container = customContainer || baseElement.appendChild(div)

const plugins = mountOptions.global?.plugins || []

if (store) {
const {createStore} = require('vuex')
plugins.push(createStore(store))
}

if (routes) {
const requiredRouter = require('vue-router')
const {createRouter, createWebHistory} =
requiredRouter.default || requiredRouter

const routerPlugin = createRouter({history: createWebHistory(), routes})
plugins.push(routerPlugin)
if (store || routes) {
console.warn(`Providing 'store' or 'routes' options is no longer available.
You need to create a router/vuex instance and provide it through 'global.plugins'.
Check out the test examples on GitHub for further details.`)
}

const wrapper = mount(Component, {
...mountOptions,
attachTo: container,
global: {...mountOptions.global, plugins},
})

// this removes the additional "data-v-app" div node from VTU:
Expand Down
14 changes: 9 additions & 5 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@

import {EmitsOptions} from 'vue'
import {MountingOptions} from '@vue/test-utils'
import {StoreOptions} from 'vuex'
import {RouteRecordRaw} from 'vue-router'
import {queries, EventType, BoundFunctions} from '@testing-library/dom'
// eslint-disable-next-line import/no-extraneous-dependencies
import {OptionsReceived as PrettyFormatOptions} from 'pretty-format'
Expand Down Expand Up @@ -32,9 +30,15 @@ type VueTestUtilsRenderOptions = Omit<
MountingOptions<Record<string, any>>,
'attachTo' | 'shallow' | 'propsData'
>
type VueTestingLibraryRenderOptions = {
store?: StoreOptions<{}>
routes?: RouteRecordRaw[]
interface VueTestingLibraryRenderOptions {
/**
* @deprecated Add a Vuex instance through `global.plugins` array instead.
*/
store?: any
/**
* @deprecated Add a Router instance through `global.plugins` array instead.
*/
routes?: any
container?: Element
baseElement?: Element
}
Expand Down
6 changes: 1 addition & 5 deletions types/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,8 @@ export function testOptions() {
},
global: {
config: {isCustomElement: _ => true},
plugins: [],
},
store: {
state: {count: 3},
strict: true,
},
routes: [{path: '/', component: () => SomeComponent, name: 'route name'}],
baseElement: document.createElement('div'),
container: document.createElement('div'),
})
Expand Down

0 comments on commit 3c33dc3

Please sign in to comment.