(input = el)}
- controlledValue={controlledValue}
- />
- );
- }
- }
-
- // Initial mount. Test that this is async.
- root.render();
- // Should not have flushed yet.
- expect(ops).toEqual([]);
- expect(input).toBe(undefined);
- // Flush callbacks.
- Scheduler.unstable_flushAll();
- expect(ops).toEqual(['render: initial']);
- expect(input.value).toBe('initial');
-
- ops = [];
-
- // Trigger a click event
- input.dispatchEvent(
- new MouseEvent('mousedown', {bubbles: true, cancelable: true}),
- );
- input.dispatchEvent(
- new MouseEvent('mouseup', {bubbles: true, cancelable: true}),
- );
- // Nothing should have changed
- expect(ops).toEqual([]);
- expect(input.value).toBe('initial');
-
- // Flush callbacks.
- Scheduler.unstable_flushAll();
- // Now the click update has flushed.
- expect(ops).toEqual(['render: ']);
- expect(input.value).toBe('');
- });
});
});
diff --git a/packages/react-events/src/dom/__tests__/MixedResponders-test-internal.js b/packages/react-events/src/dom/__tests__/MixedResponders-test-internal.js
new file mode 100644
index 0000000000000..157039c9bd710
--- /dev/null
+++ b/packages/react-events/src/dom/__tests__/MixedResponders-test-internal.js
@@ -0,0 +1,326 @@
+/**
+ * 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 {createEventTarget} from '../testing-library';
+
+let React;
+let ReactFeatureFlags;
+let ReactDOM;
+let Scheduler;
+
+describe('mixing responders with the heritage event system', () => {
+ let container;
+
+ beforeEach(() => {
+ ReactFeatureFlags = require('shared/ReactFeatureFlags');
+ ReactFeatureFlags.enableFlareAPI = true;
+ React = require('react');
+ ReactDOM = require('react-dom');
+ Scheduler = require('scheduler');
+ container = document.createElement('div');
+ document.body.appendChild(container);
+ });
+
+ afterEach(() => {
+ document.body.removeChild(container);
+ container = null;
+ });
+
+ it('should properly only flush sync once when the event systems are mixed', () => {
+ const usePress = require('react-events/press').usePress;
+ const ref = React.createRef();
+ let renderCounts = 0;
+
+ function MyComponent() {
+ const [, updateCounter] = React.useState(0);
+ renderCounts++;
+
+ function handlePress() {
+ updateCounter(count => count + 1);
+ }
+
+ const listener = usePress({
+ onPress: handlePress,
+ });
+
+ return (
+
+
+
+ );
+ }
+
+ const newContainer = document.createElement('div');
+ const root = ReactDOM.unstable_createRoot(newContainer);
+ document.body.appendChild(newContainer);
+ root.render();
+ Scheduler.unstable_flushAll();
+
+ const target = createEventTarget(ref.current);
+ target.pointerdown({timeStamp: 100});
+ target.pointerup({timeStamp: 100});
+ target.click({timeStamp: 100});
+
+ if (__DEV__) {
+ expect(renderCounts).toBe(2);
+ } else {
+ expect(renderCounts).toBe(1);
+ }
+ Scheduler.unstable_flushAll();
+ if (__DEV__) {
+ expect(renderCounts).toBe(4);
+ } else {
+ expect(renderCounts).toBe(2);
+ }
+
+ target.pointerdown({timeStamp: 100});
+ target.pointerup({timeStamp: 100});
+ // Ensure the timeStamp logic works
+ target.click({timeStamp: 101});
+
+ if (__DEV__) {
+ expect(renderCounts).toBe(6);
+ } else {
+ expect(renderCounts).toBe(3);
+ }
+
+ Scheduler.unstable_flushAll();
+ document.body.removeChild(newContainer);
+ });
+
+ it('should properly flush sync when the event systems are mixed with unstable_flushDiscreteUpdates', () => {
+ const usePress = require('react-events/press').usePress;
+ const ref = React.createRef();
+ let renderCounts = 0;
+
+ function MyComponent() {
+ const [, updateCounter] = React.useState(0);
+ renderCounts++;
+
+ function handlePress() {
+ updateCounter(count => count + 1);
+ }
+
+ const listener = usePress({
+ onPress: handlePress,
+ });
+
+ return (
+
+
+
+ );
+ }
+
+ const newContainer = document.createElement('div');
+ const root = ReactDOM.unstable_createRoot(newContainer);
+ document.body.appendChild(newContainer);
+ root.render();
+ Scheduler.unstable_flushAll();
+
+ const target = createEventTarget(ref.current);
+ target.pointerdown({timeStamp: 100});
+ target.pointerup({timeStamp: 100});
+
+ if (__DEV__) {
+ expect(renderCounts).toBe(4);
+ } else {
+ expect(renderCounts).toBe(2);
+ }
+ Scheduler.unstable_flushAll();
+ if (__DEV__) {
+ expect(renderCounts).toBe(6);
+ } else {
+ expect(renderCounts).toBe(3);
+ }
+
+ target.pointerdown({timeStamp: 100});
+ // Ensure the timeStamp logic works
+ target.pointerup({timeStamp: 101});
+
+ if (__DEV__) {
+ expect(renderCounts).toBe(8);
+ } else {
+ expect(renderCounts).toBe(4);
+ }
+
+ Scheduler.unstable_flushAll();
+ document.body.removeChild(newContainer);
+ });
+
+ it(
+ 'should only flush before outermost discrete event handler when mixing ' +
+ 'event systems',
+ async () => {
+ const {useState} = React;
+ const usePress = require('react-events/press').usePress;
+
+ const button = React.createRef();
+
+ const ops = [];
+
+ function MyComponent() {
+ const [pressesCount, updatePressesCount] = useState(0);
+ const [clicksCount, updateClicksCount] = useState(0);
+
+ function handlePress() {
+ // This dispatches a synchronous, discrete event in the legacy event
+ // system. However, because it's nested inside the new event system,
+ // its updates should not flush until the end of the outer handler.
+ const target = createEventTarget(button.current);
+ target.click();
+ // Text context should not have changed
+ ops.push(newContainer.textContent);
+ updatePressesCount(pressesCount + 1);
+ }
+
+ const listener = usePress({
+ onPress: handlePress,
+ });
+
+ return (
+
+
+
+ );
+ }
+
+ const newContainer = document.createElement('div');
+ document.body.appendChild(newContainer);
+ const root = ReactDOM.unstable_createRoot(newContainer);
+
+ root.render();
+ Scheduler.unstable_flushAll();
+ expect(newContainer.textContent).toEqual('Presses: 0, Clicks: 0');
+
+ const target = createEventTarget(button.current);
+ target.pointerdown({timeStamp: 100});
+ target.pointerup({timeStamp: 100});
+
+ Scheduler.unstable_flushAll();
+ expect(newContainer.textContent).toEqual('Presses: 1, Clicks: 1');
+
+ expect(ops).toEqual(['Presses: 0, Clicks: 0']);
+ },
+ );
+
+ describe('mixing the Input and Press repsonders', () => {
+ it('is async for non-input events', () => {
+ ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
+ ReactFeatureFlags.enableUserBlockingEvents = true;
+ const usePress = require('react-events/press').usePress;
+ const useInput = require('react-events/input').useInput;
+ const root = ReactDOM.unstable_createRoot(container);
+ let input;
+
+ let ops = [];
+
+ function Component({innerRef, onChange, controlledValue, pressListener}) {
+ const inputListener = useInput({
+ onChange,
+ });
+ return (
+
+ );
+ }
+
+ function PressWrapper({innerRef, onPress, onChange, controlledValue}) {
+ const pressListener = usePress({
+ onPress,
+ });
+ return (
+ (input = el)}
+ controlledValue={controlledValue}
+ pressListener={pressListener}
+ />
+ );
+ }
+
+ class ControlledInput extends React.Component {
+ state = {value: 'initial'};
+ onChange = event => this.setState({value: event.target.value});
+ reset = () => {
+ this.setState({value: ''});
+ };
+ render() {
+ ops.push(`render: ${this.state.value}`);
+ const controlledValue =
+ this.state.value === 'changed' ? 'changed [!]' : this.state.value;
+ return (
+ (input = el)}
+ controlledValue={controlledValue}
+ />
+ );
+ }
+ }
+
+ // Initial mount. Test that this is async.
+ root.render();
+ // Should not have flushed yet.
+ expect(ops).toEqual([]);
+ expect(input).toBe(undefined);
+ // Flush callbacks.
+ Scheduler.unstable_flushAll();
+ expect(ops).toEqual(['render: initial']);
+ expect(input.value).toBe('initial');
+
+ ops = [];
+
+ // Trigger a click event
+ input.dispatchEvent(
+ new MouseEvent('mousedown', {bubbles: true, cancelable: true}),
+ );
+ input.dispatchEvent(
+ new MouseEvent('mouseup', {bubbles: true, cancelable: true}),
+ );
+ // Nothing should have changed
+ expect(ops).toEqual([]);
+ expect(input.value).toBe('initial');
+
+ // Flush callbacks.
+ Scheduler.unstable_flushAll();
+ // Now the click update has flushed.
+ expect(ops).toEqual(['render: ']);
+ expect(input.value).toBe('');
+ });
+ });
+});
diff --git a/packages/react-events/src/dom/__tests__/Press-test.internal.js b/packages/react-events/src/dom/__tests__/Press-test.internal.js
index 5e155f5ad839f..acb30e5290fc1 100644
--- a/packages/react-events/src/dom/__tests__/Press-test.internal.js
+++ b/packages/react-events/src/dom/__tests__/Press-test.internal.js
@@ -1120,334 +1120,7 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
target.pointerdown();
});
- it('should correctly pass through event properties', () => {
- const timeStamps = [];
- const ref = React.createRef();
- const eventLog = [];
- const logEvent = event => {
- const propertiesWeCareAbout = {
- pageX: event.pageX,
- pageY: event.pageY,
- screenX: event.screenX,
- screenY: event.screenY,
- clientX: event.clientX,
- clientY: event.clientY,
- pointerType: event.pointerType,
- target: event.target,
- timeStamp: event.timeStamp,
- type: event.type,
- };
- timeStamps.push(event.timeStamp);
- eventLog.push(propertiesWeCareAbout);
- };
-
- const Component = () => {
- const listener = usePress({
- onPressStart: logEvent,
- onPressEnd: logEvent,
- onPressMove: logEvent,
- onPress: logEvent,
- });
- return ;
- };
- ReactDOM.render(, container);
-
- const target = createEventTarget(ref.current);
- target.setBoundingClientRect({x: 10, y: 10, width: 100, height: 100});
- target.pointerdown({
- pointerType: 'mouse',
- pageX: 15,
- pageY: 16,
- x: 30,
- y: 31,
- });
- target.pointermove({
- pointerType: 'mouse',
- pageX: 16,
- pageY: 17,
- x: 31,
- y: 32,
- });
- target.pointerup({
- pointerType: 'mouse',
- pageX: 17,
- pageY: 18,
- x: 32,
- y: 33,
- });
- target.pointerdown({
- pointerType: 'mouse',
- pageX: 18,
- pageY: 19,
- x: 33,
- y: 34,
- });
- expect(typeof timeStamps[0] === 'number').toBe(true);
- expect(eventLog).toEqual([
- {
- pointerType: 'mouse',
- pageX: 15,
- pageY: 16,
- screenX: 30,
- screenY: 81,
- clientX: 30,
- clientY: 31,
- target: ref.current,
- timeStamp: timeStamps[0],
- type: 'pressstart',
- },
- {
- pointerType: 'mouse',
- pageX: 16,
- pageY: 17,
- screenX: 31,
- screenY: 82,
- clientX: 31,
- clientY: 32,
- target: ref.current,
- timeStamp: timeStamps[1],
- type: 'pressmove',
- },
- {
- pointerType: 'mouse',
- pageX: 17,
- pageY: 18,
- screenX: 32,
- screenY: 83,
- clientX: 32,
- clientY: 33,
- target: ref.current,
- timeStamp: timeStamps[2],
- type: 'pressend',
- },
- {
- pointerType: 'mouse',
- pageX: 17,
- pageY: 18,
- screenX: 32,
- screenY: 83,
- clientX: 32,
- clientY: 33,
- target: ref.current,
- timeStamp: timeStamps[3],
- type: 'press',
- },
- {
- pointerType: 'mouse',
- pageX: 18,
- pageY: 19,
- screenX: 33,
- screenY: 84,
- clientX: 33,
- clientY: 34,
- target: ref.current,
- timeStamp: timeStamps[4],
- type: 'pressstart',
- },
- ]);
- });
-
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;
-
- function MyComponent() {
- const [, updateCounter] = React.useState(0);
- renderCounts++;
-
- function handlePress() {
- updateCounter(count => count + 1);
- }
-
- const listener = usePress({
- onPress: handlePress,
- });
-
- return (
-
-
-
- );
- }
-
- const newContainer = document.createElement('div');
- const root = ReactDOM.unstable_createRoot(newContainer);
- document.body.appendChild(newContainer);
- root.render();
- Scheduler.unstable_flushAll();
-
- const target = ref.current;
- target.dispatchEvent(pointerdown({timeStamp: 100}));
- target.dispatchEvent(pointerup({timeStamp: 100}));
- target.dispatchEvent(click({timeStamp: 100}));
-
- if (__DEV__) {
- expect(renderCounts).toBe(2);
- } else {
- expect(renderCounts).toBe(1);
- }
- Scheduler.unstable_flushAll();
- if (__DEV__) {
- expect(renderCounts).toBe(4);
- } else {
- expect(renderCounts).toBe(2);
- }
-
- target.dispatchEvent(pointerdown({timeStamp: 100}));
- target.dispatchEvent(pointerup({timeStamp: 100}));
- // Ensure the timeStamp logic works
- target.dispatchEvent(click({timeStamp: 101}));
-
- if (__DEV__) {
- expect(renderCounts).toBe(6);
- } else {
- expect(renderCounts).toBe(3);
- }
-
- Scheduler.unstable_flushAll();
- document.body.removeChild(newContainer);
- });
-
- it('should properly flush sync when the event systems are mixed with unstable_flushDiscreteUpdates', () => {
- const ref = React.createRef();
- let renderCounts = 0;
-
- function MyComponent() {
- const [, updateCounter] = React.useState(0);
- renderCounts++;
-
- function handlePress() {
- updateCounter(count => count + 1);
- }
-
- const listener = usePress({
- onPress: handlePress,
- });
-
- return (
-
-
-
- );
- }
-
- const newContainer = document.createElement('div');
- const root = ReactDOM.unstable_createRoot(newContainer);
- document.body.appendChild(newContainer);
- root.render();
- Scheduler.unstable_flushAll();
-
- const target = ref.current;
- target.dispatchEvent(pointerdown({timeStamp: 100}));
- target.dispatchEvent(pointerup({timeStamp: 100}));
- target.dispatchEvent(click({timeStamp: 100}));
-
- if (__DEV__) {
- expect(renderCounts).toBe(4);
- } else {
- expect(renderCounts).toBe(2);
- }
- Scheduler.unstable_flushAll();
- if (__DEV__) {
- expect(renderCounts).toBe(6);
- } else {
- expect(renderCounts).toBe(3);
- }
-
- target.dispatchEvent(pointerdown({timeStamp: 100}));
- target.dispatchEvent(pointerup({timeStamp: 100}));
- // Ensure the timeStamp logic works
- target.dispatchEvent(click({timeStamp: 101}));
-
- if (__DEV__) {
- expect(renderCounts).toBe(8);
- } else {
- expect(renderCounts).toBe(4);
- }
-
- Scheduler.unstable_flushAll();
- document.body.removeChild(newContainer);
- });
-
- it(
- 'should only flush before outermost discrete event handler when mixing ' +
- 'event systems',
- async () => {
- const {useState} = React;
-
- const button = React.createRef();
-
- const ops = [];
-
- function MyComponent() {
- const [pressesCount, updatePressesCount] = useState(0);
- const [clicksCount, updateClicksCount] = useState(0);
-
- function handlePress() {
- // This dispatches a synchronous, discrete event in the legacy event
- // system. However, because it's nested inside the new event system,
- // its updates should not flush until the end of the outer handler.
- button.current.click();
- // Text context should not have changed
- ops.push(newContainer.textContent);
- updatePressesCount(pressesCount + 1);
- }
-
- const listener = usePress({
- onPress: handlePress,
- });
-
- return (
-
-
-
- );
- }
-
- const newContainer = document.createElement('div');
- document.body.appendChild(newContainer);
- const root = ReactDOM.unstable_createRoot(newContainer);
-
- root.render();
- Scheduler.unstable_flushAll();
- expect(newContainer.textContent).toEqual('Presses: 0, Clicks: 0');
-
- const target = button.current;
- target.dispatchEvent(pointerdown({timeStamp: 100}));
- target.dispatchEvent(pointerup({timeStamp: 100}));
- target.dispatchEvent(click({timeStamp: 100}));
-
- Scheduler.unstable_flushAll();
- expect(newContainer.textContent).toEqual('Presses: 1, Clicks: 1');
-
- expect(ops).toEqual(['Presses: 0, Clicks: 0']);
- },
- );
- */
it('should work correctly with stopPropagation set to true', () => {
const ref = React.createRef();
const pointerDownEvent = jest.fn();
diff --git a/packages/react-events/src/dom/testing-library/domEvents.js b/packages/react-events/src/dom/testing-library/domEvents.js
index e7b09c92c5ac9..a832757bff08c 100644
--- a/packages/react-events/src/dom/testing-library/domEvents.js
+++ b/packages/react-events/src/dom/testing-library/domEvents.js
@@ -37,6 +37,9 @@ function createEvent(type, data = {}) {
if (data != null) {
Object.keys(data).forEach(key => {
const value = data[key];
+ if (key === 'timeStamp' && !value) {
+ return;
+ }
Object.defineProperty(event, key, {value});
});
}
@@ -83,6 +86,7 @@ function createPointerEvent(
tangentialPressure = 0,
tiltX = 0,
tiltY = 0,
+ timeStamp,
twist = 0,
width,
x = 0,
@@ -122,6 +126,7 @@ function createPointerEvent(
tangentialPressure,
tiltX,
tiltY,
+ timeStamp,
twist,
width: isMouse ? 1 : width != null ? width : defaultPointerSize,
});
@@ -171,6 +176,7 @@ function createMouseEvent(
screenX,
screenY,
shiftKey = false,
+ timeStamp,
x = 0,
y = 0,
} = {},
@@ -198,6 +204,7 @@ function createMouseEvent(
screenX: screenX === 0 ? screenX : x,
screenY: screenY === 0 ? screenY : y + defaultBrowserChromeSize,
shiftKey,
+ timeStamp,
});
}
@@ -209,6 +216,8 @@ function createTouchEvent(type, payload) {
let metaKey = false;
let preventDefault = emptyFunction;
let shiftKey = false;
+ let timeStamp;
+
if (firstTouch != null) {
if (firstTouch.altKey != null) {
altKey = firstTouch.altKey;
@@ -225,6 +234,9 @@ function createTouchEvent(type, payload) {
if (firstTouch.shiftKey != null) {
shiftKey = firstTouch.shiftKey;
}
+ if (firstTouch.timeStamp != null) {
+ timeStamp = firstTouch.timeStamp;
+ }
}
const touches = touchesPayload.map(
@@ -268,6 +280,7 @@ function createTouchEvent(type, payload) {
firesTouchEvents: true,
},
targetTouches: activeTouches,
+ timeStamp,
touches: activeTouches,
});
}