diff --git a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js index 5c092f5853288..6ab8fe74a9cf0 100644 --- a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js +++ b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js @@ -146,7 +146,10 @@ describe('ReactHooksInspectionIntegration', () => { ); } - let renderer = ReactTestRenderer.create(); + let renderer; + act(() => { + renderer = ReactTestRenderer.create(); + }); let childFiber = renderer.root.findByType(Foo)._currentFiber(); diff --git a/packages/react-dom/src/__tests__/ReactDOMHooks-test.js b/packages/react-dom/src/__tests__/ReactDOMHooks-test.js index 4f24770546176..ec23c88fa981f 100644 --- a/packages/react-dom/src/__tests__/ReactDOMHooks-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMHooks-test.js @@ -53,23 +53,32 @@ describe('ReactDOMHooks', () => { return 3 * n; } - ReactDOM.render(, container); - expect(container.textContent).toBe('1'); - expect(container2.textContent).toBe(''); - expect(container3.textContent).toBe(''); - Scheduler.flushAll(); - expect(container.textContent).toBe('1'); - expect(container2.textContent).toBe('2'); - expect(container3.textContent).toBe('3'); - - ReactDOM.render(, container); - expect(container.textContent).toBe('2'); - expect(container2.textContent).toBe('2'); // Not flushed yet - expect(container3.textContent).toBe('3'); // Not flushed yet - Scheduler.flushAll(); - expect(container.textContent).toBe('2'); - expect(container2.textContent).toBe('4'); - expect(container3.textContent).toBe('6'); + // we explicitly catch the missing act() warnings + // to simulate this tricky repro + expect(() => { + ReactDOM.render(, container); + expect(container.textContent).toBe('1'); + expect(container2.textContent).toBe(''); + expect(container3.textContent).toBe(''); + Scheduler.flushAll(); + expect(container.textContent).toBe('1'); + expect(container2.textContent).toBe('2'); + expect(container3.textContent).toBe('3'); + + ReactDOM.render(, container); + expect(container.textContent).toBe('2'); + expect(container2.textContent).toBe('2'); // Not flushed yet + expect(container3.textContent).toBe('3'); // Not flushed yet + Scheduler.flushAll(); + expect(container.textContent).toBe('2'); + expect(container2.textContent).toBe('4'); + expect(container3.textContent).toBe('6'); + }).toWarnDev([ + 'An update to Example1 ran an effect', + 'An update to Example2 ran an effect', + 'An update to Example1 ran an effect', + 'An update to Example2 ran an effect', + ]); }); it('should not bail out when an update is scheduled from within an event handler', () => { diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationAttributes-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationAttributes-test.js index 50fd7ae16bc8e..c60ed8770b310 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationAttributes-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationAttributes-test.js @@ -13,6 +13,7 @@ const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegratio let React; let ReactDOM; +let ReactTestUtils; let ReactDOMServer; function initModules() { @@ -21,11 +22,13 @@ function initModules() { React = require('react'); ReactDOM = require('react-dom'); ReactDOMServer = require('react-dom/server'); + ReactTestUtils = require('react-dom/test-utils'); // Make them available to the helpers. return { ReactDOM, ReactDOMServer, + ReactTestUtils, }; } diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationBasic-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationBasic-test.js index 114907ef7d177..72ac5ff06b49f 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationBasic-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationBasic-test.js @@ -16,6 +16,7 @@ const TEXT_NODE_TYPE = 3; let React; let ReactDOM; let ReactDOMServer; +let ReactTestUtils; function initModules() { // Reset warning cache. @@ -23,11 +24,13 @@ function initModules() { React = require('react'); ReactDOM = require('react-dom'); ReactDOMServer = require('react-dom/server'); + ReactTestUtils = require('react-dom/test-utils'); // Make them available to the helpers. return { ReactDOM, ReactDOMServer, + ReactTestUtils, }; } diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationCheckbox-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationCheckbox-test.js index 03e7c4b984480..3280ed5e0626c 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationCheckbox-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationCheckbox-test.js @@ -16,6 +16,7 @@ const {disableInputAttributeSyncing} = require('shared/ReactFeatureFlags'); let React; let ReactDOM; let ReactDOMServer; +let ReactTestUtils; function initModules() { // Reset warning cache. @@ -23,11 +24,13 @@ function initModules() { React = require('react'); ReactDOM = require('react-dom'); ReactDOMServer = require('react-dom/server'); + ReactTestUtils = require('react-dom/test-utils'); // Make them available to the helpers. return { ReactDOM, ReactDOMServer, + ReactTestUtils, }; } diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationClassContextType-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationClassContextType-test.js index 19839aa7cd6c2..a24e969d0f527 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationClassContextType-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationClassContextType-test.js @@ -14,6 +14,7 @@ const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegratio let React; let ReactDOM; let ReactDOMServer; +let ReactTestUtils; function initModules() { // Reset warning cache. @@ -21,11 +22,13 @@ function initModules() { React = require('react'); ReactDOM = require('react-dom'); ReactDOMServer = require('react-dom/server'); + ReactTestUtils = require('react-dom/test-utils'); // Make them available to the helpers. return { ReactDOM, ReactDOMServer, + ReactTestUtils, }; } diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationElements-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationElements-test.js index 535191d66e8a7..8b66321f8692a 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationElements-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationElements-test.js @@ -16,17 +16,20 @@ const TEXT_NODE_TYPE = 3; let React; let ReactDOM; let ReactDOMServer; +let ReactTestUtils; function initModules() { jest.resetModuleRegistry(); React = require('react'); ReactDOM = require('react-dom'); ReactDOMServer = require('react-dom/server'); + ReactTestUtils = require('react-dom/test-utils'); // Make them available to the helpers. return { ReactDOM, ReactDOMServer, + ReactTestUtils, }; } diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationFragment-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationFragment-test.js index 8c93ee9eea556..d1144aebdaedf 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationFragment-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationFragment-test.js @@ -14,6 +14,7 @@ const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegratio let React; let ReactDOM; let ReactDOMServer; +let ReactTestUtils; function initModules() { // Reset warning cache. @@ -21,11 +22,13 @@ function initModules() { React = require('react'); ReactDOM = require('react-dom'); ReactDOMServer = require('react-dom/server'); + ReactTestUtils = require('react-dom/test-utils'); // Make them available to the helpers. return { ReactDOM, ReactDOMServer, + ReactTestUtils, }; } diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.internal.js index a0a91763a82cf..b4f99453cd328 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.internal.js @@ -17,6 +17,7 @@ let React; let ReactFeatureFlags; let ReactDOM; let ReactDOMServer; +let ReactTestUtils; let useState; let useReducer; let useEffect; @@ -41,6 +42,7 @@ function initModules() { React = require('react'); ReactDOM = require('react-dom'); ReactDOMServer = require('react-dom/server'); + ReactTestUtils = require('react-dom/test-utils'); useState = React.useState; useReducer = React.useReducer; useEffect = React.useEffect; @@ -67,6 +69,7 @@ function initModules() { return { ReactDOM, ReactDOMServer, + ReactTestUtils, }; } @@ -539,18 +542,30 @@ describe('ReactDOMServerHooks', () => { }); describe('useEffect', () => { + const yields = []; itRenders('should ignore effects on the server', async render => { function Counter(props) { useEffect(() => { - yieldValue('should not be invoked'); + yieldValue('invoked on client'); }); return ; } + const domNode = await render(); - expect(clearYields()).toEqual(['Count: 0']); + yields.push(clearYields()); expect(domNode.tagName).toEqual('SPAN'); expect(domNode.textContent).toEqual('Count: 0'); }); + + it('verifies yields in order', () => { + expect(yields).toEqual([ + ['Count: 0'], // server render + ['Count: 0'], // server stream + ['Count: 0', 'invoked on client'], // clean render + ['Count: 0', 'invoked on client'], // hydrated render + // nothing yielded for bad markup + ]); + }); }); describe('useCallback', () => { diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationInput-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationInput-test.js index d637755d02c0b..9ca1c92d8d427 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationInput-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationInput-test.js @@ -16,6 +16,7 @@ const {disableInputAttributeSyncing} = require('shared/ReactFeatureFlags'); let React; let ReactDOM; let ReactDOMServer; +let ReactTestUtils; function initModules() { // Reset warning cache. @@ -23,11 +24,13 @@ function initModules() { React = require('react'); ReactDOM = require('react-dom'); ReactDOMServer = require('react-dom/server'); + ReactTestUtils = require('react-dom/test-utils'); // Make them available to the helpers. return { ReactDOM, ReactDOMServer, + ReactTestUtils, }; } diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationLegacyContext-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationLegacyContext-test.js index e801902011b61..4c78919bd8480 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationLegacyContext-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationLegacyContext-test.js @@ -15,6 +15,7 @@ let PropTypes; let React; let ReactDOM; let ReactDOMServer; +let ReactTestUtils; function initModules() { // Reset warning cache. @@ -23,11 +24,13 @@ function initModules() { React = require('react'); ReactDOM = require('react-dom'); ReactDOMServer = require('react-dom/server'); + ReactTestUtils = require('react-dom/test-utils'); // Make them available to the helpers. return { ReactDOM, ReactDOMServer, + ReactTestUtils, }; } diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationModes-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationModes-test.js index b9f8a5c48c8a1..188ab02991868 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationModes-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationModes-test.js @@ -14,6 +14,7 @@ const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegratio let React; let ReactDOM; let ReactDOMServer; +let ReactTestUtils; function initModules() { // Reset warning cache. @@ -21,11 +22,13 @@ function initModules() { React = require('react'); ReactDOM = require('react-dom'); ReactDOMServer = require('react-dom/server'); + ReactTestUtils = require('react-dom/test-utils'); // Make them available to the helpers. return { ReactDOM, ReactDOMServer, + ReactTestUtils, }; } diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationNewContext-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationNewContext-test.js index 071dd8715751c..6089daa80af29 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationNewContext-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationNewContext-test.js @@ -14,6 +14,7 @@ const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegratio let React; let ReactDOM; let ReactDOMServer; +let ReactTestUtils; function initModules() { // Reset warning cache. @@ -21,11 +22,13 @@ function initModules() { React = require('react'); ReactDOM = require('react-dom'); ReactDOMServer = require('react-dom/server'); + ReactTestUtils = require('react-dom/test-utils'); // Make them available to the helpers. return { ReactDOM, ReactDOMServer, + ReactTestUtils, }; } diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationReconnecting-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationReconnecting-test.js index b6428b1fa3824..aba74c2f34fcf 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationReconnecting-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationReconnecting-test.js @@ -14,6 +14,7 @@ const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegratio let React; let ReactDOM; let ReactDOMServer; +let ReactTestUtils; function initModules() { // Reset warning cache. @@ -22,11 +23,13 @@ function initModules() { React = require('react'); ReactDOM = require('react-dom'); ReactDOMServer = require('react-dom/server'); + ReactTestUtils = require('react-dom/test-utils'); // Make them available to the helpers. return { ReactDOM, ReactDOMServer, + ReactTestUtils, }; } diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationRefs-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationRefs-test.js index ccb9a60607833..4d39fce80c885 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationRefs-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationRefs-test.js @@ -14,6 +14,7 @@ const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegratio let React; let ReactDOM; let ReactDOMServer; +let ReactTestUtils; function initModules() { // Reset warning cache. @@ -21,11 +22,13 @@ function initModules() { React = require('react'); ReactDOM = require('react-dom'); ReactDOMServer = require('react-dom/server'); + ReactTestUtils = require('react-dom/test-utils'); // Make them available to the helpers. return { ReactDOM, ReactDOMServer, + ReactTestUtils, }; } diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationSelect-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationSelect-test.js index 486b2a81a6925..3871ad975e743 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationSelect-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationSelect-test.js @@ -14,6 +14,7 @@ const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegratio let React; let ReactDOM; let ReactDOMServer; +let ReactTestUtils; function initModules() { // Reset warning cache. @@ -21,11 +22,13 @@ function initModules() { React = require('react'); ReactDOM = require('react-dom'); ReactDOMServer = require('react-dom/server'); + ReactTestUtils = require('react-dom/test-utils'); // Make them available to the helpers. return { ReactDOM, ReactDOMServer, + ReactTestUtils, }; } diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationSpecialTypes-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationSpecialTypes-test.js index b56d36b626a76..2cc696ae6e4e8 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationSpecialTypes-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationSpecialTypes-test.js @@ -14,6 +14,7 @@ const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegratio let React; let ReactDOM; let ReactDOMServer; +let ReactTestUtils; let forwardRef; let memo; let yieldedValues; @@ -26,6 +27,7 @@ function initModules() { React = require('react'); ReactDOM = require('react-dom'); ReactDOMServer = require('react-dom/server'); + ReactTestUtils = require('react-dom/test-utils'); forwardRef = React.forwardRef; memo = React.memo; @@ -43,6 +45,7 @@ function initModules() { return { ReactDOM, ReactDOMServer, + ReactTestUtils, }; } diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationTextarea-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationTextarea-test.js index 2b0faddcd2c44..261b6c041495a 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationTextarea-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationTextarea-test.js @@ -14,6 +14,7 @@ const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegratio let React; let ReactDOM; let ReactDOMServer; +let ReactTestUtils; function initModules() { // Reset warning cache. @@ -21,11 +22,13 @@ function initModules() { React = require('react'); ReactDOM = require('react-dom'); ReactDOMServer = require('react-dom/server'); + ReactTestUtils = require('react-dom/test-utils'); // Make them available to the helpers. return { ReactDOM, ReactDOMServer, + ReactTestUtils, }; } diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationUntrustedURL-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationUntrustedURL-test.internal.js index 21a4cc1b6dc87..897e2d51183b2 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationUntrustedURL-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationUntrustedURL-test.internal.js @@ -16,6 +16,7 @@ const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegratio let React; let ReactDOM; let ReactDOMServer; +let ReactTestUtils; function runTests(itRenders, itRejectsRendering, expectToReject) { itRenders('a http link with the word javascript in it', async render => { @@ -152,11 +153,13 @@ describe('ReactDOMServerIntegration - Untrusted URLs', () => { React = require('react'); ReactDOM = require('react-dom'); ReactDOMServer = require('react-dom/server'); + ReactTestUtils = require('react-dom/test-utils'); // Make them available to the helpers. return { ReactDOM, ReactDOMServer, + ReactTestUtils, }; } @@ -185,11 +188,13 @@ describe('ReactDOMServerIntegration - Untrusted URLs - disableJavaScriptURLs', ( React = require('react'); ReactDOM = require('react-dom'); ReactDOMServer = require('react-dom/server'); + ReactTestUtils = require('react-dom/test-utils'); // Make them available to the helpers. return { ReactDOM, ReactDOMServer, + ReactTestUtils, }; } diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationUserInteraction-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationUserInteraction-test.js index 558aad7648b40..c0e2733c54169 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationUserInteraction-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationUserInteraction-test.js @@ -14,6 +14,7 @@ const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegratio let React; let ReactDOM; let ReactDOMServer; +let ReactTestUtils; function initModules() { // Reset warning cache. @@ -21,11 +22,13 @@ function initModules() { React = require('react'); ReactDOM = require('react-dom'); ReactDOMServer = require('react-dom/server'); + ReactTestUtils = require('react-dom/test-utils'); // Make them available to the helpers. return { ReactDOM, ReactDOMServer, + ReactTestUtils, }; } diff --git a/packages/react-dom/src/__tests__/ReactDOMServerSuspense-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerSuspense-test.internal.js index bab90592430de..434fc6c8a507f 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerSuspense-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerSuspense-test.internal.js @@ -15,6 +15,7 @@ let React; let ReactDOM; let ReactDOMServer; let ReactFeatureFlags; +let ReactTestUtils; function initModules() { // Reset warning cache. @@ -26,11 +27,13 @@ function initModules() { React = require('react'); ReactDOM = require('react-dom'); ReactDOMServer = require('react-dom/server'); + ReactTestUtils = require('react-dom/test-utils'); // Make them available to the helpers. return { ReactDOM, ReactDOMServer, + ReactTestUtils, }; } diff --git a/packages/react-dom/src/__tests__/ReactDOMserverIntegrationProgress-test.js b/packages/react-dom/src/__tests__/ReactDOMserverIntegrationProgress-test.js index 4a8eb06faace6..5ff530b10a858 100644 --- a/packages/react-dom/src/__tests__/ReactDOMserverIntegrationProgress-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMserverIntegrationProgress-test.js @@ -14,6 +14,7 @@ const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegratio let React; let ReactDOM; let ReactDOMServer; +let ReactTestUtils; function initModules() { // Reset warning cache. @@ -21,11 +22,13 @@ function initModules() { React = require('react'); ReactDOM = require('react-dom'); ReactDOMServer = require('react-dom/server'); + ReactTestUtils = require('react-dom/test-utils'); // Make them available to the helpers. return { ReactDOM, ReactDOMServer, + ReactTestUtils, }; } diff --git a/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js b/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js index 3b19a87ecbf87..478d30d136538 100644 --- a/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js @@ -12,8 +12,8 @@ let PropTypes; let React; let ReactDOM; +let act; let ReactFeatureFlags; -let Scheduler; describe('ReactErrorBoundaries', () => { let log; @@ -45,7 +45,7 @@ describe('ReactErrorBoundaries', () => { ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false; ReactDOM = require('react-dom'); React = require('react'); - Scheduler = require('scheduler'); + act = require('react-dom/test-utils').act; log = []; @@ -1835,25 +1835,26 @@ describe('ReactErrorBoundaries', () => { it('catches errors in useEffect', () => { const container = document.createElement('div'); - ReactDOM.render( - - Initial value - , - container, - ); - expect(log).toEqual([ - 'ErrorBoundary constructor', - 'ErrorBoundary componentWillMount', - 'ErrorBoundary render success', - 'BrokenUseEffect render', - 'ErrorBoundary componentDidMount', - ]); - - expect(container.firstChild.textContent).toBe('Initial value'); - log.length = 0; - - // Flush passive effects and handle the error - Scheduler.flushAll(); + act(() => { + ReactDOM.render( + + Initial value + , + container, + ); + expect(log).toEqual([ + 'ErrorBoundary constructor', + 'ErrorBoundary componentWillMount', + 'ErrorBoundary render success', + 'BrokenUseEffect render', + 'ErrorBoundary componentDidMount', + ]); + + expect(container.firstChild.textContent).toBe('Initial value'); + log.length = 0; + }); + + // verify flushed passive effects and handle the error expect(log).toEqual([ 'BrokenUseEffect useEffect [!]', // Handle the error diff --git a/packages/react-dom/src/__tests__/ReactUpdates-test.js b/packages/react-dom/src/__tests__/ReactUpdates-test.js index 0b6a4b2792f3f..6d7435fe4cde7 100644 --- a/packages/react-dom/src/__tests__/ReactUpdates-test.js +++ b/packages/react-dom/src/__tests__/ReactUpdates-test.js @@ -12,6 +12,7 @@ let React; let ReactDOM; let ReactTestUtils; +let act; let Scheduler; describe('ReactUpdates', () => { @@ -20,6 +21,7 @@ describe('ReactUpdates', () => { React = require('react'); ReactDOM = require('react-dom'); ReactTestUtils = require('react-dom/test-utils'); + act = ReactTestUtils.act; Scheduler = require('scheduler'); }); @@ -1322,30 +1324,31 @@ describe('ReactUpdates', () => { } const root = ReactDOM.unstable_createRoot(container); - root.render(); - if (__DEV__) { - expect(Scheduler).toFlushAndYieldThrough([ - 'Foo', - 'Foo', - 'Baz', - 'Foo#effect', - ]); - } else { - expect(Scheduler).toFlushAndYieldThrough(['Foo', 'Baz', 'Foo#effect']); - } - - const hiddenDiv = container.firstChild.firstChild; - expect(hiddenDiv.hidden).toBe(true); - expect(hiddenDiv.innerHTML).toBe(''); - - // Run offscreen update - if (__DEV__) { - expect(Scheduler).toFlushAndYield(['Bar', 'Bar']); - } else { - expect(Scheduler).toFlushAndYield(['Bar']); - } - expect(hiddenDiv.hidden).toBe(true); - expect(hiddenDiv.innerHTML).toBe('

bar 0

'); + let hiddenDiv; + act(() => { + root.render(); + if (__DEV__) { + expect(Scheduler).toFlushAndYieldThrough([ + 'Foo', + 'Foo', + 'Baz', + 'Foo#effect', + ]); + } else { + expect(Scheduler).toFlushAndYieldThrough(['Foo', 'Baz', 'Foo#effect']); + } + hiddenDiv = container.firstChild.firstChild; + expect(hiddenDiv.hidden).toBe(true); + expect(hiddenDiv.innerHTML).toBe(''); + // Run offscreen update + if (__DEV__) { + expect(Scheduler).toFlushAndYield(['Bar', 'Bar']); + } else { + expect(Scheduler).toFlushAndYield(['Bar']); + } + expect(hiddenDiv.hidden).toBe(true); + expect(hiddenDiv.innerHTML).toBe('

bar 0

'); + }); ReactDOM.flushSync(() => { setCounter(1); @@ -1623,12 +1626,18 @@ describe('ReactUpdates', () => { }; try { const container = document.createElement('div'); - ReactDOM.render(, container); - while (error === null) { - Scheduler.unstable_flushNumberOfYields(1); - } - expect(error).toContain('Warning: Maximum update depth exceeded.'); - expect(stack).toContain('in NonTerminating'); + expect(() => { + act(() => { + ReactDOM.render(, container); + while (error === null) { + Scheduler.unstable_flushNumberOfYields(1); + } + expect(error).toContain('Warning: Maximum update depth exceeded.'); + expect(stack).toContain('in NonTerminating'); + // rethrow error to prevent going into an infinite loop when act() exits + throw error; + }); + }).toThrow('Maximum update depth exceeded.'); } finally { console.error = originalConsoleError; } @@ -1651,7 +1660,9 @@ describe('ReactUpdates', () => { } const container = document.createElement('div'); - ReactDOM.render(, container); + act(() => { + ReactDOM.render(, container); + }); // Verify we can flush them asynchronously without warning for (let i = 0; i < LIMIT * 2; i++) { @@ -1660,16 +1671,16 @@ describe('ReactUpdates', () => { expect(container.textContent).toBe('50'); // Verify restarting from 0 doesn't cross the limit - expect(() => { + act(() => { _setStep(0); - }).toWarnDev( - 'An update to Terminating inside a test was not wrapped in act', - ); - expect(container.textContent).toBe('0'); - for (let i = 0; i < LIMIT * 2; i++) { + // flush once to update the dom Scheduler.unstable_flushNumberOfYields(1); - } - expect(container.textContent).toBe('50'); + expect(container.textContent).toBe('0'); + for (let i = 0; i < LIMIT * 2; i++) { + Scheduler.unstable_flushNumberOfYields(1); + } + expect(container.textContent).toBe('50'); + }); }); it('can have many updates inside useEffect without triggering a warning', () => { @@ -1685,8 +1696,11 @@ describe('ReactUpdates', () => { } const container = document.createElement('div'); - ReactDOM.render(, container); - expect(Scheduler).toFlushAndYield(['Done']); + act(() => { + ReactDOM.render(, container); + }); + + expect(Scheduler).toHaveYielded(['Done']); expect(container.textContent).toBe('1000'); }); } diff --git a/packages/react-dom/src/__tests__/utils/ReactDOMServerIntegrationTestUtils.js b/packages/react-dom/src/__tests__/utils/ReactDOMServerIntegrationTestUtils.js index 55350b42a378e..c4f7f7bada09f 100644 --- a/packages/react-dom/src/__tests__/utils/ReactDOMServerIntegrationTestUtils.js +++ b/packages/react-dom/src/__tests__/utils/ReactDOMServerIntegrationTestUtils.js @@ -14,9 +14,10 @@ const stream = require('stream'); module.exports = function(initModules) { let ReactDOM; let ReactDOMServer; + let ReactTestUtils; function resetModules() { - ({ReactDOM, ReactDOMServer} = initModules()); + ({ReactDOM, ReactDOMServer, ReactTestUtils} = initModules()); } function shouldUseDocument(reactElement) { @@ -48,9 +49,13 @@ module.exports = function(initModules) { function asyncReactDOMRender(reactElement, domElement, forceHydrate) { return new Promise(resolve => { if (forceHydrate) { - ReactDOM.hydrate(reactElement, domElement); + ReactTestUtils.act(() => { + ReactDOM.hydrate(reactElement, domElement); + }); } else { - ReactDOM.render(reactElement, domElement); + ReactTestUtils.act(() => { + ReactDOM.render(reactElement, domElement); + }); } // We can't use the callback for resolution because that will not catch // errors. They're thrown. diff --git a/packages/react-reconciler/src/ReactFiberHooks.js b/packages/react-reconciler/src/ReactFiberHooks.js index 7f9372bdc7159..1ed7ecb7247f7 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.js +++ b/packages/react-reconciler/src/ReactFiberHooks.js @@ -35,6 +35,7 @@ import { computeExpirationForFiber, flushPassiveEffects, requestCurrentTime, + warnIfNotCurrentlyActingEffectsInDEV, warnIfNotCurrentlyActingUpdatesInDev, warnIfNotScopedWithMatchingAct, markRenderEventTimeAndConfig, @@ -898,6 +899,14 @@ function mountEffect( create: () => (() => void) | void, deps: Array | void | null, ): void { + if (__DEV__) { + // $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests + if ('undefined' !== typeof jest) { + warnIfNotCurrentlyActingEffectsInDEV( + ((currentlyRenderingFiber: any): Fiber), + ); + } + } return mountEffectImpl( UpdateEffect | PassiveEffect, UnmountPassive | MountPassive, @@ -910,6 +919,14 @@ function updateEffect( create: () => (() => void) | void, deps: Array | void | null, ): void { + if (__DEV__) { + // $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests + if ('undefined' !== typeof jest) { + warnIfNotCurrentlyActingEffectsInDEV( + ((currentlyRenderingFiber: any): Fiber), + ); + } + } return updateEffectImpl( UpdateEffect | PassiveEffect, UnmountPassive | MountPassive, diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js index 57b0913dd8d91..ebf98cf617b41 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js @@ -2429,6 +2429,29 @@ export function warnIfNotScopedWithMatchingAct(fiber: Fiber): void { } } +export function warnIfNotCurrentlyActingEffectsInDEV(fiber: Fiber): void { + if (__DEV__) { + if (ReactCurrentActingRendererSigil.current !== ReactActingRendererSigil) { + warningWithoutStack( + false, + 'An update to %s ran an effect, but was not wrapped in act(...).\n\n' + + 'When testing, code that causes React state updates should be ' + + 'wrapped into act(...):\n\n' + + 'act(() => {\n' + + ' /* fire events that update state */\n' + + '});\n' + + '/* assert on the output */\n\n' + + "This ensures that you're testing the behavior the user would see " + + 'in the browser.' + + ' Learn more at https://fb.me/react-wrap-tests-with-act' + + '%s', + getComponentName(fiber.type), + getStackByFiberInDevAndProd(fiber), + ); + } + } +} + function warnIfNotCurrentlyActingUpdatesInDEV(fiber: Fiber): void { if (__DEV__) { if ( diff --git a/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js b/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js index 74152cbcc6c31..ea6e3103edfe1 100644 --- a/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js @@ -652,7 +652,9 @@ describe('ReactHooks', () => { } expect(() => { - ReactTestRenderer.create(); + act(() => { + ReactTestRenderer.create(); + }); }).toWarnDev([ 'Warning: useEffect received a final argument that is not an array (instead, received `string`). ' + 'When specified, the final argument must be an array.', @@ -664,7 +666,9 @@ describe('ReactHooks', () => { 'When specified, the final argument must be an array.', ]); expect(() => { - ReactTestRenderer.create(); + act(() => { + ReactTestRenderer.create(); + }); }).toWarnDev([ 'Warning: useEffect received a final argument that is not an array (instead, received `number`). ' + 'When specified, the final argument must be an array.', @@ -676,7 +680,9 @@ describe('ReactHooks', () => { 'When specified, the final argument must be an array.', ]); expect(() => { - ReactTestRenderer.create(); + act(() => { + ReactTestRenderer.create(); + }); }).toWarnDev([ 'Warning: useEffect received a final argument that is not an array (instead, received `object`). ' + 'When specified, the final argument must be an array.', @@ -687,9 +693,12 @@ describe('ReactHooks', () => { 'Warning: useCallback received a final argument that is not an array (instead, received `object`). ' + 'When specified, the final argument must be an array.', ]); - ReactTestRenderer.create(); - ReactTestRenderer.create(); - ReactTestRenderer.create(); + + act(() => { + ReactTestRenderer.create(); + ReactTestRenderer.create(); + ReactTestRenderer.create(); + }); }); it('warns if deps is not an array for useImperativeHandle', () => { @@ -980,8 +989,11 @@ describe('ReactHooks', () => { return null; } - const root = ReactTestRenderer.create(); - expect(() => root.update()).toThrow( + expect(() => { + act(() => { + ReactTestRenderer.create(); + }); + }).toThrow( // The exact message doesn't matter, just make sure we don't allow this 'Context can only be read while React is rendering', ); @@ -1173,7 +1185,9 @@ describe('ReactHooks', () => { } // Verify it doesn't think we're still inside a Hook. // Should have no warnings. - ReactTestRenderer.create(); + act(() => { + ReactTestRenderer.create(); + }); // Verify warnings don't get permanently disabled. expect(() => { @@ -1499,10 +1513,15 @@ describe('ReactHooks', () => { return null; /* eslint-enable no-unused-vars */ } - let root = ReactTestRenderer.create(); + let root; + act(() => { + root = ReactTestRenderer.create(); + }); expect(() => { try { - root.update(); + act(() => { + root.update(); + }); } catch (error) { // Swapping certain types of hooks will cause runtime errors. // This is okay as far as this test is concerned. @@ -1521,7 +1540,9 @@ describe('ReactHooks', () => { // further warnings for this component are silenced try { - root.update(); + act(() => { + root.update(); + }); } catch (error) { // Swapping certain types of hooks will cause runtime errors. // This is okay as far as this test is concerned. @@ -1542,10 +1563,16 @@ describe('ReactHooks', () => { return null; /* eslint-enable no-unused-vars */ } - let root = ReactTestRenderer.create(); + let root; + act(() => { + root = ReactTestRenderer.create(); + }); + expect(() => { try { - root.update(); + act(() => { + root.update(); + }); } catch (error) { // Swapping certain types of hooks will cause runtime errors. // This is okay as far as this test is concerned. @@ -1604,9 +1631,15 @@ describe('ReactHooks', () => { return null; /* eslint-enable no-unused-vars */ } - let root = ReactTestRenderer.create(); + let root; + act(() => { + root = ReactTestRenderer.create(); + }); + expect(() => { - root.update(); + act(() => { + root.update(); + }); }).toThrow('Rendered fewer hooks than expected.'); }); }); @@ -1767,6 +1800,7 @@ describe('ReactHooks', () => { globalListener(); globalListener(); }).toWarnDev([ + 'An update to C ran an effect', 'An update to C inside a test was not wrapped in act', 'An update to C inside a test was not wrapped in act', // Note: should *not* warn about updates on unmounted component. @@ -1908,11 +1942,14 @@ describe('ReactHooks', () => { return 'Throw!'; } - const root = ReactTestRenderer.create( - - - , - ); + let root; + act(() => { + root = ReactTestRenderer.create( + + + , + ); + }); expect(root).toMatchRenderedOutput('Throw!'); act(() => setShouldThrow(true)); diff --git a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.internal.js b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.internal.js index 496cd6d1f7579..8c49c606a87de 100644 --- a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.internal.js @@ -622,21 +622,25 @@ describe('ReactHooksWithNoopRenderer', () => { }); return ; } - ReactNoop.render(, () => - Scheduler.yieldValue('Sync effect'), - ); - expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); - // Effects are deferred until after the commit - expect(Scheduler).toFlushAndYield(['Passive effect [0]']); + act(() => { + ReactNoop.render(, () => + Scheduler.yieldValue('Sync effect'), + ); + expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); + expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + // Effects are deferred until after the commit + expect(Scheduler).toFlushAndYield(['Passive effect [0]']); + }); - ReactNoop.render(, () => - Scheduler.yieldValue('Sync effect'), - ); - expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); - // Effects are deferred until after the commit - expect(Scheduler).toFlushAndYield(['Passive effect [1]']); + act(() => { + ReactNoop.render(, () => + Scheduler.yieldValue('Sync effect'), + ); + expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']); + expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); + // Effects are deferred until after the commit + expect(Scheduler).toFlushAndYield(['Passive effect [1]']); + }); }); it('flushes passive effects even with sibling deletions', () => { @@ -653,25 +657,24 @@ describe('ReactHooksWithNoopRenderer', () => { return ; } let passive = ; - ReactNoop.render([, passive]); - expect(Scheduler).toFlushAndYieldThrough([ - 'Layout', - 'Passive', - 'Layout effect', - ]); - expect(ReactNoop.getChildren()).toEqual([ - span('Layout'), - span('Passive'), - ]); - - // Destroying the first child shouldn't prevent the passive effect from - // being executed - ReactNoop.render([passive]); - expect(Scheduler).toFlushAndYield(['Passive effect']); - expect(ReactNoop.getChildren()).toEqual([span('Passive')]); - - // (No effects are left to flush.) - ReactNoop.flushPassiveEffects(); + act(() => { + ReactNoop.render([, passive]); + expect(Scheduler).toFlushAndYieldThrough([ + 'Layout', + 'Passive', + 'Layout effect', + ]); + expect(ReactNoop.getChildren()).toEqual([ + span('Layout'), + span('Passive'), + ]); + // Destroying the first child shouldn't prevent the passive effect from + // being executed + ReactNoop.render([passive]); + expect(Scheduler).toFlushAndYield(['Passive effect']); + expect(ReactNoop.getChildren()).toEqual([span('Passive')]); + }); + // exiting act calls flushPassiveEffects(), but there are none left to flush. expect(Scheduler).toHaveYielded([]); }); @@ -728,18 +731,20 @@ describe('ReactHooksWithNoopRenderer', () => { }); return ; } - ReactNoop.render([, ]); - expect(Scheduler).toFlushAndYield([ - 'Passive', - 'Layout', - 'Layout effect', - 'Passive effect', - 'New Root', - ]); - expect(ReactNoop.getChildren()).toEqual([ - span('Passive'), - span('Layout'), - ]); + act(() => { + ReactNoop.render([, ]); + expect(Scheduler).toFlushAndYield([ + 'Passive', + 'Layout', + 'Layout effect', + 'Passive effect', + 'New Root', + ]); + expect(ReactNoop.getChildren()).toEqual([ + span('Passive'), + span('Layout'), + ]); + }); }); it( @@ -762,25 +767,25 @@ describe('ReactHooksWithNoopRenderer', () => { }); return ; } - ReactNoop.render(, () => - Scheduler.yieldValue('Sync effect'), - ); - expect(Scheduler).toFlushAndYieldThrough([0, 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span(0)]); - - // Before the effects have a chance to flush, schedule another update - ReactNoop.render(, () => - Scheduler.yieldValue('Sync effect'), - ); - expect(Scheduler).toFlushAndYieldThrough([ - // The previous effect flushes before the reconciliation - 'Committed state when effect was fired: 0', - 1, - 'Sync effect', - ]); - expect(ReactNoop.getChildren()).toEqual([span(1)]); + act(() => { + ReactNoop.render(, () => + Scheduler.yieldValue('Sync effect'), + ); + expect(Scheduler).toFlushAndYieldThrough([0, 'Sync effect']); + expect(ReactNoop.getChildren()).toEqual([span(0)]); + // Before the effects have a chance to flush, schedule another update + ReactNoop.render(, () => + Scheduler.yieldValue('Sync effect'), + ); + expect(Scheduler).toFlushAndYieldThrough([ + // The previous effect flushes before the reconciliation + 'Committed state when effect was fired: 0', + 1, + 'Sync effect', + ]); + expect(ReactNoop.getChildren()).toEqual([span(1)]); + }); - ReactNoop.flushPassiveEffects(); expect(Scheduler).toHaveYielded([ 'Committed state when effect was fired: 1', ]); @@ -799,26 +804,30 @@ describe('ReactHooksWithNoopRenderer', () => { ); return ; } - ReactNoop.render(, () => - Scheduler.yieldValue('Sync effect'), - ); - expect(Scheduler).toFlushAndYieldThrough([ - 'Count: (empty)', - 'Sync effect', - ]); - expect(ReactNoop.getChildren()).toEqual([span('Count: (empty)')]); - ReactNoop.flushPassiveEffects(); - expect(Scheduler).toHaveYielded(['Schedule update [0]']); - expect(Scheduler).toFlushAndYield(['Count: 0']); + act(() => { + ReactNoop.render(, () => + Scheduler.yieldValue('Sync effect'), + ); + expect(Scheduler).toFlushAndYieldThrough([ + 'Count: (empty)', + 'Sync effect', + ]); + expect(ReactNoop.getChildren()).toEqual([span('Count: (empty)')]); + ReactNoop.flushPassiveEffects(); + expect(Scheduler).toHaveYielded(['Schedule update [0]']); + expect(Scheduler).toFlushAndYield(['Count: 0']); + }); - ReactNoop.render(, () => - Scheduler.yieldValue('Sync effect'), - ); - expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); - ReactNoop.flushPassiveEffects(); - expect(Scheduler).toHaveYielded(['Schedule update [1]']); - expect(Scheduler).toFlushAndYield(['Count: 1']); + act(() => { + ReactNoop.render(, () => + Scheduler.yieldValue('Sync effect'), + ); + expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); + expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + ReactNoop.flushPassiveEffects(); + expect(Scheduler).toHaveYielded(['Schedule update [1]']); + expect(Scheduler).toFlushAndYield(['Count: 1']); + }); }); it('updates have async priority even if effects are flushed early', () => { @@ -833,32 +842,33 @@ describe('ReactHooksWithNoopRenderer', () => { ); return ; } - ReactNoop.render(, () => - Scheduler.yieldValue('Sync effect'), - ); - expect(Scheduler).toFlushAndYieldThrough([ - 'Count: (empty)', - 'Sync effect', - ]); - expect(ReactNoop.getChildren()).toEqual([span('Count: (empty)')]); - - // Rendering again should flush the previous commit's effects - ReactNoop.render(, () => - Scheduler.yieldValue('Sync effect'), - ); - expect(Scheduler).toFlushAndYieldThrough([ - 'Schedule update [0]', - 'Count: 0', - ]); - expect(ReactNoop.getChildren()).toEqual([span('Count: (empty)')]); + act(() => { + ReactNoop.render(, () => + Scheduler.yieldValue('Sync effect'), + ); + expect(Scheduler).toFlushAndYieldThrough([ + 'Count: (empty)', + 'Sync effect', + ]); + expect(ReactNoop.getChildren()).toEqual([span('Count: (empty)')]); - expect(Scheduler).toFlushAndYieldThrough(['Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + // Rendering again should flush the previous commit's effects + ReactNoop.render(, () => + Scheduler.yieldValue('Sync effect'), + ); + expect(Scheduler).toFlushAndYieldThrough([ + 'Schedule update [0]', + 'Count: 0', + ]); + expect(ReactNoop.getChildren()).toEqual([span('Count: (empty)')]); - ReactNoop.flushPassiveEffects(); - expect(Scheduler).toHaveYielded(['Schedule update [1]']); - expect(Scheduler).toFlushAndYield(['Count: 1']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); + expect(Scheduler).toFlushAndYieldThrough(['Sync effect']); + expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + ReactNoop.flushPassiveEffects(); + expect(Scheduler).toHaveYielded(['Schedule update [1]']); + expect(Scheduler).toFlushAndYield(['Count: 1']); + expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); + }); }); it('flushes passive effects when flushing discrete updates', () => { @@ -873,15 +883,18 @@ describe('ReactHooksWithNoopRenderer', () => { return ; } - ReactNoop.render(, () => - Scheduler.yieldValue('Sync effect'), - ); - expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + // we explicitly wait for missing act() warnings here since + // it's a lot harder to simulate this condition inside an act scope + expect(() => { + ReactNoop.render(, () => + Scheduler.yieldValue('Sync effect'), + ); + expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); + expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + }).toWarnDev(['An update to Counter ran an effect']); // A discrete event forces the passive effect to be flushed -- // updateCount(1) happens first, so 2 wins. - ReactNoop.flushDiscreteUpdates(); ReactNoop.discreteUpdates(() => { // (use batchedUpdates to silence the act() warning) @@ -890,7 +903,13 @@ describe('ReactHooksWithNoopRenderer', () => { }); }); expect(Scheduler).toHaveYielded(['Will set count to 1']); - expect(Scheduler).toFlushAndYield(['Count: 2']); + expect(() => { + expect(Scheduler).toFlushAndYield(['Count: 2']); + }).toWarnDev([ + 'An update to Counter ran an effect', + 'An update to Counter ran an effect', + ]); + expect(ReactNoop.getChildren()).toEqual([span('Count: 2')]); }); @@ -921,17 +940,21 @@ describe('ReactHooksWithNoopRenderer', () => { } const tracingEvent = {id: 0, name: 'hello', timestamp: 0}; - SchedulerTracing.unstable_trace( - tracingEvent.name, - tracingEvent.timestamp, - () => { - ReactNoop.render(, () => - Scheduler.yieldValue('Sync effect'), - ); - }, - ); - expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + // we explicitly wait for missing act() warnings here since + // it's a lot harder to simulate this condition inside an act scope + expect(() => { + SchedulerTracing.unstable_trace( + tracingEvent.name, + tracingEvent.timestamp, + () => { + ReactNoop.render(, () => + Scheduler.yieldValue('Sync effect'), + ); + }, + ); + expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); + expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + }).toWarnDev(['An update to Counter ran an effect']); expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(0); @@ -939,11 +962,19 @@ describe('ReactHooksWithNoopRenderer', () => { // updateCount(1) happens first, so 2 wins. ReactNoop.flushDiscreteUpdates(); ReactNoop.discreteUpdates(() => { - // use batchedUpdates to silence the act warning - ReactNoop.batchedUpdates(() => _updateCount(2)); + // (use batchedUpdates to silence the act() warning) + ReactNoop.batchedUpdates(() => { + _updateCount(2); + }); }); expect(Scheduler).toHaveYielded(['Will set count to 1']); - expect(Scheduler).toFlushAndYield(['Count: 2']); + expect(() => { + expect(Scheduler).toFlushAndYield(['Count: 2']); + }).toWarnDev([ + 'An update to Counter ran an effect', + 'An update to Counter ran an effect', + ]); + expect(ReactNoop.getChildren()).toEqual([span('Count: 2')]); expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); @@ -971,12 +1002,14 @@ describe('ReactHooksWithNoopRenderer', () => { ); return ; } - ReactNoop.renderLegacySyncRoot(); - // Even in sync mode, effects are deferred until after paint - expect(Scheduler).toHaveYielded(['Count: (empty)']); - expect(ReactNoop.getChildren()).toEqual([span('Count: (empty)')]); - // Now fire the effects - ReactNoop.flushPassiveEffects(); + act(() => { + ReactNoop.renderLegacySyncRoot(); + // Even in sync mode, effects are deferred until after paint + expect(Scheduler).toFlushAndYieldThrough(['Count: (empty)']); + expect(ReactNoop.getChildren()).toEqual([span('Count: (empty)')]); + }); + + // effects get fored on exiting act() // There were multiple updates, but there should only be a // single render expect(Scheduler).toHaveYielded(['Count: 0']); @@ -998,18 +1031,19 @@ describe('ReactHooksWithNoopRenderer', () => { ); return ; } - ReactNoop.render(, () => - Scheduler.yieldValue('Sync effect'), - ); - expect(Scheduler).toFlushAndYieldThrough([ - 'Count: (empty)', - 'Sync effect', - ]); - expect(ReactNoop.getChildren()).toEqual([span('Count: (empty)')]); - - expect(() => { - ReactNoop.flushPassiveEffects(); - }).toThrow('flushSync was called from inside a lifecycle method'); + act(() => { + ReactNoop.render(, () => + Scheduler.yieldValue('Sync effect'), + ); + expect(Scheduler).toFlushAndYieldThrough([ + 'Count: (empty)', + 'Sync effect', + ]); + expect(ReactNoop.getChildren()).toEqual([span('Count: (empty)')]); + expect(() => { + ReactNoop.flushPassiveEffects(); + }).toThrow('flushSync was called from inside a lifecycle method'); + }); }); it('unmounts previous effect', () => { @@ -1022,20 +1056,24 @@ describe('ReactHooksWithNoopRenderer', () => { }); return ; } - ReactNoop.render(, () => - Scheduler.yieldValue('Sync effect'), - ); - expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); - ReactNoop.flushPassiveEffects(); + act(() => { + ReactNoop.render(, () => + Scheduler.yieldValue('Sync effect'), + ); + expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); + expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + }); + expect(Scheduler).toHaveYielded(['Did create [0]']); - ReactNoop.render(, () => - Scheduler.yieldValue('Sync effect'), - ); - expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); - ReactNoop.flushPassiveEffects(); + act(() => { + ReactNoop.render(, () => + Scheduler.yieldValue('Sync effect'), + ); + expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']); + expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); + }); + expect(Scheduler).toHaveYielded(['Did destroy [0]', 'Did create [1]']); }); @@ -1049,12 +1087,14 @@ describe('ReactHooksWithNoopRenderer', () => { }); return ; } - ReactNoop.render(, () => - Scheduler.yieldValue('Sync effect'), - ); - expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); - ReactNoop.flushPassiveEffects(); + act(() => { + ReactNoop.render(, () => + Scheduler.yieldValue('Sync effect'), + ); + expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); + expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + }); + expect(Scheduler).toHaveYielded(['Did create [0]']); ReactNoop.render(null); @@ -1072,20 +1112,24 @@ describe('ReactHooksWithNoopRenderer', () => { }, []); return ; } - ReactNoop.render(, () => - Scheduler.yieldValue('Sync effect'), - ); - expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); - ReactNoop.flushPassiveEffects(); + act(() => { + ReactNoop.render(, () => + Scheduler.yieldValue('Sync effect'), + ); + expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); + expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + }); + expect(Scheduler).toHaveYielded(['Did create [0]']); - ReactNoop.render(, () => - Scheduler.yieldValue('Sync effect'), - ); - expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); - ReactNoop.flushPassiveEffects(); + act(() => { + ReactNoop.render(, () => + Scheduler.yieldValue('Sync effect'), + ); + expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']); + expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); + }); + expect(Scheduler).toHaveYielded([]); ReactNoop.render(null); @@ -1104,20 +1148,24 @@ describe('ReactHooksWithNoopRenderer', () => { useEffect(effect); return ; } - ReactNoop.render(, () => - Scheduler.yieldValue('Sync effect'), - ); - expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); - ReactNoop.flushPassiveEffects(); + act(() => { + ReactNoop.render(, () => + Scheduler.yieldValue('Sync effect'), + ); + expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); + expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + }); + expect(Scheduler).toHaveYielded(['Did create']); - ReactNoop.render(, () => - Scheduler.yieldValue('Sync effect'), - ); - expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); - ReactNoop.flushPassiveEffects(); + act(() => { + ReactNoop.render(, () => + Scheduler.yieldValue('Sync effect'), + ); + expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']); + expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); + }); + expect(Scheduler).toHaveYielded(['Did destroy', 'Did create']); ReactNoop.render(null); @@ -1139,42 +1187,50 @@ describe('ReactHooksWithNoopRenderer', () => { ); return ; } - ReactNoop.render(, () => - Scheduler.yieldValue('Sync effect'), - ); - expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); - ReactNoop.flushPassiveEffects(); + act(() => { + ReactNoop.render(, () => + Scheduler.yieldValue('Sync effect'), + ); + expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); + }); + expect(Scheduler).toHaveYielded(['Did create [Count: 0]']); expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); - ReactNoop.render(, () => - Scheduler.yieldValue('Sync effect'), - ); - // Count changed - expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); - ReactNoop.flushPassiveEffects(); + act(() => { + ReactNoop.render(, () => + Scheduler.yieldValue('Sync effect'), + ); + // Count changed + expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']); + expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); + }); + expect(Scheduler).toHaveYielded([ 'Did destroy [Count: 0]', 'Did create [Count: 1]', ]); - ReactNoop.render(, () => - Scheduler.yieldValue('Sync effect'), - ); - // Nothing changed, so no effect should have fired - expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']); - ReactNoop.flushPassiveEffects(); + act(() => { + ReactNoop.render(, () => + Scheduler.yieldValue('Sync effect'), + ); + // Nothing changed, so no effect should have fired + expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']); + }); + expect(Scheduler).toHaveYielded([]); expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); - ReactNoop.render(, () => - Scheduler.yieldValue('Sync effect'), - ); - // Label changed - expect(Scheduler).toFlushAndYieldThrough(['Total: 1', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Total: 1')]); - ReactNoop.flushPassiveEffects(); + act(() => { + ReactNoop.render(, () => + Scheduler.yieldValue('Sync effect'), + ); + // Label changed + expect(Scheduler).toFlushAndYieldThrough(['Total: 1', 'Sync effect']); + expect(ReactNoop.getChildren()).toEqual([span('Total: 1')]); + }); + expect(Scheduler).toHaveYielded([ 'Did destroy [Count: 1]', 'Did create [Total: 1]', @@ -1191,20 +1247,23 @@ describe('ReactHooksWithNoopRenderer', () => { }); return ; } - ReactNoop.render(, () => - Scheduler.yieldValue('Sync effect'), - ); - expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); - ReactNoop.flushPassiveEffects(); + act(() => { + ReactNoop.render(, () => + Scheduler.yieldValue('Sync effect'), + ); + expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); + expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + }); + expect(Scheduler).toHaveYielded(['Did commit 1 [0]', 'Did commit 2 [0]']); - ReactNoop.render(, () => - Scheduler.yieldValue('Sync effect'), - ); - expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); - ReactNoop.flushPassiveEffects(); + act(() => { + ReactNoop.render(, () => + Scheduler.yieldValue('Sync effect'), + ); + expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']); + expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); + }); expect(Scheduler).toHaveYielded(['Did commit 1 [1]', 'Did commit 2 [1]']); }); @@ -1224,20 +1283,23 @@ describe('ReactHooksWithNoopRenderer', () => { }); return ; } - ReactNoop.render(, () => - Scheduler.yieldValue('Sync effect'), - ); - expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); - ReactNoop.flushPassiveEffects(); + act(() => { + ReactNoop.render(, () => + Scheduler.yieldValue('Sync effect'), + ); + expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); + expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + }); + expect(Scheduler).toHaveYielded(['Mount A [0]', 'Mount B [0]']); - ReactNoop.render(, () => - Scheduler.yieldValue('Sync effect'), - ); - expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); - ReactNoop.flushPassiveEffects(); + act(() => { + ReactNoop.render(, () => + Scheduler.yieldValue('Sync effect'), + ); + expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']); + expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); + }); expect(Scheduler).toHaveYielded([ 'Unmount A [0]', 'Unmount B [0]', @@ -1265,12 +1327,15 @@ describe('ReactHooksWithNoopRenderer', () => { }); return ; } - ReactNoop.render(, () => - Scheduler.yieldValue('Sync effect'), - ); - expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); - expect(() => ReactNoop.flushPassiveEffects()).toThrow('Oops'); + act(() => { + ReactNoop.render(, () => + Scheduler.yieldValue('Sync effect'), + ); + expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); + expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + expect(() => ReactNoop.flushPassiveEffects()).toThrow('Oops'); + }); + expect(Scheduler).toHaveYielded([ 'Mount A [0]', 'Oops!', @@ -1301,31 +1366,35 @@ describe('ReactHooksWithNoopRenderer', () => { }); return ; } - ReactNoop.render(, () => - Scheduler.yieldValue('Sync effect'), - ); - expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); - ReactNoop.flushPassiveEffects(); - expect(Scheduler).toHaveYielded(['Mount A [0]', 'Mount B [0]']); + act(() => { + ReactNoop.render(, () => + Scheduler.yieldValue('Sync effect'), + ); + expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); + expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + ReactNoop.flushPassiveEffects(); + expect(Scheduler).toHaveYielded(['Mount A [0]', 'Mount B [0]']); + }); - // This update will trigger an errror - ReactNoop.render(, () => - Scheduler.yieldValue('Sync effect'), - ); - expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); - expect(() => ReactNoop.flushPassiveEffects()).toThrow('Oops'); - expect(Scheduler).toHaveYielded([ - 'Unmount A [0]', - 'Unmount B [0]', - 'Mount A [1]', - 'Oops!', - // Clean up effect A. There's no effect B to clean-up, because it - // never mounted. - 'Unmount A [1]', - ]); - expect(ReactNoop.getChildren()).toEqual([]); + act(() => { + // This update will trigger an errror + ReactNoop.render(, () => + Scheduler.yieldValue('Sync effect'), + ); + expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']); + expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); + expect(() => ReactNoop.flushPassiveEffects()).toThrow('Oops'); + expect(Scheduler).toHaveYielded([ + 'Unmount A [0]', + 'Unmount B [0]', + 'Mount A [1]', + 'Oops!', + // Clean up effect A. There's no effect B to clean-up, because it + // never mounted. + 'Unmount A [1]', + ]); + expect(ReactNoop.getChildren()).toEqual([]); + }); }); it('handles errors on unmount', () => { @@ -1347,27 +1416,31 @@ describe('ReactHooksWithNoopRenderer', () => { }); return ; } - ReactNoop.render(, () => - Scheduler.yieldValue('Sync effect'), - ); - expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); - ReactNoop.flushPassiveEffects(); - expect(Scheduler).toHaveYielded(['Mount A [0]', 'Mount B [0]']); + act(() => { + ReactNoop.render(, () => + Scheduler.yieldValue('Sync effect'), + ); + expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); + expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + ReactNoop.flushPassiveEffects(); + expect(Scheduler).toHaveYielded(['Mount A [0]', 'Mount B [0]']); + }); - // This update will trigger an errror - ReactNoop.render(, () => - Scheduler.yieldValue('Sync effect'), - ); - expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); - expect(() => ReactNoop.flushPassiveEffects()).toThrow('Oops'); - expect(Scheduler).toHaveYielded([ - 'Oops!', - // B unmounts even though an error was thrown in the previous effect - 'Unmount B [0]', - ]); - expect(ReactNoop.getChildren()).toEqual([]); + act(() => { + // This update will trigger an errror + ReactNoop.render(, () => + Scheduler.yieldValue('Sync effect'), + ); + expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']); + expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); + expect(() => ReactNoop.flushPassiveEffects()).toThrow('Oops'); + expect(Scheduler).toHaveYielded([ + 'Oops!', + // B unmounts even though an error was thrown in the previous effect + 'Unmount B [0]', + ]); + expect(ReactNoop.getChildren()).toEqual([]); + }); }); it('works with memo', () => { @@ -1470,27 +1543,27 @@ describe('ReactHooksWithNoopRenderer', () => { return null; } - ReactNoop.render(, () => - Scheduler.yieldValue('Sync effect'), - ); - expect(Scheduler).toFlushAndYieldThrough([ - 'Mount layout [current: 0]', - 'Sync effect', - ]); - expect(committedText).toEqual('0'); - - ReactNoop.render(, () => - Scheduler.yieldValue('Sync effect'), - ); - expect(Scheduler).toFlushAndYieldThrough([ - 'Mount normal [current: 0]', - 'Unmount layout [current: 0]', - 'Mount layout [current: 1]', - 'Sync effect', - ]); - expect(committedText).toEqual('1'); + act(() => { + ReactNoop.render(, () => + Scheduler.yieldValue('Sync effect'), + ); + expect(Scheduler).toFlushAndYieldThrough([ + 'Mount layout [current: 0]', + 'Sync effect', + ]); + expect(committedText).toEqual('0'); + ReactNoop.render(, () => + Scheduler.yieldValue('Sync effect'), + ); + expect(Scheduler).toFlushAndYieldThrough([ + 'Mount normal [current: 0]', + 'Unmount layout [current: 0]', + 'Mount layout [current: 1]', + 'Sync effect', + ]); + expect(committedText).toEqual('1'); + }); - ReactNoop.flushPassiveEffects(); expect(Scheduler).toHaveYielded([ 'Unmount normal [current: 1]', 'Mount normal [current: 1]', @@ -1684,8 +1757,10 @@ describe('ReactHooksWithNoopRenderer', () => { return null; } - ReactNoop.render(); - expect(Scheduler).toFlushAndYield([]); + act(() => { + ReactNoop.render(); + }); + expect(Scheduler).toHaveYielded([]); ping(1); ping(2); @@ -1963,28 +2038,32 @@ describe('ReactHooksWithNoopRenderer', () => { return null; } - ReactNoop.render(, () => - Scheduler.yieldValue('Sync effect'), - ); - expect(Scheduler).toFlushAndYieldThrough(['Sync effect']); - ReactNoop.flushPassiveEffects(); + act(() => { + ReactNoop.render(, () => + Scheduler.yieldValue('Sync effect'), + ); + expect(Scheduler).toFlushAndYieldThrough(['Sync effect']); + }); + expect(Scheduler).toHaveYielded(['Mount A']); - ReactNoop.render(); - expect(() => { + act(() => { + ReactNoop.render(); expect(() => { - expect(Scheduler).toFlushAndYield([]); - }).toThrow('Rendered more hooks than during the previous render'); - }).toWarnDev([ - 'Warning: React has detected a change in the order of Hooks called by App. ' + - 'This will lead to bugs and errors if not fixed. For more information, ' + - 'read the Rules of Hooks: https://fb.me/rules-of-hooks\n\n' + - ' Previous render Next render\n' + - ' ------------------------------------------------------\n' + - '1. useEffect useEffect\n' + - '2. undefined useEffect\n' + - ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n', - ]); + expect(() => { + expect(Scheduler).toFlushAndYield([]); + }).toThrow('Rendered more hooks than during the previous render'); + }).toWarnDev([ + 'Warning: React has detected a change in the order of Hooks called by App. ' + + 'This will lead to bugs and errors if not fixed. For more information, ' + + 'read the Rules of Hooks: https://fb.me/rules-of-hooks\n\n' + + ' Previous render Next render\n' + + ' ------------------------------------------------------\n' + + '1. useEffect useEffect\n' + + '2. undefined useEffect\n' + + ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n', + ]); + }); // Uncomment if/when we support this again // ReactNoop.flushPassiveEffects(); @@ -2026,15 +2105,17 @@ describe('ReactHooksWithNoopRenderer', () => { return count; } - ReactNoop.render(); - expect(Scheduler).toFlushAndYield([ - 'Render: -1', - 'Effect: 1', - 'Reducer: 1', - 'Reducer: 1', - 'Render: 1', - ]); - expect(ReactNoop).toMatchRenderedOutput('1'); + act(() => { + ReactNoop.render(); + expect(Scheduler).toFlushAndYield([ + 'Render: -1', + 'Effect: 1', + 'Reducer: 1', + 'Reducer: 1', + 'Render: 1', + ]); + expect(ReactNoop).toMatchRenderedOutput('1'); + }); act(() => { setCounter(2); @@ -2109,19 +2190,19 @@ describe('ReactHooksWithNoopRenderer', () => { return ; } - ReactNoop.render(, () => - Scheduler.yieldValue('Sync effect'), - ); - expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); - - // Enqueuing this update forces the passive effect to be flushed -- - // updateCount(1) happens first, so 2 wins. - // (use batchedUpdates to silence the act() warning) - ReactNoop.batchedUpdates(() => _updateCount(2)); - expect(Scheduler).toHaveYielded(['Will set count to 1']); - expect(Scheduler).toFlushAndYield(['Count: 2']); - expect(ReactNoop.getChildren()).toEqual([span('Count: 2')]); + act(() => { + ReactNoop.render(, () => + Scheduler.yieldValue('Sync effect'), + ); + expect(Scheduler).toFlushAndYieldThrough(['Count: 0', 'Sync effect']); + expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); + // Enqueuing this update forces the passive effect to be flushed -- + // updateCount(1) happens first, so 2 wins. + act(() => _updateCount(2)); + expect(Scheduler).toHaveYielded(['Will set count to 1']); + expect(Scheduler).toFlushAndYield(['Count: 2']); + expect(ReactNoop.getChildren()).toEqual([span('Count: 2')]); + }); }); }); }); diff --git a/packages/react-reconciler/src/__tests__/ReactIncrementalScheduling-test.internal.js b/packages/react-reconciler/src/__tests__/ReactIncrementalScheduling-test.internal.js index ddc62c60aa6fa..9064249f96dc4 100644 --- a/packages/react-reconciler/src/__tests__/ReactIncrementalScheduling-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactIncrementalScheduling-test.internal.js @@ -111,30 +111,30 @@ describe('ReactIncrementalScheduling', () => { expect(ReactNoop.getChildrenAsJSX('c')).toEqual('c:1'); // Schedule deferred work in the reverse order - ReactNoop.batchedUpdates(() => { + ReactNoop.act(() => { ReactNoop.renderToRootWithID(, 'c'); ReactNoop.renderToRootWithID(, 'b'); - }); - // Ensure it starts in the order it was scheduled - expect(Scheduler).toFlushAndYieldThrough(['c:2']); + // Ensure it starts in the order it was scheduled + expect(Scheduler).toFlushAndYieldThrough(['c:2']); + + expect(ReactNoop.getChildrenAsJSX('a')).toEqual('a:1'); + expect(ReactNoop.getChildrenAsJSX('b')).toEqual('b:1'); + expect(ReactNoop.getChildrenAsJSX('c')).toEqual('c:2'); + // Schedule last bit of work, it will get processed the last - expect(ReactNoop.getChildrenAsJSX('a')).toEqual('a:1'); - expect(ReactNoop.getChildrenAsJSX('b')).toEqual('b:1'); - expect(ReactNoop.getChildrenAsJSX('c')).toEqual('c:2'); - // Schedule last bit of work, it will get processed the last - ReactNoop.batchedUpdates(() => { ReactNoop.renderToRootWithID(, 'a'); - }); - // Keep performing work in the order it was scheduled - expect(Scheduler).toFlushAndYieldThrough(['b:2']); - expect(ReactNoop.getChildrenAsJSX('a')).toEqual('a:1'); - expect(ReactNoop.getChildrenAsJSX('b')).toEqual('b:2'); - expect(ReactNoop.getChildrenAsJSX('c')).toEqual('c:2'); - expect(Scheduler).toFlushAndYieldThrough(['a:2']); - expect(ReactNoop.getChildrenAsJSX('a')).toEqual('a:2'); - expect(ReactNoop.getChildrenAsJSX('b')).toEqual('b:2'); - expect(ReactNoop.getChildrenAsJSX('c')).toEqual('c:2'); + // Keep performing work in the order it was scheduled + expect(Scheduler).toFlushAndYieldThrough(['b:2']); + expect(ReactNoop.getChildrenAsJSX('a')).toEqual('a:1'); + expect(ReactNoop.getChildrenAsJSX('b')).toEqual('b:2'); + expect(ReactNoop.getChildrenAsJSX('c')).toEqual('c:2'); + + expect(Scheduler).toFlushAndYieldThrough(['a:2']); + expect(ReactNoop.getChildrenAsJSX('a')).toEqual('a:2'); + expect(ReactNoop.getChildrenAsJSX('b')).toEqual('b:2'); + expect(ReactNoop.getChildrenAsJSX('c')).toEqual('c:2'); + }); }); it('schedules sync updates when inside componentDidMount/Update', () => { diff --git a/packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.internal.js b/packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.internal.js index 26af1eb0014aa..8e46223f7fc3e 100644 --- a/packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.internal.js @@ -476,70 +476,74 @@ describe('ReactIncrementalUpdates', () => { // First, as a sanity check, assert what happens when four low pri // updates in separate batches are all flushed in the same callback - ReactNoop.render(); - Scheduler.advanceTime(1000); - expect(Scheduler).toFlushAndYieldThrough(['Render: ']); - interrupt(); - - ReactNoop.render(); - Scheduler.advanceTime(1000); - expect(Scheduler).toFlushAndYieldThrough(['Render: ']); - interrupt(); - - ReactNoop.render(); - Scheduler.advanceTime(1000); - expect(Scheduler).toFlushAndYieldThrough(['Render: ']); - interrupt(); - - ReactNoop.render(); - Scheduler.advanceTime(1000); - expect(Scheduler).toFlushAndYieldThrough(['Render: ']); - interrupt(); - - // Each update flushes in a separate commit. - // Note: This isn't necessarily the ideal behavior. It might be better to - // batch all of these updates together. The fact that they don't is an - // implementation detail. The important part of this unit test is what - // happens when they expire, in which case they really should be batched to - // avoid blocking the main thread for a long time. - expect(Scheduler).toFlushAndYield([ - 'Render: ', - 'Commit: ', - 'Render: he', - 'Commit: he', - 'Render: hell', - 'Commit: hell', - 'Render: hello', - 'Commit: hello', - ]); + ReactNoop.act(() => { + ReactNoop.render(); + Scheduler.advanceTime(1000); + expect(Scheduler).toFlushAndYieldThrough(['Render: ']); + interrupt(); + + ReactNoop.render(); + Scheduler.advanceTime(1000); + expect(Scheduler).toFlushAndYieldThrough(['Render: ']); + interrupt(); + + ReactNoop.render(); + Scheduler.advanceTime(1000); + expect(Scheduler).toFlushAndYieldThrough(['Render: ']); + interrupt(); + + ReactNoop.render(); + Scheduler.advanceTime(1000); + expect(Scheduler).toFlushAndYieldThrough(['Render: ']); + interrupt(); + + // Each update flushes in a separate commit. + // Note: This isn't necessarily the ideal behavior. It might be better to + // batch all of these updates together. The fact that they don't is an + // implementation detail. The important part of this unit test is what + // happens when they expire, in which case they really should be batched to + // avoid blocking the main thread for a long time. + expect(Scheduler).toFlushAndYield([ + 'Render: ', + 'Commit: ', + 'Render: he', + 'Commit: he', + 'Render: hell', + 'Commit: hell', + 'Render: hello', + 'Commit: hello', + ]); + }); - // Now do the same thing over again, but this time, expire all the updates - // instead of flushing them normally. - ReactNoop.render(); - Scheduler.advanceTime(1000); - expect(Scheduler).toFlushAndYieldThrough(['Render: ']); - interrupt(); - - ReactNoop.render(); - Scheduler.advanceTime(1000); - expect(Scheduler).toFlushAndYieldThrough(['Render: ']); - interrupt(); - - ReactNoop.render(); - Scheduler.advanceTime(1000); - expect(Scheduler).toFlushAndYieldThrough(['Render: ']); - interrupt(); - - ReactNoop.render(); - Scheduler.advanceTime(1000); - expect(Scheduler).toFlushAndYieldThrough(['Render: ']); - interrupt(); - - // All the updates should render and commit in a single batch. - Scheduler.advanceTime(10000); - expect(Scheduler).toHaveYielded(['Render: goodbye']); - // Passive effect - expect(Scheduler).toFlushAndYield(['Commit: goodbye']); + ReactNoop.act(() => { + // Now do the same thing over again, but this time, expire all the updates + // instead of flushing them normally. + ReactNoop.render(); + Scheduler.advanceTime(1000); + expect(Scheduler).toFlushAndYieldThrough(['Render: ']); + interrupt(); + + ReactNoop.render(); + Scheduler.advanceTime(1000); + expect(Scheduler).toFlushAndYieldThrough(['Render: ']); + interrupt(); + + ReactNoop.render(); + Scheduler.advanceTime(1000); + expect(Scheduler).toFlushAndYieldThrough(['Render: ']); + interrupt(); + + ReactNoop.render(); + Scheduler.advanceTime(1000); + expect(Scheduler).toFlushAndYieldThrough(['Render: ']); + interrupt(); + + // All the updates should render and commit in a single batch. + Scheduler.advanceTime(10000); + expect(Scheduler).toHaveYielded(['Render: goodbye']); + // Passive effect + expect(Scheduler).toFlushAndYield(['Commit: goodbye']); + }); }); it('flushes all expired updates in a single batch across multiple roots', () => { @@ -559,92 +563,95 @@ describe('ReactIncrementalUpdates', () => { ReactNoop.renderToRootWithID(null, 'other-root'); }); } + ReactNoop.act(() => { + // First, as a sanity check, assert what happens when four low pri + // updates in separate batches are all flushed in the same callback + ReactNoop.renderToRootWithID(, 'a'); + ReactNoop.renderToRootWithID(, 'b'); + Scheduler.advanceTime(1000); + expect(Scheduler).toFlushAndYieldThrough(['Render: ']); + interrupt(); + + ReactNoop.renderToRootWithID(, 'a'); + ReactNoop.renderToRootWithID(, 'b'); + Scheduler.advanceTime(1000); + expect(Scheduler).toFlushAndYieldThrough(['Render: ']); + interrupt(); + + ReactNoop.renderToRootWithID(, 'a'); + ReactNoop.renderToRootWithID(, 'b'); + Scheduler.advanceTime(1000); + expect(Scheduler).toFlushAndYieldThrough(['Render: ']); + interrupt(); + + ReactNoop.renderToRootWithID(, 'a'); + ReactNoop.renderToRootWithID(, 'b'); + Scheduler.advanceTime(1000); + expect(Scheduler).toFlushAndYieldThrough(['Render: ']); + interrupt(); + + // Each update flushes in a separate commit. + // Note: This isn't necessarily the ideal behavior. It might be better to + // batch all of these updates together. The fact that they don't is an + // implementation detail. The important part of this unit test is what + // happens when they expire, in which case they really should be batched to + // avoid blocking the main thread for a long time. + expect(Scheduler).toFlushAndYield([ + 'Render: ', + 'Commit: ', + 'Render: ', + 'Commit: ', + 'Render: he', + 'Commit: he', + 'Render: he', + 'Commit: he', + 'Render: hell', + 'Commit: hell', + 'Render: hell', + 'Commit: hell', + 'Render: hello', + 'Commit: hello', + 'Render: hello', + 'Commit: hello', + ]); + }); - // First, as a sanity check, assert what happens when four low pri - // updates in separate batches are all flushed in the same callback - ReactNoop.renderToRootWithID(, 'a'); - ReactNoop.renderToRootWithID(, 'b'); - Scheduler.advanceTime(1000); - expect(Scheduler).toFlushAndYieldThrough(['Render: ']); - interrupt(); - - ReactNoop.renderToRootWithID(, 'a'); - ReactNoop.renderToRootWithID(, 'b'); - Scheduler.advanceTime(1000); - expect(Scheduler).toFlushAndYieldThrough(['Render: ']); - interrupt(); - - ReactNoop.renderToRootWithID(, 'a'); - ReactNoop.renderToRootWithID(, 'b'); - Scheduler.advanceTime(1000); - expect(Scheduler).toFlushAndYieldThrough(['Render: ']); - interrupt(); - - ReactNoop.renderToRootWithID(, 'a'); - ReactNoop.renderToRootWithID(, 'b'); - Scheduler.advanceTime(1000); - expect(Scheduler).toFlushAndYieldThrough(['Render: ']); - interrupt(); - - // Each update flushes in a separate commit. - // Note: This isn't necessarily the ideal behavior. It might be better to - // batch all of these updates together. The fact that they don't is an - // implementation detail. The important part of this unit test is what - // happens when they expire, in which case they really should be batched to - // avoid blocking the main thread for a long time. - expect(Scheduler).toFlushAndYield([ - 'Render: ', - 'Commit: ', - 'Render: ', - 'Commit: ', - 'Render: he', - 'Commit: he', - 'Render: he', - 'Commit: he', - 'Render: hell', - 'Commit: hell', - 'Render: hell', - 'Commit: hell', - 'Render: hello', - 'Commit: hello', - 'Render: hello', - 'Commit: hello', - ]); - - // Now do the same thing over again, but this time, expire all the updates - // instead of flushing them normally. - ReactNoop.renderToRootWithID(, 'a'); - ReactNoop.renderToRootWithID(, 'b'); - Scheduler.advanceTime(1000); - expect(Scheduler).toFlushAndYieldThrough(['Render: ']); - interrupt(); - - ReactNoop.renderToRootWithID(, 'a'); - ReactNoop.renderToRootWithID(, 'b'); - Scheduler.advanceTime(1000); - expect(Scheduler).toFlushAndYieldThrough(['Render: ']); - interrupt(); - - ReactNoop.renderToRootWithID(, 'a'); - ReactNoop.renderToRootWithID(, 'b'); - Scheduler.advanceTime(1000); - expect(Scheduler).toFlushAndYieldThrough(['Render: ']); - interrupt(); - - ReactNoop.renderToRootWithID(, 'a'); - ReactNoop.renderToRootWithID(, 'b'); - Scheduler.advanceTime(1000); - expect(Scheduler).toFlushAndYieldThrough(['Render: ']); - interrupt(); - - // All the updates should render and commit in a single batch. - Scheduler.advanceTime(10000); - expect(Scheduler).toHaveYielded([ - 'Render: goodbye', - 'Commit: goodbye', - 'Render: goodbye', - ]); - // Passive effect - expect(Scheduler).toFlushAndYield(['Commit: goodbye']); + ReactNoop.act(() => { + // Now do the same thing over again, but this time, expire all the updates + // instead of flushing them normally. + ReactNoop.renderToRootWithID(, 'a'); + ReactNoop.renderToRootWithID(, 'b'); + Scheduler.advanceTime(1000); + expect(Scheduler).toFlushAndYieldThrough(['Render: ']); + interrupt(); + + ReactNoop.renderToRootWithID(, 'a'); + ReactNoop.renderToRootWithID(, 'b'); + Scheduler.advanceTime(1000); + expect(Scheduler).toFlushAndYieldThrough(['Render: ']); + interrupt(); + + ReactNoop.renderToRootWithID(, 'a'); + ReactNoop.renderToRootWithID(, 'b'); + Scheduler.advanceTime(1000); + expect(Scheduler).toFlushAndYieldThrough(['Render: ']); + interrupt(); + + ReactNoop.renderToRootWithID(, 'a'); + ReactNoop.renderToRootWithID(, 'b'); + Scheduler.advanceTime(1000); + expect(Scheduler).toFlushAndYieldThrough(['Render: ']); + interrupt(); + + // All the updates should render and commit in a single batch. + Scheduler.advanceTime(10000); + expect(Scheduler).toHaveYielded([ + 'Render: goodbye', + 'Commit: goodbye', + 'Render: goodbye', + ]); + // Passive effect + expect(Scheduler).toFlushAndYield(['Commit: goodbye']); + }); }); }); diff --git a/packages/react-reconciler/src/__tests__/ReactSchedulerIntegration-test.internal.js b/packages/react-reconciler/src/__tests__/ReactSchedulerIntegration-test.internal.js index dec4bfbfc57b1..7f123fbb66238 100644 --- a/packages/react-reconciler/src/__tests__/ReactSchedulerIntegration-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactSchedulerIntegration-test.internal.js @@ -133,21 +133,22 @@ describe('ReactSchedulerIntegration', () => { }); return null; } + ReactNoop.act(() => { + ReactNoop.render(); + expect(Scheduler).toFlushAndYield([ + 'Render priority: Normal', + 'Passive priority: Normal', + ]); - ReactNoop.render(); - expect(Scheduler).toFlushAndYield([ - 'Render priority: Normal', - 'Passive priority: Normal', - ]); + runWithPriority(UserBlockingPriority, () => { + ReactNoop.render(); + }); - runWithPriority(UserBlockingPriority, () => { - ReactNoop.render(); + expect(Scheduler).toFlushAndYield([ + 'Render priority: UserBlocking', + 'Passive priority: UserBlocking', + ]); }); - - expect(Scheduler).toFlushAndYield([ - 'Render priority: UserBlocking', - 'Passive priority: UserBlocking', - ]); }); it('after completing a level of work, infers priority of the next batch based on its expiration time', () => { diff --git a/packages/react-refresh/src/__tests__/ReactFresh-test.js b/packages/react-refresh/src/__tests__/ReactFresh-test.js index f56a3bd5bf03e..790a418013b6b 100644 --- a/packages/react-refresh/src/__tests__/ReactFresh-test.js +++ b/packages/react-refresh/src/__tests__/ReactFresh-test.js @@ -2676,14 +2676,16 @@ describe('ReactFresh', () => { expect(container.firstChild.nextSibling.nextSibling).toBe(secondP); // Perform a hot update that fixes the error. - patch(() => { - function Hello() { - const [x] = React.useState(''); - React.useEffect(() => {}, []); // Removes the bad effect code. - x.slice(); // Doesn't throw initially. - return

Fixed!

; - } - $RefreshReg$(Hello, 'Hello'); + act(() => { + patch(() => { + function Hello() { + const [x] = React.useState(''); + React.useEffect(() => {}, []); // Removes the bad effect code. + x.slice(); // Doesn't throw initially. + return

Fixed!

; + } + $RefreshReg$(Hello, 'Hello'); + }); }); // This should remount the error boundary (but not anything above it). @@ -2693,15 +2695,18 @@ describe('ReactFresh', () => { // Verify next hot reload doesn't remount anything. const helloNode = container.firstChild.nextSibling; - patch(() => { - function Hello() { - const [x] = React.useState(''); - React.useEffect(() => {}, []); - x.slice(); - return

Nice.

; - } - $RefreshReg$(Hello, 'Hello'); + act(() => { + patch(() => { + function Hello() { + const [x] = React.useState(''); + React.useEffect(() => {}, []); + x.slice(); + return

Nice.

; + } + $RefreshReg$(Hello, 'Hello'); + }); }); + expect(container.firstChild.nextSibling).toBe(helloNode); expect(helloNode.textContent).toBe('Nice.'); } diff --git a/packages/react/src/__tests__/ReactDOMTracing-test.internal.js b/packages/react/src/__tests__/ReactDOMTracing-test.internal.js index 10442f72aeff3..3e90fb3c50342 100644 --- a/packages/react/src/__tests__/ReactDOMTracing-test.internal.js +++ b/packages/react/src/__tests__/ReactDOMTracing-test.internal.js @@ -104,34 +104,32 @@ describe('ReactDOMTracing', () => { const root = ReactDOM.unstable_createRoot(container); SchedulerTracing.unstable_trace('initialization', 0, () => { interaction = Array.from(SchedulerTracing.unstable_getCurrent())[0]; + TestUtils.act(() => { + root.render( + + + , + ); + expect(onInteractionTraced).toHaveBeenCalledTimes(1); + expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction( + interaction, + ); + expect(Scheduler).toFlushAndYieldThrough(['App', 'App:mount']); + expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); + expect(onRender).toHaveBeenCalledTimes(1); + expect(onRender).toHaveLastRenderedWithInteractions( + new Set([interaction]), + ); + expect(Scheduler).toFlushAndYieldThrough(['Child', 'Child:mount']); + expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); + expect(onRender).toHaveBeenCalledTimes(2); + expect(onRender).toHaveLastRenderedWithInteractions( + new Set([interaction]), + ); - root.render( - - - , - ); + expect(Scheduler).toFlushAndYield(['Child', 'Child:update']); + }); }); - - expect(onInteractionTraced).toHaveBeenCalledTimes(1); - expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction( - interaction, - ); - - expect(Scheduler).toFlushAndYieldThrough(['App', 'App:mount']); - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - expect(onRender).toHaveBeenCalledTimes(1); - expect(onRender).toHaveLastRenderedWithInteractions( - new Set([interaction]), - ); - - expect(Scheduler).toFlushAndYieldThrough(['Child', 'Child:mount']); - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - expect(onRender).toHaveBeenCalledTimes(2); - expect(onRender).toHaveLastRenderedWithInteractions( - new Set([interaction]), - ); - - expect(Scheduler).toFlushAndYield(['Child', 'Child:update']); expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); expect( onInteractionScheduledWorkCompleted, @@ -172,33 +170,34 @@ describe('ReactDOMTracing', () => { SchedulerTracing.unstable_trace('initialization', 0, () => { interaction = Array.from(SchedulerTracing.unstable_getCurrent())[0]; - root.render( - - - , - ); - }); - - expect(onInteractionTraced).toHaveBeenCalledTimes(1); - expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction( - interaction, - ); + TestUtils.act(() => { + root.render( + + + , + ); + expect(onInteractionTraced).toHaveBeenCalledTimes(1); + expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction( + interaction, + ); - expect(Scheduler).toFlushAndYieldThrough(['App', 'App:mount']); - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - expect(onRender).toHaveBeenCalledTimes(1); - expect(onRender).toHaveLastRenderedWithInteractions( - new Set([interaction]), - ); + expect(Scheduler).toFlushAndYieldThrough(['App', 'App:mount']); + expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); + expect(onRender).toHaveBeenCalledTimes(1); + expect(onRender).toHaveLastRenderedWithInteractions( + new Set([interaction]), + ); - expect(wrapped).not.toBeNull(); + expect(wrapped).not.toBeNull(); - expect(Scheduler).toFlushAndYield(['Child']); - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - expect(onRender).toHaveBeenCalledTimes(2); - expect(onRender).toHaveLastRenderedWithInteractions( - new Set([interaction]), - ); + expect(Scheduler).toFlushAndYield(['Child']); + expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); + expect(onRender).toHaveBeenCalledTimes(2); + expect(onRender).toHaveLastRenderedWithInteractions( + new Set([interaction]), + ); + }); + }); wrapped(); expect(onInteractionTraced).toHaveBeenCalledTimes(1); @@ -249,34 +248,35 @@ describe('ReactDOMTracing', () => { const root = ReactDOM.unstable_createRoot(container); SchedulerTracing.unstable_trace('initialization', 0, () => { interaction = Array.from(SchedulerTracing.unstable_getCurrent())[0]; + TestUtils.act(() => { + root.render( + + + , + ); + expect(onInteractionTraced).toHaveBeenCalledTimes(1); + expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction( + interaction, + ); - root.render( - - - , - ); - }); - - expect(onInteractionTraced).toHaveBeenCalledTimes(1); - expect(onInteractionTraced).toHaveBeenLastNotifiedOfInteraction( - interaction, - ); + expect(Scheduler).toFlushAndYieldThrough(['App', 'App:mount']); + expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); + expect(onRender).toHaveBeenCalledTimes(1); + expect(onRender).toHaveLastRenderedWithInteractions( + new Set([interaction]), + ); - expect(Scheduler).toFlushAndYieldThrough(['App', 'App:mount']); - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - expect(onRender).toHaveBeenCalledTimes(1); - expect(onRender).toHaveLastRenderedWithInteractions( - new Set([interaction]), - ); + expect(Scheduler).toFlushAndYieldThrough(['Child', 'Child:mount']); + expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); + expect(onRender).toHaveBeenCalledTimes(2); + expect(onRender).toHaveLastRenderedWithInteractions( + new Set([interaction]), + ); - expect(Scheduler).toFlushAndYieldThrough(['Child', 'Child:mount']); - expect(onInteractionScheduledWorkCompleted).not.toHaveBeenCalled(); - expect(onRender).toHaveBeenCalledTimes(2); - expect(onRender).toHaveLastRenderedWithInteractions( - new Set([interaction]), - ); + expect(Scheduler).toFlushAndYield(['Child', 'Child:update']); + }); + }); - expect(Scheduler).toFlushAndYield(['Child', 'Child:update']); expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1); expect( onInteractionScheduledWorkCompleted, diff --git a/packages/react/src/__tests__/withComponentStack-test.js b/packages/react/src/__tests__/withComponentStack-test.js index e46d7f2e54997..3f9a709816e85 100644 --- a/packages/react/src/__tests__/withComponentStack-test.js +++ b/packages/react/src/__tests__/withComponentStack-test.js @@ -44,16 +44,13 @@ describe('withComponentStack', () => { let React = null; let ReactTestRenderer = null; let error = null; - let scheduler = null; let warn = null; beforeEach(() => { jest.resetModules(); - jest.mock('scheduler', () => require('scheduler/unstable_mock')); React = require('react'); ReactTestRenderer = require('react-test-renderer'); - scheduler = require('scheduler'); error = React.error; warn = React.warn; @@ -178,10 +175,9 @@ describe('withComponentStack', () => { }); return null; } - - ReactTestRenderer.create(); - - scheduler.flushAll(); // Flush passive effects + ReactTestRenderer.act(() => { + ReactTestRenderer.create(); + }); expectMessageAndStack( 'logged in child render method',