Skip to content

Commit 7259231

Browse files
authored
Create packages/dom-event-testing-library (#17660)
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.
1 parent e7494c8 commit 7259231

25 files changed

+119
-18
lines changed

.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ module.exports = {
141141
'**/__tests__/**/*.js',
142142
'scripts/**/*.js',
143143
'packages/*/npm/**/*.js',
144+
'packages/dom-event-testing-library/**/*.js',
144145
'packages/react-devtools*/**/*.js'
145146
],
146147
rules: {
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# `dom-event-testing-library`
2+
3+
A library for unit testing events via high-level interactions, e.g., `pointerdown`,
4+
that produce realistic and complete DOM event sequences.
5+
6+
There are number of challenges involved in unit testing modules that work with
7+
DOM events.
8+
9+
1. Gesture recognizers may need to support environments with and without support for
10+
the `PointerEvent` API.
11+
2. Gesture recognizers may need to support various user interaction modes including
12+
mouse, touch, and pen use.
13+
3. Gesture recognizers must account for the actual event sequences browsers produce
14+
(e.g., emulated touch and mouse events.)
15+
4. Gesture recognizers must work with "virtual" events produced by tools like
16+
screen-readers.
17+
18+
Writing unit tests to cover all these scenarios is tedious and error prone. This
19+
event testing library is designed to solve these issues by allowing developers to
20+
more easily dispatch events in unit tests, and to more reliably test pointer
21+
interactions using a high-level API based on `PointerEvent`. Here's a basic example:
22+
23+
```js
24+
import {
25+
describeWithPointerEvent,
26+
testWithPointerType,
27+
createEventTarget,
28+
setPointerEvent,
29+
resetActivePointers
30+
} from 'dom-event-testing-library';
31+
32+
describeWithPointerEvent('useTap', hasPointerEvent => {
33+
beforeEach(() => {
34+
// basic PointerEvent mock
35+
setPointerEvent(hasPointerEvent);
36+
});
37+
38+
afterEach(() => {
39+
// clear active pointers between test runs
40+
resetActivePointers();
41+
});
42+
43+
// test all the pointer types supported by the environment
44+
testWithPointerType('pointer down', pointerType => {
45+
const ref = createRef(null);
46+
const onTapStart = jest.fn();
47+
render(() => {
48+
useTap(ref, { onTapStart });
49+
return <div ref={ref} />
50+
});
51+
52+
// create an event target
53+
const target = createEventTarget(ref.current);
54+
// dispatch high-level pointer event
55+
target.pointerdown({ pointerType });
56+
57+
expect(onTapStart).toBeCalled();
58+
});
59+
});
60+
```
61+
62+
This tests the interaction in multiple scenarios. In each case, a realistic DOM
63+
event sequence–with complete mock events–is produced. When running in a mock
64+
environment without the `PointerEvent` API, the test runs for both `mouse` and
65+
`touch` pointer types. When `touch` is the pointer type it produces emulated mouse
66+
events. When running in a mock environment with the `PointerEvent` API, the test
67+
runs for `mouse`, `touch`, and `pen` pointer types.
68+
69+
It's important to cover all these scenarios because it's very easy to introduce
70+
bugs – e.g., double calling of callbacks – if not accounting for emulated mouse
71+
events, differences in target capturing between `touch` and `mouse` pointers, and
72+
the different semantics of `button` across event APIs.
73+
74+
Default values are provided for the expected native events properties. They can
75+
also be customized as needed in a test.
76+
77+
```js
78+
target.pointerdown({
79+
button: 0,
80+
buttons: 1,
81+
pageX: 10,
82+
pageY: 10,
83+
pointerType,
84+
// NOTE: use x,y instead of clientX,clientY
85+
x: 10,
86+
y: 10
87+
});
88+
```
89+
90+
Tests that dispatch multiple pointer events will dispatch multi-touch native events
91+
on the target.
92+
93+
```js
94+
// first pointer is active
95+
target.pointerdown({pointerId: 1, pointerType});
96+
// second pointer is active
97+
target.pointerdown({pointerId: 2, pointerType});
98+
```
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"private": true,
3+
"name": "dom-event-testing-library",
4+
"version": "0.0.0"
5+
}
Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ export function addTouch(touch) {
2525
}
2626
if (activeTouches.get(target).get(identifier)) {
2727
// Do not allow existing touches to be overwritten
28-
// eslint-disable-next-line react-internal/no-production-logging
2928
console.error(
3029
'Touch with identifier %s already exists. Did not record touch start.',
3130
identifier,
@@ -41,7 +40,6 @@ export function updateTouch(touch) {
4140
if (activeTouches.get(target) != null) {
4241
activeTouches.get(target).set(identifier, touch);
4342
} else {
44-
// eslint-disable-next-line react-internal/no-production-logging
4543
console.error(
4644
'Touch with identifier %s does not exist. Cannot record touch move without a touch start.',
4745
identifier,
@@ -56,7 +54,6 @@ export function removeTouch(touch) {
5654
if (activeTouches.get(target).has(identifier)) {
5755
activeTouches.get(target).delete(identifier);
5856
} else {
59-
// eslint-disable-next-line react-internal/no-production-logging
6057
console.error(
6158
'Touch with identifier %s does not exist. Cannot record touch end without a touch start.',
6259
identifier,

0 commit comments

Comments
 (0)