diff --git a/.eslintrc.js b/.eslintrc.js
index 74b87ab13a679..7d34ba704554a 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -168,6 +168,7 @@ module.exports = {
__PROFILE__: true,
__UMD__: true,
__EXPERIMENTAL__: true,
+ __TEST__: true,
trustedTypes: true,
},
};
diff --git a/fixtures/dom/src/__tests__/nested-act-test.js b/fixtures/dom/src/__tests__/nested-act-test.js
index a0500f47e9603..757fbcd0c0a15 100644
--- a/fixtures/dom/src/__tests__/nested-act-test.js
+++ b/fixtures/dom/src/__tests__/nested-act-test.js
@@ -8,18 +8,21 @@
*/
let React;
-let TestUtils;
+let ReactDOM;
let TestRenderer;
global.__DEV__ = process.env.NODE_ENV !== 'production';
+jest.mock('react', () => require.requireActual('react/testing'));
+jest.mock('react-dom', () => require.requireActual('react-dom/testing'));
+
expect.extend(require('../toWarnDev'));
describe('unmocked scheduler', () => {
beforeEach(() => {
jest.resetModules();
React = require('react');
- TestUtils = require('react-dom/test-utils');
+ ReactDOM = require('react-dom');
TestRenderer = require('react-test-renderer');
});
@@ -33,7 +36,7 @@ describe('unmocked scheduler', () => {
}
// in legacy mode, this tests whether an act only flushes its own effects
TestRenderer.act(() => {
- TestUtils.act(() => {
+ ReactDOM.act(() => {
TestRenderer.create();
});
expect(log).toEqual([]);
@@ -42,7 +45,7 @@ describe('unmocked scheduler', () => {
log = [];
// for doublechecking, we flip it inside out, and assert on the outermost
- TestUtils.act(() => {
+ ReactDOM.act(() => {
TestRenderer.act(() => {
TestRenderer.create();
});
@@ -59,7 +62,7 @@ describe('mocked scheduler', () => {
require.requireActual('scheduler/unstable_mock')
);
React = require('react');
- TestUtils = require('react-dom/test-utils');
+ ReactDOM = require('react-dom');
TestRenderer = require('react-test-renderer');
});
@@ -77,7 +80,7 @@ describe('mocked scheduler', () => {
}
// with a mocked scheduler, this tests whether it flushes all work only on the outermost act
TestRenderer.act(() => {
- TestUtils.act(() => {
+ ReactDOM.act(() => {
TestRenderer.create();
});
expect(log).toEqual([]);
@@ -86,7 +89,7 @@ describe('mocked scheduler', () => {
log = [];
// for doublechecking, we flip it inside out, and assert on the outermost
- TestUtils.act(() => {
+ ReactDOM.act(() => {
TestRenderer.act(() => {
TestRenderer.create();
});
diff --git a/fixtures/dom/src/__tests__/wrong-act-test.js b/fixtures/dom/src/__tests__/wrong-act-test.js
index 6c054efec1bf9..7164c3eec7705 100644
--- a/fixtures/dom/src/__tests__/wrong-act-test.js
+++ b/fixtures/dom/src/__tests__/wrong-act-test.js
@@ -12,13 +12,15 @@ let ReactDOM;
let ReactART;
let ARTSVGMode;
let ARTCurrentMode;
-let TestUtils;
let TestRenderer;
let ARTTest;
global.__DEV__ = process.env.NODE_ENV !== 'production';
global.__EXPERIMENTAL__ = process.env.RELEASE_CHANNEL === 'experimental';
+jest.mock('react', () => require.requireActual('react/testing'));
+jest.mock('react-dom', () => require.requireActual('react-dom/testing'));
+
expect.extend(require('../toWarnDev'));
function App(props) {
@@ -32,7 +34,6 @@ beforeEach(() => {
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);
@@ -70,8 +71,8 @@ beforeEach(() => {
});
it("doesn't warn when you use the right act + renderer: dom", () => {
- TestUtils.act(() => {
- TestUtils.renderIntoDocument();
+ ReactDOM.act(() => {
+ ReactDOM.render(, document.createElement('div'));
});
});
@@ -86,7 +87,7 @@ it('resets correctly across renderers', () => {
React.useEffect(() => {}, []);
return null;
}
- TestUtils.act(() => {
+ ReactDOM.act(() => {
TestRenderer.act(() => {});
expect(() => {
TestRenderer.create();
@@ -99,7 +100,7 @@ it('resets correctly across renderers', () => {
it('warns when using the wrong act version - test + dom: render', () => {
expect(() => {
TestRenderer.act(() => {
- TestUtils.renderIntoDocument();
+ ReactDOM.render(, document.createElement('div'));
});
}).toWarnDev(["It looks like you're using the wrong act()"], {
withoutStack: true,
@@ -113,7 +114,7 @@ it('warns when using the wrong act version - test + dom: updates', () => {
setCtr = _setCtr;
return ctr;
}
- TestUtils.renderIntoDocument();
+ ReactDOM.render(, document.createElement('div'));
expect(() => {
TestRenderer.act(() => {
setCtr(1);
@@ -123,7 +124,7 @@ it('warns when using the wrong act version - test + dom: updates', () => {
it('warns when using the wrong act version - dom + test: .create()', () => {
expect(() => {
- TestUtils.act(() => {
+ ReactDOM.act(() => {
TestRenderer.create();
});
}).toWarnDev(["It looks like you're using the wrong act()"], {
@@ -134,7 +135,7 @@ it('warns when using the wrong act version - dom + test: .create()', () => {
it('warns when using the wrong act version - dom + test: .update()', () => {
const root = TestRenderer.create();
expect(() => {
- TestUtils.act(() => {
+ ReactDOM.act(() => {
root.update();
});
}).toWarnDev(["It looks like you're using the wrong act()"], {
@@ -151,15 +152,15 @@ it('warns when using the wrong act version - dom + test: updates', () => {
}
TestRenderer.create();
expect(() => {
- TestUtils.act(() => {
+ ReactDOM.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();
+ ReactDOM.act(() => {
+ ReactDOM.render(, document.createElement('div'));
});
});
@@ -171,7 +172,7 @@ it('does not warn when nesting react-act inside react-test-renderer', () => {
it("doesn't warn if you use nested acts from different renderers", () => {
TestRenderer.act(() => {
- TestUtils.act(() => {
+ ReactDOM.act(() => {
TestRenderer.create();
});
});
diff --git a/packages/react-dom/npm/testing.js b/packages/react-dom/npm/testing.js
new file mode 100644
index 0000000000000..94d13ac5e7f4c
--- /dev/null
+++ b/packages/react-dom/npm/testing.js
@@ -0,0 +1,3 @@
+'use strict';
+
+module.exports = require('./cjs/react-dom.testing.js');
diff --git a/packages/react-dom/package.json b/packages/react-dom/package.json
index 7e6cf5e5c0ee8..716e71d74392e 100644
--- a/packages/react-dom/package.json
+++ b/packages/react-dom/package.json
@@ -34,6 +34,7 @@
"server.js",
"server.browser.js",
"server.node.js",
+ "testing.js",
"test-utils.js",
"unstable-fire.js",
"unstable-fizz.js",
diff --git a/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js b/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js
index d68a4a1141709..8e1b6bb45cec3 100644
--- a/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js
+++ b/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js
@@ -721,7 +721,7 @@ function runActTests(label, render, unmount, rerender) {
});
describe('suspense', () => {
- if (__DEV__ && __EXPERIMENTAL__) {
+ if (__TEST__ && __EXPERIMENTAL__) {
it('triggers fallbacks if available', async () => {
let resolved = false;
let resolve;
diff --git a/packages/react-dom/src/client/ReactDOM.js b/packages/react-dom/src/client/ReactDOM.js
index 19a3db22387c0..ce6dbfa406f19 100644
--- a/packages/react-dom/src/client/ReactDOM.js
+++ b/packages/react-dom/src/client/ReactDOM.js
@@ -75,6 +75,12 @@ import {
queueExplicitHydrationTarget,
} from '../events/ReactDOMEventReplaying';
+// used by __TEST__ builds
+import ReactSharedInternals from 'shared/ReactSharedInternals';
+import enqueueTask from 'shared/enqueueTask';
+import * as Scheduler from 'scheduler';
+// end __TEST__ imports
+
setAttemptSynchronousHydration(attemptSynchronousHydration);
setAttemptUserBlockingHydration(attemptUserBlockingHydration);
setAttemptContinuousHydration(attemptContinuousHydration);
@@ -245,4 +251,192 @@ if (__DEV__) {
}
}
+if (__TEST__) {
+ const {IsSomeRendererActing} = ReactSharedInternals;
+ const isSchedulerMocked =
+ typeof Scheduler.unstable_flushAllWithoutAsserting === 'function';
+ const flushWork =
+ Scheduler.unstable_flushAllWithoutAsserting ||
+ function() {
+ let didFlushWork = false;
+ while (flushPassiveEffects()) {
+ didFlushWork = true;
+ }
+
+ return didFlushWork;
+ };
+
+ // eslint-disable-next-line no-inner-declarations
+ 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;
+ let didWarnAboutUsingActInProd = false;
+
+ // eslint-disable-next-line no-inner-declarations
+ function act(callback: () => Thenable) {
+ if (!__DEV__) {
+ if (didWarnAboutUsingActInProd === false) {
+ didWarnAboutUsingActInProd = true;
+ // eslint-disable-next-line react-internal/no-production-logging
+ console.error(
+ 'act(...) is not supported in production builds of React, and might not behave as expected.',
+ );
+ }
+ }
+
+ let previousActingUpdatesScopeDepth = actingUpdatesScopeDepth;
+ let previousIsSomeRendererActing;
+ let previousIsThisRendererActing;
+ actingUpdatesScopeDepth++;
+
+ previousIsSomeRendererActing = IsSomeRendererActing.current;
+ 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: () => void, reject: (?Error) => void) {
+ 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: () => void) {
+ if (__DEV__) {
+ console.error(
+ 'Do not await the result of calling act(...) with sync logic, it is not a Promise.',
+ );
+ }
+ resolve();
+ },
+ };
+ }
+ }
+
+ ReactDOM.act = act;
+}
+
export default ReactDOM;
diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js
index a4664613ac224..9b9b61d91f82f 100644
--- a/packages/react-reconciler/src/ReactFiberWorkLoop.js
+++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js
@@ -791,7 +791,7 @@ function finishConcurrentRender(
hasNotProcessedNewUpdates &&
// do not delay if we're inside an act() scope
!(
- __DEV__ &&
+ __TEST__ &&
flushSuspenseFallbacksInTests &&
IsThisRendererActing.current
)
@@ -858,7 +858,7 @@ function finishConcurrentRender(
if (
// do not delay if we're inside an act() scope
!(
- __DEV__ &&
+ __TEST__ &&
flushSuspenseFallbacksInTests &&
IsThisRendererActing.current
)
@@ -949,7 +949,7 @@ function finishConcurrentRender(
if (
// do not delay if we're inside an act() scope
!(
- __DEV__ &&
+ __TEST__ &&
flushSuspenseFallbacksInTests &&
IsThisRendererActing.current
) &&
diff --git a/packages/react/npm/testing.js b/packages/react/npm/testing.js
new file mode 100644
index 0000000000000..1bc3f5234d07b
--- /dev/null
+++ b/packages/react/npm/testing.js
@@ -0,0 +1,3 @@
+'use strict';
+
+module.exports = require('./cjs/react.testing.js');
diff --git a/packages/react/package.json b/packages/react/package.json
index a1d1a21e0d93d..7388a66156e31 100644
--- a/packages/react/package.json
+++ b/packages/react/package.json
@@ -13,6 +13,7 @@
"README.md",
"build-info.json",
"index.js",
+ "testing.js",
"cjs/",
"umd/"
],
diff --git a/scripts/flow/environment.js b/scripts/flow/environment.js
index 524cae905bc2e..de6b36c7027c2 100644
--- a/scripts/flow/environment.js
+++ b/scripts/flow/environment.js
@@ -12,6 +12,7 @@
declare var __PROFILE__: boolean;
declare var __UMD__: boolean;
declare var __EXPERIMENTAL__: boolean;
+declare var __TEST__: boolean;
declare var __REACT_DEVTOOLS_GLOBAL_HOOK__: any; /*?{
inject: ?((stuff: Object) => void)
diff --git a/scripts/jest/setupEnvironment.js b/scripts/jest/setupEnvironment.js
index 7eef6432e73cf..23488e896ad90 100644
--- a/scripts/jest/setupEnvironment.js
+++ b/scripts/jest/setupEnvironment.js
@@ -7,6 +7,7 @@ if (NODE_ENV !== 'development' && NODE_ENV !== 'production') {
global.__DEV__ = NODE_ENV === 'development';
global.__PROFILE__ = NODE_ENV === 'development';
global.__UMD__ = false;
+global.__TEST__ = false;
const RELEASE_CHANNEL = process.env.RELEASE_CHANNEL;
diff --git a/scripts/rollup/build.js b/scripts/rollup/build.js
index ec0cc2c1754bb..b20782ec05895 100644
--- a/scripts/rollup/build.js
+++ b/scripts/rollup/build.js
@@ -48,12 +48,15 @@ const {
UMD_DEV,
UMD_PROD,
UMD_PROFILING,
+ UMD_TESTING,
NODE_DEV,
NODE_PROD,
NODE_PROFILING,
+ NODE_TESTING,
FB_WWW_DEV,
FB_WWW_PROD,
FB_WWW_PROFILING,
+ FB_WWW_TESTING,
RN_OSS_DEV,
RN_OSS_PROD,
RN_OSS_PROFILING,
@@ -139,6 +142,7 @@ function getBabelConfig(
case FB_WWW_DEV:
case FB_WWW_PROD:
case FB_WWW_PROFILING:
+ case FB_WWW_TESTING:
return Object.assign({}, options, {
plugins: options.plugins.concat([
// Minify invariant messages
@@ -163,9 +167,11 @@ function getBabelConfig(
case UMD_DEV:
case UMD_PROD:
case UMD_PROFILING:
+ case UMD_TESTING:
case NODE_DEV:
case NODE_PROD:
case NODE_PROFILING:
+ case NODE_TESTING:
return Object.assign({}, options, {
plugins: options.plugins.concat([
// Use object-assign polyfill in open source
@@ -207,13 +213,16 @@ function getFormat(bundleType) {
case UMD_DEV:
case UMD_PROD:
case UMD_PROFILING:
+ case UMD_TESTING:
return `umd`;
case NODE_DEV:
case NODE_PROD:
case NODE_PROFILING:
+ case NODE_TESTING:
case FB_WWW_DEV:
case FB_WWW_PROD:
case FB_WWW_PROFILING:
+ case FB_WWW_TESTING:
case RN_OSS_DEV:
case RN_OSS_PROD:
case RN_OSS_PROFILING:
@@ -234,12 +243,16 @@ function getFilename(name, globalName, bundleType) {
return `${name}.production.min.js`;
case UMD_PROFILING:
return `${name}.profiling.min.js`;
+ case UMD_TESTING:
+ return `${name}.testing.js`;
case NODE_DEV:
return `${name}.development.js`;
case NODE_PROD:
return `${name}.production.min.js`;
case NODE_PROFILING:
return `${name}.profiling.min.js`;
+ case NODE_TESTING:
+ return `${name}.testing.js`;
case FB_WWW_DEV:
case RN_OSS_DEV:
case RN_FB_DEV:
@@ -252,14 +265,19 @@ function getFilename(name, globalName, bundleType) {
case RN_FB_PROFILING:
case RN_OSS_PROFILING:
return `${globalName}-profiling.js`;
+ case FB_WWW_TESTING:
+ return `${globalName}-testing.js`;
}
}
function isProductionBundleType(bundleType) {
switch (bundleType) {
case UMD_DEV:
+ case UMD_TESTING:
case NODE_DEV:
+ case NODE_TESTING:
case FB_WWW_DEV:
+ case FB_WWW_TESTING:
case RN_OSS_DEV:
case RN_FB_DEV:
return false;
@@ -283,14 +301,17 @@ function isProfilingBundleType(bundleType) {
switch (bundleType) {
case FB_WWW_DEV:
case FB_WWW_PROD:
+ case FB_WWW_TESTING:
case NODE_DEV:
case NODE_PROD:
+ case NODE_TESTING:
case RN_FB_DEV:
case RN_FB_PROD:
case RN_OSS_DEV:
case RN_OSS_PROD:
case UMD_DEV:
case UMD_PROD:
+ case UMD_TESTING:
return false;
case FB_WWW_PROFILING:
case NODE_PROFILING:
@@ -303,6 +324,33 @@ function isProfilingBundleType(bundleType) {
}
}
+function isTestingBuildBundleType(bundleType) {
+ switch (bundleType) {
+ case FB_WWW_DEV:
+ case FB_WWW_PROD:
+ case FB_WWW_PROFILING:
+ case NODE_DEV:
+ case NODE_PROD:
+ case NODE_PROFILING:
+ case RN_FB_DEV:
+ case RN_FB_PROD:
+ case RN_FB_PROFILING:
+ case RN_OSS_DEV:
+ case RN_OSS_PROD:
+ case RN_OSS_PROFILING:
+ case UMD_DEV:
+ case UMD_PROD:
+ case UMD_PROFILING:
+ return false;
+ case FB_WWW_TESTING:
+ case NODE_TESTING:
+ case UMD_TESTING:
+ return true;
+ default:
+ throw new Error(`Unknown type: ${bundleType}`);
+ }
+}
+
function forbidFBJSImports() {
return {
name: 'forbidFBJSImports',
@@ -332,14 +380,17 @@ function getPlugins(
const forks = Modules.getForks(bundleType, entry, moduleType);
const isProduction = isProductionBundleType(bundleType);
const isProfiling = isProfilingBundleType(bundleType);
+ const isTestingBuild = isTestingBuildBundleType(bundleType);
const isUMDBundle =
bundleType === UMD_DEV ||
bundleType === UMD_PROD ||
- bundleType === UMD_PROFILING;
+ bundleType === UMD_PROFILING ||
+ bundleType === UMD_TESTING;
const isFBBundle =
bundleType === FB_WWW_DEV ||
bundleType === FB_WWW_PROD ||
- bundleType === FB_WWW_PROFILING;
+ bundleType === FB_WWW_PROFILING ||
+ bundleType === FB_WWW_TESTING;
const isRNBundle =
bundleType === RN_OSS_DEV ||
bundleType === RN_OSS_PROD ||
@@ -391,6 +442,7 @@ function getPlugins(
__DEV__: isProduction ? 'false' : 'true',
__PROFILE__: isProfiling || !isProduction ? 'true' : 'false',
__UMD__: isUMDBundle ? 'true' : 'false',
+ __TEST__: isTestingBuild ? 'true' : 'false',
'process.env.NODE_ENV': isProduction ? "'production'" : "'development'",
__EXPERIMENTAL__,
}),
@@ -488,7 +540,8 @@ async function createBundle(bundle, bundleType) {
const isFBBundle =
bundleType === FB_WWW_DEV ||
bundleType === FB_WWW_PROD ||
- bundleType === FB_WWW_PROFILING;
+ bundleType === FB_WWW_PROFILING ||
+ bundleType === FB_WWW_TESTING;
if (isFBBundle) {
const resolvedFBEntry = resolvedEntry.replace('.js', '.fb.js');
if (fs.existsSync(resolvedFBEntry)) {
@@ -499,7 +552,8 @@ async function createBundle(bundle, bundleType) {
const shouldBundleDependencies =
bundleType === UMD_DEV ||
bundleType === UMD_PROD ||
- bundleType === UMD_PROFILING;
+ bundleType === UMD_PROFILING ||
+ bundleType === UMD_TESTING;
const peerGlobals = Modules.getPeerGlobals(bundle.externals, bundleType);
let externals = Object.keys(peerGlobals);
if (!shouldBundleDependencies) {
@@ -545,7 +599,8 @@ async function createBundle(bundle, bundleType) {
legacy:
bundleType === FB_WWW_DEV ||
bundleType === FB_WWW_PROD ||
- bundleType === FB_WWW_PROFILING,
+ bundleType === FB_WWW_PROFILING ||
+ bundleType === FB_WWW_TESTING,
};
const [mainOutputPath, ...otherOutputPaths] = Packaging.getBundleOutputPaths(
bundleType,
@@ -681,9 +736,11 @@ async function buildEverything() {
[bundle, UMD_DEV],
[bundle, UMD_PROD],
[bundle, UMD_PROFILING],
+ [bundle, UMD_TESTING],
[bundle, NODE_DEV],
[bundle, NODE_PROD],
[bundle, NODE_PROFILING],
+ [bundle, NODE_TESTING],
[bundle, RN_OSS_DEV],
[bundle, RN_OSS_PROD],
[bundle, RN_OSS_PROFILING]
@@ -695,6 +752,7 @@ async function buildEverything() {
[bundle, FB_WWW_DEV],
[bundle, FB_WWW_PROD],
[bundle, FB_WWW_PROFILING],
+ [bundle, FB_WWW_TESTING],
[bundle, RN_FB_DEV],
[bundle, RN_FB_PROD],
[bundle, RN_FB_PROFILING]
diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js
index aefbb65e4f1b1..ff79584e534ab 100644
--- a/scripts/rollup/bundles.js
+++ b/scripts/rollup/bundles.js
@@ -4,12 +4,15 @@ const bundleTypes = {
UMD_DEV: 'UMD_DEV',
UMD_PROD: 'UMD_PROD',
UMD_PROFILING: 'UMD_PROFILING',
+ UMD_TESTING: 'UMD_TESTING',
NODE_DEV: 'NODE_DEV',
NODE_PROD: 'NODE_PROD',
NODE_PROFILING: 'NODE_PROFILING',
+ NODE_TESTING: 'NODE_TESTING',
FB_WWW_DEV: 'FB_WWW_DEV',
FB_WWW_PROD: 'FB_WWW_PROD',
FB_WWW_PROFILING: 'FB_WWW_PROFILING',
+ FB_WWW_TESTING: 'FB_WWW_TESTING',
RN_OSS_DEV: 'RN_OSS_DEV',
RN_OSS_PROD: 'RN_OSS_PROD',
RN_OSS_PROFILING: 'RN_OSS_PROFILING',
@@ -22,12 +25,15 @@ const {
UMD_DEV,
UMD_PROD,
UMD_PROFILING,
+ UMD_TESTING,
NODE_DEV,
NODE_PROD,
NODE_PROFILING,
+ NODE_TESTING,
FB_WWW_DEV,
FB_WWW_PROD,
FB_WWW_PROFILING,
+ FB_WWW_TESTING,
RN_OSS_DEV,
RN_OSS_PROD,
RN_OSS_PROFILING,
@@ -64,11 +70,14 @@ const bundles = [
UMD_DEV,
UMD_PROD,
UMD_PROFILING,
+ UMD_TESTING,
NODE_DEV,
NODE_PROD,
+ NODE_TESTING,
FB_WWW_DEV,
FB_WWW_PROD,
FB_WWW_PROFILING,
+ FB_WWW_TESTING,
],
moduleType: ISOMORPHIC,
entry: 'react',
@@ -82,12 +91,15 @@ const bundles = [
UMD_DEV,
UMD_PROD,
UMD_PROFILING,
+ UMD_TESTING,
NODE_DEV,
NODE_PROD,
NODE_PROFILING,
+ NODE_TESTING,
FB_WWW_DEV,
FB_WWW_PROD,
FB_WWW_PROFILING,
+ FB_WWW_TESTING,
],
moduleType: RENDERER,
entry: 'react-dom',
@@ -360,7 +372,7 @@ const bundles = [
/******* React Reconciler *******/
{
- bundleTypes: [NODE_DEV, NODE_PROD],
+ bundleTypes: [NODE_DEV, NODE_PROD], // todo - should this have a testing build?
moduleType: RECONCILER,
entry: 'react-reconciler',
global: 'ReactReconciler',
@@ -369,7 +381,7 @@ const bundles = [
/******* React Persistent Reconciler *******/
{
- bundleTypes: [NODE_DEV, NODE_PROD],
+ bundleTypes: [NODE_DEV, NODE_PROD], // todo - should this have a testing build?
moduleType: RECONCILER,
entry: 'react-reconciler/persistent',
global: 'ReactPersistentReconciler',
@@ -421,7 +433,7 @@ const bundles = [
FB_WWW_PROD,
UMD_DEV,
UMD_PROD,
- ],
+ ], // todo - should this have a testing build?
moduleType: ISOMORPHIC,
entry: 'react-is',
global: 'ReactIs',
@@ -501,7 +513,7 @@ const bundles = [
NODE_PROD,
FB_WWW_DEV,
FB_WWW_PROD,
- ],
+ ], // should we call this scheduler.testing.js instead?
moduleType: ISOMORPHIC,
entry: 'scheduler/unstable_mock',
global: 'SchedulerMock',
@@ -517,7 +529,7 @@ const bundles = [
externals: [],
},
- /******* ESLint Plugin for Hooks (proposal) *******/
+ /******* ESLint Plugin for Hooks *******/
{
// TODO: it's awkward to create a bundle for this but if we don't, the package
// won't get copied. We also can't create just DEV bundle because it contains a
diff --git a/scripts/rollup/forks.js b/scripts/rollup/forks.js
index eeb0c1caee8a7..2167fb63b3329 100644
--- a/scripts/rollup/forks.js
+++ b/scripts/rollup/forks.js
@@ -1,23 +1,25 @@
'use strict';
-const bundleTypes = require('./bundles').bundleTypes;
-const moduleTypes = require('./bundles').moduleTypes;
+const {bundleTypes, moduleTypes} = require('./bundles');
const inlinedHostConfigs = require('../shared/inlinedHostConfigs');
-const UMD_DEV = bundleTypes.UMD_DEV;
-const UMD_PROD = bundleTypes.UMD_PROD;
-const UMD_PROFILING = bundleTypes.UMD_PROFILING;
-const FB_WWW_DEV = bundleTypes.FB_WWW_DEV;
-const FB_WWW_PROD = bundleTypes.FB_WWW_PROD;
-const FB_WWW_PROFILING = bundleTypes.FB_WWW_PROFILING;
-const RN_OSS_DEV = bundleTypes.RN_OSS_DEV;
-const RN_OSS_PROD = bundleTypes.RN_OSS_PROD;
-const RN_OSS_PROFILING = bundleTypes.RN_OSS_PROFILING;
-const RN_FB_DEV = bundleTypes.RN_FB_DEV;
-const RN_FB_PROD = bundleTypes.RN_FB_PROD;
-const RN_FB_PROFILING = bundleTypes.RN_FB_PROFILING;
-const RENDERER = moduleTypes.RENDERER;
-const RECONCILER = moduleTypes.RECONCILER;
+const {
+ UMD_DEV,
+ UMD_PROD,
+ UMD_PROFILING,
+ UMD_TESTING,
+ FB_WWW_DEV,
+ FB_WWW_PROD,
+ FB_WWW_PROFILING,
+ FB_WWW_TESTING,
+ RN_OSS_DEV,
+ RN_OSS_PROD,
+ RN_OSS_PROFILING,
+ RN_FB_DEV,
+ RN_FB_PROD,
+ RN_FB_PROFILING,
+} = bundleTypes;
+const {RENDERER, RECONCILER} = moduleTypes;
// If you need to replace a file with another file for a specific environment,
// add it to this list with the logic for choosing the right replacement.
@@ -28,7 +30,8 @@ const forks = Object.freeze({
if (
bundleType !== UMD_DEV &&
bundleType !== UMD_PROD &&
- bundleType !== UMD_PROFILING
+ bundleType !== UMD_PROFILING &&
+ bundleType !== UMD_TESTING
) {
// It's only relevant for UMD bundles since that's where the duplication
// happens. Other bundles just require('object-assign') anyway.
@@ -103,6 +106,7 @@ const forks = Object.freeze({
case FB_WWW_DEV:
case FB_WWW_PROD:
case FB_WWW_PROFILING:
+ case FB_WWW_TESTING:
return 'shared/forks/ReactFeatureFlags.test-renderer.www.js';
}
return 'shared/forks/ReactFeatureFlags.test-renderer.js';
@@ -111,6 +115,7 @@ const forks = Object.freeze({
case FB_WWW_DEV:
case FB_WWW_PROD:
case FB_WWW_PROFILING:
+ case FB_WWW_TESTING:
return 'shared/forks/ReactFeatureFlags.www.js';
}
}
@@ -122,6 +127,7 @@ const forks = Object.freeze({
case UMD_DEV:
case UMD_PROD:
case UMD_PROFILING:
+ case UMD_TESTING:
if (dependencies.indexOf('react') === -1) {
// It's only safe to use this fork for modules that depend on React,
// because they read the re-exported API from the SECRET_INTERNALS object.
@@ -141,6 +147,7 @@ const forks = Object.freeze({
case UMD_DEV:
case UMD_PROD:
case UMD_PROFILING:
+ case UMD_TESTING:
if (dependencies.indexOf('react') === -1) {
// It's only safe to use this fork for modules that depend on React,
// because they read the re-exported API from the SECRET_INTERNALS object.
@@ -159,7 +166,8 @@ const forks = Object.freeze({
if (
bundleType === FB_WWW_DEV ||
bundleType === FB_WWW_PROD ||
- bundleType === FB_WWW_PROFILING
+ bundleType === FB_WWW_PROFILING ||
+ bundleType === FB_WWW_TESTING
) {
return 'scheduler/src/forks/SchedulerFeatureFlags.www.js';
}
@@ -181,6 +189,7 @@ const forks = Object.freeze({
'shared/consoleWithStackDev': (bundleType, entry) => {
switch (bundleType) {
case FB_WWW_DEV:
+ case FB_WWW_TESTING:
return 'shared/forks/consoleWithStackDev.www.js';
default:
return null;
@@ -194,6 +203,7 @@ const forks = Object.freeze({
case FB_WWW_DEV:
case FB_WWW_PROD:
case FB_WWW_PROFILING:
+ case FB_WWW_TESTING:
return 'react/src/forks/ReactCurrentOwner.www.js';
default:
return null;
@@ -207,6 +217,7 @@ const forks = Object.freeze({
case FB_WWW_DEV:
case FB_WWW_PROD:
case FB_WWW_PROFILING:
+ case FB_WWW_TESTING:
return 'react/src/forks/ReactCurrentDispatcher.www.js';
default:
return null;
@@ -218,6 +229,7 @@ const forks = Object.freeze({
case UMD_DEV:
case UMD_PROD:
case UMD_PROFILING:
+ case UMD_TESTING:
return 'react/src/forks/ReactSharedInternals.umd.js';
default:
return null;
@@ -230,6 +242,7 @@ const forks = Object.freeze({
case FB_WWW_DEV:
case FB_WWW_PROD:
case FB_WWW_PROFILING:
+ case FB_WWW_TESTING:
return 'shared/forks/invokeGuardedCallbackImpl.www.js';
default:
return null;
@@ -242,6 +255,7 @@ const forks = Object.freeze({
case FB_WWW_DEV:
case FB_WWW_PROD:
case FB_WWW_PROFILING:
+ case FB_WWW_TESTING:
// Use the www fork which shows an error dialog.
return 'react-reconciler/src/forks/ReactFiberErrorDialog.www.js';
case RN_OSS_DEV:
@@ -378,6 +392,7 @@ const forks = Object.freeze({
case FB_WWW_DEV:
case FB_WWW_PROD:
case FB_WWW_PROFILING:
+ case FB_WWW_TESTING:
// Use the www fork which is integrated with TimeSlice profiling.
return 'react-dom/src/events/forks/EventListener-www.js';
default:
diff --git a/scripts/rollup/modules.js b/scripts/rollup/modules.js
index 968f98bc3f081..e748e56b25286 100644
--- a/scripts/rollup/modules.js
+++ b/scripts/rollup/modules.js
@@ -1,11 +1,12 @@
'use strict';
const forks = require('./forks');
-const bundleTypes = require('./bundles').bundleTypes;
-
-const UMD_DEV = bundleTypes.UMD_DEV;
-const UMD_PROD = bundleTypes.UMD_PROD;
-const UMD_PROFILING = bundleTypes.UMD_PROFILING;
+const {
+ UMD_DEV,
+ UMD_PROD,
+ UMD_PROFILING,
+ UMD_TESTING,
+} = require('./bundles').bundleTypes;
// For any external that is used in a DEV-only condition, explicitly
// specify whether it has side effects during import or not. This lets
@@ -40,7 +41,8 @@ function getPeerGlobals(externals, bundleType) {
!knownGlobals[name] &&
(bundleType === UMD_DEV ||
bundleType === UMD_PROD ||
- bundleType === UMD_PROFILING)
+ bundleType === UMD_PROFILING ||
+ bundleType === UMD_TESTING)
) {
throw new Error('Cannot build UMD without a global name for: ' + name);
}
diff --git a/scripts/rollup/packaging.js b/scripts/rollup/packaging.js
index c5ed04df5ab6c..98a7ad14c806d 100644
--- a/scripts/rollup/packaging.js
+++ b/scripts/rollup/packaging.js
@@ -13,12 +13,15 @@ const {
UMD_DEV,
UMD_PROD,
UMD_PROFILING,
+ UMD_TESTING,
NODE_DEV,
NODE_PROD,
NODE_PROFILING,
+ NODE_TESTING,
FB_WWW_DEV,
FB_WWW_PROD,
FB_WWW_PROFILING,
+ FB_WWW_TESTING,
RN_OSS_DEV,
RN_OSS_PROD,
RN_OSS_PROFILING,
@@ -39,10 +42,12 @@ function getBundleOutputPaths(bundleType, filename, packageName) {
case NODE_DEV:
case NODE_PROD:
case NODE_PROFILING:
+ case NODE_TESTING:
return [`build/node_modules/${packageName}/cjs/${filename}`];
case UMD_DEV:
case UMD_PROD:
case UMD_PROFILING:
+ case UMD_TESTING:
return [
`build/node_modules/${packageName}/umd/${filename}`,
`build/dist/${filename}`,
@@ -50,6 +55,7 @@ function getBundleOutputPaths(bundleType, filename, packageName) {
case FB_WWW_DEV:
case FB_WWW_PROD:
case FB_WWW_PROFILING:
+ case FB_WWW_TESTING:
return [`build/facebook-www/${filename}`];
case RN_OSS_DEV:
case RN_OSS_PROD:
diff --git a/scripts/rollup/wrappers.js b/scripts/rollup/wrappers.js
index 7f17491936968..5db32f2fbd0bb 100644
--- a/scripts/rollup/wrappers.js
+++ b/scripts/rollup/wrappers.js
@@ -1,25 +1,30 @@
'use strict';
-const Bundles = require('./bundles');
+const {bundleTypes, moduleTypes} = require('./bundles');
const reactVersion = require('../../package.json').version;
-const UMD_DEV = Bundles.bundleTypes.UMD_DEV;
-const UMD_PROD = Bundles.bundleTypes.UMD_PROD;
-const UMD_PROFILING = Bundles.bundleTypes.UMD_PROFILING;
-const NODE_DEV = Bundles.bundleTypes.NODE_DEV;
-const NODE_PROD = Bundles.bundleTypes.NODE_PROD;
-const NODE_PROFILING = Bundles.bundleTypes.NODE_PROFILING;
-const FB_WWW_DEV = Bundles.bundleTypes.FB_WWW_DEV;
-const FB_WWW_PROD = Bundles.bundleTypes.FB_WWW_PROD;
-const FB_WWW_PROFILING = Bundles.bundleTypes.FB_WWW_PROFILING;
-const RN_OSS_DEV = Bundles.bundleTypes.RN_OSS_DEV;
-const RN_OSS_PROD = Bundles.bundleTypes.RN_OSS_PROD;
-const RN_OSS_PROFILING = Bundles.bundleTypes.RN_OSS_PROFILING;
-const RN_FB_DEV = Bundles.bundleTypes.RN_FB_DEV;
-const RN_FB_PROD = Bundles.bundleTypes.RN_FB_PROD;
-const RN_FB_PROFILING = Bundles.bundleTypes.RN_FB_PROFILING;
-
-const RECONCILER = Bundles.moduleTypes.RECONCILER;
+const {
+ UMD_DEV,
+ UMD_PROD,
+ UMD_PROFILING,
+ UMD_TESTING,
+ NODE_DEV,
+ NODE_PROD,
+ NODE_PROFILING,
+ NODE_TESTING,
+ FB_WWW_DEV,
+ FB_WWW_PROD,
+ FB_WWW_PROFILING,
+ FB_WWW_TESTING,
+ RN_OSS_DEV,
+ RN_OSS_PROD,
+ RN_OSS_PROFILING,
+ RN_FB_DEV,
+ RN_FB_PROD,
+ RN_FB_PROFILING,
+} = bundleTypes;
+
+const {RECONCILER} = moduleTypes;
const license = ` * Copyright (c) Facebook, Inc. and its affiliates.
*
@@ -60,6 +65,16 @@ ${license}
${source}`;
},
+ /***************** UMD_TESTING *****************/
+ [UMD_TESTING](source, globalName, filename, moduleType) {
+ return `/** @license React v${reactVersion}
+ * ${filename}
+ *
+${license}
+ */
+${source}`;
+ },
+
/***************** NODE_DEV *****************/
[NODE_DEV](source, globalName, filename, moduleType) {
return `/** @license React v${reactVersion}
@@ -125,6 +140,25 @@ ${
${source}`;
},
+ /***************** NODE_TESTING *****************/
+ [NODE_TESTING](source, globalName, filename, moduleType) {
+ return `/** @license React v${reactVersion}
+ * ${filename}
+ *
+${license}
+ */
+${
+ globalName === 'ReactNoopRenderer' ||
+ globalName === 'ReactNoopRendererPersistent'
+ ? // React Noop needs regenerator runtime because it uses
+ // generators but GCC doesn't handle them in the output.
+ // So we use Babel for them.
+ `const regeneratorRuntime = require("regenerator-runtime");`
+ : ``
+}
+${source}`;
+ },
+
/****************** FB_WWW_DEV ******************/
[FB_WWW_DEV](source, globalName, filename, moduleType) {
return `/**
@@ -167,6 +201,19 @@ ${license}
* @preserve-invariant-messages
*/
+${source}`;
+ },
+
+ /****************** FB_WWW_TESTING ******************/
+ [FB_WWW_TESTING](source, globalName, filename, moduleType) {
+ return `/**
+${license}
+ *
+ * @noflow
+ * @preventMunge
+ * @preserve-invariant-messages
+ */
+
${source}`;
},
@@ -293,6 +340,21 @@ ${source}
${license}
*/
module.exports = function $$$reconciler($$$hostConfig) {
+${source}
+ var $$$renderer = module.exports;
+ module.exports = $$$reconciler;
+ return $$$renderer;
+};`;
+ },
+
+ /***************** NODE_TESTING (reconciler only) *****************/
+ [NODE_TESTING](source, globalName, filename, moduleType) {
+ return `/** @license React v${reactVersion}
+ * ${filename}
+ *
+${license}
+ */
+module.exports = function $$$reconciler($$$hostConfig) {
${source}
var $$$renderer = module.exports;
module.exports = $$$reconciler;