diff --git a/README.md b/README.md index 0f1c20ad..858b1d1b 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,14 @@

Simple and complete DOM testing utilities that encourage good testing practices.

+ +[**Read the docs**](https://react-testing-library-docs.netlify.com/) | [Edit the docs](https://github.com/alexkrolick/react-testing-library-docs) +
+ [![Build Status][build-badge]][build] [![Code Coverage][coverage-badge]][coverage] @@ -32,6 +36,23 @@ +## Table of Contents + + + + +- [The Problem](#the-problem) +- [This Solution](#this-solution) +- [Example](#example) +- [Installation](#installation) +- [Implementations](#implementations) +- [Other Solutions](#other-solutions) +- [Guiding Principles](#guiding-principles) +- [Contributors](#contributors) +- [LICENSE](#license) + + + ## The problem You want to write maintainable tests for your Web UI. As a part of @@ -54,68 +75,6 @@ principle is: > [The more your tests resemble the way your software is used, the more confidence they can give you.][guiding-principle] -As part of this goal, the utilities this library provides facilitate querying -the DOM in the same way the user would. Finding for elements by their label text -(just like a user would), finding links and buttons from their text -(like a user would), and more. It also exposes a recommended way to find -elements by a `data-testid` as an "escape hatch" for elements where the text -content and label do not make sense or is not practical. - -This library encourages your applications to be more accessible and allows you -to get your tests closer to using your components the way a user will, which -allows your tests to give you more confidence that your application will work -when a real user uses it. - -**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. See [Using Without Jest](#using-without-jest)) - -## Table of Contents - - - - -- [Installation](#installation) -- [Usage](#usage) - - [`getByLabelText`](#getbylabeltext) - - [`getByPlaceholderText`](#getbyplaceholdertext) - - [`getByText`](#getbytext) - - [`getByAltText`](#getbyalttext) - - [`getByTitle`](#getbytitle) - - [`getByDisplayValue`](#getbydisplayvalue) - - [`getByRole`](#getbyrole) - - [`getByTestId`](#getbytestid) - - [`wait`](#wait) - - [`waitForElement`](#waitforelement) - - [`waitForDomChange`](#waitfordomchange) - - [`fireEvent`](#fireevent) - - [`getNodeText`](#getnodetext) -- [Custom Jest Matchers](#custom-jest-matchers) -- [Custom Queries](#custom-queries) - - [Using other assertion libraries](#using-other-assertion-libraries) -- [`TextMatch`](#textmatch) - - [Precision](#precision) - - [Normalization](#normalization) - - [TextMatch Examples](#textmatch-examples) -- [`query` APIs](#query-apis) -- [`queryAll` and `getAll` APIs](#queryall-and-getall-apis) -- [`within` and `getQueriesForElement` APIs](#within-and-getqueriesforelement-apis) -- [Debugging](#debugging) - - [`prettyDOM`](#prettydom) -- [Configuration](#configuration) -- [Great companion](#great-companion) -- [Implementations](#implementations) -- [Using Without Jest](#using-without-jest) -- [FAQ](#faq) -- [Other Solutions](#other-solutions) -- [Guiding Principles](#guiding-principles) -- [Contributors](#contributors) -- [LICENSE](#license) - - - ## Installation This module is distributed via [npm][npm] which is bundled with [node][node] and @@ -125,888 +84,7 @@ should be installed as one of your project's `devDependencies`: npm install --save-dev dom-testing-library ``` -## Usage - -Note: - -- Each of the `get` APIs below have a matching [`getAll`](#queryall-and-getall-apis) API that returns all elements instead of just the first one, and [`query`](#query-apis)/[`queryAll`](#queryall-and-getall-apis) that return `null`/`[]` instead of throwing an error. -- See [TextMatch](#textmatch) for details on the `exact`, `trim`, and `collapseWhitespace` options. - -```javascript -// src/__tests__/example.js -// query utilities: -import { - getByLabelText, - getByText, - getByTestId, - queryByTestId, - // Tip: all queries are also exposed on an object - // called "queries" which you could import here as well - wait, -} from 'dom-testing-library' -// adds special assertions like toHaveTextContent -import 'jest-dom/extend-expect' - -function getExampleDOM() { - // This is just a raw example of setting up some DOM - // that we can interact with. Swap this with your UI - // framework of choice šŸ˜‰ - const div = document.createElement('div') - div.innerHTML = ` - - - - ` - const button = div.querySelector('button') - const input = div.querySelector('input') - button.addEventListener('click', () => { - // let's pretend this is making a server request, so it's async - // (you'd want to mock this imaginary request in your unit tests)... - setTimeout(() => { - const printedUsernameContainer = document.createElement('div') - printedUsernameContainer.innerHTML = ` -
${input.value}
- ` - div.appendChild(printedUsernameContainer) - }, Math.floor(Math.random() * 200)) - }) - return div -} - -test('examples of some things', async () => { - const famousWomanInHistory = 'Ada Lovelace' - const container = getExampleDOM() - - // Get form elements by their label text. - // An error will be thrown if one cannot be found (accessibility FTW!) - const input = getByLabelText(container, 'Username') - input.value = famousWomanInHistory - - // Get elements by their text, just like a real user does. - getByText(container, 'Print Username').click() - - await wait(() => - expect(queryByTestId(container, 'printed-username')).toBeTruthy(), - ) - - // getByTestId and queryByTestId are an escape hatch to get elements - // by a test id (could also attempt to get this element by it's text) - expect(getByTestId(container, 'printed-username')).toHaveTextContent( - famousWomanInHistory, - ) - // jest snapshots work great with regular DOM nodes! - expect(container).toMatchSnapshot() -}) -``` - -### `getByLabelText` - -```typescript -getByLabelText( - container: HTMLElement, - text: TextMatch, - options?: { - selector?: string = '*', - exact?: boolean = true, - normalizer?: NormalizerFn, - }): HTMLElement -``` - -This will search for the label that matches the given [`TextMatch`](#textmatch), -then find the element associated with that label. - -```javascript -const inputNode = getByLabelText(container, 'Username') - -// this would find the input node for the following DOM structures: -// The "for" attribute (NOTE: in JSX with React you'll write "htmlFor" rather than "for") -// -// -// -// The aria-labelledby attribute with form elements -// -// -// -// The aria-labelledby attribute with other elements -//
-//

Section One

-//

some content...

-//
-// -// Wrapper labels -// -// -// It will NOT find the input node for this: -// -// -// For this case, you can provide a `selector` in the options: -const inputNode = getByLabelText(container, 'username', {selector: 'input'}) -// and that would work -// Note that will also work, but take -// care because this is not a label that users can see on the page. So -// the purpose of your input should be obvious for those users. -``` - -> Note: This method will throw an error if it cannot find the node. If you don't -> want this behavior (for example you wish to assert that it doesn't exist), -> then use `queryByLabelText` instead. - -### `getByPlaceholderText` - -```typescript -getByPlaceholderText( - container: HTMLElement, - text: TextMatch, - options?: { - exact?: boolean = true, - normalizer?: NormalizerFn, - }): HTMLElement -``` - -This will search for all elements with a placeholder attribute and find one -that matches the given [`TextMatch`](#textmatch). - -```javascript -// -const inputNode = getByPlaceholderText(container, 'Username') -``` - -> NOTE: a placeholder is not a good substitute for a label so you should -> generally use `getByLabelText` instead. - -### `getByText` - -```typescript -getByText( - container: HTMLElement, - text: TextMatch, - options?: { - selector?: string = '*', - exact?: boolean = true, - ignore?: string|boolean = 'script, style', - normalizer?: NormalizerFn, - }): HTMLElement -``` - -This will search for all elements that have a text node with `textContent` -matching the given [`TextMatch`](#textmatch). - -```javascript -// About ā„¹ļø -const aboutAnchorNode = getByText(container, /about/i) -``` - -It also works properly with `input`s whose `type` attribute is either `submit` -or `button`: - -```javascript -// -const submitButton = getByText(container, /send data/i) -``` - -> NOTE: see [`getByLabelText`](#getbylabeltext) for more details on how and when to use the `selector` option - -The `ignore` option accepts a query selector. If the -[`node.matches`](https://developer.mozilla.org/en-US/docs/Web/API/Element/matches) -returns true for that selector, the node will be ignored. This defaults to -`'script'` because generally you don't want to select script tags, but if your -content is in an inline script file, then the script tag could be returned. - -If you'd rather disable this behavior, set `ignore` to `false`. - -### `getByAltText` - -```typescript -getByAltText( - container: HTMLElement, - text: TextMatch, - options?: { - exact?: boolean = true, - normalizer?: NormalizerFn, - }): HTMLElement -``` - -This will return the element (normally an ``) that has the given `alt` -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). - -```javascript -// Incredibles 2 Poster -const incrediblesPosterImg = getByAltText(container, /incredibles.*poster$/i) -``` - -### `getByTitle` - -```typescript -getByTitle( - container: HTMLElement, - title: TextMatch, - options?: { - exact?: boolean = true, - normalizer?: NormalizerFn, - }): HTMLElement -``` - -Returns the element that has the matching `title` attribute. - -```javascript -// -const deleteElement = getByTitle(container, 'Delete') -``` - -Will also find a `title` element within an SVG. - -```javascript -// Close -const closeElement = getByTitle(container, 'Close') -``` - -### `getByDisplayValue` - -```typescript -getByDisplayValue( - container: HTMLElement, - value: TextMatch, - options?: { - exact?: boolean = true, - normalizer?: NormalizerFn, - }): HTMLElement -``` - -Returns the `input`, `textarea`, or `select` element that has the matching display value. - -#### `input` - -```javascript -// -// document.getElementById('lastName').value = 'Norris' - -const lastNameInput = getByDisplayValue(container, 'Norris') -``` - -#### `textarea` - -```javascript -// -// document.getElementById('messageTextArea').value = 'Hello World' - -const messageTextArea = getByDisplayValue(container, 'Hello World') -``` - -#### `select` - -```javascript -// - -const selectElement = getByDisplayName(container, 'Alaska') -``` - -In case of `select`, this will search for a ` -const usernameInputElement = getByTestId(container, 'username-input') -``` - -> In the spirit of [the guiding principles](#guiding-principles), it is -> recommended to use this only after the other queries 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 or styling css class names. Learn more about -> `data-testid`s from the blog post -> ["Making your UI tests resilient to change"][data-testid-blog-post] - -#### Overriding `data-testid` - -The `...ByTestId` functions in `dom-testing-library` use the attribute `data-testid` -by default, following the precedent set by -[React Native Web](https://github.com/kentcdodds/react-testing-library/issues/1) -which uses a `testID` prop to emit a `data-testid` attribute on the element, -and we recommend you adopt that attribute where possible. -But if you already have an existing codebase that uses a different attribute -for this purpose, you can override this value via -`configure({testIdAttribute: 'data-my-test-attribute'})`. - -### `wait` - -```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: - -```javascript -// ... -// 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(container, 'username')) -getByLabelText(container, 'username').value = 'chucknorris' -// ... -``` - -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. - -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 (in the case -of mocked API calls with promises that resolve immediately). - -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). - -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. - -### `waitForElement` - -```typescript -function waitForElement( - callback: () => T, - options?: { - container?: HTMLElement - timeout?: number - mutationObserverOptions?: MutationObserverInit - }, -): Promise -``` - -When in need to wait for DOM elements to appear, disappear, or change you can use `waitForElement`. -The `waitForElement` function is a small wrapper around the [`MutationObserver`](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver). - -Here's a simple example: - -```javascript -// ... -// Wait until the callback does not throw an error and returns a truthy value. In this case, that means -// it'll wait until we can get a form control with a label that matches "username". -// The difference from `wait` is that rather than running your callback on -// an interval, it's run as soon as there are DOM changes in the container -// and returns the value returned by the callback. -const usernameElement = await waitForElement( - () => getByLabelText(container, 'username'), - {container}, -) -usernameElement.value = 'chucknorris' -// ... -``` - -You can also wait for multiple elements at once: - -```javascript -const [usernameElement, passwordElement] = await waitForElement( - () => [ - getByLabelText(container, 'username'), - getByLabelText(container, 'password'), - ], - {container}, -) -``` - -Using [`MutationObserver`](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) is more efficient than polling the DOM at regular intervals with `wait`. This library sets up a [`'mutationobserver-shim'`](https://github.com/megawac/MutationObserver.js) on the global `window` object for cross-platform compatibility with older browsers and the [`jsdom`](https://github.com/jsdom/jsdom/issues/639) that is usually used in Node-based tests. - -The default `container` is the global `document`. Make sure the elements you wait for will be attached to it, or set a different `container`. - -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). - -The default `mutationObserverOptions` is `{subtree: true, childList: true, attributes: true, characterData: true}` which will detect -additions and removals of child elements (including text nodes) in the `container` and any of its descendants. It will also detect attribute changes. - -### `waitForDomChange` - -```typescript -function waitForDomChange(options?: { - container?: HTMLElement - timeout?: number - mutationObserverOptions?: MutationObserverInit -}): Promise -``` - -When in need to wait for the DOM to change you can use `waitForDomChange`. The `waitForDomChange` -function is a small wrapper around the -[`MutationObserver`](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver). - -Here is an example where the promise will be resolved because the container is changed: - -```javascript -const container = document.createElement('div') -waitForDomChange({container}) - .then(() => console.log('DOM changed!')) - .catch(err => console.log(`Error you need to deal with: ${err}`)) -container.append(document.createElement('p')) -// if šŸ‘† was the only code affecting the container and it was not run, -// waitForDomChange would throw an error -``` - -The promise will resolve with a [`mutationsList`](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver/MutationObserver) which you can use to determine what kind of a change (or changes) affected the container - -```javascript -const container = document.createElement('div') -container.setAttribute('data-cool', 'true') -waitForDomChange({container}).then(mutationsList => { - const mutation = mutationsList[0] - console.log( - `was cool: ${mutation.oldValue}\ncurrently cool: ${ - mutation.target.dataset.cool - }`, - ) -}) -container.setAttribute('data-cool', 'false') -/* - logs: - was cool: true - currently cool: false -*/ -``` - -Using [`MutationObserver`](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) is more efficient than polling the DOM at regular intervals with `wait`. This library sets up a [`'mutationobserver-shim'`](https://github.com/megawac/MutationObserver.js) on the global `window` object for cross-platform compatibility with older browsers and the [`jsdom`](https://github.com/jsdom/jsdom/issues/639) that is usually used in Node-based tests. - -The default `container` is the global `document`. Make sure the elements you wait for will be attached to it, or set a different `container`. - -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). - -The default `mutationObserverOptions` is `{subtree: true, childList: true, attributes: true, characterData: true}` which will detect -additions and removals of child elements (including text nodes) in the `container` and any of its descendants. It will also detect attribute changes. - -### `fireEvent` - -```typescript -fireEvent(node: HTMLElement, event: Event) -``` - -Fire DOM events. - -```javascript -// -fireEvent( - getByText(container, 'Submit'), - new MouseEvent('click', { - bubbles: true, - cancelable: true, - }), -) -``` - -#### `fireEvent[eventName]` - -```typescript -fireEvent[eventName](node: HTMLElement, eventProperties: Object) -``` - -Convenience methods for firing DOM events. Check out -[src/events.js](https://github.com/kentcdodds/dom-testing-library/blob/master/src/events.js) -for a full list as well as default `eventProperties`. - -```javascript -// -const rightClick = {button: 2} -fireEvent.click(getByText('Submit'), rightClick) -// default `button` property for click events is set to `0` which is a left click. -``` - -**target**: When an event is dispatched on an element, the event has the -subjected element on a property called `target`. As a convenience, if you -provide a `target` property in the `eventProperties` (second argument), then -those properties will be assigned to the node which is receiving the event. - -This is particularly useful for a change event: - -```javascript -fireEvent.change(getByLabelText(/username/i), {target: {value: 'a'}}) - -// note: attempting to manually set the files property of an HTMLInputElement -// results in an error as the files property is read-only. -// this feature works around that by using Object.defineProperty. -fireEvent.change(getByLabelText(/picture/i), { - target: { - files: [new File(['(āŒā–”_ā–”)'], 'chucknorris.png', {type: 'image/png'})], - }, -}) -``` - -**Keyboard events**: There are three event types related to keyboard input - `keyPress`, `keyDown`, and `keyUp`. When firing these you need to reference an element in the DOM and the key you want to fire. - -```javascript -fireEvent.keyDown(domNode, {key: 'Enter', code: 13}) -``` - -You can find out which key code to use at [https://keycode.info/](https://keycode.info/). - -### `getNodeText` - -```typescript -getNodeText(node: HTMLElement) -``` - -Returns the complete text content of a html element, removing any extra -whitespace. The intention is to treat text in nodes exactly as how it is -perceived by users in a browser, where any extra whitespace within words in the -html code is not meaningful when the text is rendered. - -```javascript -//
-// Hello -// World ! -//
-const text = getNodeText(container.querySelector('div')) // "Hello World !" -``` - -This function is also used internally when querying nodes by their text content. -This enables functions like `getByText` and `queryByText` to work as expected, -finding elements in the DOM similarly to how users would do. - -The function has a special behavior for some input elements: - -```javascript -// -// -const submitText = getNodeText(container.querySelector('input[type=submit]')) // "Send data" -const buttonText = getNodeText(container.querySelector('input[type=button]')) // "Push me" - -These elements use the attribute `value` and display its value to the user. -``` - -## Custom Jest Matchers - -When using [jest][], it is convenient to import a set of custom matchers that -make it easier to check several aspects of the state of a DOM element. For -example, you can use the ones provided by -[jest-dom](https://github.com/gnapse/jest-dom): - -```javascript -import 'jest-dom/extend-expect' - -// Hello World -expect(queryByTestId(container, 'greetings')).not.toHaveTextContent('Bye bye') -// ... -``` - -> Note: when using some of these matchers, you may need to make sure -> you use a query function (like `queryByTestId`) rather than a get -> function (like `getByTestId`). Otherwise the `get*` function could -> throw an error before your assertion. - -Check out [jest-dom's documentation](https://github.com/gnapse/jest-dom#readme) -for a full list of available matchers. - -## Custom Queries - -`dom-testing-library` exposes many of the helper functions that are used to implement the default queries. You can use the helpers to build custom queries. For example, the code below shows a way to override the default `testId` queries to use a different data-attribute. (Note: test files would import `test-utils.js` instead of using `dom-testing-library` directly). - -```js -// test-utils.js -const domTestingLib = require('dom-testing-library') -const {queryHelpers} = domTestingLib - -export const queryByTestId = queryHelpers.queryByAttribute.bind( - null, - 'data-test-id', -) -export const queryAllByTestId = queryHelpers.queryAllByAttribute.bind( - null, - 'data-test-id', -) - -export function getAllByTestId(container, id, ...rest) { - const els = queryAllByTestId(container, id, ...rest) - if (!els.length) { - throw queryHelpers.getElementError( - `Unable to find an element by: [data-test-id="${id}"]`, - container, - ) - } - return els -} - -export function getByTestId(...args) { - return queryHelpers.firstResultOrNull(getAllByTestId, ...args) -} - -// re-export with overrides -module.exports = { - ...domTestingLib, - getByTestId, - getAllByTestId, - queryByTestId, - queryAllByTestId, -} -``` - -### Using other assertion libraries - -If you're not using jest, you may be able to find a similar set of custom -assertions for your library of choice. Here's a list of alternatives to jest-dom -for other popular assertion libraries: - -- [chai-dom](https://github.com/nathanboktae/chai-dom) - -If you're aware of some other alternatives, please [make a pull request][prs] -and add it here! - -## `TextMatch` - -Several APIs accept a `TextMatch` which can be a `string`, `regex` or a -`function` which returns `true` for a match and `false` for a mismatch. - -### Precision - -Some APIs accept an object as the final argument that can contain options that -affect the precision of string matching: - -- `exact`: Defaults to `true`; matches full strings, case-sensitive. When false, - matches substrings and is not case-sensitive. - - `exact` has no effect on `regex` or `function` arguments. - - In most cases using a regex instead of a string gives you more control over - fuzzy matching and should be preferred over `{ exact: false }`. -- `normalizer`: An optional function which overrides normalization behavior. - See [`Normalization`](#normalization). - -### Normalization - -Before running any matching logic against text in the DOM, `dom-testing-library` -automatically normalizes that text. By default, normalization consists of -trimming whitespace from the start and end of text, and collapsing multiple -adjacent whitespace characters into a single space. - -If you want to prevent that normalization, or provide alternative -normalization (e.g. to remove Unicode control characters), you can provide a -`normalizer` function in the options object. This function will be given -a string and is expected to return a normalized version of that string. - -Note: Specifying a value for `normalizer` _replaces_ the built-in normalization, but -you can call `getDefaultNormalizer` to obtain a built-in normalizer, either -to adjust that normalization or to call it from your own normalizer. - -`getDefaultNormalizer` takes an options object which allows the selection of behaviour: - -- `trim`: Defaults to `true`. Trims leading and trailing whitespace -- `collapseWhitespace`: Defaults to `true`. Collapses inner whitespace (newlines, tabs, repeated spaces) into a single space. - -#### Normalization Examples - -To perform a match against text without trimming: - -```javascript -getByText(node, 'text', {normalizer: getDefaultNormalizer({trim: false})}) -``` - -To override normalization to remove some Unicode characters whilst keeping some (but not all) of the built-in normalization behavior: - -```javascript -getByText(node, 'text', { - normalizer: str => - getDefaultNormalizer({trim: false})(str).replace(/[\u200E-\u200F]*/g, ''), -}) -``` - -### TextMatch Examples - -```javascript -//
-// Hello World -//
- -// WILL find the div: - -// Matching a string: -getByText(container, 'Hello World') // full string match -getByText(container, 'llo Worl', {exact: false}) // substring match -getByText(container, 'hello world', {exact: false}) // ignore case - -// Matching a regex: -getByText(container, /World/) // substring match -getByText(container, /world/i) // substring match, ignore case -getByText(container, /^hello world$/i) // full string match, ignore case -getByText(container, /Hello W?oRlD/i) // advanced regex - -// Matching with a custom function: -getByText(container, (content, element) => content.startsWith('Hello')) - -// WILL NOT find the div: - -getByText(container, 'Goodbye World') // full string does not match -getByText(container, /hello world/) // case-sensitive regex with different case -// function looking for a span when it's actually a div: -getByText(container, (content, element) => { - return element.tagName.toLowerCase() === 'span' && content.startsWith('Hello') -}) -``` - -## `query` APIs - -Each of the `get` APIs listed in [the 'Usage'](#usage) section above have a -complimentary `query` API. The `get` APIs will throw errors if a proper node -cannot be found. This is normally the desired effect. However, if you want to -make an assertion that an element is _not_ present in the DOM, then you can use -the `query` API instead: - -```javascript -const submitButton = queryByText(container, 'submit') -expect(submitButton).toBeNull() // it doesn't exist -// or if you're using the custom matchers: -expect(submitButton).not.toBeTruthy() -``` - -## `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. - -```javascript -const submitButtons = queryAllByText(container, 'submit') -expect(submitButtons).toHaveLength(3) // expect 3 elements -expect(submitButtons[0]).toBeTruthy() -``` - -## `within` and `getQueriesForElement` APIs - -`within` (an alias to `getQueriesForElement`) takes a DOM element and binds it to the raw query functions, allowing them -to be used without specifying a container. It is the recommended approach for libraries built on this API -and is in use in `react-testing-library` and `vue-testing-library`. - -Example: To get the text 'hello' only within a section called 'messages', you could do: - -```javascript -import {within} from 'dom-testing-library' - -const {getByText} = within(document.getElementById('messages')) -const helloMessage = getByText('hello') -``` - -## Debugging - -When you use any `get` calls in your test cases, the current state of the `container` -(DOM) gets printed on the console. For example: - -```javascript -//
Hello world
-getByText(container, 'Goodbye world') // will fail by throwing error -``` - -The above test case will fail, however it prints the state of your DOM under test, -so you will get to see: - -``` -Unable to find an element with the text: Goodbye world. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible. -Here is the state of your container: -
-
- Hello World! -
-
-``` - -Note: Since the DOM size can get really large, you can set the limit of DOM content -to be printed via environment variable `DEBUG_PRINT_LIMIT`. The default value is -`7000`. You will see `...` in the console, when the DOM content is stripped off, -because of the length you have set or due to default size limit. Here's how you -might increase this limit when running tests: - -``` -DEBUG_PRINT_LIMIT=10000 npm test -``` - -This works on macOS/linux, you'll need to do something else for windows. If you'd -like a solution that works for both, see [`cross-env`](https://www.npmjs.com/package/cross-env) - -### `prettyDOM` - -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. - -It is defined as: - -```typescript -function prettyDOM(node: HTMLElement, maxLength?: number): string -``` - -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. - -This function is usually used alongside `console.log` to temporarily print out -DOM trees during tests for debugging purposes: - -```javascript -const div = document.createElement('div') -div.innerHTML = '

Hello World

' -console.log(prettyDOM(div)) -//
-//

Hello World

-//
-``` - -This function is what also powers [the automatic debugging output described above](#debugging). - -## Configuration - -The library can be configured via the `configure` function, which accepts: - -- a plain JS object; this will be merged into the existing configuration. e.g. `configure({testIdAttribute: 'not-data-testid'})` -- a function; the function will be given the existing configuration, and should return a plain JS object which will be merged as above, e.g. - `configure(existingConfig => ({something: [...existingConfig.something, 'extra value for the something array']}))` - -Configuration options: - -`testIdAttribute`: The attribute used by `getByTestId` and related queries. -Defaults to `data-testid`. See [`getByTestId`](#getbytestid). - -## Great companion - -A helper library named [`user-event`](https://github.com/Gpx/user-event) has been written in companion with `dom-testing-library` which eases common interactions, such as typing, clicking etc. The `type`-interaction will allow to easily dispatch the appropriate keyboard events for inserting text, or dispatch these events for each character of the to be typed text. +> [**Docs**](https://react-testing-library-docs.netlify.com/docs/install) ## Implementations @@ -1017,143 +95,8 @@ Implementations include: - [`react-testing-library`](https://github.com/kentcdodds/react-testing-library) - [`pptr-testing-library`](https://github.com/patrickhulce/pptr-testing-library) - -## Using Without Jest - -If you're running your tests in the browser bundled with webpack (or similar) -then `dom-testing-library` should work out of the box for you. However, most -people using `dom-testing-library` are using it with -[the Jest testing framework](https://jestjs.io/) with the `testEnvironment` -set to [`jest-environment-jsdom`](https://www.npmjs.com/package/jest-environment-jsdom) -(which is the default configuration with Jest). - -[jsdom](https://github.com/jsdom/jsdom) is a pure JavaScript implementation -of the DOM and browser APIs that runs in node. If you're not using Jest and -you would like to run your tests in Node, then you must install jsdom yourself. -There's also a package called -[jsdom-global](https://github.com/rstacruz/jsdom-global) which can be used -to setup the global environment to simulate the browser APIs. - -First, install jsdom and jsdom-global. - -``` -npm install --save-dev jsdom jsdom-global -``` - -With mocha, the test command would look something like this: - -``` -mocha --require jsdom-global/register -``` - -> Note, depending on the version of Node you're running, you may also need to install -> `@babel/polyfill` (if you're using babel 7) or `babel-polyfill` (for babel 6). - -## FAQ - -
- -Which get method should I use? - -Based on [the Guiding Principles](#guiding-principles), your test should -resemble how your code (component, page, etc.) 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/). - 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. -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). - -Other than that, you can also use the `container` to query the rendered -component as well (using the regular -[`querySelector` API](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector)). - -
- -
- -Can I write unit tests with this library? - -Definitely yes! You can write unit, integration, functional, and end-to-end -tests with this library. - -
- -
- -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 too bad anyway). - -
- -
- -I really don't like data-testids, but none of the other queries make sense. Do I have to use a data-testid? - -Definitely not. That said, a common reason people don't like the `data-testid` -attribute is they're concerned about shipping that to production. I'd suggest -that you probably want some simple E2E tests that run in production on occasion -to make certain that things are working smoothly. In that case the `data-testid` -attributes will be very useful. Even if you don't run these in production, you -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 -[this simple babel plugin](https://www.npmjs.com/package/babel-plugin-react-remove-properties) -to remove them. - -If you don't want to use them at all, then you can simply use regular DOM -methods and properties to query elements off your container. - -```javascript -const firstLiInDiv = container.querySelector('div li') -const allLisInDiv = container.querySelectorAll('div li') -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. - -```javascript -const thirdLiInUl = container.querySelector('ul > li:nth-child(3)') -``` - -Or you could include the index or an ID in your attribute: - -```javascript -;`
  • {item.text}
  • ` -``` - -And then you could use the `getByTestId` utility: - -```javascript -const items = [ - /* your items */ -] -const container = render(/* however you render this stuff */) -const thirdItem = getByTestId(container, `item-${items[2].id}`) -``` - -
    +- [`cypress-testing-library`](https://github.com/kentcdodds/cypress-testing-library) +- [`vue-testing-library`](https://github.com/dfcook/vue-testing-library) ## Other Solutions