diff --git a/src/__tests__/components/Store/VuexTest.vue b/src/__tests__/components/Store/VuexTest.vue index 42a9b3d9..e11a014e 100644 --- a/src/__tests__/components/Store/VuexTest.vue +++ b/src/__tests__/components/Store/VuexTest.vue @@ -1,24 +1,20 @@ - - - + + + diff --git a/src/__tests__/deprecated-options.js b/src/__tests__/deprecated-options.js new file mode 100644 index 00000000..8e01026a --- /dev/null +++ b/src/__tests__/deprecated-options.js @@ -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: `
`} + + 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: `
`} + + render(Component, { + routes: 'anything', + }) + + expect(console.warn).toHaveBeenCalledTimes(1) + expect(console.warn).toHaveBeenCalledWith( + expect.stringContaining( + `Providing 'store' or 'routes' options is no longer available`, + ), + ) +}) diff --git a/src/__tests__/plugins.js b/src/__tests__/plugins.js deleted file mode 100644 index def1b78e..00000000 --- a/src/__tests__/plugins.js +++ /dev/null @@ -1,49 +0,0 @@ -import '@testing-library/jest-dom' -import ElementPlus from 'element-plus' -import userEvent from '@testing-library/user-event' -import {defineComponent} from 'vue' -import {render, screen, fireEvent, waitFor} from '..' -import {store} from './components/Store/store' - -test('can set global options and custom options such as a store', async () => { - const ComponentWithStore = defineComponent({ - template: ` - - - - {{ $store.state.count }} - - `, - }) - - const DirectiveStub = { - template: '

Search now

', - } - - render(ComponentWithStore, { - store, - global: { - plugins: [ElementPlus], - stubs: { - Directive: DirectiveStub, - }, - }, - }) - - expect(screen.getByText('Search now')).toBeInTheDocument() - - const button = screen.getByText('Hover to activate') - userEvent.hover(button) - - await waitFor(() => expect(screen.getByText('this is content')).toBeVisible()) - - expect(screen.getByTestId('count-value')).toHaveTextContent('0') - - await fireEvent.click(button) - - expect(screen.getByTestId('count-value')).toHaveTextContent('1') -}) diff --git a/src/__tests__/vue-router-mocha.js b/src/__tests__/vue-router-mocha.js deleted file mode 100644 index c7263c78..00000000 --- a/src/__tests__/vue-router-mocha.js +++ /dev/null @@ -1,14 +0,0 @@ -import '@testing-library/jest-dom' -import {render} from '..' - -import About from './components/Router/About.vue' - -const routes = [] -test('uses require("vue-router").default when require("vue-router") is undefined (useful for mocha users)', () => { - // Test for fix https://github.com/testing-library/vue-testing-library/issues/119 - jest.mock('vue-router', () => undefined) - - expect(() => render(About, {routes})).toThrowError( - new TypeError("Cannot read property 'default' of undefined"), - ) -}) diff --git a/src/__tests__/vue-router.js b/src/__tests__/vue-router.js index 221175ab..66db0540 100644 --- a/src/__tests__/vue-router.js +++ b/src/__tests__/vue-router.js @@ -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')) @@ -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: `Learn More`, + }) + + const route = { + name: 'routeName', + path: '/', + component: defineComponent({template: `
`}), + } + + 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) +}) diff --git a/src/__tests__/vuex.js b/src/__tests__/vuex.js index 7873f5bf..d0faa53a 100644 --- a/src/__tests__/vuex.js +++ b/src/__tests__/vuex.js @@ -1,8 +1,30 @@ 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. @@ -10,10 +32,15 @@ import {store} from './components/Store/store' // 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 () => { @@ -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('-')) @@ -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') diff --git a/src/render.js b/src/render.js index e5c303d8..0171441e 100644 --- a/src/render.js +++ b/src/render.js @@ -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: diff --git a/types/index.d.ts b/types/index.d.ts index c886447a..f479111f 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -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' @@ -32,9 +30,15 @@ type VueTestUtilsRenderOptions = Omit< MountingOptions>, '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 } diff --git a/types/test.ts b/types/test.ts index aeb5df89..06aea9c4 100644 --- a/types/test.ts +++ b/types/test.ts @@ -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'), })