mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Add tests for non-React discrete events flushing in a microtask (#20772)
* Convert some old discrete tests to Hooks I'm planning to copy paste so why not update them anyway. * Copy paste discrete tests into another file These are still using React events. I'll change that next. * Convert the test to use native events
This commit is contained in:
+58
-76
@@ -148,13 +148,6 @@ describe('ReactDOMFiberAsync', () => {
|
||||
});
|
||||
|
||||
describe('concurrent mode', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
|
||||
ReactDOM = require('react-dom');
|
||||
Scheduler = require('scheduler');
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
it('does not perform deferred updates synchronously', () => {
|
||||
const inputRef = React.createRef();
|
||||
@@ -399,28 +392,26 @@ describe('ReactDOMFiberAsync', () => {
|
||||
|
||||
let formSubmitted = false;
|
||||
|
||||
class Form extends React.Component {
|
||||
state = {active: true};
|
||||
disableForm = () => {
|
||||
this.setState({active: false});
|
||||
};
|
||||
submitForm = () => {
|
||||
formSubmitted = true; // This should not get invoked
|
||||
};
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<button onClick={this.disableForm} ref={disableButtonRef}>
|
||||
Disable
|
||||
</button>
|
||||
{this.state.active ? (
|
||||
<button onClick={this.submitForm} ref={submitButtonRef}>
|
||||
Submit
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
function Form() {
|
||||
const [active, setActive] = React.useState(true);
|
||||
function disableForm() {
|
||||
setActive(false);
|
||||
}
|
||||
function submitForm() {
|
||||
formSubmitted = true; // This should not get invoked
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<button onClick={disableForm} ref={disableButtonRef}>
|
||||
Disable
|
||||
</button>
|
||||
{active ? (
|
||||
<button onClick={submitForm} ref={submitButtonRef}>
|
||||
Submit
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
@@ -461,33 +452,29 @@ describe('ReactDOMFiberAsync', () => {
|
||||
|
||||
let formSubmitted = false;
|
||||
|
||||
class Form extends React.Component {
|
||||
state = {active: true};
|
||||
disableForm = () => {
|
||||
this.setState({active: false});
|
||||
};
|
||||
submitForm = () => {
|
||||
formSubmitted = true; // This should not get invoked
|
||||
};
|
||||
disabledSubmitForm = () => {
|
||||
// The form is disabled.
|
||||
};
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<button onClick={this.disableForm} ref={disableButtonRef}>
|
||||
Disable
|
||||
</button>
|
||||
<button
|
||||
onClick={
|
||||
this.state.active ? this.submitForm : this.disabledSubmitForm
|
||||
}
|
||||
ref={submitButtonRef}>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
function Form() {
|
||||
const [active, setActive] = React.useState(true);
|
||||
function disableForm() {
|
||||
setActive(false);
|
||||
}
|
||||
function submitForm() {
|
||||
formSubmitted = true; // This should not get invoked
|
||||
}
|
||||
function disabledSubmitForm() {
|
||||
// The form is disabled.
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<button onClick={disableForm} ref={disableButtonRef}>
|
||||
Disable
|
||||
</button>
|
||||
<button
|
||||
onClick={active ? submitForm : disabledSubmitForm}
|
||||
ref={submitButtonRef}>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
@@ -526,29 +513,24 @@ describe('ReactDOMFiberAsync', () => {
|
||||
|
||||
let formSubmitted = false;
|
||||
|
||||
class Form extends React.Component {
|
||||
state = {active: false};
|
||||
enableForm = () => {
|
||||
this.setState({active: true});
|
||||
};
|
||||
submitForm = () => {
|
||||
formSubmitted = true; // This should happen
|
||||
};
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<button onClick={this.enableForm} ref={enableButtonRef}>
|
||||
Enable
|
||||
</button>
|
||||
<button
|
||||
onClick={this.state.active ? this.submitForm : null}
|
||||
ref={submitButtonRef}>
|
||||
Submit
|
||||
</button>{' '}
|
||||
: null}
|
||||
</div>
|
||||
);
|
||||
function Form() {
|
||||
const [active, setActive] = React.useState(false);
|
||||
function enableForm() {
|
||||
setActive(true);
|
||||
}
|
||||
function submitForm() {
|
||||
formSubmitted = true; // This should not get invoked
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<button onClick={enableForm} ref={enableButtonRef}>
|
||||
Enable
|
||||
</button>
|
||||
<button onClick={active ? submitForm : null} ref={submitButtonRef}>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
/**
|
||||
* 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';
|
||||
|
||||
let React;
|
||||
|
||||
let ReactDOM;
|
||||
let Scheduler;
|
||||
|
||||
describe('ReactDOMNativeEventHeuristic-test', () => {
|
||||
let container;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
container = document.createElement('div');
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
Scheduler = require('scheduler');
|
||||
|
||||
document.body.appendChild(container);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
document.body.removeChild(container);
|
||||
});
|
||||
|
||||
function dispatchAndSetCurrentEvent(el, event) {
|
||||
try {
|
||||
window.event = event;
|
||||
el.dispatchEvent(event);
|
||||
} finally {
|
||||
window.event = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// @gate experimental
|
||||
// @gate enableDiscreteEventMicroTasks && enableNativeEventPriorityInference
|
||||
it('ignores discrete events on a pending removed element', async () => {
|
||||
const disableButtonRef = React.createRef();
|
||||
const submitButtonRef = React.createRef();
|
||||
|
||||
function Form() {
|
||||
const [active, setActive] = React.useState(true);
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
disableButtonRef.current.onclick = disableForm;
|
||||
});
|
||||
|
||||
function disableForm() {
|
||||
setActive(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button ref={disableButtonRef}>Disable</button>
|
||||
{active ? <button ref={submitButtonRef}>Submit</button> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
root.render(<Form />);
|
||||
// Flush
|
||||
Scheduler.unstable_flushAll();
|
||||
|
||||
const disableButton = disableButtonRef.current;
|
||||
expect(disableButton.tagName).toBe('BUTTON');
|
||||
|
||||
// Dispatch a click event on the Disable-button.
|
||||
const firstEvent = document.createEvent('Event');
|
||||
firstEvent.initEvent('click', true, true);
|
||||
expect(() =>
|
||||
dispatchAndSetCurrentEvent(disableButton, firstEvent),
|
||||
).toErrorDev(['An update to Form inside a test was not wrapped in act']);
|
||||
|
||||
// There should now be a pending update to disable the form.
|
||||
// This should not have flushed yet since it's in concurrent mode.
|
||||
const submitButton = submitButtonRef.current;
|
||||
expect(submitButton.tagName).toBe('BUTTON');
|
||||
|
||||
// Discrete events should be flushed in a microtask.
|
||||
// Verify that the second button was removed.
|
||||
await null;
|
||||
expect(submitButtonRef.current).toBe(null);
|
||||
// We'll assume that the browser won't let the user click it.
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
// @gate enableDiscreteEventMicroTasks && enableNativeEventPriorityInference
|
||||
it('ignores discrete events on a pending removed event listener', async () => {
|
||||
const disableButtonRef = React.createRef();
|
||||
const submitButtonRef = React.createRef();
|
||||
|
||||
let formSubmitted = false;
|
||||
|
||||
function Form() {
|
||||
const [active, setActive] = React.useState(true);
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
disableButtonRef.current.onclick = disableForm;
|
||||
submitButtonRef.current.onclick = active
|
||||
? submitForm
|
||||
: disabledSubmitForm;
|
||||
});
|
||||
|
||||
function disableForm() {
|
||||
setActive(false);
|
||||
}
|
||||
|
||||
function submitForm() {
|
||||
formSubmitted = true; // This should not get invoked
|
||||
}
|
||||
|
||||
function disabledSubmitForm() {
|
||||
// The form is disabled.
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button ref={disableButtonRef}>Disable</button>
|
||||
<button ref={submitButtonRef}>Submit</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
root.render(<Form />);
|
||||
// Flush
|
||||
Scheduler.unstable_flushAll();
|
||||
|
||||
const disableButton = disableButtonRef.current;
|
||||
expect(disableButton.tagName).toBe('BUTTON');
|
||||
|
||||
// Dispatch a click event on the Disable-button.
|
||||
const firstEvent = document.createEvent('Event');
|
||||
firstEvent.initEvent('click', true, true);
|
||||
expect(() => {
|
||||
dispatchAndSetCurrentEvent(disableButton, firstEvent);
|
||||
}).toErrorDev(['An update to Form inside a test was not wrapped in act']);
|
||||
|
||||
// There should now be a pending update to disable the form.
|
||||
// This should not have flushed yet since it's in concurrent mode.
|
||||
const submitButton = submitButtonRef.current;
|
||||
expect(submitButton.tagName).toBe('BUTTON');
|
||||
|
||||
// Discrete events should be flushed in a microtask.
|
||||
await null;
|
||||
|
||||
// Now let's dispatch an event on the submit button.
|
||||
const secondEvent = document.createEvent('Event');
|
||||
secondEvent.initEvent('click', true, true);
|
||||
dispatchAndSetCurrentEvent(submitButton, secondEvent);
|
||||
|
||||
// Therefore the form should never have been submitted.
|
||||
expect(formSubmitted).toBe(false);
|
||||
});
|
||||
|
||||
// @gate experimental
|
||||
// @gate enableDiscreteEventMicroTasks && enableNativeEventPriorityInference
|
||||
it('uses the newest discrete events on a pending changed event listener', async () => {
|
||||
const enableButtonRef = React.createRef();
|
||||
const submitButtonRef = React.createRef();
|
||||
|
||||
let formSubmitted = false;
|
||||
|
||||
function Form() {
|
||||
const [active, setActive] = React.useState(false);
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
enableButtonRef.current.onclick = enableForm;
|
||||
submitButtonRef.current.onclick = active ? submitForm : null;
|
||||
});
|
||||
|
||||
function enableForm() {
|
||||
setActive(true);
|
||||
}
|
||||
|
||||
function submitForm() {
|
||||
formSubmitted = true; // This should not get invoked
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button ref={enableButtonRef}>Enable</button>
|
||||
<button ref={submitButtonRef}>Submit</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
root.render(<Form />);
|
||||
// Flush
|
||||
Scheduler.unstable_flushAll();
|
||||
|
||||
const enableButton = enableButtonRef.current;
|
||||
expect(enableButton.tagName).toBe('BUTTON');
|
||||
|
||||
// Dispatch a click event on the Enable-button.
|
||||
const firstEvent = document.createEvent('Event');
|
||||
firstEvent.initEvent('click', true, true);
|
||||
expect(() => {
|
||||
dispatchAndSetCurrentEvent(enableButton, firstEvent);
|
||||
}).toErrorDev(['An update to Form inside a test was not wrapped in act']);
|
||||
|
||||
// There should now be a pending update to enable the form.
|
||||
// This should not have flushed yet since it's in concurrent mode.
|
||||
const submitButton = submitButtonRef.current;
|
||||
expect(submitButton.tagName).toBe('BUTTON');
|
||||
|
||||
// Discrete events should be flushed in a microtask.
|
||||
await null;
|
||||
|
||||
// Now let's dispatch an event on the submit button.
|
||||
const secondEvent = document.createEvent('Event');
|
||||
secondEvent.initEvent('click', true, true);
|
||||
dispatchAndSetCurrentEvent(submitButton, secondEvent);
|
||||
|
||||
// Therefore the form should have been submitted.
|
||||
expect(formSubmitted).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user