diff --git a/README.md b/README.md index 5c1cbc72..d83512d9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@

Vue Testing Library

+
+ -[**Read the Docs**][docs] | [Edit the docs][docs-edit] +[**Read the docs**][docs] | [Edit the docs][docs-edit]
@@ -36,6 +38,9 @@

Table of Contents

+ + + - [Installation](#installation) - [A simple example](#a-simple-example) - [More examples](#more-examples) @@ -44,9 +49,11 @@ - [License](#license) - [Contributors](#contributors) + + ## Installation -This module is distributed via npm and should be installed as one of your +This module is distributed via `npm` and should be installed as one of your project's `devDependencies`: ``` @@ -57,7 +64,7 @@ This library has `peerDependencies` listings for `Vue` and `vue-template-compiler`. You may also be interested in installing `jest-dom` so you can use -[the custom Jest matchers](https://github.com/gnapse/jest-dom#readme). +[the custom Jest matchers](https://github.com/testing-library/jest-dom#readme). ## A simple example @@ -90,7 +97,7 @@ import {render, fireEvent} from '@testing-library/vue' import TestComponent from './TestComponent.vue' test('increments value on click', async () => { - // The render method returns a collection of utilities to query your component. + // The render method returns a collection of utilities to query the component. const {getByText} = render(TestComponent) // getByText returns the first matching node for the provided text, and @@ -100,7 +107,7 @@ test('increments value on click', async () => { // `button` is the actual DOM element. const button = getByText('increment') - // Dispatch a native click event. + // Dispatch a couple of native click events. await fireEvent.click(button) await fireEvent.click(button) @@ -124,12 +131,12 @@ Feel free to contribute with more examples! ## Docs -[**Read the Docs**][docs] | [Edit the docs][docs-edit] +[**Read the docs**][docs] | [Edit the docs][docs-edit] ## Typings The TypeScript type definitions are in the -[DefinitelyTyped repo](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/testing-library__vue) +[DefinitelyTyped repo][types] and bundled with Vue Testing Library. ## License @@ -166,13 +173,14 @@ and bundled with Vue Testing Library. [npm]: https://badge.fury.io/js/%40testing-library%2Fvue [license-badge]: https://img.shields.io/github/license/testing-library/vue-testing-library.svg [license]: https://github.com/testing-library/vue-testing-library/blob/master/LICENSE +[types]: https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/testing-library__vue [docs]: https://testing-library.com/vue [docs-edit]: https://github.com/testing-library/testing-library-docs -[test-directory]: https://github.com/testing-library/vue-testing-library/tree/master/tests/__tests__ -[vuex-example]: https://github.com/testing-library/vue-testing-library/tree/master/tests/__tests__/vuex.js -[vue-router-example]: https://github.com/testing-library/vue-testing-library/tree/master/tests/__tests__/vue-router.js -[vee-validate-example]: https://github.com/testing-library/vue-testing-library/tree/master/tests/__tests__/validate-plugin.js +[test-directory]: https://github.com/testing-library/vue-testing-library/blob/master/src/__tests__ +[vuex-example]: https://github.com/testing-library/vue-testing-library/blob/master/src/__tests__/vuex.js +[vue-router-example]: https://github.com/testing-library/vue-testing-library/blob/master/src/__tests__/vue-router.js +[vee-validate-example]: https://github.com/testing-library/vue-testing-library/blob/master/src/__tests__/validate-plugin.js [vue-i18n-example]: https://github.com/testing-library/vue-testing-library/blob/master/tests/__tests__/vueI18n.js diff --git a/src/__tests__/__snapshots__/axios-mock.js.snap b/src/__tests__/__snapshots__/axios-mock.js.snap deleted file mode 100644 index 8083a688..00000000 --- a/src/__tests__/__snapshots__/axios-mock.js.snap +++ /dev/null @@ -1,9 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`makes an API call and displays the greeting when load-greeting is clicked 1`] = ` -
- hello there -
-`; diff --git a/src/__tests__/auto-cleanup-skip.js b/src/__tests__/auto-cleanup-skip.js index 5423ef52..bcd4595a 100644 --- a/src/__tests__/auto-cleanup-skip.js +++ b/src/__tests__/auto-cleanup-skip.js @@ -5,8 +5,8 @@ beforeAll(async () => { render = vtl.render }) -// This one verifies that if VTL_SKIP_AUTO_CLEANUP is set -// then we DON'T auto-wire up the afterEach for folks +// This test verifies that if VTL_SKIP_AUTO_CLEANUP is set then we DON'T +// auto-wire up the afterEach cleanup for folks. test('first test render a vue component', () => { render({ template: `

Hello World

`, diff --git a/src/__tests__/auto-cleanup.js b/src/__tests__/auto-cleanup.js index 37fa8a29..cf3ebbdf 100644 --- a/src/__tests__/auto-cleanup.js +++ b/src/__tests__/auto-cleanup.js @@ -1,9 +1,8 @@ import {render} from '@testing-library/vue' import '@testing-library/jest-dom/extend-expect' -// This just verifies that by importing VTL in an -// environment which supports afterEach (like jest) -// we'll get automatic cleanup between tests. +// This just verifies that by importing VTL in an environment which supports +// afterEach (like jest) we'll get automatic cleanup between tests. test('render the first component', () => { render({ template: `

Hello World

`, diff --git a/src/__tests__/axios-mock.js b/src/__tests__/axios-mock.js index 93eff0f3..5687692e 100644 --- a/src/__tests__/axios-mock.js +++ b/src/__tests__/axios-mock.js @@ -1,9 +1,9 @@ +import '@testing-library/jest-dom/extend-expect' import axiosMock from 'axios' import {render, fireEvent} from '@testing-library/vue' import Component from './components/Fetch.vue' -import '@testing-library/jest-dom/extend-expect' -test('makes an API call and displays the greeting when load-greeting is clicked', async () => { +test('mocks an API call when load-greeting is clicked', async () => { axiosMock.get.mockImplementationOnce(() => Promise.resolve({ data: {greeting: 'hello there'}, @@ -12,7 +12,6 @@ test('makes an API call and displays the greeting when load-greeting is clicked' const {html, getByText} = render(Component, {props: {url: '/greeting'}}) - // Act await fireEvent.click(getByText('Fetch')) expect(axiosMock.get).toHaveBeenCalledTimes(1) @@ -23,5 +22,11 @@ test('makes an API call and displays the greeting when load-greeting is clicked' // that Snapshot Testing should not be treated as a replacement for regular // tests. // More about the topic: https://twitter.com/searls/status/919594505938112512 - expect(html()).toMatchSnapshot() + expect(html()).toMatchInlineSnapshot(` +
+ hello there +
+ `) }) diff --git a/src/__tests__/components/Translations.vue b/src/__tests__/components/Translations.vue new file mode 100644 index 00000000..0510b5d8 --- /dev/null +++ b/src/__tests__/components/Translations.vue @@ -0,0 +1,19 @@ + + + diff --git a/src/__tests__/debug.js b/src/__tests__/debug.js index f3a33c56..89df1bb3 100644 --- a/src/__tests__/debug.js +++ b/src/__tests__/debug.js @@ -34,7 +34,7 @@ test('debug pretty prints the provided parameter', () => { expect.stringContaining('Lorem ipsum dolor sit amet'), ) - // Notice the 'not' particle + // Notice the 'not' particle. expect(console.log).not.toHaveBeenCalledWith( expect.stringContaining('Hello World'), ) diff --git a/src/__tests__/disappearance.js b/src/__tests__/disappearance.js index 228afe94..66ca8ed5 100644 --- a/src/__tests__/disappearance.js +++ b/src/__tests__/disappearance.js @@ -9,7 +9,8 @@ test('waits for the data to be loaded', async () => { getByText('Loading...') expect(queryByText(/Loaded this message/)).not.toBeInTheDocument() - // Line reads as follows: "Wait until element with text 'Loading...' is gone." + // Following line reads as follows: + // "Wait until element with text 'Loading...' is gone." await waitForElementToBeRemoved(() => queryByText('Loading...')) // It is equivalent to: // @@ -17,7 +18,7 @@ test('waits for the data to be loaded', async () => { // expect(queryByText('Loading...')).not.toBeInTheDocument() // }) // - // `wait()` waits until the callback function passes or times out. + // because `wait()` waits until the callback function passes or times out. // After 'Loading...' is gone, we can assert that fetched data is rendered. expect(queryByTestId('message')).toHaveTextContent(/Hello World/) diff --git a/src/__tests__/form.js b/src/__tests__/form.js index 2e984a9c..4d26da2f 100644 --- a/src/__tests__/form.js +++ b/src/__tests__/form.js @@ -24,7 +24,7 @@ test('Review form submits', async () => { const submitButton = getByText('Submit') - // Initially the submit button should be disabled + // Initially the submit button should be disabled. expect(submitButton).toBeDisabled() const titleInput = getByLabelText(/title of the movie/i) @@ -33,7 +33,7 @@ test('Review form submits', async () => { const reviewTextarea = getByPlaceholderText('Write an awesome review') await fireEvent.update(reviewTextarea, fakeReview.review) - // Rating Radio buttons + // Rating Radio buttons. const initiallySelectedInput = getByLabelText('Awful') const ratingSelect = getByLabelText('Wonderful') @@ -45,23 +45,20 @@ test('Review form submits', async () => { expect(ratingSelect.checked).toBe(true) expect(initiallySelectedInput.checked).toBe(false) - // Get the Input element by its implicit ARIA role + // Get the Input element by its implicit ARIA role. const recommendInput = getByRole('checkbox') expect(recommendInput.checked).toBe(false) await fireEvent.update(recommendInput) expect(recommendInput.checked).toBe(true) - // NOTE: in jsdom, it's not possible to trigger a form submission - // by clicking on the submit button. This is really unfortunate. - // So the next best thing is to fireEvent a submit on the form itself - // then ensure that there's a submit button + // Make sure the submit button is enabled. expect(submitButton).toBeEnabled() expect(submitButton).toHaveAttribute('type', 'submit') await fireEvent.click(submitButton) - // Assert event has been emitted + // Assert the right event has been emitted. expect(emitted()).toHaveProperty('submit') expect(emitted().submit[0][0]).toMatchObject(fakeReview) }) diff --git a/src/__tests__/functional.js b/src/__tests__/functional.js index 033677b1..06a58d41 100644 --- a/src/__tests__/functional.js +++ b/src/__tests__/functional.js @@ -8,13 +8,13 @@ const Functional = { }, } -test('renders functional comp', () => { +test('renders functional component', () => { const {getByText} = render(Functional) getByText('Hi!') }) -test('renders functional SFC comp', () => { +test('renders functional SFC component', () => { const {getByText} = render(FunctionalSFC) getByText('Hi!') diff --git a/src/__tests__/select.js b/src/__tests__/select.js index 437488a8..f83a8512 100644 --- a/src/__tests__/select.js +++ b/src/__tests__/select.js @@ -2,25 +2,25 @@ import {render, fireEvent} from '@testing-library/vue' import '@testing-library/jest-dom/extend-expect' import Select from './components/Select' -// In this test file we showcase several ways to interact with a Select element +// In this test file we showcase several ways to interact with a Select element. test('Select component', async () => { let optionElement const {getByDisplayValue, getByText} = render(Select) - // Get the Select element by using the initially displayed value + // Get the Select element by using the initially displayed value. const select = getByDisplayValue('Tyrannosaurus') expect(select.value).toBe('dino1') - // Update it by manually sending a valid option value + // Update it by manually sending a valid option value. await fireEvent.update(select, 'dino2') expect(select.value).toBe('dino2') - // We can trigger an update event by directly getting the + // ...even if option is within an . optionElement = getByText('Diplodocus') await fireEvent.update(optionElement) expect(select.value).toBe('dino4') diff --git a/src/__tests__/simple-button.js b/src/__tests__/simple-button.js index f02299b9..e8216dfd 100644 --- a/src/__tests__/simple-button.js +++ b/src/__tests__/simple-button.js @@ -5,28 +5,28 @@ import '@testing-library/jest-dom/extend-expect' test('renders button with text', () => { const text = "Click me; I'm sick" - // Set the prop value by using the second argument of `render()` + // Set the prop value by using the second argument of `render()`. const {getByRole} = render(Button, { props: {text}, }) - // Get the only element with a 'button' role + // Get the only element with a 'button' role. const button = getByRole('button') expect(button).toHaveTextContent(text) }) -test('click event is emitted when button is clicked', async () => { +test('emits click event when button is clicked', async () => { const text = 'Click me' const {getByRole, emitted} = render(Button, { props: {text}, }) - // Send a click event + // Send a click event. await fireEvent.click(getByRole('button')) // Expect that the event emitted a "click" event. We should test for emitted - // events has they are part of the public API of the component + // events has they are part of the public API of the component. expect(emitted()).toHaveProperty('click') }) diff --git a/src/__tests__/stopwatch.js b/src/__tests__/stopwatch.js index 3eba0c94..f57a68f1 100644 --- a/src/__tests__/stopwatch.js +++ b/src/__tests__/stopwatch.js @@ -1,22 +1,6 @@ +import '@testing-library/jest-dom/extend-expect' import {render, wait, fireEvent} from '@testing-library/vue' import StopWatch from './components/StopWatch.vue' -import '@testing-library/jest-dom/extend-expect' - -test('unmounts a component', async () => { - jest.spyOn(console, 'error').mockImplementation(() => {}) - - const {unmount, isUnmounted, getByText} = render(StopWatch) - await fireEvent.click(getByText('Start')) - - // Destroys a Vue component instance. - unmount() - - expect(isUnmounted()).toBe(true) - - await wait() - - expect(console.error).not.toHaveBeenCalled() -}) test('updates component state', async () => { const {getByTestId, getByText} = render(StopWatch) @@ -42,3 +26,19 @@ test('updates component state', async () => { // content has changed. expect(elapsedTime).not.toHaveTextContent('0ms') }) + +test('unmounts a component', async () => { + jest.spyOn(console, 'error').mockImplementation(() => {}) + + const {unmount, isUnmounted, getByText} = render(StopWatch) + await fireEvent.click(getByText('Start')) + + // Destroys a Vue component instance. + unmount() + + expect(isUnmounted()).toBe(true) + + await wait() + + expect(console.error).not.toHaveBeenCalled() +}) diff --git a/src/__tests__/update-props.js b/src/__tests__/update-props.js index 5bc2728c..19f13056 100644 --- a/src/__tests__/update-props.js +++ b/src/__tests__/update-props.js @@ -1,7 +1,11 @@ +import '@testing-library/jest-dom/extend-expect' import {render} from '@testing-library/vue' import NumberDisplay from './components/NumberDisplay.vue' -import '@testing-library/jest-dom/extend-expect' +// It'd probably be better if you test the component that's doing the prop +// updating to ensure that the props are being updated correctly. +// That said, if you'd prefer to update the props of a rendered component, this +// function can be used to update props of the rendered component. test('calling render with the same component but different props does not remount', async () => { const {getByTestId, updateProps} = render(NumberDisplay, { props: {number: 1}, diff --git a/src/__tests__/vue-i18n.js b/src/__tests__/vue-i18n.js new file mode 100644 index 00000000..70ab8f21 --- /dev/null +++ b/src/__tests__/vue-i18n.js @@ -0,0 +1,40 @@ +import '@testing-library/jest-dom/extend-expect' +import {render, fireEvent} from '@testing-library/vue' +import Vuei18n from 'vue-i18n' +import Translations from './components/Translations' + +const messages = { + en: { + Hello: 'Hello', + }, + ja: { + Hello: 'こんにちは', + }, +} + +test('renders translations', async () => { + const {queryByText, getByText} = render(Translations, {}, vue => { + // Let's register and configure Vuei18n normally + vue.use(Vuei18n) + + const i18n = new Vuei18n({ + locale: 'en', + fallbackLocale: 'en', + messages, + }) + + // Notice how we return an object from the callback function. It will be + // merged as an additional option on the created Vue instance. + return { + i18n, + } + }) + + expect(getByText('Hello')).toBeInTheDocument() + + await fireEvent.click(getByText('Japanese')) + + expect(getByText('こんにちは')).toBeInTheDocument() + + expect(queryByText('Hello')).toBeNull() +})