-
Notifications
You must be signed in to change notification settings - Fork 256
docs: move readme content to docs #704
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. Latest deployment of this branch, based on commit 07ee027:
|
Codecov Report
@@ Coverage Diff @@
## main #704 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 50 50
Lines 934 934
Branches 368 368
=========================================
Hits 934 934 Continue to review full report at Codecov.
|
| ## Table of Contents | ||
|
|
||
| <!-- START doctoc generated TOC please keep comment here to allow auto update --> | ||
| <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> | ||
|
|
||
| - [The problem](#the-problem) | ||
| - [The solution](#the-solution) | ||
| - [Installation](#installation) | ||
| - [Docs](#docs) | ||
| - [Known limitations](#known-limitations) | ||
| - [Issues](#issues) | ||
| - [🐛 Bugs](#-bugs) | ||
| - [💡 Feature Requests](#-feature-requests) | ||
| - [❓ Questions](#-questions) | ||
| - [Contributors](#contributors) | ||
| - [LICENSE](#license) | ||
|
|
||
| <!-- END doctoc generated TOC please keep comment here to allow auto update --> | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure ToC is even needed anymore as the README is a lot shorter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The DOM Testing Library readme is about the same length and still has a table of contents, so I'd rather be consistent here.
| ## Docs | ||
|
|
||
| ```js | ||
| userEvent.click(elem, {ctrlKey: true, shiftKey: true}) | ||
| ``` | ||
|
|
||
| See the | ||
| [`MouseEvent`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent) | ||
| constructor documentation for more options. | ||
|
|
||
| Note that `click` will trigger hover events before clicking. To disable this, | ||
| set the `skipHover` option to `true`. Also note that trying to click an element | ||
| with `pointer-events` being set to `"none"` (i.e. unclickable) will throw an | ||
| error. | ||
|
|
||
| ### `dblClick(element, eventInit, options)` | ||
|
|
||
| Clicks `element` twice, depending on what `element` is it can have different | ||
| side effects. | ||
|
|
||
| ```jsx | ||
| import React from 'react' | ||
| import {render, screen} from '@testing-library/react' | ||
| import userEvent from '@testing-library/user-event' | ||
|
|
||
| test('double click', () => { | ||
| const onChange = jest.fn() | ||
| render(<input type="checkbox" onChange={onChange} />) | ||
| const checkbox = screen.getByRole('checkbox') | ||
| userEvent.dblClick(checkbox) | ||
| expect(onChange).toHaveBeenCalledTimes(2) | ||
| expect(checkbox).not.toBeChecked() | ||
| }) | ||
| ``` | ||
|
|
||
| ### `type(element, text, [options])` | ||
|
|
||
| Writes `text` inside an `<input>` or a `<textarea>`. | ||
|
|
||
| ```jsx | ||
| import React from 'react' | ||
| import {render, screen} from '@testing-library/react' | ||
| import userEvent from '@testing-library/user-event' | ||
|
|
||
| test('type', () => { | ||
| render(<textarea />) | ||
|
|
||
| userEvent.type(screen.getByRole('textbox'), 'Hello,{enter}World!') | ||
| expect(screen.getByRole('textbox')).toHaveValue('Hello,\nWorld!') | ||
| }) | ||
| ``` | ||
|
|
||
| `options.delay` is the number of milliseconds that pass between two characters | ||
| are typed. By default it's 0. You can use this option if your component has a | ||
| different behavior for fast or slow users. If you do this, you need to make sure | ||
| to `await`! | ||
|
|
||
| `type` will click the element before typing. To disable this, set the | ||
| `skipClick` option to `true`. | ||
|
|
||
| #### Special characters | ||
|
|
||
| The following special character strings are supported: | ||
|
|
||
| | Text string | Key | Modifier | Notes | | ||
| | -------------- | ---------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ||
| | `{enter}` | Enter | N/A | Will insert a newline character (`<textarea />` only). | | ||
| | `{space}` | `' '` | N/A | | | ||
| | `{esc}` | Escape | N/A | | | ||
| | `{backspace}` | Backspace | N/A | Will delete the previous character (or the characters within the `selectedRange`, see example below). | | ||
| | `{del}` | Delete | N/A | Will delete the next character (or the characters within the `selectedRange`, see example below) | | ||
| | `{selectall}` | N/A | N/A | Selects all the text of the element. Note that this will only work for elements that support selection ranges (so, not `email`, `password`, `number`, among others) | | ||
| | `{arrowleft}` | ArrowLeft | N/A | | | ||
| | `{arrowright}` | ArrowRight | N/A | | | ||
| | `{arrowup}` | ArrowUp | N/A | | | ||
| | `{arrowdown}` | ArrowDown | N/A | | | ||
| | `{home}` | Home | N/A | | | ||
| | `{end}` | End | N/A | | | ||
| | `{shift}` | Shift | `shiftKey` | Does **not** capitalize following characters. | | ||
| | `{ctrl}` | Control | `ctrlKey` | | | ||
| | `{alt}` | Alt | `altKey` | | | ||
| | `{meta}` | OS | `metaKey` | | | ||
| | `{capslock}` | CapsLock | `modifierCapsLock` | Fires both keydown and keyup when used (simulates a user clicking their "Caps Lock" button to enable caps lock). | | ||
|
|
||
| > **A note about modifiers:** Modifier keys (`{shift}`, `{ctrl}`, `{alt}`, | ||
| > `{meta}`) will activate their corresponding event modifiers for the duration | ||
| > of type command or until they are closed (via `{/shift}`, `{/ctrl}`, etc.). If | ||
| > they are not closed explicitly, then events will be fired to close them | ||
| > automatically (to disable this, set the `skipAutoClose` option to `true`). | ||
| <!-- space out these notes --> | ||
|
|
||
| > We take the same | ||
| > [stance as Cypress](https://docs.cypress.io/api/commands/type.html#Modifiers) | ||
| > in that we do not simulate the behavior that happens with modifier key | ||
| > combinations as different operating systems function differently in this | ||
| > regard. | ||
| An example of an usage with a selection range: | ||
|
|
||
| ```jsx | ||
| import React from 'react' | ||
| import {render, screen} from '@testing-library/react' | ||
| import userEvent from '@testing-library/user-event' | ||
|
|
||
| test('delete characters within the selectedRange', () => { | ||
| render( | ||
| <div> | ||
| <label htmlFor="my-input">Example:</label> | ||
| <input id="my-input" type="text" value="This is a bad example" /> | ||
| </div>, | ||
| ) | ||
| const input = screen.getByLabelText(/example/i) | ||
| input.setSelectionRange(10, 13) | ||
| userEvent.type(input, '{backspace}good') | ||
|
|
||
| expect(input).toHaveValue('This is a good example') | ||
| }) | ||
| ``` | ||
|
|
||
| #### `<input type="time" />` support | ||
|
|
||
| The following is an example of usage of this library with | ||
| `<input type="time" />` | ||
|
|
||
| ```jsx | ||
| import React from 'react' | ||
| import {render, screen} from '@testing-library/react' | ||
| import userEvent from '@testing-library/user-event' | ||
|
|
||
| test('types into the input', () => { | ||
| render( | ||
| <> | ||
| <label for="time">Enter a time</label> | ||
| <input type="time" id="time" /> | ||
| </>, | ||
| ) | ||
| const input = screen.getByLabelText(/enter a time/i) | ||
| userEvent.type(input, '13:58') | ||
| expect(input.value).toBe('13:58') | ||
| }) | ||
| ``` | ||
|
|
||
| ### `keyboard(text, options)` | ||
|
|
||
| Simulates the keyboard events described by `text`. This is similar to | ||
| `userEvent.type()` but without any clicking or changing the selection range. | ||
|
|
||
| > You should use `userEvent.keyboard` if you want to just simulate pressing | ||
| > buttons on the keyboard. You should use `userEvent.type` if you just want to | ||
| > conveniently insert some text into an input field or textarea. | ||
| Keystrokes can be described: | ||
|
|
||
| - Per printable character | ||
| ```js | ||
| userEvent.keyboard('foo') // translates to: f, o, o | ||
| ``` | ||
| The brackets `{` and `[` are used as special character and can be referenced | ||
| by doubling them. | ||
| ```js | ||
| userEvent.keyboard('{{a[[') // translates to: {, a, [ | ||
| ``` | ||
| - Per | ||
| [KeyboardEvent.key](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key) | ||
| (only supports alphanumeric values of `key`) | ||
| ```js | ||
| userEvent.keyboard('{Shift}{f}{o}{o}') // translates to: Shift, f, o, o | ||
| ``` | ||
| This does not keep any key pressed. So `Shift` will be lifted before pressing | ||
| `f`. | ||
| - Per | ||
| [KeyboardEvent.code](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code) | ||
| ```js | ||
| userEvent.keyboard('[ShiftLeft][KeyF][KeyO][KeyO]') // translates to: Shift, f, o, o | ||
| ``` | ||
| - Per legacy `userEvent.type` modifier/specialChar The modifiers like `{shift}` | ||
| (note the lowercase) will automatically be kept pressed, just like before. You | ||
| can cancel this behavior by adding a `/` to the end of the descriptor. | ||
| ```js | ||
| userEvent.keyboard('{shift}{ctrl/}a{/shift}') // translates to: Shift(down), Control(down+up), a, Shift(up) | ||
| ``` | ||
|
|
||
| Keys can be kept pressed by adding a `>` to the end of the descriptor - and | ||
| lifted by adding a `/` to the beginning of the descriptor: | ||
|
|
||
| ```js | ||
| userEvent.keyboard('{Shift>}A{/Shift}') // translates to: Shift(down), A, Shift(up) | ||
| ``` | ||
|
|
||
| `userEvent.keyboard` returns a keyboard state that can be used to continue | ||
| keyboard operations. | ||
|
|
||
| ```js | ||
| const keyboardState = userEvent.keyboard('[ControlLeft>]') // keydown [ControlLeft] | ||
| // ... inspect some changes ... | ||
| userEvent.keyboard('a', {keyboardState}) // press [KeyA] with active ctrlKey modifier | ||
| ``` | ||
|
|
||
| The mapping of `key` to `code` is performed by a | ||
| [default key map](https://github.com/testing-library/user-event/blob/main/src/keyboard/keyMap.ts) | ||
| portraying a "default" US-keyboard. You can provide your own local keyboard | ||
| mapping per option. | ||
|
|
||
| ```js | ||
| userEvent.keyboard('?', {keyboardMap: myOwnLocaleKeyboardMap}) | ||
| ``` | ||
|
|
||
| > Future versions might try to interpolate the modifiers needed to reach a | ||
| > printable key on the keyboard. E.g. Automatically pressing `{Shift}` when | ||
| > CapsLock is not active and `A` is referenced. If you don't wish this behavior, | ||
| > you can pass `autoModify: false` when using `userEvent.keyboard` in your code. | ||
| ### `upload(element, file, [{ clickInit, changeInit }], [options])` | ||
|
|
||
| Uploads file to an `<input>`. For uploading multiple files use `<input>` with | ||
| the `multiple` attribute and the second `upload` argument as an array. It's also | ||
| possible to initialize a click or change event using a third argument. | ||
|
|
||
| If `options.applyAccept` is set to `true` and there is an `accept` attribute on | ||
| the element, files that don't match will be discarded. | ||
|
|
||
| ```jsx | ||
| import React from 'react' | ||
| import {render, screen} from '@testing-library/react' | ||
| import userEvent from '@testing-library/user-event' | ||
|
|
||
| test('upload file', () => { | ||
| const file = new File(['hello'], 'hello.png', {type: 'image/png'}) | ||
|
|
||
| render( | ||
| <div> | ||
| <label htmlFor="file-uploader">Upload file:</label> | ||
| <input id="file-uploader" type="file" /> | ||
| </div>, | ||
| ) | ||
| const input = screen.getByLabelText(/upload file/i) | ||
| userEvent.upload(input, file) | ||
|
|
||
| expect(input.files[0]).toStrictEqual(file) | ||
| expect(input.files.item(0)).toStrictEqual(file) | ||
| expect(input.files).toHaveLength(1) | ||
| }) | ||
|
|
||
| test('upload multiple files', () => { | ||
| const files = [ | ||
| new File(['hello'], 'hello.png', {type: 'image/png'}), | ||
| new File(['there'], 'there.png', {type: 'image/png'}), | ||
| ] | ||
|
|
||
| render( | ||
| <div> | ||
| <label htmlFor="file-uploader">Upload file:</label> | ||
| <input id="file-uploader" type="file" multiple /> | ||
| </div>, | ||
| ) | ||
| const input = screen.getByLabelText(/upload file/i) | ||
| userEvent.upload(input, files) | ||
|
|
||
| expect(input.files).toHaveLength(2) | ||
| expect(input.files[0]).toStrictEqual(files[0]) | ||
| expect(input.files[1]).toStrictEqual(files[1]) | ||
| }) | ||
| ``` | ||
|
|
||
| ### `clear(element)` | ||
|
|
||
| Selects the text inside an `<input>` or `<textarea>` and deletes it. | ||
|
|
||
| ```jsx | ||
| import React from 'react' | ||
| import {render, screen} from '@testing-library/react' | ||
| import userEvent from '@testing-library/user-event' | ||
|
|
||
| test('clear', () => { | ||
| render(<textarea value="Hello, World!" />) | ||
|
|
||
| userEvent.clear(screen.getByRole('textbox')) | ||
| expect(screen.getByRole('textbox')).toHaveAttribute('value', '') | ||
| }) | ||
| ``` | ||
|
|
||
| ### `selectOptions(element, values)` | ||
|
|
||
| Selects the specified option(s) of a `<select>` or a `<select multiple>` | ||
| element. | ||
|
|
||
| ```jsx | ||
| import React from 'react' | ||
| import {render, screen} from '@testing-library/react' | ||
| import userEvent from '@testing-library/user-event' | ||
|
|
||
| test('selectOptions', () => { | ||
| render( | ||
| <select multiple> | ||
| <option value="1">A</option> | ||
| <option value="2">B</option> | ||
| <option value="3">C</option> | ||
| </select>, | ||
| ) | ||
|
|
||
| userEvent.selectOptions(screen.getByRole('listbox'), ['1', '3']) | ||
|
|
||
| expect(screen.getByRole('option', {name: 'A'}).selected).toBe(true) | ||
| expect(screen.getByRole('option', {name: 'B'}).selected).toBe(false) | ||
| expect(screen.getByRole('option', {name: 'C'}).selected).toBe(true) | ||
| }) | ||
| ``` | ||
|
|
||
| The `values` parameter can be either an array of values or a singular scalar | ||
| value. | ||
|
|
||
| It also accepts option nodes: | ||
|
|
||
| ```js | ||
| userEvent.selectOptions(screen.getByTestId('select-multiple'), [ | ||
| screen.getByText('A'), | ||
| screen.getByText('B'), | ||
| ]) | ||
| ``` | ||
|
|
||
| ### `deselectOptions(element, values)` | ||
|
|
||
| Remove the selection for the specified option(s) of a `<select multiple>` | ||
| element. | ||
|
|
||
| ```jsx | ||
| import * as React from 'react' | ||
| import {render, screen} from '@testing-library/react' | ||
| import userEvent from '@testing-library/user-event' | ||
|
|
||
| test('deselectOptions', () => { | ||
| render( | ||
| <select multiple> | ||
| <option value="1">A</option> | ||
| <option value="2">B</option> | ||
| <option value="3">C</option> | ||
| </select>, | ||
| ) | ||
|
|
||
| userEvent.selectOptions(screen.getByRole('listbox'), '2') | ||
| expect(screen.getByText('B').selected).toBe(true) | ||
| userEvent.deselectOptions(screen.getByRole('listbox'), '2') | ||
| expect(screen.getByText('B').selected).toBe(false) | ||
| // can do multiple at once as well: | ||
| // userEvent.deselectOptions(screen.getByRole('listbox'), ['1', '2']) | ||
| }) | ||
| ``` | ||
|
|
||
| The `values` parameter can be either an array of values or a singular scalar | ||
| value. | ||
|
|
||
| ### `tab({shift, focusTrap})` | ||
|
|
||
| Fires a tab event changing the document.activeElement in the same way the | ||
| browser does. | ||
|
|
||
| Options: | ||
|
|
||
| - `shift` (default `false`) can be true or false to invert tab direction. | ||
| - `focusTrap` (default `document`) a container element to restrict the tabbing | ||
| within. | ||
|
|
||
| > **A note about tab**: | ||
| > [jsdom does not support tabbing](https://github.com/jsdom/jsdom/issues/2102), | ||
| > so this feature is a way to enable tests to verify tabbing from the end user's | ||
| > perspective. However, this limitation in jsdom will mean that components like | ||
| > [focus-trap-react](https://github.com/davidtheclark/focus-trap-react) will not | ||
| > work with `userEvent.tab()` or jsdom. For that reason, the `focusTrap` option | ||
| > is available to let you ensure your user is restricted within a focus-trap. | ||
| ```jsx | ||
| import React from 'react' | ||
| import {render, screen} from '@testing-library/react' | ||
| import '@testing-library/jest-dom/extend-expect' | ||
| import userEvent from '@testing-library/user-event' | ||
|
|
||
| it('should cycle elements in document tab order', () => { | ||
| render( | ||
| <div> | ||
| <input data-testid="element" type="checkbox" /> | ||
| <input data-testid="element" type="radio" /> | ||
| <input data-testid="element" type="number" /> | ||
| </div>, | ||
| ) | ||
|
|
||
| const [checkbox, radio, number] = screen.getAllByTestId('element') | ||
|
|
||
| expect(document.body).toHaveFocus() | ||
|
|
||
| userEvent.tab() | ||
|
|
||
| expect(checkbox).toHaveFocus() | ||
|
|
||
| userEvent.tab() | ||
|
|
||
| expect(radio).toHaveFocus() | ||
|
|
||
| userEvent.tab() | ||
|
|
||
| expect(number).toHaveFocus() | ||
|
|
||
| userEvent.tab() | ||
|
|
||
| // cycle goes back to the body element | ||
| expect(document.body).toHaveFocus() | ||
|
|
||
| userEvent.tab() | ||
|
|
||
| expect(checkbox).toHaveFocus() | ||
| }) | ||
| ``` | ||
|
|
||
| ### `hover(element)` | ||
|
|
||
| Hovers over `element`. | ||
|
|
||
| ```jsx | ||
| import React from 'react' | ||
| import {render, screen} from '@testing-library/react' | ||
| import userEvent from '@testing-library/user-event' | ||
| import Tooltip from '../tooltip' | ||
|
|
||
| test('hover', () => { | ||
| const messageText = 'Hello' | ||
| render( | ||
| <Tooltip messageText={messageText}> | ||
| <TrashIcon aria-label="Delete" /> | ||
| </Tooltip>, | ||
| ) | ||
|
|
||
| userEvent.hover(screen.getByLabelText(/delete/i)) | ||
| expect(screen.getByText(messageText)).toBeInTheDocument() | ||
| userEvent.unhover(screen.getByLabelText(/delete/i)) | ||
| expect(screen.queryByText(messageText)).not.toBeInTheDocument() | ||
| }) | ||
| ``` | ||
|
|
||
| ### `unhover(element)` | ||
|
|
||
| Unhovers out of `element`. | ||
|
|
||
| > See [above](#hoverelement) for an example | ||
| ### `paste(element, text, eventInit, options)` | ||
|
|
||
| Allows you to simulate the user pasting some text into an input. | ||
|
|
||
| ```javascript | ||
| test('should paste text in input', () => { | ||
| render(<MyInput />) | ||
|
|
||
| const text = 'Hello, world!' | ||
| const element = getByRole('textbox', {name: /paste your greeting/i}) | ||
| userEvent.paste(element, text) | ||
| expect(element).toHaveValue(text) | ||
| }) | ||
| ``` | ||
|
|
||
| You can use the `eventInit` if what you're pasting should have `clipboardData` | ||
| (like `files`). | ||
|
|
||
| ### `specialChars` | ||
|
|
||
| A handful set of special characters used in [type](#typeelement-text-options) | ||
| method. | ||
|
|
||
| | Key | Character | | ||
| | ---------- | -------------- | | ||
| | arrowLeft | `{arrowleft}` | | ||
| | arrowRight | `{arrowright}` | | ||
| | arrowDown | `{arrowdown}` | | ||
| | arrowUp | `{arrowup}` | | ||
| | home | `{home}` | | ||
| | end | `{end}` | | ||
| | enter | `{enter}` | | ||
| | escape | `{esc}` | | ||
| | delete | `{del}` | | ||
| | backspace | `{backspace}` | | ||
| | selectAll | `{selectall}` | | ||
| | space | `{space}` | | ||
| | whitespace | `' '` | | ||
|
|
||
| Usage example: | ||
|
|
||
| ```jsx | ||
| import React from 'react' | ||
| import {render, screen} from '@testing-library/react' | ||
| import userEvent, {specialChars} from '@testing-library/user-event' | ||
|
|
||
| test('delete characters within the selectedRange', () => { | ||
| render( | ||
| <div> | ||
| <label htmlFor="my-input">Example:</label> | ||
| <input id="my-input" type="text" value="This is a bad example" /> | ||
| </div>, | ||
| ) | ||
| const input = screen.getByLabelText(/example/i) | ||
| input.setSelectionRange(10, 13) | ||
| userEvent.type(input, `${specialChars.backspace}good`) | ||
|
|
||
| expect(input).toHaveValue('This is a good example') | ||
| }) | ||
| ``` | ||
| [**Read The Docs**](https://testing-library.com/docs/ecosystem-user-event) | | ||
| [Edit the docs](https://github.com/testing-library/testing-library-docs) | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can drop this section as the link to the docs is already present above and easy to find.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is also similar to the DOM Testing Library readme, though the link is formatted differently. Personally I don't think there's harm in having it an extra time since it's short and users may be scrolling to this point to try to find the old API section. If we just removed this it may be confusing.
|
🎉 This PR is included in version 13.2.1 🎉 The release is available on:
Your semantic-release bot 📦🚀 |
testing-library/testing-library-docs#519
Checklist: