diff --git a/packages/react-dom/src/client/ReactDOM.js b/packages/react-dom/src/client/ReactDOM.js index 7355e0be45a3c..eade4ea474c77 100644 --- a/packages/react-dom/src/client/ReactDOM.js +++ b/packages/react-dom/src/client/ReactDOM.js @@ -28,8 +28,8 @@ import { flushSync, flushControlled, injectIntoDevTools, - flushPassiveEffects, IsThisRendererActing, + act, attemptSynchronousHydration, attemptDiscreteHydration, attemptContinuousHydration, @@ -155,7 +155,7 @@ function renderSubtreeIntoContainer( } const Internals = { - // Keep in sync with ReactTestUtils.js, and ReactTestUtilsAct.js. + // Keep in sync with ReactTestUtils.js. // This is an array for better minification. Events: [ getInstanceFromNode, @@ -163,10 +163,10 @@ const Internals = { getFiberCurrentPropsFromNode, enqueueStateRestore, restoreStateIfNeeded, - flushPassiveEffects, - // TODO: This is related to `act`, not events. Move to separate key? - IsThisRendererActing, ], + act, + // TODO: Temporary. Only used by our internal version of `act. Will remove. + IsThisRendererActing, }; export { diff --git a/packages/react-dom/src/test-utils/ReactTestUtils.js b/packages/react-dom/src/test-utils/ReactTestUtils.js index 492ab02d11929..69c1a6afcff3a 100644 --- a/packages/react-dom/src/test-utils/ReactTestUtils.js +++ b/packages/react-dom/src/test-utils/ReactTestUtils.js @@ -18,7 +18,6 @@ import { import {SyntheticEvent} from '../events/SyntheticEvent'; import invariant from 'shared/invariant'; import {ELEMENT_NODE} from '../shared/HTMLNodeType'; -import {act} from './ReactTestUtilsPublicAct'; import {unstable_concurrentAct} from './ReactTestUtilsInternalAct'; import { rethrowCaughtError, @@ -26,17 +25,17 @@ import { } from 'shared/ReactErrorUtils'; import isArray from 'shared/isArray'; -// Keep in sync with ReactDOM.js, and ReactTestUtilsAct.js: -const EventInternals = - ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Events; +// Keep in sync with ReactDOM.js: +const SecretInternals = + ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; +const EventInternals = SecretInternals.Events; const getInstanceFromNode = EventInternals[0]; const getNodeFromInstance = EventInternals[1]; const getFiberCurrentPropsFromNode = EventInternals[2]; const enqueueStateRestore = EventInternals[3]; const restoreStateIfNeeded = EventInternals[4]; -// const flushPassiveEffects = EventInternals[5]; -// TODO: This is related to `act`, not events. Move to separate key? -// const IsThisRendererActing = EventInternals[6]; + +const act = SecretInternals.act; function Event(suffix) {} diff --git a/packages/react-dom/src/test-utils/ReactTestUtilsInternalAct.js b/packages/react-dom/src/test-utils/ReactTestUtilsInternalAct.js index 77fd1779ee56a..3adc786eed5fa 100644 --- a/packages/react-dom/src/test-utils/ReactTestUtilsInternalAct.js +++ b/packages/react-dom/src/test-utils/ReactTestUtilsInternalAct.js @@ -14,16 +14,9 @@ import ReactSharedInternals from 'shared/ReactSharedInternals'; import enqueueTask from 'shared/enqueueTask'; import * as Scheduler from 'scheduler'; -// Keep in sync with ReactDOM.js, and ReactTestUtils.js: -const EventInternals = - ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Events; -// const getInstanceFromNode = EventInternals[0]; -// const getNodeFromInstance = EventInternals[1]; -// const getFiberCurrentPropsFromNode = EventInternals[2]; -// const enqueueStateRestore = EventInternals[3]; -// const restoreStateIfNeeded = EventInternals[4]; -// const flushPassiveEffects = EventInternals[5]; -const IsThisRendererActing = EventInternals[6]; +const SecretInternals = + ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; +const IsThisRendererActing = SecretInternals.IsThisRendererActing; const batchedUpdates = ReactDOM.unstable_batchedUpdates; diff --git a/packages/react-dom/src/test-utils/ReactTestUtilsPublicAct.js b/packages/react-dom/src/test-utils/ReactTestUtilsPublicAct.js deleted file mode 100644 index 8f02e4b5f323c..0000000000000 --- a/packages/react-dom/src/test-utils/ReactTestUtilsPublicAct.js +++ /dev/null @@ -1,214 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow - */ - -import type {Thenable} from 'shared/ReactTypes'; - -import * as ReactDOM from 'react-dom'; -import ReactSharedInternals from 'shared/ReactSharedInternals'; -import enqueueTask from 'shared/enqueueTask'; -import * as Scheduler from 'scheduler'; -import invariant from 'shared/invariant'; - -// Keep in sync with ReactDOM.js, and ReactTestUtils.js: -const EventInternals = - ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Events; -// const getInstanceFromNode = EventInternals[0]; -// const getNodeFromInstance = EventInternals[1]; -// const getFiberCurrentPropsFromNode = EventInternals[2]; -// const enqueueStateRestore = EventInternals[3]; -// const restoreStateIfNeeded = EventInternals[4]; -const flushPassiveEffects = EventInternals[5]; -const IsThisRendererActing = EventInternals[6]; - -const batchedUpdates = ReactDOM.unstable_batchedUpdates; - -const {IsSomeRendererActing} = ReactSharedInternals; - -// This is the public version of `ReactTestUtils.act`. It is implemented in -// "userspace" (i.e. not the reconciler), so that it doesn't add to the -// production bundle size. -// TODO: Remove this implementation of `act` in favor of the one exported by -// the reconciler. To do this, we must first drop support for `act` in -// production mode. - -// TODO: Remove support for the mock scheduler build, which was only added for -// the purposes of internal testing. Internal tests should use -// `unstable_concurrentAct` instead. -const isSchedulerMocked = - typeof Scheduler.unstable_flushAllWithoutAsserting === 'function'; -const flushWork = - Scheduler.unstable_flushAllWithoutAsserting || - function() { - let didFlushWork = false; - while (flushPassiveEffects()) { - didFlushWork = true; - } - - return didFlushWork; - }; - -function flushWorkAndMicroTasks(onDone: (err: ?Error) => void) { - try { - flushWork(); - enqueueTask(() => { - if (flushWork()) { - flushWorkAndMicroTasks(onDone); - } else { - onDone(); - } - }); - } catch (err) { - onDone(err); - } -} - -// we track the 'depth' of the act() calls with this counter, -// so we can tell if any async act() calls try to run in parallel. - -let actingUpdatesScopeDepth = 0; - -export function act(callback: () => Thenable): Thenable { - if (!__DEV__) { - invariant( - false, - 'act(...) is not supported in production builds of React.', - ); - } - const previousActingUpdatesScopeDepth = actingUpdatesScopeDepth; - actingUpdatesScopeDepth++; - - const previousIsSomeRendererActing = IsSomeRendererActing.current; - const previousIsThisRendererActing = IsThisRendererActing.current; - IsSomeRendererActing.current = true; - IsThisRendererActing.current = true; - - function onDone() { - actingUpdatesScopeDepth--; - IsSomeRendererActing.current = previousIsSomeRendererActing; - IsThisRendererActing.current = previousIsThisRendererActing; - if (__DEV__) { - if (actingUpdatesScopeDepth > previousActingUpdatesScopeDepth) { - // if it's _less than_ previousActingUpdatesScopeDepth, then we can assume the 'other' one has warned - console.error( - 'You seem to have overlapping act() calls, this is not supported. ' + - 'Be sure to await previous act() calls before making a new one. ', - ); - } - } - } - - let result; - try { - result = batchedUpdates(callback); - } catch (error) { - // on sync errors, we still want to 'cleanup' and decrement actingUpdatesScopeDepth - onDone(); - throw error; - } - - if ( - result !== null && - typeof result === 'object' && - typeof result.then === 'function' - ) { - // setup a boolean that gets set to true only - // once this act() call is await-ed - let called = false; - if (__DEV__) { - if (typeof Promise !== 'undefined') { - //eslint-disable-next-line no-undef - Promise.resolve() - .then(() => {}) - .then(() => { - if (called === false) { - console.error( - 'You called act(async () => ...) without await. ' + - 'This could lead to unexpected testing behaviour, interleaving multiple act ' + - 'calls and mixing their scopes. You should - await act(async () => ...);', - ); - } - }); - } - } - - // in the async case, the returned thenable runs the callback, flushes - // effects and microtasks in a loop until flushPassiveEffects() === false, - // and cleans up - return { - then(resolve, reject) { - called = true; - result.then( - () => { - if ( - actingUpdatesScopeDepth > 1 || - (isSchedulerMocked === true && - previousIsSomeRendererActing === true) - ) { - onDone(); - resolve(); - return; - } - // we're about to exit the act() scope, - // now's the time to flush tasks/effects - flushWorkAndMicroTasks((err: ?Error) => { - onDone(); - if (err) { - reject(err); - } else { - resolve(); - } - }); - }, - err => { - onDone(); - reject(err); - }, - ); - }, - }; - } else { - if (__DEV__) { - if (result !== undefined) { - console.error( - 'The callback passed to act(...) function ' + - 'must return undefined, or a Promise. You returned %s', - result, - ); - } - } - - // flush effects until none remain, and cleanup - try { - if ( - actingUpdatesScopeDepth === 1 && - (isSchedulerMocked === false || previousIsSomeRendererActing === false) - ) { - // we're about to exit the act() scope, - // now's the time to flush effects - flushWork(); - } - onDone(); - } catch (err) { - onDone(); - throw err; - } - - // in the sync case, the returned thenable only warns *if* await-ed - return { - then(resolve) { - if (__DEV__) { - console.error( - 'Do not await the result of calling act(...) with sync logic, it is not a Promise.', - ); - } - resolve(); - }, - }; - } -}