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:
Dan Abramov
2021-02-09 19:56:15 +00:00
committed by GitHub
parent 97fce318a6
commit d919e2c41c
2 changed files with 286 additions and 76 deletions
+58 -76
View File
@@ -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);
});
});