diff --git a/.eslintrc.js b/.eslintrc.js index 82115d52504ea..50e448a47620b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -141,6 +141,7 @@ module.exports = { '**/__tests__/**/*.js', 'scripts/**/*.js', 'packages/*/npm/**/*.js', + 'packages/dom-event-testing-library/**/*.js', 'packages/react-devtools*/**/*.js' ], rules: { diff --git a/packages/dom-event-testing-library/README.md b/packages/dom-event-testing-library/README.md new file mode 100644 index 0000000000000..d6ad0f183c0bc --- /dev/null +++ b/packages/dom-event-testing-library/README.md @@ -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
+ }); + + // 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}); +``` diff --git a/packages/react-interactions/events/src/dom/event-testing-library/constants.js b/packages/dom-event-testing-library/constants.js similarity index 100% rename from packages/react-interactions/events/src/dom/event-testing-library/constants.js rename to packages/dom-event-testing-library/constants.js diff --git a/packages/react-interactions/events/src/dom/event-testing-library/domEnvironment.js b/packages/dom-event-testing-library/domEnvironment.js similarity index 100% rename from packages/react-interactions/events/src/dom/event-testing-library/domEnvironment.js rename to packages/dom-event-testing-library/domEnvironment.js diff --git a/packages/react-interactions/events/src/dom/event-testing-library/domEventSequences.js b/packages/dom-event-testing-library/domEventSequences.js similarity index 100% rename from packages/react-interactions/events/src/dom/event-testing-library/domEventSequences.js rename to packages/dom-event-testing-library/domEventSequences.js diff --git a/packages/react-interactions/events/src/dom/event-testing-library/domEvents.js b/packages/dom-event-testing-library/domEvents.js similarity index 100% rename from packages/react-interactions/events/src/dom/event-testing-library/domEvents.js rename to packages/dom-event-testing-library/domEvents.js diff --git a/packages/react-interactions/events/src/dom/event-testing-library/index.js b/packages/dom-event-testing-library/index.js similarity index 100% rename from packages/react-interactions/events/src/dom/event-testing-library/index.js rename to packages/dom-event-testing-library/index.js diff --git a/packages/dom-event-testing-library/package.json b/packages/dom-event-testing-library/package.json new file mode 100644 index 0000000000000..bade785d4664d --- /dev/null +++ b/packages/dom-event-testing-library/package.json @@ -0,0 +1,5 @@ +{ + "private": true, + "name": "dom-event-testing-library", + "version": "0.0.0" +} diff --git a/packages/react-interactions/events/src/dom/event-testing-library/testHelpers.js b/packages/dom-event-testing-library/testHelpers.js similarity index 100% rename from packages/react-interactions/events/src/dom/event-testing-library/testHelpers.js rename to packages/dom-event-testing-library/testHelpers.js diff --git a/packages/react-interactions/events/src/dom/event-testing-library/touchStore.js b/packages/dom-event-testing-library/touchStore.js similarity index 90% rename from packages/react-interactions/events/src/dom/event-testing-library/touchStore.js rename to packages/dom-event-testing-library/touchStore.js index 25fc03dd6bbfb..f8c8becd4085d 100644 --- a/packages/react-interactions/events/src/dom/event-testing-library/touchStore.js +++ b/packages/dom-event-testing-library/touchStore.js @@ -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, @@ -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, @@ -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, diff --git a/packages/react-interactions/accessibility/src/__tests__/FocusContain-test.internal.js b/packages/react-interactions/accessibility/src/__tests__/FocusContain-test.internal.js index d59cf1636d70c..b37a8a11672d7 100644 --- a/packages/react-interactions/accessibility/src/__tests__/FocusContain-test.internal.js +++ b/packages/react-interactions/accessibility/src/__tests__/FocusContain-test.internal.js @@ -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; diff --git a/packages/react-interactions/accessibility/src/__tests__/FocusGroup-test.internal.js b/packages/react-interactions/accessibility/src/__tests__/FocusGroup-test.internal.js index 77124b5e208fa..f70f36009eb59 100644 --- a/packages/react-interactions/accessibility/src/__tests__/FocusGroup-test.internal.js +++ b/packages/react-interactions/accessibility/src/__tests__/FocusGroup-test.internal.js @@ -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; diff --git a/packages/react-interactions/accessibility/src/__tests__/FocusTable-test.internal.js b/packages/react-interactions/accessibility/src/__tests__/FocusTable-test.internal.js index bfad6d9ae5c3a..7a0167ead78fb 100644 --- a/packages/react-interactions/accessibility/src/__tests__/FocusTable-test.internal.js +++ b/packages/react-interactions/accessibility/src/__tests__/FocusTable-test.internal.js @@ -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; diff --git a/packages/react-interactions/accessibility/src/shared/emulateBrowserTab.js b/packages/react-interactions/accessibility/src/shared/emulateBrowserTab.js index 6b34509b722b5..35a1b9b83e653 100644 --- a/packages/react-interactions/accessibility/src/shared/emulateBrowserTab.js +++ b/packages/react-interactions/accessibility/src/shared/emulateBrowserTab.js @@ -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 { diff --git a/packages/react-interactions/events/src/dom/__tests__/ContextMenu-test.internal.js b/packages/react-interactions/events/src/dom/__tests__/ContextMenu-test.internal.js index 2530300230b4a..f57d416459776 100644 --- a/packages/react-interactions/events/src/dom/__tests__/ContextMenu-test.internal.js +++ b/packages/react-interactions/events/src/dom/__tests__/ContextMenu-test.internal.js @@ -14,7 +14,7 @@ import { createEventTarget, platform, setPointerEvent, -} from '../event-testing-library'; +} from 'dom-event-testing-library'; let React; let ReactFeatureFlags; diff --git a/packages/react-interactions/events/src/dom/__tests__/Focus-test.internal.js b/packages/react-interactions/events/src/dom/__tests__/Focus-test.internal.js index e8c2aeb639488..66946f07abcce 100644 --- a/packages/react-interactions/events/src/dom/__tests__/Focus-test.internal.js +++ b/packages/react-interactions/events/src/dom/__tests__/Focus-test.internal.js @@ -13,7 +13,7 @@ import { createEventTarget, setPointerEvent, platform, -} from '../event-testing-library'; +} from 'dom-event-testing-library'; let React; let ReactFeatureFlags; diff --git a/packages/react-interactions/events/src/dom/__tests__/FocusWithin-test.internal.js b/packages/react-interactions/events/src/dom/__tests__/FocusWithin-test.internal.js index 9a971ee8391d1..0c2b783b03c0e 100644 --- a/packages/react-interactions/events/src/dom/__tests__/FocusWithin-test.internal.js +++ b/packages/react-interactions/events/src/dom/__tests__/FocusWithin-test.internal.js @@ -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; diff --git a/packages/react-interactions/events/src/dom/__tests__/Hover-test.internal.js b/packages/react-interactions/events/src/dom/__tests__/Hover-test.internal.js index bdea4c4044566..3c1ca4bf1e687 100644 --- a/packages/react-interactions/events/src/dom/__tests__/Hover-test.internal.js +++ b/packages/react-interactions/events/src/dom/__tests__/Hover-test.internal.js @@ -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; diff --git a/packages/react-interactions/events/src/dom/__tests__/Keyboard-test.internal.js b/packages/react-interactions/events/src/dom/__tests__/Keyboard-test.internal.js index e81472252dca0..68a69dfba6c52 100644 --- a/packages/react-interactions/events/src/dom/__tests__/Keyboard-test.internal.js +++ b/packages/react-interactions/events/src/dom/__tests__/Keyboard-test.internal.js @@ -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(); diff --git a/packages/react-interactions/events/src/dom/__tests__/MixedResponders-test-internal.js b/packages/react-interactions/events/src/dom/__tests__/MixedResponders-test-internal.js index 22ac74d4c627e..aeb265329ed27 100644 --- a/packages/react-interactions/events/src/dom/__tests__/MixedResponders-test-internal.js +++ b/packages/react-interactions/events/src/dom/__tests__/MixedResponders-test-internal.js @@ -9,7 +9,7 @@ 'use strict'; -import {createEventTarget} from '../event-testing-library'; +import {createEventTarget} from 'dom-event-testing-library'; let React; let ReactFeatureFlags; diff --git a/packages/react-interactions/events/src/dom/__tests__/Press-test.internal.js b/packages/react-interactions/events/src/dom/__tests__/Press-test.internal.js index bcc6557bd3331..6987280090aae 100644 --- a/packages/react-interactions/events/src/dom/__tests__/Press-test.internal.js +++ b/packages/react-interactions/events/src/dom/__tests__/Press-test.internal.js @@ -16,7 +16,7 @@ import { describeWithPointerEvent, resetActivePointers, setPointerEvent, -} from '../event-testing-library'; +} from 'dom-event-testing-library'; let React; let ReactFeatureFlags; diff --git a/packages/react-interactions/events/src/dom/__tests__/PressLegacy-test.internal.js b/packages/react-interactions/events/src/dom/__tests__/PressLegacy-test.internal.js index 399e063e474b5..52969f0e68cff 100644 --- a/packages/react-interactions/events/src/dom/__tests__/PressLegacy-test.internal.js +++ b/packages/react-interactions/events/src/dom/__tests__/PressLegacy-test.internal.js @@ -15,7 +15,7 @@ import { createEventTarget, resetActivePointers, setPointerEvent, -} from '../event-testing-library'; +} from 'dom-event-testing-library'; let React; let ReactFeatureFlags; diff --git a/packages/react-interactions/events/src/dom/__tests__/Scroll-test.internal.js b/packages/react-interactions/events/src/dom/__tests__/Scroll-test.internal.js index c3ccac571513c..82d5f5619ad2f 100644 --- a/packages/react-interactions/events/src/dom/__tests__/Scroll-test.internal.js +++ b/packages/react-interactions/events/src/dom/__tests__/Scroll-test.internal.js @@ -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; diff --git a/packages/react-interactions/events/src/dom/__tests__/Tap-test.internal.js b/packages/react-interactions/events/src/dom/__tests__/Tap-test.internal.js index 2e14164504ae7..39fd5d06f9beb 100644 --- a/packages/react-interactions/events/src/dom/__tests__/Tap-test.internal.js +++ b/packages/react-interactions/events/src/dom/__tests__/Tap-test.internal.js @@ -17,7 +17,7 @@ import { setPointerEvent, testWithPointerType, resetActivePointers, -} from '../event-testing-library'; +} from 'dom-event-testing-library'; let React; let ReactFeatureFlags; diff --git a/packages/react-reconciler/src/__tests__/ReactScope-test.internal.js b/packages/react-reconciler/src/__tests__/ReactScope-test.internal.js index 5a25b0b065de6..300e1ce95b146 100644 --- a/packages/react-reconciler/src/__tests__/ReactScope-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactScope-test.internal.js @@ -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;