Skip to content

Commit

Permalink
Create packages/dom-event-testing-library (#17660)
Browse files Browse the repository at this point in the history
Moves the unit testing library for events into the `packages` directory so it can more easily be used in tests for other react packages, and mirrored internally to help with testing of event hooks we prototype in www.
  • Loading branch information
necolas authored Dec 19, 2019
1 parent e7494c8 commit 7259231
Show file tree
Hide file tree
Showing 25 changed files with 119 additions and 18 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ module.exports = {
'**/__tests__/**/*.js',
'scripts/**/*.js',
'packages/*/npm/**/*.js',
'packages/dom-event-testing-library/**/*.js',
'packages/react-devtools*/**/*.js'
],
rules: {
Expand Down
98 changes: 98 additions & 0 deletions packages/dom-event-testing-library/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# `dom-event-testing-library`

A library for unit testing events via high-level interactions, e.g., `pointerdown`,
that produce realistic and complete DOM event sequences.

There are number of challenges involved in unit testing modules that work with
DOM events.

1. Gesture recognizers may need to support environments with and without support for
the `PointerEvent` API.
2. Gesture recognizers may need to support various user interaction modes including
mouse, touch, and pen use.
3. Gesture recognizers must account for the actual event sequences browsers produce
(e.g., emulated touch and mouse events.)
4. Gesture recognizers must work with "virtual" events produced by tools like
screen-readers.

Writing unit tests to cover all these scenarios is tedious and error prone. This
event testing library is designed to solve these issues by allowing developers to
more easily dispatch events in unit tests, and to more reliably test pointer
interactions using a high-level API based on `PointerEvent`. Here's a basic example:

```js
import {
describeWithPointerEvent,
testWithPointerType,
createEventTarget,
setPointerEvent,
resetActivePointers
} from 'dom-event-testing-library';

describeWithPointerEvent('useTap', hasPointerEvent => {
beforeEach(() => {
// basic PointerEvent mock
setPointerEvent(hasPointerEvent);
});

afterEach(() => {
// clear active pointers between test runs
resetActivePointers();
});

// test all the pointer types supported by the environment
testWithPointerType('pointer down', pointerType => {
const ref = createRef(null);
const onTapStart = jest.fn();
render(() => {
useTap(ref, { onTapStart });
return <div ref={ref} />
});

// create an event target
const target = createEventTarget(ref.current);
// dispatch high-level pointer event
target.pointerdown({ pointerType });

expect(onTapStart).toBeCalled();
});
});
```

This tests the interaction in multiple scenarios. In each case, a realistic DOM
event sequence–with complete mock events–is produced. When running in a mock
environment without the `PointerEvent` API, the test runs for both `mouse` and
`touch` pointer types. When `touch` is the pointer type it produces emulated mouse
events. When running in a mock environment with the `PointerEvent` API, the test
runs for `mouse`, `touch`, and `pen` pointer types.

It's important to cover all these scenarios because it's very easy to introduce
bugs – e.g., double calling of callbacks – if not accounting for emulated mouse
events, differences in target capturing between `touch` and `mouse` pointers, and
the different semantics of `button` across event APIs.

Default values are provided for the expected native events properties. They can
also be customized as needed in a test.

```js
target.pointerdown({
button: 0,
buttons: 1,
pageX: 10,
pageY: 10,
pointerType,
// NOTE: use x,y instead of clientX,clientY
x: 10,
y: 10
});
```

Tests that dispatch multiple pointer events will dispatch multi-touch native events
on the target.

```js
// first pointer is active
target.pointerdown({pointerId: 1, pointerType});
// second pointer is active
target.pointerdown({pointerId: 2, pointerType});
```
File renamed without changes.
5 changes: 5 additions & 0 deletions packages/dom-event-testing-library/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"private": true,
"name": "dom-event-testing-library",
"version": "0.0.0"
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ export function addTouch(touch) {
}
if (activeTouches.get(target).get(identifier)) {
// Do not allow existing touches to be overwritten
// eslint-disable-next-line react-internal/no-production-logging
console.error(
'Touch with identifier %s already exists. Did not record touch start.',
identifier,
Expand All @@ -41,7 +40,6 @@ export function updateTouch(touch) {
if (activeTouches.get(target) != null) {
activeTouches.get(target).set(identifier, touch);
} else {
// eslint-disable-next-line react-internal/no-production-logging
console.error(
'Touch with identifier %s does not exist. Cannot record touch move without a touch start.',
identifier,
Expand All @@ -56,7 +54,6 @@ export function removeTouch(touch) {
if (activeTouches.get(target).has(identifier)) {
activeTouches.get(target).delete(identifier);
} else {
// eslint-disable-next-line react-internal/no-production-logging
console.error(
'Touch with identifier %s does not exist. Cannot record touch end without a touch start.',
identifier,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* @flow
*/

import {createEventTarget} from 'react-interactions/events/src/dom/event-testing-library';
import {createEventTarget} from 'dom-event-testing-library';

let React;
let ReactFeatureFlags;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* @flow
*/

import {createEventTarget} from 'react-interactions/events/src/dom/event-testing-library';
import {createEventTarget} from 'dom-event-testing-library';
import {emulateBrowserTab} from '../shared/emulateBrowserTab';

let React;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* @flow
*/

import {createEventTarget} from 'react-interactions/events/src/dom/event-testing-library';
import {createEventTarget} from 'dom-event-testing-library';
import {emulateBrowserTab} from '../shared/emulateBrowserTab';

let React;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

'use strict';

import {createEventTarget} from 'react-interactions/events/src/dom/event-testing-library';
import {createEventTarget} from 'dom-event-testing-library';

// This function is used by the a11y modules for testing
export function emulateBrowserTab(backwards: boolean): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
createEventTarget,
platform,
setPointerEvent,
} from '../event-testing-library';
} from 'dom-event-testing-library';

let React;
let ReactFeatureFlags;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
createEventTarget,
setPointerEvent,
platform,
} from '../event-testing-library';
} from 'dom-event-testing-library';

let React;
let ReactFeatureFlags;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

'use strict';

import {createEventTarget, setPointerEvent} from '../event-testing-library';
import {createEventTarget, setPointerEvent} from 'dom-event-testing-library';

let React;
let ReactFeatureFlags;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

'use strict';

import {createEventTarget, setPointerEvent} from '../event-testing-library';
import {createEventTarget, setPointerEvent} from 'dom-event-testing-library';

let React;
let ReactFeatureFlags;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ let ReactFeatureFlags;
let ReactDOM;
let useKeyboard;

import {createEventTarget} from '../event-testing-library';
import {createEventTarget} from 'dom-event-testing-library';

function initializeModules(hasPointerEvents) {
jest.resetModules();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

'use strict';

import {createEventTarget} from '../event-testing-library';
import {createEventTarget} from 'dom-event-testing-library';

let React;
let ReactFeatureFlags;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
describeWithPointerEvent,
resetActivePointers,
setPointerEvent,
} from '../event-testing-library';
} from 'dom-event-testing-library';

let React;
let ReactFeatureFlags;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
createEventTarget,
resetActivePointers,
setPointerEvent,
} from '../event-testing-library';
} from 'dom-event-testing-library';

let React;
let ReactFeatureFlags;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

'use strict';

import {createEventTarget, setPointerEvent} from '../event-testing-library';
import {createEventTarget, setPointerEvent} from 'dom-event-testing-library';

let React;
let ReactFeatureFlags;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
setPointerEvent,
testWithPointerType,
resetActivePointers,
} from '../event-testing-library';
} from 'dom-event-testing-library';

let React;
let ReactFeatureFlags;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

'use strict';

import {createEventTarget} from 'react-interactions/events/src/dom/event-testing-library';
import {createEventTarget} from 'dom-event-testing-library';

let React;
let ReactFeatureFlags;
Expand Down

0 comments on commit 7259231

Please sign in to comment.