diff --git a/fixtures/dom/src/__tests__/nested-act-test.js b/fixtures/dom/src/__tests__/nested-act-test.js
new file mode 100644
index 0000000000000..a0500f47e9603
--- /dev/null
+++ b/fixtures/dom/src/__tests__/nested-act-test.js
@@ -0,0 +1,97 @@
+/**
+ * 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.
+ *
+ * @emails react-core
+ */
+
+let React;
+let TestUtils;
+let TestRenderer;
+
+global.__DEV__ = process.env.NODE_ENV !== 'production';
+
+expect.extend(require('../toWarnDev'));
+
+describe('unmocked scheduler', () => {
+ beforeEach(() => {
+ jest.resetModules();
+ React = require('react');
+ TestUtils = require('react-dom/test-utils');
+ TestRenderer = require('react-test-renderer');
+ });
+
+ it('flushes work only outside the outermost act() corresponding to its own renderer', () => {
+ let log = [];
+ function Effecty() {
+ React.useEffect(() => {
+ log.push('called');
+ }, []);
+ return null;
+ }
+ // in legacy mode, this tests whether an act only flushes its own effects
+ TestRenderer.act(() => {
+ TestUtils.act(() => {
+ TestRenderer.create();
+ });
+ expect(log).toEqual([]);
+ });
+ expect(log).toEqual(['called']);
+
+ log = [];
+ // for doublechecking, we flip it inside out, and assert on the outermost
+ TestUtils.act(() => {
+ TestRenderer.act(() => {
+ TestRenderer.create();
+ });
+ expect(log).toEqual(['called']);
+ });
+ expect(log).toEqual(['called']);
+ });
+});
+
+describe('mocked scheduler', () => {
+ beforeEach(() => {
+ jest.resetModules();
+ jest.mock('scheduler', () =>
+ require.requireActual('scheduler/unstable_mock')
+ );
+ React = require('react');
+ TestUtils = require('react-dom/test-utils');
+ TestRenderer = require('react-test-renderer');
+ });
+
+ afterEach(() => {
+ jest.unmock('scheduler');
+ });
+
+ it('flushes work only outside the outermost act()', () => {
+ let log = [];
+ function Effecty() {
+ React.useEffect(() => {
+ log.push('called');
+ }, []);
+ return null;
+ }
+ // with a mocked scheduler, this tests whether it flushes all work only on the outermost act
+ TestRenderer.act(() => {
+ TestUtils.act(() => {
+ TestRenderer.create();
+ });
+ expect(log).toEqual([]);
+ });
+ expect(log).toEqual(['called']);
+
+ log = [];
+ // for doublechecking, we flip it inside out, and assert on the outermost
+ TestUtils.act(() => {
+ TestRenderer.act(() => {
+ TestRenderer.create();
+ });
+ expect(log).toEqual([]);
+ });
+ expect(log).toEqual(['called']);
+ });
+});
diff --git a/fixtures/dom/src/__tests__/wrong-act-test.js b/fixtures/dom/src/__tests__/wrong-act-test.js
new file mode 100644
index 0000000000000..05dda494d1126
--- /dev/null
+++ b/fixtures/dom/src/__tests__/wrong-act-test.js
@@ -0,0 +1,194 @@
+/**
+ * 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.
+ *
+ * @emails react-core
+ */
+
+let React;
+let ReactDOM;
+let ReactART;
+let ARTSVGMode;
+let ARTCurrentMode;
+let TestUtils;
+let TestRenderer;
+let ARTTest;
+
+global.__DEV__ = process.env.NODE_ENV !== 'production';
+
+expect.extend(require('../toWarnDev'));
+
+function App(props) {
+ return 'hello world';
+}
+
+beforeEach(() => {
+ jest.resetModules();
+ React = require('react');
+ ReactDOM = require('react-dom');
+ ReactART = require('react-art');
+ ARTSVGMode = require('art/modes/svg');
+ ARTCurrentMode = require('art/modes/current');
+ TestUtils = require('react-dom/test-utils');
+ TestRenderer = require('react-test-renderer');
+
+ ARTCurrentMode.setCurrent(ARTSVGMode);
+
+ ARTTest = function ARTTestComponent(props) {
+ return (
+
+
+
+
+ M64.564,38.583H54l0.008-5.834c0-3.035,0.293-4.666,4.657-4.666
+ h5.833V16.429h-9.33c-11.213,0-15.159,5.654-15.159,15.16v6.994
+ h-6.99v11.652h6.99v33.815H54V50.235h9.331L64.564,38.583z
+
+
+
+ );
+ };
+});
+
+it("doesn't warn when you use the right act + renderer: dom", () => {
+ TestUtils.act(() => {
+ TestUtils.renderIntoDocument();
+ });
+});
+
+it("doesn't warn when you use the right act + renderer: test", () => {
+ TestRenderer.act(() => {
+ TestRenderer.create();
+ });
+});
+
+it('resets correctly across renderers', () => {
+ function Effecty() {
+ React.useEffect(() => {}, []);
+ return null;
+ }
+ TestUtils.act(() => {
+ TestRenderer.act(() => {});
+ expect(() => {
+ TestRenderer.create();
+ }).toWarnDev(["It looks like you're using the wrong act()"], {
+ withoutStack: true,
+ });
+ });
+});
+
+it('warns when using the wrong act version - test + dom: render', () => {
+ expect(() => {
+ TestRenderer.act(() => {
+ TestUtils.renderIntoDocument();
+ });
+ }).toWarnDev(["It looks like you're using the wrong act()"], {
+ withoutStack: true,
+ });
+});
+
+it('warns when using the wrong act version - test + dom: updates', () => {
+ let setCtr;
+ function Counter(props) {
+ const [ctr, _setCtr] = React.useState(0);
+ setCtr = _setCtr;
+ return ctr;
+ }
+ TestUtils.renderIntoDocument();
+ expect(() => {
+ TestRenderer.act(() => {
+ setCtr(1);
+ });
+ }).toWarnDev(["It looks like you're using the wrong act()"]);
+});
+
+it('warns when using the wrong act version - dom + test: .create()', () => {
+ expect(() => {
+ TestUtils.act(() => {
+ TestRenderer.create();
+ });
+ }).toWarnDev(["It looks like you're using the wrong act()"], {
+ withoutStack: true,
+ });
+});
+
+it('warns when using the wrong act version - dom + test: .update()', () => {
+ const root = TestRenderer.create();
+ expect(() => {
+ TestUtils.act(() => {
+ root.update();
+ });
+ }).toWarnDev(["It looks like you're using the wrong act()"], {
+ withoutStack: true,
+ });
+});
+
+it('warns when using the wrong act version - dom + test: updates', () => {
+ let setCtr;
+ function Counter(props) {
+ const [ctr, _setCtr] = React.useState(0);
+ setCtr = _setCtr;
+ return ctr;
+ }
+ TestRenderer.create();
+ expect(() => {
+ TestUtils.act(() => {
+ setCtr(1);
+ });
+ }).toWarnDev(["It looks like you're using the wrong act()"]);
+});
+
+it('does not warn when nesting react-act inside react-dom', () => {
+ TestUtils.act(() => {
+ TestUtils.renderIntoDocument();
+ });
+});
+
+it('does not warn when nesting react-act inside react-test-renderer', () => {
+ TestRenderer.act(() => {
+ TestRenderer.create();
+ });
+});
+
+it("doesn't warn if you use nested acts from different renderers", () => {
+ TestRenderer.act(() => {
+ TestUtils.act(() => {
+ TestRenderer.create();
+ });
+ });
+});
+
+it('warns when using createRoot() + .render', () => {
+ const root = ReactDOM.unstable_createRoot(document.createElement('div'));
+ expect(() => {
+ TestRenderer.act(() => {
+ root.render();
+ });
+ }).toWarnDev(
+ [
+ 'In Concurrent or Sync modes, the "scheduler" module needs to be mocked',
+ "It looks like you're using the wrong act()",
+ ],
+ {
+ withoutStack: true,
+ }
+ );
+});
diff --git a/fixtures/dom/src/index.test.js b/fixtures/dom/src/index.test.js
deleted file mode 100644
index e562fd61c845b..0000000000000
--- a/fixtures/dom/src/index.test.js
+++ /dev/null
@@ -1,233 +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.
- *
- * @emails react-core
- */
-
-let React;
-let ReactDOM;
-let ReactART;
-let ARTSVGMode;
-let ARTCurrentMode;
-let TestUtils;
-let TestRenderer;
-let ARTTest;
-
-global.__DEV__ = process.env.NODE_ENV !== 'production';
-
-expect.extend(require('./toWarnDev'));
-
-function App(props) {
- return 'hello world';
-}
-
-describe('legacy mode', () => {
- runTests();
-});
-
-describe('mocked scheduler', () => {
- beforeEach(() => {
- jest.mock('scheduler', () =>
- require.requireActual('scheduler/unstable_mock')
- );
- });
- afterEach(() => {
- jest.unmock('scheduler');
- });
- runTests();
-});
-
-function runTests() {
- beforeEach(() => {
- jest.resetModules();
- React = require('react');
- ReactDOM = require('react-dom');
- ReactART = require('react-art');
- ARTSVGMode = require('art/modes/svg');
- ARTCurrentMode = require('art/modes/current');
- TestUtils = require('react-dom/test-utils');
- TestRenderer = require('react-test-renderer');
-
- ARTCurrentMode.setCurrent(ARTSVGMode);
-
- ARTTest = function ARTTest(props) {
- return (
-
-
-
-
- M64.564,38.583H54l0.008-5.834c0-3.035,0.293-4.666,4.657-4.666
- h5.833V16.429h-9.33c-11.213,0-15.159,5.654-15.159,15.16v6.994
- h-6.99v11.652h6.99v33.815H54V50.235h9.331L64.564,38.583z
-
-
-
- );
- };
- });
- it("doesn't warn when you use the right act + renderer: dom", () => {
- TestUtils.act(() => {
- TestUtils.renderIntoDocument();
- });
- });
-
- it("doesn't warn when you use the right act + renderer: test", () => {
- TestRenderer.act(() => {
- TestRenderer.create();
- });
- });
-
- it('resets correctly across renderers', () => {
- function Effecty() {
- React.useEffect(() => {}, []);
- return null;
- }
- TestUtils.act(() => {
- TestRenderer.act(() => {});
- expect(() => {
- TestRenderer.create();
- }).toWarnDev(["It looks like you're using the wrong act()"], {
- withoutStack: true,
- });
- });
- });
-
- it('warns when using createRoot() + .render', () => {
- const root = ReactDOM.unstable_createRoot(document.createElement('div'));
- expect(() => {
- TestRenderer.act(() => {
- root.render();
- });
- }).toWarnDev(["It looks like you're using the wrong act()"], {
- withoutStack: true,
- });
- });
-
- it('warns when using the wrong act version - test + dom: render', () => {
- expect(() => {
- TestRenderer.act(() => {
- TestUtils.renderIntoDocument();
- });
- }).toWarnDev(["It looks like you're using the wrong act()"], {
- withoutStack: true,
- });
- });
-
- it('warns when using the wrong act version - test + dom: updates', () => {
- let setCtr;
- function Counter(props) {
- const [ctr, _setCtr] = React.useState(0);
- setCtr = _setCtr;
- return ctr;
- }
- TestUtils.renderIntoDocument();
- expect(() => {
- TestRenderer.act(() => {
- setCtr(1);
- });
- }).toWarnDev(["It looks like you're using the wrong act()"]);
- });
-
- it('warns when using the wrong act version - dom + test: .create()', () => {
- expect(() => {
- TestUtils.act(() => {
- TestRenderer.create();
- });
- }).toWarnDev(["It looks like you're using the wrong act()"], {
- withoutStack: true,
- });
- });
-
- it('warns when using the wrong act version - dom + test: .update()', () => {
- const root = TestRenderer.create();
- expect(() => {
- TestUtils.act(() => {
- root.update();
- });
- }).toWarnDev(["It looks like you're using the wrong act()"], {
- withoutStack: true,
- });
- });
-
- it('warns when using the wrong act version - dom + test: updates', () => {
- let setCtr;
- function Counter(props) {
- const [ctr, _setCtr] = React.useState(0);
- setCtr = _setCtr;
- return ctr;
- }
- const root = TestRenderer.create();
- expect(() => {
- TestUtils.act(() => {
- setCtr(1);
- });
- }).toWarnDev(["It looks like you're using the wrong act()"]);
- });
-
- it('does not warn when nesting react-act inside react-dom', () => {
- TestUtils.act(() => {
- TestUtils.renderIntoDocument();
- });
- });
-
- it('does not warn when nesting react-act inside react-test-renderer', () => {
- TestRenderer.act(() => {
- TestRenderer.create();
- });
- });
-
- it("doesn't warn if you use nested acts from different renderers", () => {
- TestRenderer.act(() => {
- TestUtils.act(() => {
- TestRenderer.create();
- });
- });
- });
-
- it('flushes work only outside the outermost act(), even when nested from different renderers', () => {
- const log = [];
- function Effecty() {
- React.useEffect(() => {
- log.push('called');
- }, []);
- return null;
- }
- // in legacy mode, this tests whether an act only flushes its own effects
- // with a mocked scheduler, this tests whether it flushes all work only on the outermost act
- TestRenderer.act(() => {
- TestUtils.act(() => {
- TestRenderer.create();
- });
- expect(log).toEqual([]);
- });
- expect(log).toEqual(['called']);
-
- log.splice(0);
- // for doublechecking, we flip it inside out, and assert on the outermost
- TestUtils.act(() => {
- TestRenderer.act(() => {
- TestRenderer.create();
- });
- });
- expect(log).toEqual(['called']);
- });
-}
diff --git a/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.internal.js b/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.internal.js
deleted file mode 100644
index d629795c0727e..0000000000000
--- a/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.internal.js
+++ /dev/null
@@ -1,28 +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.
- *
- * @emails react-core
- */
-
-let ReactFeatureFlags;
-let act;
-describe('mocked scheduler', () => {
- beforeEach(() => {
- jest.resetModules();
- ReactFeatureFlags = require('shared/ReactFeatureFlags');
- ReactFeatureFlags.warnAboutMissingMockScheduler = true;
- jest.unmock('scheduler');
- act = require('react-dom/test-utils').act;
- });
- it("should warn when the scheduler isn't mocked", () => {
- expect(() => act(() => {})).toWarnDev(
- [
- 'Starting from React v17, the "scheduler" module will need to be mocked',
- ],
- {withoutStack: true},
- );
- });
-});
diff --git a/packages/react-dom/src/__tests__/ReactUnmockedSchedulerWarning-test.internal.js b/packages/react-dom/src/__tests__/ReactUnmockedSchedulerWarning-test.internal.js
new file mode 100644
index 0000000000000..1543d9b97c3e8
--- /dev/null
+++ b/packages/react-dom/src/__tests__/ReactUnmockedSchedulerWarning-test.internal.js
@@ -0,0 +1,42 @@
+/**
+ * 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.
+ *
+ * @emails react-core
+ */
+
+let React;
+let ReactDOM;
+let ReactFeatureFlags;
+
+function App() {
+ return null;
+}
+
+beforeEach(() => {
+ jest.resetModules();
+ jest.unmock('scheduler');
+ React = require('react');
+ ReactDOM = require('react-dom');
+ ReactFeatureFlags = require('shared/ReactFeatureFlags');
+ ReactFeatureFlags.warnAboutUnmockedScheduler = true;
+});
+
+afterEach(() => {
+ ReactFeatureFlags.warnAboutUnmockedScheduler = false;
+});
+
+it('should warn in sync mode', () => {
+ expect(() => {
+ ReactDOM.render(, document.createElement('div'));
+ }).toWarnDev(
+ ['Starting from React v17, the "scheduler" module will need to be mocked'],
+ {withoutStack: true},
+ );
+ // does not warn twice
+ expect(() => {
+ ReactDOM.render(, document.createElement('div'));
+ }).toWarnDev([]);
+});
diff --git a/packages/react-dom/src/__tests__/ReactUnmockedSchedulerWarning-test.js b/packages/react-dom/src/__tests__/ReactUnmockedSchedulerWarning-test.js
new file mode 100644
index 0000000000000..ade05f823b64f
--- /dev/null
+++ b/packages/react-dom/src/__tests__/ReactUnmockedSchedulerWarning-test.js
@@ -0,0 +1,60 @@
+/**
+ * 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.
+ *
+ * @emails react-core
+ */
+
+let React;
+let ReactDOM;
+
+function App() {
+ return null;
+}
+
+beforeEach(() => {
+ jest.resetModules();
+ jest.unmock('scheduler');
+ React = require('react');
+ ReactDOM = require('react-dom');
+});
+
+it('does not warn when rendering in sync mode', () => {
+ expect(() => {
+ ReactDOM.render(, document.createElement('div'));
+ }).toWarnDev([]);
+});
+
+it('should warn when rendering in concurrent mode', () => {
+ expect(() => {
+ ReactDOM.unstable_createRoot(document.createElement('div')).render();
+ }).toWarnDev(
+ 'In Concurrent or Sync modes, the "scheduler" module needs to be mocked ' +
+ 'to guarantee consistent behaviour across tests and browsers.',
+ {withoutStack: true},
+ );
+ // does not warn twice
+ expect(() => {
+ ReactDOM.unstable_createRoot(document.createElement('div')).render();
+ }).toWarnDev([]);
+});
+
+it('should warn when rendering in batched mode', () => {
+ expect(() => {
+ ReactDOM.unstable_createSyncRoot(document.createElement('div')).render(
+ ,
+ );
+ }).toWarnDev(
+ 'In Concurrent or Sync modes, the "scheduler" module needs to be mocked ' +
+ 'to guarantee consistent behaviour across tests and browsers.',
+ {withoutStack: true},
+ );
+ // does not warn twice
+ expect(() => {
+ ReactDOM.unstable_createSyncRoot(document.createElement('div')).render(
+ ,
+ );
+ }).toWarnDev([]);
+});
diff --git a/packages/react-dom/src/test-utils/ReactTestUtilsAct.js b/packages/react-dom/src/test-utils/ReactTestUtilsAct.js
index a0ccce2c9c9a4..f12915518bdd7 100644
--- a/packages/react-dom/src/test-utils/ReactTestUtilsAct.js
+++ b/packages/react-dom/src/test-utils/ReactTestUtilsAct.js
@@ -12,7 +12,6 @@ import type {Thenable} from 'react-reconciler/src/ReactFiberWorkLoop';
import warningWithoutStack from 'shared/warningWithoutStack';
import ReactDOM from 'react-dom';
import ReactSharedInternals from 'shared/ReactSharedInternals';
-import {warnAboutMissingMockScheduler} from 'shared/ReactFeatureFlags';
import enqueueTask from 'shared/enqueueTask';
import * as Scheduler from 'scheduler';
@@ -43,27 +42,11 @@ const {IsSomeRendererActing} = ReactSharedInternals;
// this implementation should be exactly the same in
// ReactTestUtilsAct.js, ReactTestRendererAct.js, createReactNoop.js
-let hasWarnedAboutMissingMockScheduler = false;
const isSchedulerMocked =
typeof Scheduler.unstable_flushAllWithoutAsserting === 'function';
const flushWork =
Scheduler.unstable_flushAllWithoutAsserting ||
function() {
- if (warnAboutMissingMockScheduler === true) {
- if (hasWarnedAboutMissingMockScheduler === false) {
- warningWithoutStack(
- null,
- 'Starting from React v17, the "scheduler" module will need to be mocked ' +
- 'to guarantee consistent behaviour across tests and browsers. To fix this, add the following ' +
- "to the top of your tests, or in your framework's global config file -\n\n" +
- 'As an example, for jest - \n' +
- "jest.mock('scheduler', () => require.requireActual('scheduler/unstable_mock'));\n\n" +
- 'For more info, visit https://fb.me/react-mock-scheduler',
- );
- hasWarnedAboutMissingMockScheduler = true;
- }
- }
-
let didFlushWork = false;
while (flushPassiveEffects()) {
didFlushWork = true;
diff --git a/packages/react-noop-renderer/src/createReactNoop.js b/packages/react-noop-renderer/src/createReactNoop.js
index 451c3c4dccc88..42add1285cb7c 100644
--- a/packages/react-noop-renderer/src/createReactNoop.js
+++ b/packages/react-noop-renderer/src/createReactNoop.js
@@ -27,7 +27,6 @@ import {REACT_FRAGMENT_TYPE, REACT_ELEMENT_TYPE} from 'shared/ReactSymbols';
import enqueueTask from 'shared/enqueueTask';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import warningWithoutStack from 'shared/warningWithoutStack';
-import {warnAboutMissingMockScheduler} from 'shared/ReactFeatureFlags';
import {ConcurrentRoot, BatchedRoot, LegacyRoot} from 'shared/ReactRootTags';
type Container = {
@@ -599,27 +598,11 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
// this act() implementation should be exactly the same in
// ReactTestUtilsAct.js, ReactTestRendererAct.js, createReactNoop.js
- let hasWarnedAboutMissingMockScheduler = false;
const isSchedulerMocked =
typeof Scheduler.unstable_flushAllWithoutAsserting === 'function';
const flushWork =
Scheduler.unstable_flushAllWithoutAsserting ||
function() {
- if (warnAboutMissingMockScheduler === true) {
- if (hasWarnedAboutMissingMockScheduler === false) {
- warningWithoutStack(
- null,
- 'Starting from React v17, the "scheduler" module will need to be mocked ' +
- 'to guarantee consistent behaviour across tests and browsers. To fix this, add the following ' +
- "to the top of your tests, or in your framework's global config file -\n\n" +
- 'As an example, for jest - \n' +
- "jest.mock('scheduler', () => require.requireActual('scheduler/unstable_mock'));\n\n" +
- 'For more info, visit https://fb.me/react-mock-scheduler',
- );
- hasWarnedAboutMissingMockScheduler = true;
- }
- }
-
let didFlushWork = false;
while (flushPassiveEffects()) {
didFlushWork = true;
diff --git a/packages/react-reconciler/src/ReactFiberReconciler.js b/packages/react-reconciler/src/ReactFiberReconciler.js
index aade7ccb15d3b..6663699d8b700 100644
--- a/packages/react-reconciler/src/ReactFiberReconciler.js
+++ b/packages/react-reconciler/src/ReactFiberReconciler.js
@@ -58,6 +58,7 @@ import {
flushDiscreteUpdates,
flushPassiveEffects,
warnIfNotScopedWithMatchingAct,
+ warnIfUnmockedScheduler,
IsThisRendererActing,
} from './ReactFiberWorkLoop';
import {createUpdate, enqueueUpdate} from './ReactUpdateQueue';
@@ -314,6 +315,7 @@ export function updateContainer(
if (__DEV__) {
// $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
if ('undefined' !== typeof jest) {
+ warnIfUnmockedScheduler(current);
warnIfNotScopedWithMatchingAct(current);
}
}
diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js
index d4d4872450f12..53442df6e70ec 100644
--- a/packages/react-reconciler/src/ReactFiberWorkLoop.js
+++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js
@@ -25,6 +25,7 @@ import {
enableProfilerTimer,
enableSchedulerTracing,
revertPassiveEffectsChange,
+ warnAboutUnmockedScheduler,
} from 'shared/ReactFeatureFlags';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import invariant from 'shared/invariant';
@@ -47,6 +48,9 @@ import {
scheduleSyncCallback,
} from './SchedulerWithReactIntegration';
+// The scheduler is imported here *only* to detect whether it's been mocked
+import * as Scheduler from 'scheduler';
+
import {__interactionsRef, __subscriberRef} from 'scheduler/tracing';
import {
@@ -2522,6 +2526,41 @@ function warnIfNotCurrentlyActingUpdatesInDEV(fiber: Fiber): void {
export const warnIfNotCurrentlyActingUpdatesInDev = warnIfNotCurrentlyActingUpdatesInDEV;
+// In tests, we want to enforce a mocked scheduler.
+let didWarnAboutUnmockedScheduler = false;
+// TODO Before we release concurrent mode, revisit this and decide whether a mocked
+// scheduler is the actual recommendation. The alternative could be a testing build,
+// a new lib, or whatever; we dunno just yet. This message is for early adopters
+// to get their tests right.
+
+export function warnIfUnmockedScheduler(fiber: Fiber) {
+ if (__DEV__) {
+ if (didWarnAboutUnmockedScheduler === false) {
+ if (fiber.mode & BatchedMode || fiber.mode & ConcurrentMode) {
+ didWarnAboutUnmockedScheduler = true;
+ warningWithoutStack(
+ Scheduler.unstable_flushAllWithoutAsserting !== undefined,
+ 'In Concurrent or Sync modes, the "scheduler" module needs to be mocked ' +
+ 'to guarantee consistent behaviour across tests and browsers. ' +
+ 'For example, with jest: \n' +
+ "jest.mock('scheduler', () => require('scheduler/unstable_mock'));\n\n" +
+ 'For more info, visit https://fb.me/react-mock-scheduler',
+ );
+ } else if (warnAboutUnmockedScheduler === true) {
+ didWarnAboutUnmockedScheduler = true;
+ warningWithoutStack(
+ null,
+ 'Starting from React v17, the "scheduler" module will need to be mocked ' +
+ 'to guarantee consistent behaviour across tests and browsers. ' +
+ 'For example, with jest: \n' +
+ "jest.mock('scheduler', () => require('scheduler/unstable_mock'));\n\n" +
+ 'For more info, visit https://fb.me/react-mock-scheduler',
+ );
+ }
+ }
+ }
+}
+
let componentsThatSuspendedAtHighPri = null;
let componentsThatTriggeredHighPriSuspend = null;
export function checkForWrongSuspensePriorityInDEV(sourceFiber: Fiber) {
diff --git a/packages/react-test-renderer/src/ReactTestRendererAct.js b/packages/react-test-renderer/src/ReactTestRendererAct.js
index 83408301a6994..726a62a15a277 100644
--- a/packages/react-test-renderer/src/ReactTestRendererAct.js
+++ b/packages/react-test-renderer/src/ReactTestRendererAct.js
@@ -15,7 +15,6 @@ import {
} from 'react-reconciler/inline.test';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import warningWithoutStack from 'shared/warningWithoutStack';
-import {warnAboutMissingMockScheduler} from 'shared/ReactFeatureFlags';
import enqueueTask from 'shared/enqueueTask';
import * as Scheduler from 'scheduler';
@@ -24,27 +23,11 @@ const {IsSomeRendererActing} = ReactSharedInternals;
// this implementation should be exactly the same in
// ReactTestUtilsAct.js, ReactTestRendererAct.js, createReactNoop.js
-let hasWarnedAboutMissingMockScheduler = false;
const isSchedulerMocked =
typeof Scheduler.unstable_flushAllWithoutAsserting === 'function';
const flushWork =
Scheduler.unstable_flushAllWithoutAsserting ||
function() {
- if (warnAboutMissingMockScheduler === true) {
- if (hasWarnedAboutMissingMockScheduler === false) {
- warningWithoutStack(
- null,
- 'Starting from React v17, the "scheduler" module will need to be mocked ' +
- 'to guarantee consistent behaviour across tests and browsers. To fix this, add the following ' +
- "to the top of your tests, or in your framework's global config file -\n\n" +
- 'As an example, for jest - \n' +
- "jest.mock('scheduler', () => require.requireActual('scheduler/unstable_mock'));\n\n" +
- 'For more info, visit https://fb.me/react-mock-scheduler',
- );
- hasWarnedAboutMissingMockScheduler = true;
- }
- }
-
let didFlushWork = false;
while (flushPassiveEffects()) {
didFlushWork = true;
diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js
index ac92811f09541..083da1b75075a 100644
--- a/packages/shared/ReactFeatureFlags.js
+++ b/packages/shared/ReactFeatureFlags.js
@@ -70,7 +70,7 @@ export const enableJSXTransformAPI = false;
// We will enforce mocking scheduler with scheduler/unstable_mock at some point. (v17?)
// Till then, we warn about the missing mock, but still fallback to a sync mode compatible version
-export const warnAboutMissingMockScheduler = false;
+export const warnAboutUnmockedScheduler = false;
// Temporary flag to revert the fix in #15650
export const revertPassiveEffectsChange = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js
index 08baee4c30ca5..427db540edc87 100644
--- a/packages/shared/forks/ReactFeatureFlags.native-fb.js
+++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js
@@ -34,7 +34,7 @@ export const warnAboutDeprecatedSetNativeProps = true;
export const enableFlareAPI = false;
export const enableFundamentalAPI = false;
export const enableJSXTransformAPI = false;
-export const warnAboutMissingMockScheduler = true;
+export const warnAboutUnmockedScheduler = true;
export const revertPassiveEffectsChange = false;
export const enableUserBlockingEvents = false;
export const enableSuspenseCallback = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js
index e1bb4ecae4bfd..cf69b2dc91cb7 100644
--- a/packages/shared/forks/ReactFeatureFlags.native-oss.js
+++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js
@@ -29,7 +29,7 @@ export const warnAboutDeprecatedSetNativeProps = false;
export const enableFlareAPI = false;
export const enableFundamentalAPI = false;
export const enableJSXTransformAPI = false;
-export const warnAboutMissingMockScheduler = false;
+export const warnAboutUnmockedScheduler = false;
export const revertPassiveEffectsChange = false;
export const enableUserBlockingEvents = false;
export const enableSuspenseCallback = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.persistent.js b/packages/shared/forks/ReactFeatureFlags.persistent.js
index c392336fc5188..7258b2ae1a7cb 100644
--- a/packages/shared/forks/ReactFeatureFlags.persistent.js
+++ b/packages/shared/forks/ReactFeatureFlags.persistent.js
@@ -29,7 +29,7 @@ export const warnAboutDeprecatedSetNativeProps = false;
export const enableFlareAPI = false;
export const enableFundamentalAPI = false;
export const enableJSXTransformAPI = false;
-export const warnAboutMissingMockScheduler = true;
+export const warnAboutUnmockedScheduler = true;
export const revertPassiveEffectsChange = false;
export const enableUserBlockingEvents = false;
export const enableSuspenseCallback = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js
index a38d1f5ff8218..e9e16e4b3f7df 100644
--- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js
+++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js
@@ -29,7 +29,7 @@ export const warnAboutDeprecatedSetNativeProps = false;
export const enableFlareAPI = false;
export const enableFundamentalAPI = false;
export const enableJSXTransformAPI = false;
-export const warnAboutMissingMockScheduler = false;
+export const warnAboutUnmockedScheduler = false;
export const revertPassiveEffectsChange = false;
export const enableUserBlockingEvents = false;
export const enableSuspenseCallback = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
index c8f1beb3e6288..86222406e391c 100644
--- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
@@ -30,7 +30,7 @@ export const disableJavaScriptURLs = false;
export const enableFlareAPI = true;
export const enableFundamentalAPI = false;
export const enableJSXTransformAPI = true;
-export const warnAboutMissingMockScheduler = true;
+export const warnAboutUnmockedScheduler = true;
export const enableUserBlockingEvents = false;
export const enableSuspenseCallback = true;
export const warnAboutDefaultPropsOnFunctionComponents = false;
diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js
index 284416c8255b2..f0123e67b41ae 100644
--- a/packages/shared/forks/ReactFeatureFlags.www.js
+++ b/packages/shared/forks/ReactFeatureFlags.www.js
@@ -74,7 +74,7 @@ export const enableFundamentalAPI = false;
export const enableJSXTransformAPI = true;
-export const warnAboutMissingMockScheduler = true;
+export const warnAboutUnmockedScheduler = true;
export const enableSuspenseCallback = true;