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;