diff --git a/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.internal.js index 84cfb049f0a01..d49a6218cb602 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.internal.js @@ -491,5 +491,191 @@ describe('ReactDOMFiberAsync', () => { expect(container.textContent).toEqual('1'); expect(returnValue).toBe(undefined); }); + + it('ignores discrete events on a pending removed element', () => { + const disableButtonRef = React.createRef(); + const submitButtonRef = React.createRef(); + + 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 ( +
+ + {this.state.active ? ( + + ) : null} +
+ ); + } + } + + const root = ReactDOM.unstable_createRoot(container); + root.render(
); + // Flush + jest.runAllTimers(); + + let disableButton = disableButtonRef.current; + expect(disableButton.tagName).toBe('BUTTON'); + + // Dispatch a click event on the Disable-button. + let firstEvent = document.createEvent('Event'); + firstEvent.initEvent('click', true, true); + disableButton.dispatchEvent(firstEvent); + + // There should now be a pending update to disable the form. + + // This should not have flushed yet since it's in concurrent mode. + let submitButton = submitButtonRef.current; + expect(submitButton.tagName).toBe('BUTTON'); + + // In the meantime, we can dispatch a new client event on the submit button. + let secondEvent = document.createEvent('Event'); + secondEvent.initEvent('click', true, true); + // This should force the pending update to flush which disables the submit button before the event is invoked. + submitButton.dispatchEvent(secondEvent); + + // Therefore the form should never have been submitted. + expect(formSubmitted).toBe(false); + + expect(submitButtonRef.current).toBe(null); + }); + + it('ignores discrete events on a pending removed event listener', () => { + const disableButtonRef = React.createRef(); + const submitButtonRef = React.createRef(); + + 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 ( +
+ + {' '} + : null} +
+ ); + } + } + + const root = ReactDOM.unstable_createRoot(container); + root.render(); + // Flush + jest.runAllTimers(); + + let disableButton = disableButtonRef.current; + expect(disableButton.tagName).toBe('BUTTON'); + + // Dispatch a click event on the Disable-button. + let firstEvent = document.createEvent('Event'); + firstEvent.initEvent('click', true, true); + disableButton.dispatchEvent(firstEvent); + + // There should now be a pending update to disable the form. + + // This should not have flushed yet since it's in concurrent mode. + let submitButton = submitButtonRef.current; + expect(submitButton.tagName).toBe('BUTTON'); + + // In the meantime, we can dispatch a new client event on the submit button. + let secondEvent = document.createEvent('Event'); + secondEvent.initEvent('click', true, true); + // This should force the pending update to flush which disables the submit button before the event is invoked. + submitButton.dispatchEvent(secondEvent); + + // Therefore the form should never have been submitted. + expect(formSubmitted).toBe(false); + }); + + it('uses the newest discrete events on a pending changed event listener', () => { + const enableButtonRef = React.createRef(); + const submitButtonRef = React.createRef(); + + let formSubmitted = false; + + class Form extends React.Component { + state = {active: false}; + enableForm = () => { + this.setState({active: true}); + }; + submitForm = () => { + formSubmitted = true; // This should happen + }; + render() { + return ( +
+ + {' '} + : null} +
+ ); + } + } + + const root = ReactDOM.unstable_createRoot(container); + root.render(); + // Flush + jest.runAllTimers(); + + let enableButton = enableButtonRef.current; + expect(enableButton.tagName).toBe('BUTTON'); + + // Dispatch a click event on the Enable-button. + let firstEvent = document.createEvent('Event'); + firstEvent.initEvent('click', true, true); + enableButton.dispatchEvent(firstEvent); + + // There should now be a pending update to enable the form. + + // This should not have flushed yet since it's in concurrent mode. + let submitButton = submitButtonRef.current; + expect(submitButton.tagName).toBe('BUTTON'); + + // In the meantime, we can dispatch a new client event on the submit button. + let secondEvent = document.createEvent('Event'); + secondEvent.initEvent('click', true, true); + // This should force the pending update to flush which enables the submit button before the event is invoked. + submitButton.dispatchEvent(secondEvent); + + // Therefore the form should have been submitted. + expect(formSubmitted).toBe(true); + }); }); });