diff --git a/packages/react-devtools-scheduling-profiler/src/import-worker/__tests__/preprocessData-test.internal.js b/packages/react-devtools-scheduling-profiler/src/import-worker/__tests__/preprocessData-test.internal.js index e24611e9daea5..e458f8060f84b 100644 --- a/packages/react-devtools-scheduling-profiler/src/import-worker/__tests__/preprocessData-test.internal.js +++ b/packages/react-devtools-scheduling-profiler/src/import-worker/__tests__/preprocessData-test.internal.js @@ -17,6 +17,8 @@ import { } from '../../constants'; import REACT_VERSION from 'shared/ReactVersion'; +global.IS_REACT_ACT_ENVIRONMENT = true; + describe('getLanesFromTransportDecimalBitmask', () => { it('should return array of lane numbers from bitmask string', () => { expect(getLanesFromTransportDecimalBitmask('1')).toEqual([0]); diff --git a/packages/react-devtools-shared/src/__tests__/inspectedElement-test.js b/packages/react-devtools-shared/src/__tests__/inspectedElement-test.js index 68d014de3ad47..be5a852396538 100644 --- a/packages/react-devtools-shared/src/__tests__/inspectedElement-test.js +++ b/packages/react-devtools-shared/src/__tests__/inspectedElement-test.js @@ -38,6 +38,8 @@ describe('InspectedElement', () => { let ErrorBoundary; let errorBoundaryInstance; + global.IS_REACT_ACT_ENVIRONMENT = true; + beforeEach(() => { utils = require('./utils'); utils.beforeEachProfiling(); diff --git a/packages/react-reconciler/src/ReactFiberAct.new.js b/packages/react-reconciler/src/ReactFiberAct.new.js index 893e5cb5189a6..18055e7c738c7 100644 --- a/packages/react-reconciler/src/ReactFiberAct.new.js +++ b/packages/react-reconciler/src/ReactFiberAct.new.js @@ -8,9 +8,14 @@ */ import type {Fiber} from './ReactFiber.new'; + +import ReactSharedInternals from 'shared/ReactSharedInternals'; + import {warnsIfNotActing} from './ReactFiberHostConfig'; import {ConcurrentMode} from './ReactTypeOfMode'; +const {ReactCurrentActQueue} = ReactSharedInternals; + export function isActEnvironment(fiber: Fiber) { if (__DEV__) { const isReactActEnvironmentGlobal = @@ -20,6 +25,16 @@ export function isActEnvironment(fiber: Fiber) { : undefined; if (fiber.mode & ConcurrentMode) { + if ( + !isReactActEnvironmentGlobal && + ReactCurrentActQueue.current !== null + ) { + // TODO: Include link to relevant documentation page. + console.error( + 'The current testing environment is not configured to support ' + + 'act(...)', + ); + } return isReactActEnvironmentGlobal; } else { // Legacy mode. We preserve the behavior of React 17's act. It assumes an diff --git a/packages/react-reconciler/src/ReactFiberAct.old.js b/packages/react-reconciler/src/ReactFiberAct.old.js index fb404627b5462..ddae518dcb631 100644 --- a/packages/react-reconciler/src/ReactFiberAct.old.js +++ b/packages/react-reconciler/src/ReactFiberAct.old.js @@ -8,9 +8,14 @@ */ import type {Fiber} from './ReactFiber.old'; + +import ReactSharedInternals from 'shared/ReactSharedInternals'; + import {warnsIfNotActing} from './ReactFiberHostConfig'; import {ConcurrentMode} from './ReactTypeOfMode'; +const {ReactCurrentActQueue} = ReactSharedInternals; + export function isActEnvironment(fiber: Fiber) { if (__DEV__) { const isReactActEnvironmentGlobal = @@ -20,6 +25,16 @@ export function isActEnvironment(fiber: Fiber) { : undefined; if (fiber.mode & ConcurrentMode) { + if ( + !isReactActEnvironmentGlobal && + ReactCurrentActQueue.current !== null + ) { + // TODO: Include link to relevant documentation page. + console.error( + 'The current testing environment is not configured to support ' + + 'act(...)', + ); + } return isReactActEnvironmentGlobal; } else { // Legacy mode. We preserve the behavior of React 17's act. It assumes an diff --git a/packages/react-reconciler/src/__tests__/DebugTracing-test.internal.js b/packages/react-reconciler/src/__tests__/DebugTracing-test.internal.js index 94431b435054e..ee93b3f992994 100644 --- a/packages/react-reconciler/src/__tests__/DebugTracing-test.internal.js +++ b/packages/react-reconciler/src/__tests__/DebugTracing-test.internal.js @@ -19,6 +19,8 @@ describe('DebugTracing', () => { const DEFAULT_LANE_STRING = '0b0000000000000000000000000010000'; const RETRY_LANE_STRING = '0b0000000010000000000000000000000'; + global.IS_REACT_ACT_ENVIRONMENT = true; + beforeEach(() => { jest.resetModules(); diff --git a/packages/react-reconciler/src/__tests__/ReactActWarnings-test.js b/packages/react-reconciler/src/__tests__/ReactActWarnings-test.js index 77cfff3e63f0f..058e01b1fe060 100644 --- a/packages/react-reconciler/src/__tests__/ReactActWarnings-test.js +++ b/packages/react-reconciler/src/__tests__/ReactActWarnings-test.js @@ -11,15 +11,18 @@ let React; let Scheduler; let ReactNoop; let useState; +let act; // These tests are mostly concerned with concurrent roots. The legacy root // behavior is covered by other older test suites and is unchanged from // React 17. describe('act warnings', () => { beforeEach(() => { + jest.resetModules(); React = require('react'); Scheduler = require('scheduler'); ReactNoop = require('react-noop-renderer'); + act = React.unstable_act; useState = React.useState; }); @@ -73,4 +76,55 @@ describe('act warnings', () => { expect(root).toMatchRenderedOutput('3'); }); }); + + // @gate __DEV__ + test('act warns if the environment flag is not enabled', () => { + let setState; + function App() { + const [state, _setState] = useState(0); + setState = _setState; + return ; + } + + const root = ReactNoop.createRoot(); + root.render(); + expect(Scheduler).toFlushAndYield([0]); + expect(root).toMatchRenderedOutput('0'); + + // Default behavior. Flag is undefined. Warn. + expect(global.IS_REACT_ACT_ENVIRONMENT).toBe(undefined); + expect(() => { + act(() => { + setState(1); + }); + }).toErrorDev( + 'The current testing environment is not configured to support act(...)', + {withoutStack: true}, + ); + expect(Scheduler).toHaveYielded([1]); + expect(root).toMatchRenderedOutput('1'); + + // Flag is true. Don't warn. + withActEnvironment(true, () => { + act(() => { + setState(2); + }); + expect(Scheduler).toHaveYielded([2]); + expect(root).toMatchRenderedOutput('2'); + }); + + // Flag is false. Warn. + withActEnvironment(false, () => { + expect(() => { + act(() => { + setState(1); + }); + }).toErrorDev( + 'The current testing environment is not configured to support act(...)', + {withoutStack: true}, + ); + expect(Scheduler).toHaveYielded([1]); + expect(root).toMatchRenderedOutput('1'); + }); + }); }); diff --git a/packages/react-reconciler/src/__tests__/SchedulingProfilerLabels-test.internal.js b/packages/react-reconciler/src/__tests__/SchedulingProfilerLabels-test.internal.js index 09fdb95b01e6c..13ca3988b2a71 100644 --- a/packages/react-reconciler/src/__tests__/SchedulingProfilerLabels-test.internal.js +++ b/packages/react-reconciler/src/__tests__/SchedulingProfilerLabels-test.internal.js @@ -21,6 +21,8 @@ describe('SchedulingProfiler labels', () => { let featureDetectionMarkName = null; let marks; + global.IS_REACT_ACT_ENVIRONMENT = true; + function polyfillJSDomUserTiming() { featureDetectionMarkName = null;