diff --git a/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js b/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js index ff4e1b5aeeb5f..35f8d46650d55 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js @@ -427,7 +427,7 @@ describe('ReactDOMFiberAsync', () => { }); // @gate experimental - it('ignores discrete events on a pending removed event listener', () => { + it('ignores discrete events on a pending removed event listener', async () => { const disableButtonRef = React.createRef(); const submitButtonRef = React.createRef(); @@ -459,9 +459,9 @@ describe('ReactDOMFiberAsync', () => { } const root = ReactDOM.unstable_createRoot(container); - root.render(
); - // Flush - Scheduler.unstable_flushAll(); + await act(async () => { + root.render(); + }); const disableButton = disableButtonRef.current; expect(disableButton.tagName).toBe('BUTTON'); @@ -469,7 +469,9 @@ describe('ReactDOMFiberAsync', () => { // Dispatch a click event on the Disable-button. const firstEvent = document.createEvent('Event'); firstEvent.initEvent('click', true, true); - disableButton.dispatchEvent(firstEvent); + await act(async () => { + disableButton.dispatchEvent(firstEvent); + }); // There should now be a pending update to disable the form. @@ -481,14 +483,16 @@ describe('ReactDOMFiberAsync', () => { const 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); + await act(async () => { + submitButton.dispatchEvent(secondEvent); + }); // Therefore the form should never have been submitted. expect(formSubmitted).toBe(false); }); // @gate experimental - it('uses the newest discrete events on a pending changed event listener', () => { + it('uses the newest discrete events on a pending changed event listener', async () => { const enableButtonRef = React.createRef(); const submitButtonRef = React.createRef(); @@ -515,9 +519,9 @@ describe('ReactDOMFiberAsync', () => { } const root = ReactDOM.unstable_createRoot(container); - root.render(); - // Flush - Scheduler.unstable_flushAll(); + await act(async () => { + root.render(); + }); const enableButton = enableButtonRef.current; expect(enableButton.tagName).toBe('BUTTON'); @@ -525,7 +529,9 @@ describe('ReactDOMFiberAsync', () => { // Dispatch a click event on the Enable-button. const firstEvent = document.createEvent('Event'); firstEvent.initEvent('click', true, true); - enableButton.dispatchEvent(firstEvent); + await act(async () => { + enableButton.dispatchEvent(firstEvent); + }); // There should now be a pending update to enable the form. @@ -537,7 +543,9 @@ describe('ReactDOMFiberAsync', () => { const 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); + await act(async () => { + submitButton.dispatchEvent(secondEvent); + }); // Therefore the form should have been submitted. expect(formSubmitted).toBe(true); diff --git a/packages/react-dom/src/__tests__/ReactDOMNativeEventHeuristic-test.js b/packages/react-dom/src/__tests__/ReactDOMNativeEventHeuristic-test.js index 753af57ba849e..7b252a3395a1d 100644 --- a/packages/react-dom/src/__tests__/ReactDOMNativeEventHeuristic-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMNativeEventHeuristic-test.js @@ -357,6 +357,9 @@ describe('ReactDOMNativeEventHeuristic-test', () => { const pressEvent = document.createEvent('Event'); pressEvent.initEvent('click', true, true); dispatchAndSetCurrentEvent(target.current, pressEvent); + // Intentionally not using `act` so we can observe in between the press + // event and the microtask, without batching. + await null; // If this is 2, that means the `setCount` calls were not batched. expect(container.textContent).toEqual('Count: 1'); @@ -409,11 +412,7 @@ describe('ReactDOMNativeEventHeuristic-test', () => { dispatchAndSetCurrentEvent(target, pressEvent); expect(Scheduler).toHaveYielded(['Count: 0 [after batchedUpdates]']); - // TODO: There's a `flushDiscreteUpdates` call at the end of the event - // delegation listener that gets called even if no React event handlers are - // fired. Once that is removed, this will be 0, not 1. - // expect(container.textContent).toEqual('Count: 0'); - expect(container.textContent).toEqual('Count: 1'); + expect(container.textContent).toEqual('Count: 0'); // Intentionally not using `act` so we can observe in between the click // event and the microtask, without batching. diff --git a/packages/react-dom/src/events/ReactDOMUpdateBatching.js b/packages/react-dom/src/events/ReactDOMUpdateBatching.js index c768bce0c3a7b..4f0948dd68ab9 100644 --- a/packages/react-dom/src/events/ReactDOMUpdateBatching.js +++ b/packages/react-dom/src/events/ReactDOMUpdateBatching.js @@ -39,6 +39,8 @@ function finishEventHandler() { // If a controlled event was fired, we may need to restore the state of // the DOM node back to the controlled value. This is necessary when React // bails out of the update without touching the DOM. + // TODO: Restore state in the microtask, after the discrete updates flush, + // instead of early flushing them here. flushDiscreteUpdatesImpl(); restoreStateIfNeeded(); } diff --git a/packages/react-dom/src/events/plugins/__tests__/SimpleEventPlugin-test.js b/packages/react-dom/src/events/plugins/__tests__/SimpleEventPlugin-test.js index 95ea1cc598a5e..cb522ba1edf52 100644 --- a/packages/react-dom/src/events/plugins/__tests__/SimpleEventPlugin-test.js +++ b/packages/react-dom/src/events/plugins/__tests__/SimpleEventPlugin-test.js @@ -13,7 +13,7 @@ describe('SimpleEventPlugin', function() { let React; let ReactDOM; let Scheduler; - let TestUtils; + let act; let onClick; let container; @@ -40,7 +40,6 @@ describe('SimpleEventPlugin', function() { React = require('react'); ReactDOM = require('react-dom'); Scheduler = require('scheduler'); - TestUtils = require('react-dom/test-utils'); onClick = jest.fn(); }); @@ -237,10 +236,12 @@ describe('SimpleEventPlugin', function() { React = require('react'); ReactDOM = require('react-dom'); Scheduler = require('scheduler'); + + act = require('react-dom/test-utils').unstable_concurrentAct; }); // @gate experimental - it('flushes pending interactive work before exiting event handler', () => { + it('flushes pending interactive work before exiting event handler', async () => { container = document.createElement('div'); const root = ReactDOM.unstable_createRoot(container); document.body.appendChild(container); @@ -288,7 +289,7 @@ describe('SimpleEventPlugin', function() { } // Click the button to trigger the side-effect - click(); + await act(async () => click()); expect(Scheduler).toHaveYielded([ // The handler fired 'Side-effect', @@ -312,6 +313,9 @@ describe('SimpleEventPlugin', function() { expect(Scheduler).toFlushAndYield([]); }); + // NOTE: This test was written for the old behavior of discrete updates, + // where they would be async, but flushed early if another discrete update + // was dispatched. // @gate experimental it('end result of many interactive updates is deterministic', async () => { container = document.createElement('div'); @@ -355,121 +359,23 @@ describe('SimpleEventPlugin', function() { } // Click the button a single time - click(); + await act(async () => click()); // The counter should update synchronously, even in concurrent mode. expect(button.textContent).toEqual('Count: 1'); // Click the button many more times - await TestUtils.act(async () => { - click(); - click(); - click(); - click(); - click(); - click(); - }); + await act(async () => click()); + await act(async () => click()); + await act(async () => click()); + await act(async () => click()); + await act(async () => click()); + await act(async () => click()); // Flush the remaining work Scheduler.unstable_flushAll(); // The counter should equal the total number of clicks expect(button.textContent).toEqual('Count: 7'); }); - - // @gate experimental - it('flushes discrete updates in order', async () => { - container = document.createElement('div'); - document.body.appendChild(container); - - let button; - class Button extends React.Component { - state = {lowPriCount: 0}; - render() { - const text = `High-pri count: ${this.props.highPriCount}, Low-pri count: ${this.state.lowPriCount}`; - Scheduler.unstable_yieldValue(text); - return ( - - ); - } - } - - class Wrapper extends React.Component { - state = {highPriCount: 0}; - render() { - return ( -