Skip to content
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

Add @testing-library/user-event to the project #39360

Merged
merged 10 commits into from
Mar 15, 2022
Merged
80 changes: 80 additions & 0 deletions docs/contributors/code/testing-overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,86 @@ describe( 'my module', () => {
} );
```

### User interactions

Simulating user interactions is a great way to **write tests from the user's perspective**, and therefore avoid testing implementation details.

When writing tests with Testing Library, there are two main alternatives for simulating user interactions:

1. The [`fireEvent`](https://testing-library.com/docs/dom-testing-library/api-events/#fireevent) API, a utility for firing DOM events part of the Testing Library core API.
2. The [`user-event`](https://testing-library.com/docs/user-event/intro/) library, a companion library to Testing Library that simulates user interactions by dispatching the events that would happen if the interaction took place in a browser.

The built-in `fireEvent` is a utility for dispatching DOM events. It dispatches exactly the events that are described in the test spec - even if those exact events never had been dispatched in a real interaction in a browser.

On the other hand, the `user-event` library exposes higher-level methods (e.g. `type`, `selectOptions`, `clear`, `doubleClick`...), that dispatch events like they would happen if a user interacted with the document, and take care of any react-specific quirks.

For the above reasons, **the `user-event` library is recommended when writing tests for user interactions**.

**Not so good**: using `fireEvent` to dispatch DOM events.

```javascript
import { render, screen } from '@testing-library/react';

test( 'fires onChange when a new value is typed', () => {
const spyOnChange = jest.fn();

// A component with one `input` and one `select`.
render( <MyComponent onChange={ spyOnChange } /> );

const input = screen.getByRole( 'textbox' );
input.focus();
// No clicks, no key events.
fireEvent.change( input, { target: { value: 62 } } );

// The `onChange` callback gets called once with '62' as the argument.
expect( spyOnChange ).toHaveBeenCalledTimes( 1 );

const select = screen.getByRole( 'listbox' );
select.focus();
// No pointer events dispatched.
fireEvent.change( select, { target: { value: 'optionValue' } } );

// ...
```

**Good**: using `user-event` to simulate user events.

```javascript

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

test('fires onChange when a new value is typed', async () => {
const user = userEvent.setup();

const spyOnChange = jest.fn();

// A component with one `input` and one `select`.
render( <MyComponent onChange={ spyOnChange } /> );

const input = screen.getByRole( 'textbox' );
// Focus the element, select and delete all its contents.
await user.clear( input );
// Click the element, type each character separately (generating keydown,
// keypress and keyup events).
await user.type( input, '62' );

// The `onChange` callback gets called 3 times with the following arguments:
// - 1: clear ('')
// - 2: '6'
// - 3: '62'
expect( spyOnChange ).toHaveBeenCalledTimes( 3 );

const select = screen.getByRole( 'listbox' );
// Dispatches events for focus, pointer, mouse, click and change.
await user.selectOptions( select, [ 'optionValue' ] );

// ...
})
```



### Snapshot testing

This is an overview of [snapshot testing] and how to best leverage snapshot tests.
Expand Down
28 changes: 28 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
"@testing-library/jest-dom": "5.16.1",
"@testing-library/react": "11.2.2",
"@testing-library/react-native": "9.0.0",
"@testing-library/user-event": "^14.0.0-beta.13",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This the latest available version, and is the recommended one over the previously stable version (13.5.0)

From the official docs:

These docs describe user-event@14.0.0-beta. This is still a pre-release and might be subject to breaking changes if they should be deemed necessary in the light of feedback we receive for this version. All planned breaking changes are already implemented though.
If you are starting or actively working on a project, we recommend to use this pre-release, as it includes important bug fixes and new features.

"@types/classnames": "2.3.1",
"@types/eslint": "7.28.0",
"@types/estree": "0.0.50",
Expand Down
1 change: 1 addition & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

- `BaseControl`: Add `__nextHasNoMarginBottom` prop for opting into the new margin-free styles ([#39325](https://github.com/WordPress/gutenberg/pull/39325)).
- `Divider`: Make the divider visible by default (`display: inline`) in flow layout containers when the divider orientation is vertical ([#39316](https://github.com/WordPress/gutenberg/pull/39316)).
- Stop using deprecated `event.keyCode` in favor of `event.key` for keyboard events in `UnitControl` and `InputControl`. ([#39360](https://github.com/WordPress/gutenberg/pull/39360))

## 19.6.0 (2022-03-11)

Expand Down
13 changes: 6 additions & 7 deletions packages/components/src/input-control/input-field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import type {
* WordPress dependencies
*/
import { forwardRef, useRef } from '@wordpress/element';
import { UP, DOWN, ENTER, ESCAPE } from '@wordpress/keycodes';
/**
* Internal dependencies
*/
Expand Down Expand Up @@ -137,19 +136,19 @@ function InputField(
};

const handleOnKeyDown = ( event: KeyboardEvent< HTMLInputElement > ) => {
const { keyCode } = event;
const { key } = event;
onKeyDown( event );

switch ( keyCode ) {
case UP:
switch ( key ) {
case 'ArrowUp':
pressUp( event );
break;

case DOWN:
case 'ArrowDown':
pressDown( event );
break;

case ENTER:
case 'Enter':
pressEnter( event );

if ( isPressEnterToChange ) {
Expand All @@ -158,7 +157,7 @@ function InputField(
}
break;

case ESCAPE:
case 'Escape':
if ( isPressEnterToChange && isDirty ) {
event.preventDefault();
reset( valueProp, event );
Expand Down
5 changes: 2 additions & 3 deletions packages/components/src/unit-control/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import classnames from 'classnames';
*/
import { forwardRef, useMemo, useRef, useEffect } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { ENTER } from '@wordpress/keycodes';

/**
* Internal dependencies
Expand Down Expand Up @@ -170,8 +169,8 @@ function UnitControl(
const handleOnBlur: FocusEventHandler< HTMLInputElement > = mayUpdateUnit;

const handleOnKeyDown = ( event: KeyboardEvent< HTMLInputElement > ) => {
const { keyCode } = event;
if ( keyCode === ENTER ) {
const { key } = event;
if ( key === 'Enter' ) {
mayUpdateUnit( event );
}
};
Expand Down
Loading