From 777cfd365f4d248332140ec5e464b3c19db4cb0e Mon Sep 17 00:00:00 2001 From: "Kent C. Dodds" Date: Mon, 18 Jun 2018 17:21:56 -0600 Subject: [PATCH] feat(render): deprecate renderIntoDocument and make it the default Closes #116 BREAKING CHANGE: `renderIntoDocument` replaces `render` and `Simulate` is no longer exported (use `fireEvent` instead). --- .github/ISSUE_TEMPLATE.md | 8 +- .github/ISSUE_TEMPLATE/Bug_Report.md | 8 +- .github/ISSUE_TEMPLATE/Question.md | 8 +- .github/PULL_REQUEST_TEMPLATE.md | 10 +- .prettierrc | 3 +- CHANGELOG.md | 5 +- CODE_OF_CONDUCT.md | 45 +- CONTRIBUTING.md | 33 +- README.md | 416 +++++++++--------- cleanup-after-each.js | 1 + examples/README.md | 3 +- .../__tests__/mock.react-transition-group.js | 6 +- examples/__tests__/react-context.js | 4 +- examples/__tests__/react-redux.js | 12 +- examples/__tests__/react-router.js | 6 +- .../shallow.react-transition-group.js | 6 +- examples/__tests__/update-props.js | 4 +- other/MAINTAINING.md | 77 ++-- other/manual-releases.md | 9 +- package.json | 21 +- src/__tests__/debug.js | 3 +- src/__tests__/end-to-end.js | 4 +- src/__tests__/events.js | 4 +- src/__tests__/fetch.js | 6 +- src/__tests__/forms.js | 17 +- .../{render-into-document.js => render.js} | 8 +- src/__tests__/rerender.js | 4 +- src/__tests__/stopwatch.js | 9 +- src/index.js | 43 +- typings/index.d.ts | 6 +- 30 files changed, 404 insertions(+), 385 deletions(-) create mode 100644 cleanup-after-each.js rename src/__tests__/{render-into-document.js => render.js} (80%) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index f6a0575d..9e04f824 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -43,10 +43,10 @@ tutorial to learn how: http://kcd.im/pull-request --> -* `react-testing-library` version: -* `react` version: -* `node` version: -* `npm` (or `yarn`) version: +- `react-testing-library` version: +- `react` version: +- `node` version: +- `npm` (or `yarn`) version: Relevant code or config diff --git a/.github/ISSUE_TEMPLATE/Bug_Report.md b/.github/ISSUE_TEMPLATE/Bug_Report.md index de5aaa11..f1debab9 100644 --- a/.github/ISSUE_TEMPLATE/Bug_Report.md +++ b/.github/ISSUE_TEMPLATE/Bug_Report.md @@ -22,10 +22,10 @@ tutorial to learn how: http://kcd.im/pull-request --> -* `react-testing-library` version: -* `react` version: -* `node` version: -* `npm` (or `yarn`) version: +- `react-testing-library` version: +- `react` version: +- `node` version: +- `npm` (or `yarn`) version: ### Relevant code or config: diff --git a/.github/ISSUE_TEMPLATE/Question.md b/.github/ISSUE_TEMPLATE/Question.md index 595d5278..52790875 100644 --- a/.github/ISSUE_TEMPLATE/Question.md +++ b/.github/ISSUE_TEMPLATE/Question.md @@ -12,11 +12,9 @@ and feature requests so we recommend not using this medium to ask them here 😁 ## ā“ Support Forums -* React Spectrum - https://spectrum.chat/react-testing-library -* Reactiflux on Discord - https://www.reactiflux.com -* Stack Overflow +- React Spectrum https://spectrum.chat/react-testing-library +- Reactiflux on Discord https://www.reactiflux.com +- Stack Overflow https://stackoverflow.com/questions/tagged/react-testing-library **ISSUES WHICH ARE QUESTIONS WILL BE CLOSED** diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index aa0dc2b8..ee765ccf 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -34,9 +34,11 @@ merge of your pull request! -* [ ] Documentation -* [ ] Tests -* [ ] Ready to be merged -* [ ] Added myself to contributors table +- [ ] Documentation +- [ ] Tests +- [ ] Ready to be merged + +- [ ] Added myself to contributors table + diff --git a/.prettierrc b/.prettierrc index fb31ee19..f3685197 100644 --- a/.prettierrc +++ b/.prettierrc @@ -6,5 +6,6 @@ "singleQuote": true, "trailingComma": "all", "bracketSpacing": false, - "jsxBracketSameLine": false + "jsxBracketSameLine": false, + "proseWrap": "always" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 06d221aa..2a675299 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ # CHANGELOG -The changelog is automatically updated using [semantic-release](https://github.com/semantic-release/semantic-release). -You can see it on the [releases page](../../releases). +The changelog is automatically updated using +[semantic-release](https://github.com/semantic-release/semantic-release). You +can see it on the [releases page](../../releases). diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index afe24327..070cb5f6 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -5,30 +5,30 @@ In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, gender identity and expression, level of experience, -nationality, personal appearance, race, religion, or sexual identity and -orientation. +size, disability, ethnicity, gender identity and expression, level of +experience, nationality, personal appearance, race, religion, or sexual identity +and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members Examples of unacceptable behavior by participants include: -* The use of sexualized language or imagery and unwelcome sexual attention or +- The use of sexualized language or imagery and unwelcome sexual attention or advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a +- Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities @@ -37,11 +37,11 @@ Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. +Project maintainers have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, or to ban temporarily or permanently any +contributor for other behaviors that they deem inappropriate, threatening, +offensive, or harmful. ## Scope @@ -58,8 +58,9 @@ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at kent+coc@doddsfamily.us. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. +obligated to maintain confidentiality with regard to the reporter of an +incident. Further details of specific enforcement policies may be posted +separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other @@ -67,8 +68,8 @@ members of the project's leadership. ## Attribution -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at [http://contributor-covenant.org/version/1/4][version] +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2cb6bdbf..8642e953 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,8 +2,8 @@ Thanks for being willing to contribute! -**Working on your first Pull Request?** You can learn how from this _free_ series -[How to Contribute to an Open Source Project on GitHub][egghead] +**Working on your first Pull Request?** You can learn how from this _free_ +series [How to Contribute to an Open Source Project on GitHub][egghead] ## Project setup @@ -20,33 +20,31 @@ Thanks for being willing to contribute! > git branch --set-upstream-to=upstream/master master > ``` > -> This will add the original repository as a "remote" called "upstream," -> Then fetch the git information from that remote, then set your local `master` -> branch to use the upstream master branch whenever you run `git pull`. -> Then you can make all of your pull request branches based on this `master` -> branch. Whenever you want to update your version of `master`, do a regular -> `git pull`. +> This will add the original repository as a "remote" called "upstream," Then +> fetch the git information from that remote, then set your local `master` +> branch to use the upstream master branch whenever you run `git pull`. Then you +> can make all of your pull request branches based on this `master` branch. +> Whenever you want to update your version of `master`, do a regular `git pull`. ## Add yourself as a contributor -This project follows the [all contributors][all-contributors] specification. -To add yourself to the table of contributors on the `README.md`, please use the +This project follows the [all contributors][all-contributors] specification. To +add yourself to the table of contributors on the `README.md`, please use the automated script as part of your PR: ```console npm run add-contributor ``` -Follow the prompt and commit `.all-contributorsrc` and `README.md` in the PR. -If you've already added yourself to the list and are making -a new type of contribution, you can run it again and select the added -contribution type. +Follow the prompt and commit `.all-contributorsrc` and `README.md` in the PR. If +you've already added yourself to the list and are making a new type of +contribution, you can run it again and select the added contribution type. ## Committing and Pushing changes Please make sure to run the tests before you commit your changes. You can run -`npm run test:update` which will update any snapshots that need updating. -Make sure to include those changes (if they exist) in your commit. +`npm run test:update` which will update any snapshots that need updating. Make +sure to include those changes (if they exist) in your commit. ### opt into git hooks @@ -67,6 +65,7 @@ Please checkout the [the open issues][issues] Also, please watch the repo and respond to questions/bug reports/feature requests! Thanks! -[egghead]: https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github +[egghead]: + https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github [all-contributors]: https://github.com/kentcdodds/all-contributors [issues]: https://github.com/kentcdodds/react-testing-library/issues diff --git a/README.md b/README.md index 7625525e..112f86af 100644 --- a/README.md +++ b/README.md @@ -12,13 +12,11 @@ [![Build Status][build-badge]][build] [![Code Coverage][coverage-badge]][coverage] -[![version][version-badge]][package] -[![downloads][downloads-badge]][npmtrends] +[![version][version-badge]][package] [![downloads][downloads-badge]][npmtrends] [![MIT License][license-badge]][license] [![All Contributors](https://img.shields.io/badge/all_contributors-33-orange.svg?style=flat-square)](#contributors) -[![PRs Welcome][prs-badge]][prs] -[![Code of Conduct][coc-badge]][coc] +[![PRs Welcome][prs-badge]][prs] [![Code of Conduct][coc-badge]][coc] [![Join the community on Spectrum][spectrum-badge]][spectrum] [![Watch on GitHub][github-watch-badge]][github-watch] @@ -28,9 +26,9 @@ ## The problem You want to write maintainable tests for your React components. As a part of -this goal, you want your tests to avoid including implementation details of -your components and rather focus on making your tests give you the confidence -for which they are intended. As part of this, you want your testbase to be +this goal, you want your tests to avoid including implementation details of your +components and rather focus on making your tests give you the confidence for +which they are intended. As part of this, you want your testbase to be maintainable in the long run so refactors of your components (changes to implementation but not functionality) don't break your tests and slow you and your team down. @@ -39,10 +37,11 @@ your team down. The `react-testing-library` is a very light-weight solution for testing React components. It provides light utility functions on top of `react-dom` and -`react-dom/test-utils`, in a way that encourages better testing practices. -It's primary guiding principle is: +`react-dom/test-utils`, in a way that encourages better testing practices. It's +primary guiding principle is: -> [The more your tests resemble the way your software is used, the more confidence they can give you.][guiding-principle] +> [The more your tests resemble the way your software is used, the more +> confidence they can give you.][guiding-principle] So rather than dealing with instances of rendered react components, your tests will work with actual DOM nodes. The utilities this library provides facilitate @@ -66,8 +65,8 @@ facilitate testing implementation details). Read more about this in **What this library is not**: 1. A test runner or framework -2. Specific to a testing framework (though we recommend Jest as our - preference, the library works with any framework) +2. Specific to a testing framework (though we recommend Jest as our preference, + the library works with any framework) > NOTE: This library is built on top of > [`dom-testing-library`](https://github.com/kentcdodds/dom-testing-library) @@ -81,13 +80,11 @@ facilitate testing implementation details). Read more about this in - [Installation](#installation) - [Usage](#usage) - [`render`](#render) - - [`renderIntoDocument`](#renderintodocument) - [`cleanup`](#cleanup) - - [`Simulate`](#simulate) - - [`wait`](#wait) - - [`waitForElement`](#waitforelement) +- [`dom-testing-library` APIs](#dom-testing-library-apis) - [`fireEvent(node: HTMLElement, event: Event)`](#fireeventnode-htmlelement-event-event) - - [`prettyDOM`](#prettydom) + - [`waitForElement`](#waitforelement) + - [`wait`](#wait) - [`TextMatch`](#textmatch) - [`query` APIs](#query-apis) - [`queryAll` and `getAll` APIs](#queryall-and-getall-apis) @@ -124,12 +121,15 @@ You may also be interested in installing `jest-dom` so you can use ```javascript // __tests__/fetch.js import React from 'react' -import {render, Simulate, wait} from 'react-testing-library' +import {render, fireEvent, cleanup, waitForElement} from 'react-testing-library' // this add custom jest matchers from jest-dom import 'jest-dom/extend-expect' import axiosMock from 'axios' // the mock lives in a __mocks__ directory import Fetch from '../fetch' // see the tests for a full implementation +// automatically unmount and cleanup DOM after the test is finished. +afterEach(cleanup) + test('Fetch makes an API call and displays the greeting when load-greeting is clicked', async () => { // Arrange axiosMock.get.mockResolvedValueOnce({data: {greeting: 'hello there'}}) @@ -137,11 +137,13 @@ test('Fetch makes an API call and displays the greeting when load-greeting is cl const {getByText, getByTestId, container} = render() // Act - Simulate.click(getByText('Load Greeting')) + fireEvent.click(getByText('Load Greeting')) // let's wait for our mocked `get` request promise to resolve // wait will wait until the callback doesn't throw an error - await wait(() => getByTestId('greeting-text')) + const greetingTextNode = await waitForElement(() => + getByTestId('greeting-text'), + ) // Assert expect(axiosMock.get).toHaveBeenCalledTimes(1) @@ -155,6 +157,46 @@ test('Fetch makes an API call and displays the greeting when load-greeting is cl ### `render` +Defined as: + +```typescript +function render( + ui: React.ReactElement, + options?: { + /* You wont often use this, expand below for docs on options */ + }, +): RenderResult +``` + +Render into a container which is appended to `document.body`. It should be used +with [cleanup](#cleanup): + +```javascript +import {render, cleanup} from 'react-testing-library' + +afterEach(cleanup) + +render(
) +``` + +
+ +Expand to see documentation on the options + +You wont often need to specify options, but if you ever do, here are the +available options which you could provide as a second argument to `render`. + +**container**: By default, `react-testing-library` will create a `div` and +append that div to the `document.body` and this is where your react component +will be rendered. If you provide your own HTMLElement `container` via this +option, it will not be appended to the `document.body` automatically. + +**baseElement**: If the `container` is specified, then this defaults to that, +otherwise this defaults to `document.documentElement`. This is used as the base +element for the queries as well as what is printed when you use `debug()`. + +
+ In the example above, the `render` method returns an object that has a few properties: @@ -164,11 +206,13 @@ The containing DOM node of your rendered React Element (rendered using `ReactDOM.render`). It's a `div`. This is a regular DOM node, so you can call `container.querySelector` etc. to inspect the children. -> Tip: To get the root element of your rendered element, use `container.firstChild`. +> Tip: To get the root element of your rendered element, use +> `container.firstChild`. > > NOTE: When that root element is a -> [React Fragment](https://reactjs.org/docs/fragments.html), `container.firstChild` -> will only get the first child of that Fragment, not the Fragment itself. +> [React Fragment](https://reactjs.org/docs/fragments.html), +> `container.firstChild` will only get the first child of that Fragment, not the +> Fragment itself. #### `debug` @@ -183,9 +227,11 @@ debug() //
//

Hello World

//
+// you can also pass an element: debug(getByTestId('messages')) ``` -Learn more about [`prettyDOM`](#prettydom) below. +This is a simple wrapper around `prettyDOM` which is also exposed and comes from +[`dom-testing-library`](https://github.com/kentcdodds/dom-testing-library/blob/master/README.md#prettydom). #### `rerender` @@ -264,8 +310,8 @@ const inputNode = getByLabelText('username', {selector: 'input'}) #### `getByPlaceholderText(text: TextMatch): HTMLElement` -This will search for all elements with a placeholder attribute and find one -that matches the given [`TextMatch`](#textmatch). +This will search for all elements with a placeholder attribute and find one that +matches the given [`TextMatch`](#textmatch). ```javascript import {render} from 'react-testing-library' @@ -296,7 +342,9 @@ text. Note that it only supports elements which accept an `alt` attribute: [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img), [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input), and [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/area) -(intentionally excluding [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/applet) as it's deprecated). +(intentionally excluding +[``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/applet) +as it's deprecated). ```javascript import {render} from 'react-testing-library' @@ -321,180 +369,95 @@ const usernameInputElement = getByTestId('username-input') > In the spirit of [the guiding principles](#guiding-principles), it is > recommended to use this only after `getByLabel`, `getByPlaceholderText` or -> `getByText` don't work for your use case. Using data-testid attributes do -> not resemble how your software is used and should be avoided if possible. -> That said, they are _way_ better than querying based on DOM structure. -> Learn more about `data-testid`s from the blog post -> ["Making your UI tests resilient to change"][data-testid-blog-post] - -### `renderIntoDocument` - -Render into `document.body`. Should be used with [cleanup](#cleanup). -`renderIntoDocument` will return the same object as [render](#render) -but with test utilities exposed for `document.documentElement` rather -than the `container` alone. This helps to test -[React portal](https://reactjs.org/docs/portals.html) elements. - -```javascript -import {renderIntoDocument} from 'react-testing-library' - -renderIntoDocument(
) -``` +> `getByText` don't work for your use case. Using `data-testid` attributes do +> not resemble how your software is used and should be avoided if possible. That +> said, they are _way_ better than querying based on DOM structure. Learn more +> about `data-testid`s from the blog post ["Making your UI tests resilient to +> change"][data-testid-blog-post] ### `cleanup` -Unmounts React trees that were mounted with [renderIntoDocument](#renderintodocument). +Unmounts React trees that were mounted with [render](#render). ```javascript -import {cleanup, renderIntoDocument} from 'react-testing-library' +import {cleanup, render} from 'react-testing-library' afterEach(cleanup) test('renders into document', () => { - renderIntoDocument(
) + render(
) // ... }) ``` -Failing to call `cleanup` when you've called `renderIntoDocument` could -result in a memory leak and tests which are not `idempotent` (which can -lead to difficult to debug errors in your tests). - -### `Simulate` +Failing to call `cleanup` when you've called `render` could result in a memory +leak and tests which are not "idempotent" (which can lead to difficult to debug +errors in your tests). -This is simply a re-export from the `Simulate` utility from -`react-dom/test-utils`. See [the docs](https://reactjs.org/docs/test-utils.html#simulate). +**If you don't want to add this to _every single test file_** then we recommend +that you configure your test framework to run a file before your tests which +does this automatically. -Note: `Simulate` does not simulate _browser_ events, meaning if you have an element like +For example, to do this with jest, you can use +[`setupTestFrameworkScriptFile`](https://facebook.github.io/jest/docs/en/configuration.html#setuptestframeworkscriptfile-string): ```javascript - +// jest.config.js +module.exports = { + setupTestFrameworkScriptFile: require.resolve('./test/setup-test-env.js'), +} ``` -calling `Simulate.click` will not cause the submit event to be invoked. In order to get around this and for more info, see [`fireEvent`](#fireeventnode-htmlelement-event-event). - -In general, it is better to use `fireEvent` whenever possible because it mimics more closely what happens in the browser when an event happens. - -### `wait` - -Defined as: - -```typescript -function wait( - callback?: () => void, - options?: { - timeout?: number - interval?: number - }, -): Promise -``` - -When in need to wait for non-deterministic periods of time you can use `wait`, -to wait for your expectations to pass. The `wait` function is a small wrapper -around the -[`wait-for-expect`](https://github.com/TheBrainFamily/wait-for-expect) module. -Here's a simple example: +Then: ```javascript -import {render, wait} from 'react-testing-library' +// test/setup-test-env.js -test('waiting for an expectation to pass before proceeding', async () => { - const {getByLabelText} = render() - - // wait until the callback does not throw an error. In this case, that means - // it'll wait until we can get a form control with a label that matches "username" - await wait(() => getByLabelText('username')) - getByLabelText('username').value = 'chucknorris' -}) +// add some helpful assertions +import 'jest-dom/extend-expect' +// this is basically: afterEach(cleanup) +import 'react-testing-library/cleanup-after-each' ``` -This can be useful if you have a unit test that mocks API calls and you need -to wait for your mock promises to all resolve. This can also be useful when -(for example) you integration test your apollo-connected react components that -go a couple level deep, with queries fired up in consequent components. - -The default `callback` is a no-op function (used like `await wait()`). This can -be helpful if you only need to wait for one tick of the event loop. +Or if you're using react-scripts (create-react-app), it has a default value +that's set to `src/setupTests.js` so put the code above in that file. -The default `timeout` is `4500ms` which will keep you under -[Jest's default timeout of `5000ms`](https://facebook.github.io/jest/docs/en/jest-object.html#jestsettimeouttimeout). +## `dom-testing-library` APIs -The default `interval` is `50ms`. However it will run your callback immediately -on the next tick of the event loop (in a `setTimeout`) before starting the -intervals. - -> NOTE: `wait`'s callback can be called many times and because of this you should use as few `wait` calls as possible in each test and put minimum amount of code that absolutely needs to be waited on inside each `wait`'s `callback`. This will help your tests run faster and can avoid unnecessary runtime complexity. - -### `waitForElement` - -See [dom-testing-library#waitForElement](https://github.com/kentcdodds/dom-testing-library#waitforelement) - -```js -import {render, waitForElement} from 'react-testing-library' - -test('waiting for an element', async () => { - const {getByText} = render() - - await waitForElement(() => getByText('Search')) -}) -``` - -
- - Example - - -```diff -import {render, Simulate, waitForElement} from 'react-testing-library' - -test('should submit form when valid', async () => { - const mockSubmit = jest.fn() - const { - container, - getByLabelText, - getByText - } = render(
) - const nameInput = getByLabelText('Name') - nameInput.value = 'Chewbacca' - Simulate.change(nameInput) -+ // wait for button to appear and click it -+ const submitButton = await waitForElement(() => getByText('Search')) -+ Simulate.click(submitButton) -+ expect(mockSubmit).toBeCalled() -}) -``` - -
+`react-testing-library` is built on top of +[`dom-testing-library`](https://github.com/kentcdodds/dom-testing-library) and +re-exports everything from `dom-testing-library`. Some notable included exports: ### `fireEvent(node: HTMLElement, event: Event)` Fire DOM events. React attaches an event handler on the `document` and handles some DOM events -via event delegation (events bubbling up from a `target` to an ancestor). Because -of this, your `node` must be in the `document.body` for `fireEvent` to work with -React. You can render into the document using the -[renderIntoDocument](#renderintodocument) utility. This is an alternative to -simulating Synthetic React Events via [Simulate](#simulate). The benefit of +via event delegation (events bubbling up from a `target` to an ancestor). +Because of this, your `node` must be in the `document.body` for `fireEvent` to +work with React. This is why `render` appends your container to `document.body`. +This is an alternative to simulating Synthetic React Events via +[`Simulate`](https://reactjs.org/docs/test-utils.html#simulate). The benefit of using `fireEvent` over `Simulate` is that you are testing real DOM events instead of Synthetic Events. This aligns better with [the Guiding Principles](#guiding-principles). +([Also Dan Abramov told me to stop use Simulate](https://twitter.com/dan_abramov/status/980807288444383232)). -> NOTE: If you don't like having to render into the document to get `fireEvent` -> working, then feel free to try to chip into making it possible for React -> to attach event handlers to the rendered node rather than the `document`. -> Learn more here: +> NOTE: If you don't like having to use `cleanup` (which we have to do because +> we render into `document.body`) to get `fireEvent` working, then feel free to +> try to chip into making it possible for React to attach event handlers to the +> rendered node rather than the `document`. Learn more here: > [facebook/react#2043](https://github.com/facebook/react/issues/2043) ```javascript -import {renderIntoDocument, cleanup, fireEvent} from 'react-testing-library' +import {render, cleanup, fireEvent} from 'react-testing-library' // don't forget to clean up the document.body afterEach(cleanup) test('clicks submit button', () => { const spy = jest.fn() - const {getByText} = renderIntoDocument() + const {getByText} = render() fireEvent( getByText('Submit'), @@ -526,32 +489,39 @@ fireEvent.click(getElementByText('Submit'), rightClick) // default `button` property for click events is set to `0` which is a left click. ``` -### `prettyDOM` +### `waitForElement` + +> [Read full docs from `dom-testing-library`](https://github.com/kentcdodds/dom-testing-library/blob/master/README.md#waitforelement) -This helper function can be used to print out readable representation of the DOM -tree of a node. This can be helpful for instance when debugging tests. +```js +import {render, waitForElement} from 'react-testing-library' -It is defined as: +test('waiting for an element', async () => { + const {getByText} = render() -```typescript -function prettyDOM(node: HTMLElement, maxLength?: number): string + await waitForElement(() => getByText('Search')) +}) ``` -It receives the root node to print out, and an optional extra argument to limit -the size of the resulting string, for cases when it becomes too large. +### `wait` + +> [Read full docs from `dom-testing-library`](https://github.com/kentcdodds/dom-testing-library/blob/master/README.md#wait) -This function is usually used alongside `console.log` to temporarily print out -DOM trees during tests for debugging purposes: +It's recommended to prefer `waitForElement`, but this can be helpful on occasion ```javascript -import {render, prettyDOM} from 'react-testing-library' +import 'jest-dom/extend-expect' +import {render, wait} from 'react-testing-library' -const HelloWorld = () =>

Hello World

-const {container} = render() -console.log(prettyDOM(container)) -//
-//

Hello World

-//
+test('can fill in the form after loaded', async () => { + const {getByLabelText} = render() + + // wait until the callback does not throw an error. In this case, that means + // it'll wait until the element with the text that says "loading..." is gone. + await wait(() => expect(queryByText(/loading\.\.\./i).not.toBeInTheDOM()) + getByLabelText('username').value = 'chucknorris' + // continue doing stuff +}) ``` ## `TextMatch` @@ -612,7 +582,9 @@ expect(submitButton).toBeNull() // it doesn't exist ## `queryAll` and `getAll` APIs -Each of the `query` APIs have a corresponsing `queryAll` version that always returns an Array of matching nodes. `getAll` is the same but throws when the array has a length of 0. +Each of the `query` APIs have a corresponding `queryAll` version that always +returns an Array of matching nodes. `getAll` is the same but throws when the +array has a length of 0. ```javascript import {render} from 'react-testing-library' @@ -625,6 +597,9 @@ expect(submitButtons[0]).toBeInTheDOM() ## Examples +> We're in the process of moving examples to +> [`react-testing-library-examples`](https://codesandbox.io/s/github/kentcdodds/react-testing-library-examples). + You'll find examples of testing with different libraries in [the `examples` directory](https://github.com/kentcdodds/react-testing-library/blob/master/examples). Some included are: @@ -638,12 +613,17 @@ Some included are: - [Confident React](https://www.youtube.com/watch?v=qXRPHRgcXJ0&list=PLV5CVI1eNcJgNqzNwcs4UKrlJdhfDjshf) - [Test Driven Development with react-testing-library](https://www.youtube.com/watch?v=kCR3JAR7CHE&list=PLV5CVI1eNcJgCrPH_e6d57KRUTiDZgs0u) - [Testing React and Web Applications](https://kentcdodds.com/workshops/#testing-react-and-web-applications) -- [Build a joke app with TDD](https://medium.com/@mbaranovski/quick-guide-to-tdd-in-react-81888be67c64) by [@mbaranovski](https://github.com/mbaranovski) -- [Build a comment feed with TDD](https://medium.freecodecamp.org/how-to-build-sturdy-react-apps-with-tdd-and-the-react-testing-library-47ad3c5c8e47) by [@iwilsonq](https://github.com/iwilsonq) -- [A clear way to unit testing React JS components using Jest and react-testing-library](https://www.richardkotze.com/coding/react-testing-library-jest) by [Richard Kotze](https://github.com/rkotze) - -- [Intro to react-testing-library](https://chrisnoring.gitbooks.io/react/content/testing/react-testing-library.html) by [Chris Noring](https://github.com/softchris) -- [Integration testing in React](https://medium.com/@jeffreyrussom/integration-testing-in-react-21f92a55a894) by [Jeffrey Russom](https://github.com/qswitcher) +- [Build a joke app with TDD](https://medium.com/@mbaranovski/quick-guide-to-tdd-in-react-81888be67c64) + by [@mbaranovski](https://github.com/mbaranovski) +- [Build a comment feed with TDD](https://medium.freecodecamp.org/how-to-build-sturdy-react-apps-with-tdd-and-the-react-testing-library-47ad3c5c8e47) + by [@iwilsonq](https://github.com/iwilsonq) +- [A clear way to unit testing React JS components using Jest and react-testing-library](https://www.richardkotze.com/coding/react-testing-library-jest) + by [Richard Kotze](https://github.com/rkotze) + +- [Intro to react-testing-library](https://chrisnoring.gitbooks.io/react/content/testing/react-testing-library.html) + by [Chris Noring](https://github.com/softchris) +- [Integration testing in React](https://medium.com/@jeffreyrussom/integration-testing-in-react-21f92a55a894) + by [Jeffrey Russom](https://github.com/qswitcher) Feel free to contribute more! @@ -654,18 +634,19 @@ Feel free to contribute more! Which get method should I use? Based on [the Guiding Principles](#guiding-principles), your test should -resemble how your code (component, page, etc.) is used as much as possible. With this -in mind, we recommend this order of priority: +resemble how your code (component, page, etc.) is used as much as possible. With +this in mind, we recommend this order of priority: 1. `getByLabelText`: Only really good for form fields, but this is the number 1 method a user finds those elements, so it should be your top preference. -2. `getByPlaceholderText`: [A placeholder is not a substitute for a label](https://www.nngroup.com/articles/form-design-placeholders/). +2. `getByPlaceholderText`: + [A placeholder is not a substitute for a label](https://www.nngroup.com/articles/form-design-placeholders/). But if that's all you have, then it's better than alternatives. 3. `getByText`: Not useful for forms, but this is the number 1 method a user finds other elements (like buttons to click), so it should be your top preference for non-form elements. -4. `getByAltText`: If your element is one which supports `alt` text - (`img`, `area`, and `input`), then you can use this to find that element. +4. `getByAltText`: If your element is one which supports `alt` text (`img`, + `area`, and `input`), then you can use this to find that element. 5. `getByTestId`: The user cannot see (or hear) these, so this is only recommended for cases where you can't match by text or it doesn't make sense (the text is dynamic). @@ -680,15 +661,16 @@ component as well (using the regular Can I write unit tests with this library? -Definitely yes! You can write unit and integration tests with this library. -See below for more on how to mock dependencies (because this library -intentionally does NOT support shallow rendering) if you want to unit test a -high level component. The tests in this project show several examples of -unit testing with this library. +Definitely yes! You can write unit and integration tests with this library. See +below for more on how to mock dependencies (because this library intentionally +does NOT support shallow rendering) if you want to unit test a high level +component. The tests in this project show several examples of unit testing with +this library. As you write your tests, keep in mind: -> The more your tests resemble the way your software is used, the more confidence they can give you. - [17 Feb 2018][guiding-principle] +> The more your tests resemble the way your software is used, the more +> confidence they can give you. - [17 Feb 2018][guiding-principle] @@ -696,10 +678,10 @@ As you write your tests, keep in mind: What if my app is localized and I don't have access to the text in test? -This is fairly common. Our first bit of advice is to try to get the default -text used in your tests. That will make everything much easier (more than just -using this utility). If that's not possible, then you're probably best -to just stick with `data-testid`s (which is not bad anyway). +This is fairly common. Our first bit of advice is to try to get the default text +used in your tests. That will make everything much easier (more than just using +this utility). If that's not possible, then you're probably best to just stick +with `data-testid`s (which is not bad anyway). @@ -730,7 +712,7 @@ test('you can mock things with jest.mock', () => { ) expect(queryByTestId('hidden-message')).toBeTruthy() // we just care it exists // hide the message - Simulate.click(getByTestId('toggle-message')) + fireEvent.click(getByTestId('toggle-message')) // in the real world, the CSSTransition component would take some time // before finishing the animation which would actually hide the message. // So we've mocked it out for our tests to make it happen instantly @@ -761,7 +743,11 @@ Learn more about how Jest mocks work from my blog post: What if I want to verify that an element does NOT exist? -You typically will get access to rendered elements using the `getByTestId` utility. However, that function will throw an error if the element isn't found. If you want to specifically test for the absence of an element, then you should use the `queryByTestId` utility which will return the element if found or `null` if not. +You typically will get access to rendered elements using the `getByTestId` +utility. However, that function will throw an error if the element isn't found. +If you want to specifically test for the absence of an element, then you should +use the `queryByTestId` utility which will return the element if found or `null` +if not. ```javascript expect(queryByTestId('thing-that-does-not-exist')).toBeNull() @@ -782,8 +768,8 @@ may want to run some E2E tests that run on the same code you're about to ship to production. In that case, the `data-testid` attributes will be valuable there as well. -All that said, if you really don't want to ship `data-testid` attributes, then you -can use +All that said, if you really don't want to ship `data-testid` attributes, then +you can use [this simple babel plugin](https://www.npmjs.com/package/babel-plugin-react-remove-properties) to remove them. @@ -802,7 +788,8 @@ const rootElement = container.firstChild What if I’m iterating over a list of items that I want to put the data-testid="item" attribute on. How do I distinguish them from each other? -You can make your selector just choose the one you want by including :nth-child in the selector. +You can make your selector just choose the one you want by including :nth-child +in the selector. ```javascript const thirdLiInUl = container.querySelector('ul > li:nth-child(3)') @@ -840,7 +827,8 @@ state/properties) (most of enzyme's wrapper APIs allow this). The guiding principle for this library is: -> The more your tests resemble the way your software is used, the more confidence they can give you. - [17 Feb 2018][guiding-principle] +> The more your tests resemble the way your software is used, the more +> confidence they can give you. - [17 Feb 2018][guiding-principle] Because users can't directly interact with your app's component instances, assert on their internal state or what components they render, or call their @@ -857,14 +845,13 @@ react components. Why isn't snapshot diffing working? -If you use the -[snapshot-diff](https://github.com/jest-community/snapshot-diff) -library to save snapshot diffs, it won't work out of the box because -this library uses the DOM which is mutable. Changes don't return new -objects so snapshot-diff will think it's the same object and avoid diffing it. +If you use the [snapshot-diff](https://github.com/jest-community/snapshot-diff) +library to save snapshot diffs, it won't work out of the box because this +library uses the DOM which is mutable. Changes don't return new objects so +snapshot-diff will think it's the same object and avoid diffing it. -Luckily there's an easy way to make it work: clone the DOM when -passing it into snapshot-diff. It looks like this: +Luckily there's an easy way to make it work: clone the DOM when passing it into +snapshot-diff. It looks like this: ```js const firstVersion = container.cloneNode(true) @@ -878,15 +865,15 @@ snapshotDiff(firstVersion, container.cloneNode(true)) In preparing this project, [I tweeted about it](https://twitter.com/kentcdodds/status/974278185540964352) -and -[Sune Simonsen](https://github.com/sunesimonsen) +and [Sune Simonsen](https://github.com/sunesimonsen) [took up the challenge](https://twitter.com/sunesimonsen/status/974784783908818944). We had different ideas of what to include in the library, so I decided to create this one instead. ## Guiding Principles -> [The more your tests resemble the way your software is used, the more confidence they can give you.][guiding-principle] +> [The more your tests resemble the way your software is used, the more +> confidence they can give you.][guiding-principle] We try to only expose methods and utilities that encourage you to write tests that closely resemble how your react components are used. @@ -926,7 +913,8 @@ Contributions of any kind welcome! ## Issues -_Looking to contribute? Look for the [Good First Issue][good-first-issue] label._ +_Looking to contribute? Look for the [Good First Issue][good-first-issue] +label._ ### šŸ› Bugs @@ -958,6 +946,8 @@ MIT Links: --> + + [npm]: https://www.npmjs.com/ [node]: https://nodejs.org [build-badge]: https://img.shields.io/travis/kentcdodds/react-testing-library.svg?style=flat-square @@ -994,3 +984,5 @@ Links: [good-first-issue]: https://github.com/kentcdodds/react-testing-library/issues?utf8=āœ“&q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc+label%3A"good+first+issue"+ [reactiflux]: https://www.reactiflux.com/ [stackoverflow]: https://stackoverflow.com/questions/tagged/react-testing-library + + diff --git a/cleanup-after-each.js b/cleanup-after-each.js new file mode 100644 index 00000000..f0a17c95 --- /dev/null +++ b/cleanup-after-each.js @@ -0,0 +1 @@ +afterEach(require('./dist').cleanup) diff --git a/examples/README.md b/examples/README.md index 6eda8302..9c89757e 100644 --- a/examples/README.md +++ b/examples/README.md @@ -26,5 +26,6 @@ To run the tests, you can run `npm test examples`, or if you're working on a specific example, you can run `npm test name-of-your-file`. This will put you into Jest's interactive watch mode with a filter based on the name you provided. -[contributing]: https://github.com/kentcdodds/react-testing-library/blob/master/CONTRIBUTING.md +[contributing]: + https://github.com/kentcdodds/react-testing-library/blob/master/CONTRIBUTING.md [jest-dom]: https://github.com/gnapse/jest-dom diff --git a/examples/__tests__/mock.react-transition-group.js b/examples/__tests__/mock.react-transition-group.js index 2f161a4c..ef22636f 100644 --- a/examples/__tests__/mock.react-transition-group.js +++ b/examples/__tests__/mock.react-transition-group.js @@ -1,6 +1,6 @@ import React from 'react' import {CSSTransition} from 'react-transition-group' -import {render, Simulate} from 'react-testing-library' +import {render, fireEvent, cleanup} from 'react-testing-library' function Fade({children, ...props}) { return ( @@ -27,6 +27,8 @@ class HiddenMessage extends React.Component { } } +afterEach(cleanup) + jest.mock('react-transition-group', () => { const FakeTransition = jest.fn(({children}) => children) const FakeCSSTransition = jest.fn( @@ -40,7 +42,7 @@ test('you can mock things with jest.mock', () => { const {getByText, queryByText} = render() expect(getByText('Hello world')).toBeTruthy() // we just care it exists // hide the message - Simulate.click(getByText('Toggle')) + fireEvent.click(getByText('Toggle')) // in the real world, the CSSTransition component would take some time // before finishing the animation which would actually hide the message. // So we've mocked it out for our tests to make it happen instantly diff --git a/examples/__tests__/react-context.js b/examples/__tests__/react-context.js index 9cc8ef8f..4c709429 100644 --- a/examples/__tests__/react-context.js +++ b/examples/__tests__/react-context.js @@ -1,8 +1,10 @@ import React from 'react' -import {render} from 'react-testing-library' +import {render, cleanup} from 'react-testing-library' import 'jest-dom/extend-expect' import {NameContext, NameProvider, NameConsumer} from '../react-context' +afterEach(cleanup) + /** * Test default values by rendering a context consumer without a * matching provider diff --git a/examples/__tests__/react-redux.js b/examples/__tests__/react-redux.js index f5ee1764..acfb3369 100644 --- a/examples/__tests__/react-redux.js +++ b/examples/__tests__/react-redux.js @@ -1,7 +1,7 @@ import React from 'react' import {createStore} from 'redux' import {Provider, connect} from 'react-redux' -import {render, Simulate} from 'react-testing-library' +import {render, fireEvent, cleanup} from 'react-testing-library' // counter.js class Counter extends React.Component { @@ -61,6 +61,8 @@ function reducer(state = {count: 0}, action) { // Now here's what your test will look like: +afterEach(cleanup) + // this is a handy function that I normally make available for all my tests // that deal with connected components. // you can provide initialState or the entire store that the ui is rendered with @@ -79,7 +81,7 @@ function renderWithRedux( test('can render with redux with defaults', () => { const {getByTestId, getByText} = renderWithRedux() - Simulate.click(getByText('+')) + fireEvent.click(getByText('+')) expect(getByTestId('count-value').textContent).toBe('1') }) @@ -87,7 +89,7 @@ test('can render with redux with custom initial state', () => { const {getByTestId, getByText} = renderWithRedux(, { initialState: {count: 3}, }) - Simulate.click(getByText('-')) + fireEvent.click(getByText('-')) expect(getByTestId('count-value').textContent).toBe('2') }) @@ -97,8 +99,8 @@ test('can render with redux with custom store', () => { const {getByTestId, getByText} = renderWithRedux(, { store, }) - Simulate.click(getByText('+')) + fireEvent.click(getByText('+')) expect(getByTestId('count-value').textContent).toBe('1000') - Simulate.click(getByText('-')) + fireEvent.click(getByText('-')) expect(getByTestId('count-value').textContent).toBe('1000') }) diff --git a/examples/__tests__/react-router.js b/examples/__tests__/react-router.js index f21bb967..02f4c7d2 100644 --- a/examples/__tests__/react-router.js +++ b/examples/__tests__/react-router.js @@ -2,7 +2,7 @@ import React from 'react' import {withRouter} from 'react-router' import {Link, Route, Router, Switch} from 'react-router-dom' import {createMemoryHistory} from 'history' -import {render, Simulate} from 'react-testing-library' +import {render, fireEvent, cleanup} from 'react-testing-library' const About = () =>
You are on the about page
const Home = () =>
You are home
@@ -29,6 +29,8 @@ function App() { // Ok, so here's what your tests might look like +afterEach(cleanup) + // this is a handy function that I would utilize for any component // that relies on the router being in context function renderWithRouter( @@ -49,7 +51,7 @@ test('full app rendering/navigating', () => { // normally I'd use a data-testid, but just wanted to show this is also possible expect(container.innerHTML).toMatch('You are home') const leftClick = {button: 0} - Simulate.click(getByText(/about/i), leftClick) + fireEvent.click(getByText(/about/i), leftClick) // normally I'd use a data-testid, but just wanted to show this is also possible expect(container.innerHTML).toMatch('You are on the about page') }) diff --git a/examples/__tests__/shallow.react-transition-group.js b/examples/__tests__/shallow.react-transition-group.js index 3124ef37..fe7ddead 100644 --- a/examples/__tests__/shallow.react-transition-group.js +++ b/examples/__tests__/shallow.react-transition-group.js @@ -1,6 +1,6 @@ import React from 'react' import {CSSTransition} from 'react-transition-group' -import {render, Simulate} from 'react-testing-library' +import {render, fireEvent, cleanup} from 'react-testing-library' function Fade({children, ...props}) { return ( @@ -27,6 +27,8 @@ class HiddenMessage extends React.Component { } } +afterEach(cleanup) + jest.mock('react-transition-group', () => { const FakeCSSTransition = jest.fn(() => null) return {CSSTransition: FakeCSSTransition} @@ -41,7 +43,7 @@ test('you can mock things with jest.mock', () => { {in: true, ...defaultProps}, context, ) - Simulate.click(getByText(/toggle/i)) + fireEvent.click(getByText(/toggle/i)) expect(CSSTransition).toHaveBeenCalledWith( {in: true, ...defaultProps}, expect.any(Object), diff --git a/examples/__tests__/update-props.js b/examples/__tests__/update-props.js index 58731fec..c245b9ff 100644 --- a/examples/__tests__/update-props.js +++ b/examples/__tests__/update-props.js @@ -3,7 +3,7 @@ // that your first call created for you. import React from 'react' -import {render} from 'react-testing-library' +import {render, cleanup} from 'react-testing-library' let idCounter = 1 @@ -19,6 +19,8 @@ class NumberDisplay extends React.Component { } } +afterEach(cleanup) + test('calling render with the same component on the same container does not remount', () => { const {getByTestId, rerender} = render() expect(getByTestId('number-display').textContent).toBe('1') diff --git a/other/MAINTAINING.md b/other/MAINTAINING.md index 025b6732..703126da 100644 --- a/other/MAINTAINING.md +++ b/other/MAINTAINING.md @@ -4,60 +4,67 @@ This is documentation for maintainers of this project. ## Code of Conduct -Please review, understand, and be an example of it. Violations of the code of conduct are -taken seriously, even (especially) for maintainers. +Please review, understand, and be an example of it. Violations of the code of +conduct are taken seriously, even (especially) for maintainers. ## Issues -We want to support and build the community. We do that best by helping people learn to solve -their own problems. We have an issue template and hopefully most folks follow it. If it's -not clear what the issue is, invite them to create a minimal reproduction of what they're trying -to accomplish or the bug they think they've found. +We want to support and build the community. We do that best by helping people +learn to solve their own problems. We have an issue template and hopefully most +folks follow it. If it's not clear what the issue is, invite them to create a +minimal reproduction of what they're trying to accomplish or the bug they think +they've found. Once it's determined that a code change is necessary, point people to -[makeapullrequest.com](http://makeapullrequest.com) and invite them to make a pull request. -If they're the one who needs the feature, they're the one who can build it. If they need -some hand holding and you have time to lend a hand, please do so. It's an investment into -another human being, and an investment into a potential maintainer. +[makeapullrequest.com](http://makeapullrequest.com) and invite them to make a +pull request. If they're the one who needs the feature, they're the one who can +build it. If they need some hand holding and you have time to lend a hand, +please do so. It's an investment into another human being, and an investment +into a potential maintainer. -Remember that this is open source, so the code is not yours, it's ours. If someone needs a change -in the codebase, you don't have to make it happen yourself. Commit as much time to the project -as you want/need to. Nobody can ask any more of you than that. +Remember that this is open source, so the code is not yours, it's ours. If +someone needs a change in the codebase, you don't have to make it happen +yourself. Commit as much time to the project as you want/need to. Nobody can ask +any more of you than that. ## Pull Requests -As a maintainer, you're fine to make your branches on the main repo or on your own fork. Either -way is fine. +As a maintainer, you're fine to make your branches on the main repo or on your +own fork. Either way is fine. -When we receive a pull request, a travis build is kicked off automatically (see the `.travis.yml` -for what runs in the travis build). We avoid merging anything that breaks the travis build. +When we receive a pull request, a travis build is kicked off automatically (see +the `.travis.yml` for what runs in the travis build). We avoid merging anything +that breaks the travis build. -Please review PRs and focus on the code rather than the individual. You never know when this is -someone's first ever PR and we want their experience to be as positive as possible, so be -uplifting and constructive. +Please review PRs and focus on the code rather than the individual. You never +know when this is someone's first ever PR and we want their experience to be as +positive as possible, so be uplifting and constructive. When you merge the pull request, 99% of the time you should use the -[Squash and merge](https://help.github.com/articles/merging-a-pull-request/) feature. This keeps -our git history clean, but more importantly, this allows us to make any necessary changes to the -commit message so we release what we want to release. See the next section on Releases for more -about that. +[Squash and merge](https://help.github.com/articles/merging-a-pull-request/) +feature. This keeps our git history clean, but more importantly, this allows us +to make any necessary changes to the commit message so we release what we want +to release. See the next section on Releases for more about that. ## Release -Our releases are automatic. They happen whenever code lands into `master`. A travis build gets -kicked off and if it's successful, a tool called -[`semantic-release`](https://github.com/semantic-release/semantic-release) is used to -automatically publish a new release to npm as well as a changelog to GitHub. It is only able to -determine the version and whether a release is necessary by the git commit messages. With this -in mind, **please brush up on [the commit message convention][commit] which drives our releases.** +Our releases are automatic. They happen whenever code lands into `master`. A +travis build gets kicked off and if it's successful, a tool called +[`semantic-release`](https://github.com/semantic-release/semantic-release) is +used to automatically publish a new release to npm as well as a changelog to +GitHub. It is only able to determine the version and whether a release is +necessary by the git commit messages. With this in mind, **please brush up on +[the commit message convention][commit] which drives our releases.** -> One important note about this: Please make sure that commit messages do NOT contain the words -> "BREAKING CHANGE" in them unless we want to push a major version. I've been burned by this -> more than once where someone will include "BREAKING CHANGE: None" and it will end up releasing -> a new major version. Not a huge deal honestly, but kind of annoying... +> One important note about this: Please make sure that commit messages do NOT +> contain the words "BREAKING CHANGE" in them unless we want to push a major +> version. I've been burned by this more than once where someone will include +> "BREAKING CHANGE: None" and it will end up releasing a new major version. Not +> a huge deal honestly, but kind of annoying... ## Thanks! Thank you so much for helping to maintain this project! -[commit]: https://github.com/conventional-changelog-archived-repos/conventional-changelog-angular/blob/ed32559941719a130bb0327f886d6a32a8cbc2ba/convention.md +[commit]: + https://github.com/conventional-changelog-archived-repos/conventional-changelog-angular/blob/ed32559941719a130bb0327f886d6a32a8cbc2ba/convention.md diff --git a/other/manual-releases.md b/other/manual-releases.md index 2834084e..1e9a7365 100644 --- a/other/manual-releases.md +++ b/other/manual-releases.md @@ -1,9 +1,10 @@ # manual-releases -This project has an automated release set up. So things are only released when there are -useful changes in the code that justify a release. But sometimes things get messed up one way or another -and we need to trigger the release ourselves. When this happens, simply bump the number below and commit -that with the following commit message based on your needs: +This project has an automated release set up. So things are only released when +there are useful changes in the code that justify a release. But sometimes +things get messed up one way or another and we need to trigger the release +ourselves. When this happens, simply bump the number below and commit that with +the following commit message based on your needs: **Major** diff --git a/package.json b/package.json index 8b7a09b6..2fe2e7bb 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ }, "files": [ "dist", - "typings" + "typings", + "cleanup-after-each.js" ], "keywords": [ "testing", @@ -36,22 +37,22 @@ "author": "Kent C. Dodds (http://kentcdodds.com/)", "license": "MIT", "dependencies": { - "dom-testing-library": "^2.3.1", - "wait-for-expect": "^0.5.0" + "dom-testing-library": "^2.6.0", + "wait-for-expect": "^1.0.0" }, "devDependencies": { - "@types/react-dom": "^16.0.5", + "@types/react-dom": "^16.0.6", "axios": "^0.18.0", "eslint-import-resolver-jest": "^2.1.1", "history": "^4.7.2", - "jest-dom": "^1.0.0", + "jest-dom": "^1.3.1", "jest-in-case": "^1.0.2", - "kcd-scripts": "^0.37.0", - "react": "^16.3.2", - "react-dom": "^16.3.2", + "kcd-scripts": "^0.39.1", + "react": "^16.4.1", + "react-dom": "^16.4.1", "react-redux": "^5.0.7", - "react-router": "^4.2.0", - "react-router-dom": "^4.2.2", + "react-router": "^4.3.1", + "react-router-dom": "^4.3.1", "react-transition-group": "^2.3.1", "redux": "^4.0.0" }, diff --git a/src/__tests__/debug.js b/src/__tests__/debug.js index 4cab1ea9..f7c0a927 100644 --- a/src/__tests__/debug.js +++ b/src/__tests__/debug.js @@ -1,11 +1,12 @@ import React from 'react' -import {render} from '../' +import {render, cleanup} from '../' beforeEach(() => { jest.spyOn(console, 'log').mockImplementation(() => {}) }) afterEach(() => { + cleanup() console.log.mockRestore() }) diff --git a/src/__tests__/end-to-end.js b/src/__tests__/end-to-end.js index 6768dbe7..e8ad2f8b 100644 --- a/src/__tests__/end-to-end.js +++ b/src/__tests__/end-to-end.js @@ -1,5 +1,7 @@ import React from 'react' -import {render, wait} from '../' +import {render, wait, cleanup} from '../' + +afterEach(cleanup) const fetchAMessage = () => new Promise(resolve => { diff --git a/src/__tests__/events.js b/src/__tests__/events.js index 73fd1175..679412e8 100644 --- a/src/__tests__/events.js +++ b/src/__tests__/events.js @@ -1,5 +1,5 @@ import React from 'react' -import {renderIntoDocument, cleanup, fireEvent} from '../' +import {render, cleanup, fireEvent} from '../' const eventTypes = [ { @@ -141,7 +141,7 @@ eventTypes.forEach(({type, events, elementType, init}) => { const ref = React.createRef() const spy = jest.fn() - renderIntoDocument( + render( React.createElement(elementType, { [propName]: spy, ref, diff --git a/src/__tests__/fetch.js b/src/__tests__/fetch.js index 58a23fe4..c607508c 100644 --- a/src/__tests__/fetch.js +++ b/src/__tests__/fetch.js @@ -1,6 +1,8 @@ import React from 'react' import axiosMock from 'axios' -import {render, Simulate, wait} from '../' +import {render, fireEvent, cleanup, wait} from '../' + +afterEach(cleanup) // instead of importing it, we'll define it inline here // import Fetch from '../fetch' @@ -34,7 +36,7 @@ test('Fetch makes an API call and displays the greeting when load-greeting is cl const {container, getByText} = render() // Act - Simulate.click(getByText('Fetch')) + fireEvent.click(getByText('Fetch')) await wait() diff --git a/src/__tests__/forms.js b/src/__tests__/forms.js index 4371f005..6da3e06b 100644 --- a/src/__tests__/forms.js +++ b/src/__tests__/forms.js @@ -1,5 +1,7 @@ import React from 'react' -import {render, Simulate} from '../' +import {render, fireEvent, cleanup} from '../' + +afterEach(cleanup) function Login({onSubmit}) { return ( @@ -32,29 +34,20 @@ function Login({onSubmit}) { test('login form submits', () => { const fakeUser = {username: 'jackiechan', password: 'hiya! šŸ„‹'} const handleSubmit = jest.fn() - const {container, getByLabelText, getByText} = render( - , - ) + const {getByLabelText, getByText} = render() const usernameNode = getByLabelText(/username/i) const passwordNode = getByLabelText(/password/i) - const formNode = container.querySelector('form') const submitButtonNode = getByText(/submit/i) // Act usernameNode.value = fakeUser.username passwordNode.value = fakeUser.password - // 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 simulate a submit on the form itself - // then ensure that there's a submit button. - Simulate.submit(formNode) + fireEvent.click(submitButtonNode) // Assert expect(handleSubmit).toHaveBeenCalledTimes(1) expect(handleSubmit).toHaveBeenCalledWith(fakeUser) - // make sure the form is submittable - expect(submitButtonNode.type).toBe('submit') }) /* eslint jsx-a11y/label-has-for:0 */ diff --git a/src/__tests__/render-into-document.js b/src/__tests__/render.js similarity index 80% rename from src/__tests__/render-into-document.js rename to src/__tests__/render.js index 2b7351fe..066accd1 100644 --- a/src/__tests__/render-into-document.js +++ b/src/__tests__/render.js @@ -1,16 +1,16 @@ import React from 'react' -import {renderIntoDocument, cleanup} from '../' +import {render, cleanup} from '../' afterEach(cleanup) it('renders button into document', () => { const ref = React.createRef() - const {container} = renderIntoDocument(
) + const {container} = render(
) expect(container.firstChild).toBe(ref.current) }) it('access portal elements inside body', () => { - const {getByText} = renderIntoDocument(
) + const {getByText} = render(
) const portalComponent = document.createElement('div') portalComponent.appendChild(document.createTextNode('Hello World')) document.body.appendChild(portalComponent) @@ -31,7 +31,7 @@ it('cleansup document', () => { } } - renderIntoDocument() + render() cleanup() expect(document.body.innerHTML).toBe('') expect(spy).toHaveBeenCalledTimes(1) diff --git a/src/__tests__/rerender.js b/src/__tests__/rerender.js index 2a1c332d..79f80875 100644 --- a/src/__tests__/rerender.js +++ b/src/__tests__/rerender.js @@ -1,7 +1,9 @@ import React from 'react' -import {render} from '../' +import {render, cleanup} from '../' import 'jest-dom/extend-expect' +afterEach(cleanup) + test('rerender will re-render the element', () => { const Greeting = props =>
{props.message}
const {container, rerender} = render() diff --git a/src/__tests__/stopwatch.js b/src/__tests__/stopwatch.js index 1dab173a..34f58561 100644 --- a/src/__tests__/stopwatch.js +++ b/src/__tests__/stopwatch.js @@ -1,5 +1,7 @@ import React from 'react' -import {render, Simulate} from '../' +import {render, cleanup, fireEvent} from '../' + +afterEach(cleanup) class StopWatch extends React.Component { state = {lapse: 0, running: false} @@ -42,7 +44,7 @@ const wait = time => new Promise(resolve => setTimeout(resolve, time)) test('unmounts a component', async () => { jest.spyOn(console, 'error').mockImplementation(() => {}) const {unmount, getByText, container} = render() - Simulate.click(getByText('Start')) + fireEvent.click(getByText('Start')) unmount() // hey there reader! You don't need to have an assertion like this one // this is just me making sure that the unmount function works. @@ -51,7 +53,6 @@ test('unmounts a component', async () => { // just wait to see if the interval is cleared or not // if it's not, then we'll call setState on an unmounted component // and get an error. - await wait() // eslint-disable-next-line no-console - expect(console.error).not.toHaveBeenCalled() + await wait(() => expect(console.error).not.toHaveBeenCalled()) }) diff --git a/src/index.js b/src/index.js index 3d328dc9..7b2d403b 100644 --- a/src/index.js +++ b/src/index.js @@ -2,18 +2,27 @@ import ReactDOM from 'react-dom' import {Simulate} from 'react-dom/test-utils' import {getQueriesForElement, prettyDOM} from 'dom-testing-library' -function render( - ui, - {container = document.createElement('div'), baseElement = container} = {}, -) { +const mountedContainers = new Set() + +function render(ui, {container, baseElement = container} = {}) { + if (!container) { + baseElement = document.documentElement + container = document.body.appendChild(document.createElement('div')) + } + + // we'll add it to the mounted containers regardless of whether it's actually + // added to document.body so the cleanup method works regardless of whether + // they're passing us a custom container or not. + mountedContainers.add(container) + ReactDOM.render(ui, container) return { container, // eslint-disable-next-line no-console - debug: () => console.log(prettyDOM(baseElement)), + debug: (el = baseElement) => console.log(prettyDOM(el)), unmount: () => ReactDOM.unmountComponentAtNode(container), rerender: rerenderUi => { - render(rerenderUi, {container}) + render(rerenderUi, {container, baseElement}) // Intentionally do not return anything to avoid unnecessarily complicating the API. // folks can use all the same utilities we return in the first place that are bound to the container }, @@ -21,20 +30,16 @@ function render( } } -const mountedContainers = new Set() - -function renderIntoDocument(ui) { - const container = document.body.appendChild(document.createElement('div')) - mountedContainers.add(container) - return render(ui, {container, baseElement: document.documentElement}) +function cleanup() { + mountedContainers.forEach(cleanupAtContainer) } -function cleanup() { - mountedContainers.forEach(container => { - document.body.removeChild(container) - ReactDOM.unmountComponentAtNode(container) - mountedContainers.delete(container) - }) +// maybe one day we'll expose this (perhaps even as a utility returned by render). +// but let's wait until someone asks for it. +function cleanupAtContainer(container) { + document.body.removeChild(container) + ReactDOM.unmountComponentAtNode(container) + mountedContainers.delete(container) } // fallback to synthetic events for React events that the DOM doesn't support @@ -47,4 +52,4 @@ syntheticEvents.forEach(eventName => { // just re-export everything from dom-testing-library export * from 'dom-testing-library' -export {render, Simulate, renderIntoDocument, cleanup} +export {render, cleanup} diff --git a/typings/index.d.ts b/typings/index.d.ts index e698dc57..3623952d 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -50,11 +50,9 @@ export interface RenderResult extends GetsAndQueries { export function render( ui: React.ReactElement, - options?: {container: HTMLElement}, + options?: {container: HTMLElement; baseElement: HTMLElement}, ): RenderResult -export const Simulate: typeof ReactSimulate - export function wait( callback?: () => void, options?: { @@ -150,8 +148,6 @@ type FireObject = { export const fireEvent: FireFunction & FireObject -export function renderIntoDocument(ui: React.ReactElement): RenderResult - export function cleanup(): void export function getQueriesForElement(element: HTMLElement): GetsAndQueries