@@ -1172,8 +1047,10 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
};
ReactDOM.render(
, container);
- dispatchPointerDown(ref.current, {pointerType: 'touch'});
- containerRef.current.dispatchEvent(scroll());
+ const target = createEventTarget(ref.current);
+ const targetContainer = createEventTarget(containerRef.current);
+ target.pointerdown({pointerType: 'touch'});
+ targetContainer.scroll();
expect(onPressEnd).toHaveBeenCalledTimes(1);
});
@@ -1183,9 +1060,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
const outsideRef = React.createRef();
const Component = () => {
- const listener = usePressResponder({
- onPressEnd,
- });
+ const listener = usePressResponder({onPressEnd});
return (
@@ -1195,8 +1070,10 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
};
ReactDOM.render(
, container);
- dispatchPointerDown(ref.current);
- outsideRef.current.dispatchEvent(scroll());
+ const target = createEventTarget(ref.current);
+ const targetOutside = createEventTarget(outsideRef.current);
+ target.pointerdown();
+ targetOutside.scroll();
expect(onPressEnd).not.toBeCalled();
});
@@ -1208,15 +1085,16 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
const ref = React.createRef();
const Component = () => {
- return
} />;
+ const listener = usePressResponder();
+ return
;
};
ReactDOM.render(
, container);
- const target = ref.current;
- dispatchPointerDown(target);
- dispatchPointerMove(target);
- dispatchPointerUp(target);
- dispatchPointerDown(target);
+ const target = createEventTarget(ref.current);
+ target.pointerdown();
+ target.pointermove();
+ target.pointerup();
+ target.pointerdown();
});
it('should correctly pass through event properties', () => {
@@ -1251,48 +1129,35 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
};
ReactDOM.render(
, container);
- const target = ref.current;
- target.getBoundingClientRect = () => ({
- top: 10,
- left: 10,
- bottom: 110,
- right: 110,
- });
- dispatchPointerDown(target, {
+ const target = createEventTarget(ref.current);
+ target.setBoundingClientRect({x: 10, y: 10, width: 100, height: 100});
+ target.pointerdown({
pointerType: 'mouse',
pageX: 15,
pageY: 16,
- screenX: 20,
- screenY: 21,
- clientX: 30,
- clientY: 31,
+ x: 30,
+ y: 31,
});
- dispatchPointerMove(target, {
+ target.pointermove({
pointerType: 'mouse',
pageX: 16,
pageY: 17,
- screenX: 21,
- screenY: 22,
- clientX: 31,
- clientY: 32,
+ x: 31,
+ y: 32,
});
- dispatchPointerUp(target, {
+ target.pointerup({
pointerType: 'mouse',
pageX: 17,
pageY: 18,
- screenX: 22,
- screenY: 23,
- clientX: 32,
- clientY: 33,
+ x: 32,
+ y: 33,
});
- dispatchPointerDown(target, {
+ target.pointerdown({
pointerType: 'mouse',
pageX: 18,
pageY: 19,
- screenX: 23,
- screenY: 24,
- clientX: 33,
- clientY: 34,
+ x: 33,
+ y: 34,
});
expect(typeof timeStamps[0] === 'number').toBe(true);
expect(eventLog).toEqual([
@@ -1300,8 +1165,8 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
pointerType: 'mouse',
pageX: 15,
pageY: 16,
- screenX: 20,
- screenY: 21,
+ screenX: 30,
+ screenY: 81,
clientX: 30,
clientY: 31,
target: ref.current,
@@ -1312,8 +1177,8 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
pointerType: 'mouse',
pageX: 16,
pageY: 17,
- screenX: 21,
- screenY: 22,
+ screenX: 31,
+ screenY: 82,
clientX: 31,
clientY: 32,
target: ref.current,
@@ -1324,8 +1189,8 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
pointerType: 'mouse',
pageX: 17,
pageY: 18,
- screenX: 22,
- screenY: 23,
+ screenX: 32,
+ screenY: 83,
clientX: 32,
clientY: 33,
target: ref.current,
@@ -1336,8 +1201,8 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
pointerType: 'mouse',
pageX: 17,
pageY: 18,
- screenX: 22,
- screenY: 23,
+ screenX: 32,
+ screenY: 83,
clientX: 32,
clientY: 33,
target: ref.current,
@@ -1348,8 +1213,8 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
pointerType: 'mouse',
pageX: 18,
pageY: 19,
- screenX: 23,
- screenY: 24,
+ screenX: 33,
+ screenY: 84,
clientX: 33,
clientY: 34,
target: ref.current,
@@ -1360,6 +1225,8 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
});
if (hasPointerEvents) {
+ // TODO(T46067442): move these tests to core
+ /*
it('should properly only flush sync once when the event systems are mixed', () => {
const ref = React.createRef();
let renderCounts = 0;
@@ -1557,7 +1424,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
expect(ops).toEqual(['Presses: 0, Clicks: 0']);
},
);
-
+ */
it('should work correctly with stopPropagation set to true', () => {
const ref = React.createRef();
const pointerDownEvent = jest.fn();
@@ -1569,7 +1436,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
container.addEventListener('pointerdown', pointerDownEvent);
ReactDOM.render(
, container);
- dispatchPointerDown(ref.current);
+ createEventTarget(ref.current).pointerdown();
container.removeEventListener('pointerdown', pointerDownEvent);
expect(pointerDownEvent).toHaveBeenCalledTimes(0);
});
diff --git a/packages/react-events/src/dom/__tests__/Scroll-test.internal.js b/packages/react-events/src/dom/__tests__/Scroll-test.internal.js
index aa55e788ee84f..3c61944e7c5ee 100644
--- a/packages/react-events/src/dom/__tests__/Scroll-test.internal.js
+++ b/packages/react-events/src/dom/__tests__/Scroll-test.internal.js
@@ -9,33 +9,31 @@
'use strict';
+import {createEventTarget, setPointerEvent} from '../testing-library';
+
let React;
let ReactFeatureFlags;
let ReactDOM;
let useScrollResponder;
-const createEvent = (type, data) => {
- const event = document.createEvent('CustomEvent');
- event.initCustomEvent(type, true, true);
- if (data != null) {
- Object.entries(data).forEach(([key, value]) => {
- event[key] = value;
- });
- }
- return event;
+const forcePointerEvents = true;
+const table = [[forcePointerEvents], [!forcePointerEvents]];
+
+const initializeModules = hasPointerEvents => {
+ setPointerEvent(hasPointerEvents);
+ jest.resetModules();
+ ReactFeatureFlags = require('shared/ReactFeatureFlags');
+ ReactFeatureFlags.enableFlareAPI = true;
+ React = require('react');
+ ReactDOM = require('react-dom');
+ useScrollResponder = require('react-events/scroll').useScrollResponder;
};
-describe('Scroll event responder', () => {
+describe.each(table)('Scroll responder', hasPointerEvents => {
let container;
beforeEach(() => {
- jest.resetModules();
- ReactFeatureFlags = require('shared/ReactFeatureFlags');
- ReactFeatureFlags.enableFlareAPI = true;
- React = require('react');
- ReactDOM = require('react-dom');
- useScrollResponder = require('react-events/scroll').useScrollResponder;
-
+ initializeModules(hasPointerEvents);
container = document.createElement('div');
document.body.appendChild(container);
});
@@ -63,7 +61,8 @@ describe('Scroll event responder', () => {
});
it('prevents custom events being dispatched', () => {
- ref.current.dispatchEvent(createEvent('scroll'));
+ const target = createEventTarget(ref.current);
+ target.scroll();
expect(onScroll).not.toBeCalled();
});
});
@@ -84,129 +83,49 @@ describe('Scroll event responder', () => {
});
describe('is called after "scroll" event', () => {
- it('with a mouse pointerType', () => {
- ref.current.dispatchEvent(
- createEvent('pointerdown', {
- pointerType: 'mouse',
- }),
- );
- ref.current.dispatchEvent(createEvent('scroll'));
+ const pointerTypesTable = hasPointerEvents
+ ? [['mouse'], ['touch'], ['pen']]
+ : [['mouse'], ['touch']];
+ it.each(pointerTypesTable)('with pointerType: %s', pointerType => {
+ const node = ref.current;
+ const target = createEventTarget(node);
+ target.pointerdown({pointerType});
+ target.scroll();
expect(onScroll).toHaveBeenCalledTimes(1);
expect(onScroll).toHaveBeenCalledWith(
expect.objectContaining({
- pointerType: 'mouse',
+ pointerType,
type: 'scroll',
direction: '',
}),
);
onScroll.mockReset();
- ref.current.scrollTop = -1;
- ref.current.dispatchEvent(createEvent('scroll'));
+ node.scrollTop = -1;
+ target.scroll();
expect(onScroll).toHaveBeenCalledWith(
expect.objectContaining({
- pointerType: 'mouse',
+ pointerType,
type: 'scroll',
direction: 'up',
}),
);
onScroll.mockReset();
- ref.current.scrollTop = 1;
- ref.current.dispatchEvent(createEvent('scroll'));
+ node.scrollTop = 1;
+ target.scroll();
expect(onScroll).toHaveBeenCalledWith(
expect.objectContaining({
- pointerType: 'mouse',
+ pointerType,
type: 'scroll',
direction: 'down',
}),
);
});
- it('with a touch pointerType', () => {
- ref.current.dispatchEvent(
- createEvent('pointerdown', {
- pointerType: 'touch',
- }),
- );
- ref.current.dispatchEvent(createEvent('scroll'));
- expect(onScroll).toHaveBeenCalledTimes(1);
- expect(onScroll).toHaveBeenCalledWith(
- expect.objectContaining({
- pointerType: 'touch',
- type: 'scroll',
- direction: '',
- }),
- );
- onScroll.mockReset();
- ref.current.scrollTop = -1;
- ref.current.dispatchEvent(createEvent('scroll'));
- expect(onScroll).toHaveBeenCalledWith(
- expect.objectContaining({
- pointerType: 'touch',
- type: 'scroll',
- direction: 'up',
- }),
- );
- onScroll.mockReset();
- ref.current.scrollTop = 1;
- ref.current.dispatchEvent(createEvent('scroll'));
- expect(onScroll).toHaveBeenCalledWith(
- expect.objectContaining({
- pointerType: 'touch',
- type: 'scroll',
- direction: 'down',
- }),
- );
- });
-
- it('with a pen pointerType', () => {
- ref.current.dispatchEvent(
- createEvent('pointerdown', {
- pointerType: 'pen',
- }),
- );
- ref.current.dispatchEvent(createEvent('scroll'));
- expect(onScroll).toHaveBeenCalledTimes(1);
- expect(onScroll).toHaveBeenCalledWith(
- expect.objectContaining({
- pointerType: 'pen',
- type: 'scroll',
- direction: '',
- }),
- );
- onScroll.mockReset();
- ref.current.scrollTop = -1;
- ref.current.dispatchEvent(createEvent('scroll'));
- expect(onScroll).toHaveBeenCalledWith(
- expect.objectContaining({
- pointerType: 'pen',
- type: 'scroll',
- direction: 'up',
- }),
- );
- onScroll.mockReset();
- ref.current.scrollTop = 1;
- ref.current.dispatchEvent(createEvent('scroll'));
- expect(onScroll).toHaveBeenCalledWith(
- expect.objectContaining({
- pointerType: 'pen',
- type: 'scroll',
- direction: 'down',
- }),
- );
- });
-
- it('with a keyboard pointerType', () => {
- ref.current.dispatchEvent(
- createEvent('keydown', {
- key: 'A',
- }),
- );
- ref.current.dispatchEvent(
- createEvent('keyup', {
- key: 'A',
- }),
- );
- ref.current.dispatchEvent(createEvent('scroll'));
+ it('with pointerType: keyboard', () => {
+ const target = createEventTarget(ref.current);
+ target.keydown({key: 'A'});
+ target.keyup({key: 'A'});
+ target.scroll();
expect(onScroll).toHaveBeenCalledTimes(1);
expect(onScroll).toHaveBeenCalledWith(
expect.objectContaining({
@@ -235,13 +154,9 @@ describe('Scroll event responder', () => {
});
it('works as expected with touch events', () => {
- ref.current.dispatchEvent(
- createEvent('pointerdown', {
- pointerType: 'touch',
- }),
- );
- ref.current.dispatchEvent(createEvent('touchstart'));
- ref.current.dispatchEvent(createEvent('scroll'));
+ const target = createEventTarget(ref.current);
+ target.pointerdown({pointerType: 'touch'});
+ target.scroll();
expect(onScrollDragStart).toHaveBeenCalledTimes(1);
expect(onScrollDragStart).toHaveBeenCalledWith(
expect.objectContaining({
@@ -269,14 +184,10 @@ describe('Scroll event responder', () => {
});
it('works as expected with touch events', () => {
- ref.current.dispatchEvent(
- createEvent('pointerdown', {
- pointerType: 'touch',
- }),
- );
- ref.current.dispatchEvent(createEvent('touchstart'));
- ref.current.dispatchEvent(createEvent('scroll'));
- ref.current.dispatchEvent(createEvent('touchend'));
+ const target = createEventTarget(ref.current);
+ target.pointerdown({pointerType: 'touch'});
+ target.scroll();
+ target.pointerup({pointerType: 'touch'});
expect(onScrollDragEnd).toHaveBeenCalledTimes(1);
expect(onScrollDragEnd).toHaveBeenCalledWith(
expect.objectContaining({
diff --git a/packages/react-events/src/dom/test-utils.js b/packages/react-events/src/dom/test-utils.js
deleted file mode 100644
index 9597af0781946..0000000000000
--- a/packages/react-events/src/dom/test-utils.js
+++ /dev/null
@@ -1,481 +0,0 @@
-/**
- * Copyright (c) Facebook, Inc. and its affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * @emails react-core
- */
-
-'use strict';
-
-/* eslint-disable no-unused-vars */
-
-/**
- * Change environment support for PointerEvent.
- */
-
-function hasPointerEvent() {
- return global != null && global.PointerEvent != null;
-}
-
-function setPointerEvent(bool) {
- global.PointerEvent = bool ? function() {} : undefined;
-}
-
-/**
- * Change environment host platform.
- */
-
-const platformGetter = jest.spyOn(global.navigator, 'platform', 'get');
-
-const platform = {
- clear() {
- platformGetter.mockClear();
- },
- get() {
- return global.navigator.platform === 'MacIntel' ? 'mac' : 'windows';
- },
- set(name: 'mac' | 'windows') {
- switch (name) {
- case 'mac': {
- platformGetter.mockReturnValue('MacIntel');
- break;
- }
- case 'windows': {
- platformGetter.mockReturnValue('Win32');
- break;
- }
- default: {
- break;
- }
- }
- },
-};
-
-/**
- * Mock native events
- */
-
-function createEvent(type, data = {}) {
- const event = document.createEvent('CustomEvent');
- event.initCustomEvent(type, true, true);
- event.clientX = data.x || 0;
- event.clientY = data.y || 0;
- event.x = data.x || 0;
- event.y = data.y || 0;
- if (data != null) {
- Object.keys(data).forEach(key => {
- const value = data[key];
- Object.defineProperty(event, key, {value});
- });
- }
- return event;
-}
-
-function createTouchEvent(type, data = {}, id) {
- return createEvent(type, {
- changedTouches: [
- {
- identifier: id,
- clientX: data.x || 0,
- clientY: data.y || 0,
- ...data,
- },
- ],
- targetTouches: [
- {
- identifier: id,
- clientX: 0 || data.x,
- clientY: 0 || data.y,
- ...data,
- },
- ],
- });
-}
-
-const createKeyboardEvent = (type, data) => {
- return new KeyboardEvent(type, {
- bubbles: true,
- cancelable: true,
- ...data,
- });
-};
-
-function blur(data) {
- return createEvent('blur', data);
-}
-
-function click(data) {
- return createEvent('click', data);
-}
-
-function contextmenu(data) {
- return createEvent('contextmenu', data);
-}
-
-function dragstart(data) {
- return createEvent('dragstart', data);
-}
-
-function focus(data) {
- return createEvent('focus', data);
-}
-
-function gotpointercapture(data) {
- return createEvent('gotpointercapture', data);
-}
-
-function keydown(data) {
- return createEvent('keydown', data);
-}
-
-function keyup(data) {
- return createEvent('keyup', data);
-}
-
-function lostpointercapture(data) {
- return createEvent('lostpointercapture', data);
-}
-
-function mousedown(data) {
- return createEvent('mousedown', data);
-}
-
-function mouseenter(data) {
- return createEvent('mouseenter', data);
-}
-
-function mouseleave(data) {
- return createEvent('mouseleave', data);
-}
-
-function mousemove(data) {
- return createEvent('mousemove', data);
-}
-
-function mouseout(data) {
- return createEvent('mouseout', data);
-}
-
-function mouseover(data) {
- return createEvent('mouseover', data);
-}
-
-function mouseup(data) {
- return createEvent('mouseup', data);
-}
-
-function pointercancel(data) {
- return createEvent('pointercancel', data);
-}
-
-function pointerdown(data) {
- return createEvent('pointerdown', data);
-}
-
-function pointerenter(data) {
- return createEvent('pointerenter', data);
-}
-
-function pointerleave(data) {
- return createEvent('pointerleave', data);
-}
-
-function pointermove(data) {
- return createEvent('pointermove', data);
-}
-
-function pointerout(data) {
- return createEvent('pointerout', data);
-}
-
-function pointerover(data) {
- return createEvent('pointerover', data);
-}
-
-function pointerup(data) {
- return createEvent('pointerup', data);
-}
-
-function scroll(data) {
- return createEvent('scroll', data);
-}
-
-function touchcancel(data, id) {
- return createTouchEvent('touchcancel', data, id);
-}
-
-function touchend(data, id) {
- return createTouchEvent('touchend', data, id);
-}
-
-function touchmove(data, id) {
- return createTouchEvent('touchmove', data, id);
-}
-
-function touchstart(data, id) {
- return createTouchEvent('touchstart', data, id);
-}
-
-/**
- * Dispatch high-level event sequences
- */
-
-function emptyFunction() {}
-
-function dispatchLongPressContextMenu(
- target,
- {preventDefault = emptyFunction} = {},
-) {
- const dispatch = arg => target.dispatchEvent(arg);
- const button = 0;
- if (hasPointerEvent()) {
- dispatch(pointerdown({button, pointerType: 'touch'}));
- }
- dispatch(touchstart());
- dispatch(contextmenu({button, preventDefault}));
-}
-
-function dispatchRightClickContextMenu(
- target,
- {preventDefault = emptyFunction} = {},
-) {
- const dispatch = arg => target.dispatchEvent(arg);
- const button = 2;
- if (hasPointerEvent()) {
- dispatch(pointerdown({button, pointerType: 'mouse'}));
- }
- dispatch(mousedown({button}));
- dispatch(contextmenu({button, preventDefault}));
-}
-
-function dispatchModifiedClickContextMenu(
- target,
- {preventDefault = emptyFunction} = {},
-) {
- const dispatch = arg => target.dispatchEvent(arg);
- const button = 0;
- const ctrlKey = true;
- if (hasPointerEvent()) {
- dispatch(pointerdown({button, ctrlKey, pointerType: 'mouse'}));
- }
- dispatch(mousedown({button, ctrlKey}));
- if (platform.get() === 'mac') {
- dispatch(contextmenu({button, ctrlKey, preventDefault}));
- }
-}
-
-function dispatchPointerHoverEnter(target, {relatedTarget, x, y} = {}) {
- const dispatch = arg => target.dispatchEvent(arg);
- const button = -1;
- const pointerType = 'mouse';
- const event = {
- button,
- relatedTarget,
- clientX: x,
- clientY: y,
- pageX: x,
- pageY: y,
- };
- if (hasPointerEvent()) {
- dispatch(pointerover({pointerType, ...event}));
- dispatch(pointerenter({pointerType, ...event}));
- }
- dispatch(mouseover(event));
- dispatch(mouseenter(event));
-}
-
-function dispatchPointerHoverMove(target, {x, y} = {}) {
- const dispatch = arg => target.dispatchEvent(arg);
- const button = -1;
- const pointerId = 1;
- const pointerType = 'mouse';
- function dispatchMove() {
- const event = {
- button,
- clientX: x,
- clientY: y,
- pageX: x,
- pageY: y,
- };
- if (hasPointerEvent()) {
- dispatch(pointermove({pointerId, pointerType, ...event}));
- }
- dispatch(mousemove(event));
- }
- dispatchMove();
-}
-
-function dispatchPointerHoverExit(target, {relatedTarget, x, y} = {}) {
- const dispatch = arg => target.dispatchEvent(arg);
- const button = -1;
- const pointerType = 'mouse';
- const event = {
- button,
- relatedTarget,
- clientX: x,
- clientY: y,
- pageX: x,
- pageY: y,
- };
- if (hasPointerEvent()) {
- dispatch(pointerout({pointerType, ...event}));
- dispatch(pointerleave({pointerType, ...event}));
- }
- dispatch(mouseout(event));
- dispatch(mouseleave(event));
-}
-
-function dispatchPointerCancel(target, {pointerType = 'mouse', ...rest} = {}) {
- const dispatchEvent = arg => target.dispatchEvent(arg);
- if (hasPointerEvent()) {
- dispatchEvent(pointercancel({pointerType, ...rest}));
- } else {
- if (pointerType === 'mouse') {
- dispatchEvent(dragstart({...rest}));
- } else {
- dispatchEvent(touchcancel({...rest}));
- }
- }
-}
-
-function dispatchPointerDown(
- target,
- {button = 0, pointerType = 'mouse', ...rest} = {},
-) {
- const dispatch = arg => target.dispatchEvent(arg);
- const pointerId = 1;
- const pointerEvent = {button, pointerId, pointerType, ...rest};
- const mouseEvent = {button, ...rest};
- const touch = {...rest};
-
- if (pointerType === 'mouse') {
- if (hasPointerEvent()) {
- dispatch(pointerover(pointerEvent));
- dispatch(pointerenter(pointerEvent));
- }
- dispatch(mouseover(mouseEvent));
- dispatch(mouseenter(mouseEvent));
- if (hasPointerEvent()) {
- dispatch(pointerdown(pointerEvent));
- }
- dispatch(mousedown(mouseEvent));
- if (document.activeElement !== target) {
- dispatch(focus());
- }
- } else {
- if (hasPointerEvent()) {
- dispatch(pointerover(pointerEvent));
- dispatch(pointerenter(pointerEvent));
- dispatch(pointerdown(pointerEvent));
- }
- dispatch(touchstart(touch, pointerId));
- if (hasPointerEvent()) {
- dispatch(gotpointercapture(pointerEvent));
- }
- }
-}
-
-function dispatchPointerUp(
- target,
- {button = 0, pointerType = 'mouse', ...rest} = {},
-) {
- const dispatch = arg => target.dispatchEvent(arg);
- const pointerId = 1;
- const pointerEvent = {button, pointerId, pointerType, ...rest};
- const mouseEvent = {button, ...rest};
- const touch = {...rest};
-
- if (pointerType === 'mouse') {
- if (hasPointerEvent()) {
- dispatch(pointerup(pointerEvent));
- }
- dispatch(mouseup(mouseEvent));
- dispatch(click(mouseEvent));
- } else {
- if (hasPointerEvent()) {
- dispatch(pointerup(pointerEvent));
- dispatch(lostpointercapture(pointerEvent));
- dispatch(pointerout(pointerEvent));
- dispatch(pointerleave(pointerEvent));
- }
- dispatch(touchend(touch, pointerId));
- dispatch(mouseover(mouseEvent));
- dispatch(mousemove(mouseEvent));
- dispatch(mousedown(mouseEvent));
- if (document.activeElement !== target) {
- dispatch(focus());
- }
- dispatch(mouseup(mouseEvent));
- dispatch(click(mouseEvent));
- }
-}
-
-function dispatchPointerMove(
- target,
- {button = 0, pointerType = 'mouse', ...rest} = {},
-) {
- const dispatch = arg => target.dispatchEvent(arg);
- const pointerId = 1;
- const pointerEvent = {
- button,
- pointerId,
- pointerType,
- ...rest,
- };
- const mouseEvent = {
- button,
- ...rest,
- };
- const touch = {
- ...rest,
- };
-
- if (hasPointerEvent()) {
- dispatch(pointermove(pointerEvent));
- }
- if (pointerType === 'mouse') {
- dispatch(mousemove(mouseEvent));
- }
- if (pointerType === 'touch') {
- dispatch(touchmove(touch, pointerId));
- }
-}
-
-function dispatchTouchTap(target) {
- dispatchPointerDown(target, {pointerType: 'touch'});
- dispatchPointerUp(target, {pointerType: 'touch'});
-}
-
-function dispatchMouseTap(target) {
- dispatchPointerDown(target, {pointerType: 'mouse'});
- dispatchPointerUp(target, {pointerType: 'mouse'});
-}
-
-module.exports = {
- blur,
- click,
- focus,
- createEvent,
- dispatchLongPressContextMenu,
- dispatchRightClickContextMenu,
- dispatchModifiedClickContextMenu,
- dispatchPointerCancel,
- dispatchPointerHoverEnter,
- dispatchPointerHoverExit,
- dispatchPointerHoverMove,
- dispatchPointerMove,
- dispatchPointerDown,
- dispatchPointerUp,
- dispatchTouchTap,
- dispatchMouseTap,
- keydown,
- keyup,
- scroll,
- pointerdown,
- pointerup,
- platform,
- hasPointerEvent,
- setPointerEvent,
-};
diff --git a/packages/react-events/src/dom/testing-library/domEnvironment.js b/packages/react-events/src/dom/testing-library/domEnvironment.js
new file mode 100644
index 0000000000000..035673ae81ace
--- /dev/null
+++ b/packages/react-events/src/dom/testing-library/domEnvironment.js
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @emails react-core
+ */
+
+'use strict';
+/**
+ * Change environment support for PointerEvent.
+ */
+
+export function hasPointerEvent() {
+ return global != null && global.PointerEvent != null;
+}
+
+export function setPointerEvent(bool) {
+ global.PointerEvent = bool ? function() {} : undefined;
+}
+
+/**
+ * Change environment host platform.
+ */
+
+const platformGetter = jest.spyOn(global.navigator, 'platform', 'get');
+
+export const platform = {
+ clear() {
+ platformGetter.mockClear();
+ },
+ get() {
+ return global.navigator.platform === 'MacIntel' ? 'mac' : 'windows';
+ },
+ set(name: 'mac' | 'windows') {
+ switch (name) {
+ case 'mac': {
+ platformGetter.mockReturnValue('MacIntel');
+ break;
+ }
+ case 'windows': {
+ platformGetter.mockReturnValue('Win32');
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ },
+};
diff --git a/packages/react-events/src/dom/testing-library/domEventSequences.js b/packages/react-events/src/dom/testing-library/domEventSequences.js
new file mode 100644
index 0000000000000..d84feacc5ebf3
--- /dev/null
+++ b/packages/react-events/src/dom/testing-library/domEventSequences.js
@@ -0,0 +1,175 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @emails react-core
+ */
+
+'use strict';
+
+import * as domEvents from './domEvents';
+import {hasPointerEvent, platform} from './domEnvironment';
+
+function emptyFunction() {}
+
+function getPointerType(payload) {
+ let pointerType = 'mouse';
+ if (payload != null && payload.pointerType != null) {
+ pointerType = payload.pointerType;
+ }
+ return pointerType;
+}
+
+export function contextmenu(
+ target,
+ {preventDefault = emptyFunction} = {},
+ {pointerType = 'mouse', modified} = {},
+) {
+ const dispatch = arg => target.dispatchEvent(arg);
+ if (pointerType === 'touch') {
+ const button = 0;
+ if (hasPointerEvent()) {
+ dispatch(domEvents.pointerdown({button, pointerType}));
+ }
+ dispatch(domEvents.touchstart());
+ dispatch(domEvents.contextmenu({button, preventDefault}));
+ } else if (pointerType === 'mouse') {
+ if (modified === true) {
+ const button = 0;
+ const ctrlKey = true;
+ if (hasPointerEvent()) {
+ dispatch(domEvents.pointerdown({button, ctrlKey, pointerType}));
+ }
+ dispatch(domEvents.mousedown({button, ctrlKey}));
+ if (platform.get() === 'mac') {
+ dispatch(domEvents.contextmenu({button, ctrlKey, preventDefault}));
+ }
+ } else {
+ const button = 2;
+ if (hasPointerEvent()) {
+ dispatch(domEvents.pointerdown({button, pointerType}));
+ }
+ dispatch(domEvents.mousedown({button}));
+ dispatch(domEvents.contextmenu({button, preventDefault}));
+ }
+ }
+}
+
+export function pointercancel(target, payload) {
+ const dispatchEvent = arg => target.dispatchEvent(arg);
+ const pointerType = getPointerType(payload);
+ if (hasPointerEvent()) {
+ dispatchEvent(domEvents.pointercancel(payload));
+ } else {
+ if (pointerType === 'mouse') {
+ dispatchEvent(domEvents.dragstart(payload));
+ } else {
+ dispatchEvent(domEvents.touchcancel(payload));
+ }
+ }
+}
+
+export function pointerdown(target, defaultPayload) {
+ const dispatch = arg => target.dispatchEvent(arg);
+ const pointerType = getPointerType(defaultPayload);
+ const payload = {button: 0, buttons: 2, ...defaultPayload};
+
+ if (pointerType === 'mouse') {
+ if (hasPointerEvent()) {
+ dispatch(domEvents.pointerover(payload));
+ dispatch(domEvents.pointerenter(payload));
+ }
+ dispatch(domEvents.mouseover(payload));
+ dispatch(domEvents.mouseenter(payload));
+ if (hasPointerEvent()) {
+ dispatch(domEvents.pointerdown(payload));
+ }
+ dispatch(domEvents.mousedown(payload));
+ if (document.activeElement !== target) {
+ dispatch(domEvents.focus());
+ }
+ } else {
+ if (hasPointerEvent()) {
+ dispatch(domEvents.pointerover(payload));
+ dispatch(domEvents.pointerenter(payload));
+ dispatch(domEvents.pointerdown(payload));
+ }
+ dispatch(domEvents.touchstart(payload));
+ if (hasPointerEvent()) {
+ dispatch(domEvents.gotpointercapture(payload));
+ }
+ }
+}
+
+export function pointerenter(target, payload) {
+ const dispatch = arg => target.dispatchEvent(arg);
+ if (hasPointerEvent()) {
+ dispatch(domEvents.pointerover(payload));
+ dispatch(domEvents.pointerenter(payload));
+ }
+ dispatch(domEvents.mouseover(payload));
+ dispatch(domEvents.mouseenter(payload));
+}
+
+export function pointerexit(target, payload) {
+ const dispatch = arg => target.dispatchEvent(arg);
+ if (hasPointerEvent()) {
+ dispatch(domEvents.pointerout(payload));
+ dispatch(domEvents.pointerleave(payload));
+ }
+ dispatch(domEvents.mouseout(payload));
+ dispatch(domEvents.mouseleave(payload));
+}
+
+export function pointerhover(target, payload) {
+ const dispatch = arg => target.dispatchEvent(arg);
+ if (hasPointerEvent()) {
+ dispatch(domEvents.pointermove(payload));
+ }
+ dispatch(domEvents.mousemove(payload));
+}
+
+export function pointermove(target, payload) {
+ const dispatch = arg => target.dispatchEvent(arg);
+ const pointerType = getPointerType(payload);
+ if (hasPointerEvent()) {
+ dispatch(domEvents.pointermove(payload));
+ }
+ if (pointerType === 'mouse') {
+ dispatch(domEvents.mousemove(payload));
+ } else {
+ dispatch(domEvents.touchmove(payload));
+ }
+}
+
+export function pointerup(target, defaultPayload) {
+ const dispatch = arg => target.dispatchEvent(arg);
+ const pointerType = getPointerType(defaultPayload);
+ const payload = {button: 0, buttons: 2, ...defaultPayload};
+
+ if (pointerType === 'mouse') {
+ if (hasPointerEvent()) {
+ dispatch(domEvents.pointerup(payload));
+ }
+ dispatch(domEvents.mouseup(payload));
+ dispatch(domEvents.click(payload));
+ } else {
+ if (hasPointerEvent()) {
+ dispatch(domEvents.pointerup(payload));
+ dispatch(domEvents.lostpointercapture(payload));
+ dispatch(domEvents.pointerout(payload));
+ dispatch(domEvents.pointerleave(payload));
+ }
+ dispatch(domEvents.touchend(payload));
+ dispatch(domEvents.mouseover(payload));
+ dispatch(domEvents.mousemove(payload));
+ dispatch(domEvents.mousedown(payload));
+ if (document.activeElement !== target) {
+ dispatch(domEvents.focus());
+ }
+ dispatch(domEvents.mouseup(payload));
+ dispatch(domEvents.click(payload));
+ }
+}
diff --git a/packages/react-events/src/dom/testing-library/domEvents.js b/packages/react-events/src/dom/testing-library/domEvents.js
new file mode 100644
index 0000000000000..01ed26a568908
--- /dev/null
+++ b/packages/react-events/src/dom/testing-library/domEvents.js
@@ -0,0 +1,370 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @emails react-core
+ */
+
+'use strict';
+
+/**
+ * Native event object mocks for higher-level events.
+ *
+ * 1. Each event type defines the exact object that it accepts. This ensures
+ * that no arbitrary properties can be assigned to events, and the properties
+ * that don't exist on specific event types (e.g., 'pointerType') are not added
+ * to the respective native event.
+ *
+ * 2. Properties that cannot be relied on due to inconsistent browser support (e.g., 'x' and 'y') are not
+ * added to the native event. Others that shouldn't be arbitrarily customized (e.g., 'screenX')
+ * are automatically inferred from associated values.
+ *
+ * 3. PointerEvent and TouchEvent fields are normalized (e.g., 'rotationAngle' -> 'twist')
+ */
+
+function emptyFunction() {}
+
+function createEvent(type, data = {}) {
+ const event = document.createEvent('CustomEvent');
+ event.initCustomEvent(type, true, true);
+ if (data != null) {
+ Object.keys(data).forEach(key => {
+ const value = data[key];
+ Object.defineProperty(event, key, {value});
+ });
+ }
+ return event;
+}
+
+function createGetModifierState(keyArg, data) {
+ if (keyArg === 'Alt') {
+ return data.altKey || false;
+ }
+ if (keyArg === 'Control') {
+ return data.ctrlKey || false;
+ }
+ if (keyArg === 'Meta') {
+ return data.metaKey || false;
+ }
+ if (keyArg === 'Shift') {
+ return data.shiftKey || false;
+ }
+}
+
+function createPointerEvent(
+ type,
+ {
+ altKey = false,
+ button = -1,
+ buttons = 0,
+ ctrlKey = false,
+ height,
+ metaKey = false,
+ movementX = 0,
+ movementY = 0,
+ offsetX = 0,
+ offsetY = 0,
+ pageX,
+ pageY,
+ pointerId = 1,
+ pressure = 0,
+ preventDefault = emptyFunction,
+ pointerType = 'mouse',
+ shiftKey = false,
+ tangentialPressure = 0,
+ tiltX = 0,
+ tiltY = 0,
+ twist = 0,
+ width,
+ x = 0,
+ y = 0,
+ } = {},
+) {
+ const modifierState = {altKey, ctrlKey, metaKey, shiftKey};
+
+ return createEvent(type, {
+ altKey,
+ button,
+ buttons,
+ clientX: x,
+ clientY: y,
+ ctrlKey,
+ getModifierState(keyArg) {
+ createGetModifierState(keyArg, modifierState);
+ },
+ height: pointerType === 'mouse' ? 1 : height != null ? height : 11.5,
+ metaKey,
+ movementX,
+ movementY,
+ offsetX,
+ offsetY,
+ pageX: pageX || x,
+ pageY: pageY || y,
+ pointerId,
+ pointerType,
+ pressure,
+ preventDefault,
+ screenX: x,
+ screenY: y + 50, // arbitrary value to emulate browser chrome, etc
+ shiftKey,
+ tangentialPressure,
+ tiltX,
+ tiltY,
+ twist,
+ width: pointerType === 'mouse' ? 1 : width != null ? width : 11.5,
+ });
+}
+
+function createKeyboardEvent(
+ type,
+ {
+ altKey = false,
+ ctrlKey = false,
+ key = '',
+ metaKey = false,
+ preventDefault = emptyFunction,
+ shiftKey = false,
+ } = {},
+) {
+ const modifierState = {altKey, ctrlKey, metaKey, shiftKey};
+
+ return createEvent(type, {
+ altKey,
+ ctrlKey,
+ getModifierState(keyArg) {
+ createGetModifierState(keyArg, modifierState);
+ },
+ key,
+ metaKey,
+ preventDefault,
+ shiftKey,
+ });
+}
+
+function createMouseEvent(
+ type,
+ {
+ altKey = false,
+ button = -1,
+ buttons = 0,
+ ctrlKey = false,
+ metaKey = false,
+ movementX = 0,
+ movementY = 0,
+ offsetX = 0,
+ offsetY = 0,
+ pageX,
+ pageY,
+ preventDefault = emptyFunction,
+ shiftKey = false,
+ x = 0,
+ y = 0,
+ } = {},
+) {
+ const modifierState = {altKey, ctrlKey, metaKey, shiftKey};
+
+ return createEvent(type, {
+ altKey,
+ button,
+ buttons,
+ clientX: x,
+ clientY: y,
+ ctrlKey,
+ getModifierState(keyArg) {
+ createGetModifierState(keyArg, modifierState);
+ },
+ metaKey,
+ movementX,
+ movementY,
+ offsetX,
+ offsetY,
+ pageX: pageX || x,
+ pageY: pageY || y,
+ preventDefault,
+ screenX: x,
+ screenY: y + 50, // arbitrary value to emulate browser chrome, etc
+ shiftKey,
+ });
+}
+
+function createTouchEvent(
+ type,
+ {
+ altKey = false,
+ ctrlKey = false,
+ height = 11.5,
+ metaKey = false,
+ pageX,
+ pageY,
+ pointerId = 1,
+ preventDefault = emptyFunction,
+ shiftKey = false,
+ twist = 0,
+ width = 11.5,
+ x = 0,
+ y = 0,
+ } = {},
+) {
+ const touch = {
+ clientX: x,
+ clientY: y,
+ force: 1,
+ identifier: pointerId,
+ pageX: pageX || x,
+ pageY: pageY || y,
+ radiusX: width,
+ radiusY: height,
+ rotationAngle: twist,
+ screenX: x,
+ screenY: y + 50, // arbitrary value to emulate browser chrome, etc
+ };
+
+ return createEvent(type, {
+ altKey,
+ changedTouches: [touch],
+ ctrlKey,
+ metaKey,
+ preventDefault,
+ shiftKey,
+ targetTouches: type !== 'touchend' ? [touch] : null,
+ touches: [touch],
+ });
+}
+
+/**
+ * Mock event objects
+ */
+
+export function blur({relatedTarget} = {}) {
+ return createEvent('blur', {relatedTarget});
+}
+
+export function click(payload) {
+ return createMouseEvent('click', payload);
+}
+
+export function contextmenu(payload) {
+ return createMouseEvent('contextmenu', payload);
+}
+
+export function dragstart(payload) {
+ return createMouseEvent('dragstart', payload);
+}
+
+export function focus({relatedTarget} = {}) {
+ return createEvent('focus', {relatedTarget});
+}
+
+export function scroll() {
+ return createEvent('scroll');
+}
+
+/**
+ * Key events
+ */
+
+export function keydown(payload) {
+ return createKeyboardEvent('keydown', payload);
+}
+
+export function keyup(payload) {
+ return createKeyboardEvent('keyup', payload);
+}
+
+/**
+ * Pointer events
+ */
+
+export function gotpointercapture(payload) {
+ return createPointerEvent('gotpointercapture', payload);
+}
+
+export function lostpointercapture(payload) {
+ return createPointerEvent('lostpointercapture', payload);
+}
+
+export function pointercancel(payload) {
+ return createPointerEvent('pointercancel', payload);
+}
+
+export function pointerdown(payload) {
+ return createPointerEvent('pointerdown', {button: 0, buttons: 2, ...payload});
+}
+
+export function pointerenter(payload) {
+ return createPointerEvent('pointerenter', payload);
+}
+
+export function pointerleave(payload) {
+ return createPointerEvent('pointerleave', payload);
+}
+
+export function pointermove(payload) {
+ return createPointerEvent('pointermove', payload);
+}
+
+export function pointerout(payload) {
+ return createPointerEvent('pointerout', payload);
+}
+
+export function pointerover(payload) {
+ return createPointerEvent('pointerover', payload);
+}
+
+export function pointerup(payload) {
+ return createPointerEvent('pointerup', {button: 0, buttons: 2, ...payload});
+}
+
+/**
+ * Mouse events
+ */
+
+export function mousedown(payload) {
+ return createMouseEvent('mousedown', {button: 0, buttons: 2, ...payload});
+}
+
+export function mouseenter(payload) {
+ return createMouseEvent('mouseenter', payload);
+}
+
+export function mouseleave(payload) {
+ return createMouseEvent('mouseleave', payload);
+}
+
+export function mousemove(payload) {
+ return createMouseEvent('mousemove', payload);
+}
+
+export function mouseout(payload) {
+ return createMouseEvent('mouseout', payload);
+}
+
+export function mouseover(payload) {
+ return createMouseEvent('mouseover', payload);
+}
+
+export function mouseup(payload) {
+ return createMouseEvent('mouseup', {button: 0, buttons: 2, ...payload});
+}
+
+/**
+ * Touch events
+ */
+
+export function touchcancel(payload) {
+ return createTouchEvent('touchcancel', payload);
+}
+
+export function touchend(payload) {
+ return createTouchEvent('touchend', payload);
+}
+
+export function touchmove(payload) {
+ return createTouchEvent('touchmove', payload);
+}
+
+export function touchstart(payload) {
+ return createTouchEvent('touchstart', payload);
+}
diff --git a/packages/react-events/src/dom/testing-library/index.js b/packages/react-events/src/dom/testing-library/index.js
new file mode 100644
index 0000000000000..cb549dc5b53c7
--- /dev/null
+++ b/packages/react-events/src/dom/testing-library/index.js
@@ -0,0 +1,104 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @emails react-core
+ */
+
+'use strict';
+
+import * as domEvents from './domEvents';
+import * as domEventSequences from './domEventSequences';
+import {hasPointerEvent, setPointerEvent, platform} from './domEnvironment';
+
+const createEventTarget = node => ({
+ node,
+ /**
+ * General events abstraction.
+ */
+ blur(payload) {
+ node.dispatchEvent(domEvents.blur(payload));
+ },
+ click(payload) {
+ node.dispatchEvent(domEvents.click(payload));
+ },
+ contextmenu(payload, options) {
+ domEventSequences.contextmenu(node, payload, options);
+ },
+ focus(payload) {
+ node.dispatchEvent(domEvents.focus(payload));
+ },
+ /**
+ * KeyboardEvent abstraction.
+ */
+ keydown(payload) {
+ node.dispatchEvent(domEvents.keydown(payload));
+ },
+ keyup(payload) {
+ node.dispatchEvent(domEvents.keyup(payload));
+ },
+ scroll(payload) {
+ node.dispatchEvent(domEvents.scroll(payload));
+ },
+ /**
+ * PointerEvent abstraction.
+ * Dispatches the expected sequence of PointerEvents, MouseEvents, and
+ * TouchEvents for a given environment.
+ */
+ // node no longer receives events for the pointer
+ pointercancel(payload) {
+ domEventSequences.pointercancel(node, payload);
+ },
+ // node dispatches down events
+ pointerdown(payload) {
+ domEventSequences.pointerdown(node, payload);
+ },
+ // node dispatches move events (pointer is not down)
+ pointerhover(payload) {
+ domEventSequences.pointerhover(node, payload);
+ },
+ // node dispatches move events (pointer is down)
+ pointermove(payload) {
+ domEventSequences.pointermove(node, payload);
+ },
+ // node dispatches enter & over events
+ pointerenter(payload) {
+ domEventSequences.pointerenter(node, payload);
+ },
+ // node dispatches exit & out events
+ pointerexit(payload) {
+ domEventSequences.pointerexit(node, payload);
+ },
+ // node dispatches up events
+ pointerup(payload) {
+ domEventSequences.pointerup(node, payload);
+ },
+ /**
+ * Gesture abstractions.
+ * Helpers for event sequences expected in a gesture.
+ * target.tap({ pointerType: 'touch' })
+ */
+ tap(payload) {
+ domEventSequences.pointerdown(payload);
+ domEventSequences.pointerup(payload);
+ },
+ /**
+ * Utilities
+ */
+ setBoundingClientRect({x, y, width, height}) {
+ node.getBoundingClientRect = function() {
+ return {
+ width,
+ height,
+ left: x,
+ right: x + width,
+ top: y,
+ bottom: y + height,
+ };
+ };
+ },
+});
+
+export {createEventTarget, platform, hasPointerEvent, setPointerEvent};