mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[react-events] Split out mixed event responder tests (#16608)
This commit is contained in:
@@ -994,4 +994,68 @@ describe('DOMEventResponderSystem', () => {
|
||||
dispatchClickEvent(ref.current);
|
||||
expect(log).toEqual([{counter: 1}]);
|
||||
});
|
||||
|
||||
it('should correctly pass through event properties', () => {
|
||||
const timeStamps = [];
|
||||
const ref = React.createRef();
|
||||
const eventLog = [];
|
||||
const logEvent = event => {
|
||||
const propertiesWeCareAbout = {
|
||||
counter: event.counter,
|
||||
target: event.target,
|
||||
timeStamp: event.timeStamp,
|
||||
type: event.type,
|
||||
};
|
||||
timeStamps.push(event.timeStamp);
|
||||
eventLog.push(propertiesWeCareAbout);
|
||||
};
|
||||
let counter = 0;
|
||||
|
||||
const TestResponder = createEventResponder({
|
||||
targetEventTypes: ['click'],
|
||||
onEvent(event, context, props) {
|
||||
const obj = {
|
||||
counter,
|
||||
timeStamp: context.getTimeStamp(),
|
||||
target: event.responderTarget,
|
||||
type: 'click-test',
|
||||
};
|
||||
context.dispatchEvent(obj, props.onClick, DiscreteEvent);
|
||||
},
|
||||
});
|
||||
|
||||
const Component = () => {
|
||||
const listener = React.unstable_useResponder(TestResponder, {
|
||||
onClick: logEvent,
|
||||
});
|
||||
return <button ref={ref} listeners={listener} />;
|
||||
};
|
||||
ReactDOM.render(<Component />, container);
|
||||
dispatchClickEvent(ref.current);
|
||||
counter++;
|
||||
dispatchClickEvent(ref.current);
|
||||
counter++;
|
||||
dispatchClickEvent(ref.current);
|
||||
expect(typeof timeStamps[0] === 'number').toBe(true);
|
||||
expect(eventLog).toEqual([
|
||||
{
|
||||
counter: 0,
|
||||
target: ref.current,
|
||||
timeStamp: timeStamps[0],
|
||||
type: 'click-test',
|
||||
},
|
||||
{
|
||||
counter: 1,
|
||||
target: ref.current,
|
||||
timeStamp: timeStamps[1],
|
||||
type: 'click-test',
|
||||
},
|
||||
{
|
||||
counter: 2,
|
||||
target: ref.current,
|
||||
timeStamp: timeStamps[2],
|
||||
type: 'click-test',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,7 +14,6 @@ let ReactFeatureFlags;
|
||||
let ReactDOM;
|
||||
let InputResponder;
|
||||
let useInput;
|
||||
let usePress;
|
||||
let Scheduler;
|
||||
|
||||
const setUntrackedChecked = Object.getOwnPropertyDescriptor(
|
||||
@@ -42,7 +41,6 @@ const modulesInit = () => {
|
||||
Scheduler = require('scheduler');
|
||||
InputResponder = require('react-events/input').InputResponder;
|
||||
useInput = require('react-events/input').useInput;
|
||||
usePress = require('react-events/press').usePress;
|
||||
};
|
||||
|
||||
describe('Input event responder', () => {
|
||||
@@ -948,96 +946,6 @@ describe('Input event responder', () => {
|
||||
// Value should be the controlled value, not the original one
|
||||
expect(textarea.value).toBe('changed [!]');
|
||||
});
|
||||
|
||||
it('is async for non-input events', () => {
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
let input;
|
||||
|
||||
let ops = [];
|
||||
|
||||
function Component({
|
||||
innerRef,
|
||||
onChange,
|
||||
controlledValue,
|
||||
pressListener,
|
||||
}) {
|
||||
const inputListener = useInput({
|
||||
onChange,
|
||||
});
|
||||
return (
|
||||
<input
|
||||
type="text"
|
||||
ref={innerRef}
|
||||
value={controlledValue}
|
||||
listeners={[inputListener, pressListener]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function PressWrapper({innerRef, onPress, onChange, controlledValue}) {
|
||||
const pressListener = usePress({
|
||||
onPress,
|
||||
});
|
||||
return (
|
||||
<Component
|
||||
onChange={onChange}
|
||||
innerRef={el => (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 (
|
||||
<PressWrapper
|
||||
onPress={this.reset}
|
||||
onChange={this.onChange}
|
||||
innerRef={el => (input = el)}
|
||||
controlledValue={controlledValue}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Initial mount. Test that this is async.
|
||||
root.render(<ControlledInput />);
|
||||
// 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('');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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 (
|
||||
<div>
|
||||
<button
|
||||
ref={ref}
|
||||
listeners={listener}
|
||||
onClick={() => {
|
||||
updateCounter(count => count + 1);
|
||||
}}>
|
||||
Press me
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const newContainer = document.createElement('div');
|
||||
const root = ReactDOM.unstable_createRoot(newContainer);
|
||||
document.body.appendChild(newContainer);
|
||||
root.render(<MyComponent />);
|
||||
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 (
|
||||
<div>
|
||||
<button
|
||||
ref={ref}
|
||||
listeners={listener}
|
||||
onClick={() => {
|
||||
// This should flush synchronously
|
||||
ReactDOM.unstable_flushDiscreteUpdates();
|
||||
updateCounter(count => count + 1);
|
||||
}}>
|
||||
Press me
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const newContainer = document.createElement('div');
|
||||
const root = ReactDOM.unstable_createRoot(newContainer);
|
||||
document.body.appendChild(newContainer);
|
||||
root.render(<MyComponent />);
|
||||
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 (
|
||||
<div>
|
||||
<button
|
||||
listeners={listener}
|
||||
ref={button}
|
||||
onClick={() => updateClicksCount(clicksCount + 1)}>
|
||||
Presses: {pressesCount}, Clicks: {clicksCount}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const newContainer = document.createElement('div');
|
||||
document.body.appendChild(newContainer);
|
||||
const root = ReactDOM.unstable_createRoot(newContainer);
|
||||
|
||||
root.render(<MyComponent />);
|
||||
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 (
|
||||
<input
|
||||
type="text"
|
||||
ref={innerRef}
|
||||
value={controlledValue}
|
||||
listeners={[inputListener, pressListener]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function PressWrapper({innerRef, onPress, onChange, controlledValue}) {
|
||||
const pressListener = usePress({
|
||||
onPress,
|
||||
});
|
||||
return (
|
||||
<Component
|
||||
onChange={onChange}
|
||||
innerRef={el => (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 (
|
||||
<PressWrapper
|
||||
onPress={this.reset}
|
||||
onChange={this.onChange}
|
||||
innerRef={el => (input = el)}
|
||||
controlledValue={controlledValue}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Initial mount. Test that this is async.
|
||||
root.render(<ControlledInput />);
|
||||
// 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('');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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 <button ref={ref} listeners={listener} />;
|
||||
};
|
||||
ReactDOM.render(<Component />, 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 (
|
||||
<div>
|
||||
<button
|
||||
ref={ref}
|
||||
listeners={listener}
|
||||
onClick={() => {
|
||||
updateCounter(count => count + 1);
|
||||
}}>
|
||||
Press me
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const newContainer = document.createElement('div');
|
||||
const root = ReactDOM.unstable_createRoot(newContainer);
|
||||
document.body.appendChild(newContainer);
|
||||
root.render(<MyComponent />);
|
||||
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 (
|
||||
<div>
|
||||
<button
|
||||
ref={ref}
|
||||
listeners={listener}
|
||||
onClick={() => {
|
||||
// This should flush synchronously
|
||||
ReactDOM.unstable_flushDiscreteUpdates();
|
||||
updateCounter(count => count + 1);
|
||||
}}>
|
||||
Press me
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const newContainer = document.createElement('div');
|
||||
const root = ReactDOM.unstable_createRoot(newContainer);
|
||||
document.body.appendChild(newContainer);
|
||||
root.render(<MyComponent />);
|
||||
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 (
|
||||
<div>
|
||||
<button
|
||||
listeners={listener}
|
||||
ref={button}
|
||||
onClick={() => updateClicksCount(clicksCount + 1)}>
|
||||
Presses: {pressesCount}, Clicks: {clicksCount}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const newContainer = document.createElement('div');
|
||||
document.body.appendChild(newContainer);
|
||||
const root = ReactDOM.unstable_createRoot(newContainer);
|
||||
|
||||
root.render(<MyComponent />);
|
||||
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();
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user