diff --git a/fixtures/dom/.gitignore b/fixtures/dom/.gitignore
index 9f05c1cc2b73d..724d1459422c9 100644
--- a/fixtures/dom/.gitignore
+++ b/fixtures/dom/.gitignore
@@ -14,6 +14,8 @@ public/react-dom.development.js
public/react-dom.production.min.js
public/react-dom-server.browser.development.js
public/react-dom-server.browser.production.min.js
+public/react-dom-test-utils.development.js
+public/react-dom-test-utils.production.min.js
# misc
.DS_Store
diff --git a/fixtures/dom/package.json b/fixtures/dom/package.json
index 940b66736ce4e..f5f84257f7c82 100644
--- a/fixtures/dom/package.json
+++ b/fixtures/dom/package.json
@@ -18,7 +18,7 @@
},
"scripts": {
"start": "react-scripts start",
- "prestart": "cp ../../build/node_modules/react/umd/react.development.js ../../build/node_modules/react-dom/umd/react-dom.development.js ../../build/node_modules/react/umd/react.production.min.js ../../build/node_modules/react-dom/umd/react-dom.production.min.js ../../build/node_modules/react-dom/umd/react-dom-server.browser.development.js ../../build/node_modules/react-dom/umd/react-dom-server.browser.production.min.js public/",
+ "prestart": "cp ../../build/node_modules/react/umd/react.development.js ../../build/node_modules/react-dom/umd/react-dom.development.js ../../build/node_modules/react/umd/react.production.min.js ../../build/node_modules/react-dom/umd/react-dom.production.min.js ../../build/node_modules/react-dom/umd/react-dom-server.browser.development.js ../../build/node_modules/react-dom/umd/react-dom-server.browser.production.min.js ../../build/node_modules/react-dom/umd/react-dom-test-utils.development.js ../../build/node_modules/react-dom/umd/react-dom-test-utils.production.min.js public/",
"build": "react-scripts build && cp build/index.html build/200.html",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
diff --git a/fixtures/dom/public/act-dom.html b/fixtures/dom/public/act-dom.html
new file mode 100644
index 0000000000000..2fb4a437721df
--- /dev/null
+++ b/fixtures/dom/public/act-dom.html
@@ -0,0 +1,41 @@
+
+
+
+ sanity test for ReactTestUtils.act
+
+
+ this page tests whether act runs properly in a browser.
+
+ your console should say "5"
+
+
+
+
+
+
diff --git a/packages/react-dom/src/__tests__/ReactTestUtils-test.js b/packages/react-dom/src/__tests__/ReactTestUtils-test.js
index 3e35c28dbc20d..687dbd1aaec2c 100644
--- a/packages/react-dom/src/__tests__/ReactTestUtils-test.js
+++ b/packages/react-dom/src/__tests__/ReactTestUtils-test.js
@@ -14,7 +14,6 @@ let React;
let ReactDOM;
let ReactDOMServer;
let ReactTestUtils;
-let act;
function getTestDocument(markup) {
const doc = document.implementation.createHTMLDocument('');
@@ -34,7 +33,6 @@ describe('ReactTestUtils', () => {
ReactDOM = require('react-dom');
ReactDOMServer = require('react-dom/server');
ReactTestUtils = require('react-dom/test-utils');
- act = ReactTestUtils.act;
});
it('Simulate should have locally attached media events', () => {
@@ -517,173 +515,4 @@ describe('ReactTestUtils', () => {
ReactTestUtils.renderIntoDocument();
expect(mockArgs.length).toEqual(0);
});
-
- it('can use act to batch effects', () => {
- function App(props) {
- React.useEffect(props.callback);
- return null;
- }
- const container = document.createElement('div');
- document.body.appendChild(container);
-
- try {
- let called = false;
- act(() => {
- ReactDOM.render(
- {
- called = true;
- }}
- />,
- container,
- );
- });
-
- expect(called).toBe(true);
- } finally {
- document.body.removeChild(container);
- }
- });
-
- it('flushes effects on every call', () => {
- function App(props) {
- let [ctr, setCtr] = React.useState(0);
- React.useEffect(() => {
- props.callback(ctr);
- });
- return (
-
- );
- }
-
- const container = document.createElement('div');
- document.body.appendChild(container);
- let calledCtr = 0;
- act(() => {
- ReactDOM.render(
- {
- calledCtr = val;
- }}
- />,
- container,
- );
- });
- const button = document.getElementById('button');
- function click() {
- button.dispatchEvent(new MouseEvent('click', {bubbles: true}));
- }
-
- act(() => {
- click();
- click();
- click();
- });
- expect(calledCtr).toBe(3);
- act(click);
- expect(calledCtr).toBe(4);
- act(click);
- expect(calledCtr).toBe(5);
-
- document.body.removeChild(container);
- });
-
- it('can use act to batch effects on updates too', () => {
- function App() {
- let [ctr, setCtr] = React.useState(0);
- return (
-
- );
- }
- const container = document.createElement('div');
- document.body.appendChild(container);
- let button;
- act(() => {
- ReactDOM.render(, container);
- });
- button = document.getElementById('button');
- expect(button.innerHTML).toBe('0');
- act(() => {
- button.dispatchEvent(new MouseEvent('click', {bubbles: true}));
- });
- expect(button.innerHTML).toBe('1');
- document.body.removeChild(container);
- });
-
- it('detects setState being called outside of act(...)', () => {
- let setValueRef = null;
- function App() {
- let [value, setValue] = React.useState(0);
- setValueRef = setValue;
- return (
-
- );
- }
- const container = document.createElement('div');
- document.body.appendChild(container);
- let button;
- act(() => {
- ReactDOM.render(, container);
- button = container.querySelector('#button');
- button.dispatchEvent(new MouseEvent('click', {bubbles: true}));
- });
- expect(button.innerHTML).toBe('2');
- expect(() => setValueRef(1)).toWarnDev([
- 'An update to App inside a test was not wrapped in act(...).',
- ]);
- document.body.removeChild(container);
- });
-
- it('lets a ticker update', () => {
- function App() {
- let [toggle, setToggle] = React.useState(0);
- React.useEffect(() => {
- let timeout = setTimeout(() => {
- setToggle(1);
- }, 200);
- return () => clearTimeout(timeout);
- });
- return toggle;
- }
- const container = document.createElement('div');
-
- act(() => {
- act(() => {
- ReactDOM.render(, container);
- });
- jest.advanceTimersByTime(250);
- });
-
- expect(container.innerHTML).toBe('1');
- });
-
- it('warns if you return a value inside act', () => {
- expect(() => act(() => null)).toWarnDev(
- [
- 'The callback passed to ReactTestUtils.act(...) function must not return anything.',
- ],
- {withoutStack: true},
- );
- expect(() => act(() => 123)).toWarnDev(
- [
- 'The callback passed to ReactTestUtils.act(...) function must not return anything.',
- ],
- {withoutStack: true},
- );
- });
-
- it('warns if you try to await an .act call', () => {
- expect(act(() => {}).then).toWarnDev(
- [
- 'Do not await the result of calling ReactTestUtils.act(...), it is not a Promise.',
- ],
- {withoutStack: true},
- );
- });
});
diff --git a/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js b/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js
new file mode 100644
index 0000000000000..6e037088c9204
--- /dev/null
+++ b/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js
@@ -0,0 +1,403 @@
+/**
+ * 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 ReactTestUtils;
+let act;
+
+jest.useRealTimers();
+
+function sleep(period) {
+ return new Promise(resolve => {
+ setTimeout(() => {
+ resolve(true);
+ }, period);
+ });
+}
+
+describe('ReactTestUtils.act()', () => {
+ beforeEach(() => {
+ jest.resetModules();
+ React = require('react');
+ ReactDOM = require('react-dom');
+ ReactTestUtils = require('react-dom/test-utils');
+ act = ReactTestUtils.act;
+ });
+
+ describe('sync', () => {
+ it('can use act to flush effects', () => {
+ function App(props) {
+ React.useEffect(props.callback);
+ return null;
+ }
+
+ let calledLog = [];
+ act(() => {
+ ReactDOM.render(
+ {
+ calledLog.push(calledLog.length);
+ }}
+ />,
+ document.createElement('div'),
+ );
+ });
+
+ expect(calledLog).toEqual([0]);
+ });
+
+ it('flushes effects on every call', () => {
+ function App(props) {
+ let [ctr, setCtr] = React.useState(0);
+ React.useEffect(() => {
+ props.callback(ctr);
+ });
+ return (
+
+ );
+ }
+
+ const container = document.createElement('div');
+ // attach to body so events works
+ document.body.appendChild(container);
+ let calledCounter = 0;
+ act(() => {
+ ReactDOM.render(
+ {
+ calledCounter = val;
+ }}
+ />,
+ container,
+ );
+ });
+ const button = document.getElementById('button');
+ function click() {
+ button.dispatchEvent(new MouseEvent('click', {bubbles: true}));
+ }
+
+ act(() => {
+ click();
+ click();
+ click();
+ });
+ expect(calledCounter).toBe(3);
+ act(click);
+ expect(calledCounter).toBe(4);
+ act(click);
+ expect(calledCounter).toBe(5);
+ expect(button.innerHTML).toBe('5');
+
+ document.body.removeChild(container);
+ });
+
+ it('should flush effects recursively', () => {
+ function App() {
+ let [ctr, setCtr] = React.useState(0);
+ React.useEffect(() => {
+ if (ctr < 5) {
+ setCtr(x => x + 1);
+ }
+ });
+ return ctr;
+ }
+
+ const container = document.createElement('div');
+ act(() => {
+ ReactDOM.render(, container);
+ });
+
+ expect(container.innerHTML).toBe('5');
+ });
+
+ it('detects setState being called outside of act(...)', () => {
+ let setValue = null;
+ function App() {
+ let [value, _setValue] = React.useState(0);
+ setValue = _setValue;
+ return (
+
+ );
+ }
+ const container = document.createElement('div');
+ document.body.appendChild(container);
+ let button;
+ act(() => {
+ ReactDOM.render(, container);
+ button = container.querySelector('#button');
+ button.dispatchEvent(new MouseEvent('click', {bubbles: true}));
+ });
+ expect(button.innerHTML).toBe('2');
+ expect(() => setValue(1)).toWarnDev([
+ 'An update to App inside a test was not wrapped in act(...).',
+ ]);
+ document.body.removeChild(container);
+ });
+ describe('fake timers', () => {
+ beforeEach(() => {
+ jest.useFakeTimers();
+ });
+ afterEach(() => {
+ jest.useRealTimers();
+ });
+ it('lets a ticker update', () => {
+ function App() {
+ let [toggle, setToggle] = React.useState(0);
+ React.useEffect(() => {
+ let timeout = setTimeout(() => {
+ setToggle(1);
+ }, 200);
+ return () => clearTimeout(timeout);
+ }, []);
+ return toggle;
+ }
+ const container = document.createElement('div');
+
+ act(() => {
+ ReactDOM.render(, container);
+ });
+ act(() => {
+ jest.runAllTimers();
+ });
+
+ expect(container.innerHTML).toBe('1');
+ });
+ it('can use the async version to catch microtasks', async () => {
+ function App() {
+ let [toggle, setToggle] = React.useState(0);
+ React.useEffect(() => {
+ // just like the previous test, except we
+ // use a promise and schedule the update
+ // after it resolves
+ sleep(200).then(() => setToggle(1));
+ }, []);
+ return toggle;
+ }
+ const container = document.createElement('div');
+
+ act(() => {
+ ReactDOM.render(, container);
+ });
+ await act(async () => {
+ jest.runAllTimers();
+ });
+
+ expect(container.innerHTML).toBe('1');
+ });
+ it('can handle cascading promises with fake timers', async () => {
+ // this component triggers an effect, that waits a tick,
+ // then sets state. repeats this 5 times.
+ function App() {
+ let [state, setState] = React.useState(0);
+ async function ticker() {
+ await null;
+ setState(x => x + 1);
+ }
+ React.useEffect(
+ () => {
+ ticker();
+ },
+ [Math.min(state, 4)],
+ );
+ return state;
+ }
+ const el = document.createElement('div');
+ await act(async () => {
+ ReactDOM.render(, el);
+ });
+
+ // all 5 ticks present and accounted for
+ expect(el.innerHTML).toBe('5');
+ });
+ });
+
+ it('warns if you return a value inside act', () => {
+ expect(() => act(() => null)).toWarnDev(
+ [
+ 'The callback passed to act(...) function must return undefined, or a Promise.',
+ ],
+ {withoutStack: true},
+ );
+ expect(() => act(() => 123)).toWarnDev(
+ [
+ 'The callback passed to act(...) function must return undefined, or a Promise.',
+ ],
+ {withoutStack: true},
+ );
+ });
+
+ it('warns if you try to await an .act call', () => {
+ expect(() => act(() => {}).then(() => {})).toWarnDev(
+ [
+ 'Do not await the result of calling act(...) with sync logic, it is not a Promise.',
+ ],
+ {withoutStack: true},
+ );
+ });
+ });
+ describe('asynchronous tests', () => {
+ it('can handle timers', async () => {
+ function App() {
+ let [ctr, setCtr] = React.useState(0);
+ function doSomething() {
+ setTimeout(() => {
+ setCtr(1);
+ }, 50);
+ }
+
+ React.useEffect(() => {
+ doSomething();
+ }, []);
+ return ctr;
+ }
+ const el = document.createElement('div');
+ await act(async () => {
+ act(() => {
+ ReactDOM.render(, el);
+ });
+
+ await sleep(100);
+ expect(el.innerHTML).toBe('1');
+ });
+ });
+
+ it('can handle async/await', async () => {
+ function App() {
+ let [ctr, setCtr] = React.useState(0);
+ async function someAsyncFunction() {
+ // queue a bunch of promises to be sure they all flush
+ await null;
+ await null;
+ await null;
+ setCtr(1);
+ }
+ React.useEffect(() => {
+ someAsyncFunction();
+ }, []);
+ return ctr;
+ }
+ const el = document.createElement('div');
+
+ await act(async () => {
+ act(() => {
+ ReactDOM.render(, el);
+ });
+ // pending promises will close before this ends
+ });
+ expect(el.innerHTML).toEqual('1');
+ });
+
+ it('warns if you do not await an act call', async () => {
+ spyOnDevAndProd(console, 'error');
+ act(async () => {});
+ // it's annoying that we have to wait a tick before this warning comes in
+ await sleep(0);
+ if (__DEV__) {
+ expect(console.error.calls.count()).toEqual(1);
+ expect(console.error.calls.argsFor(0)[0]).toMatch(
+ 'You called act(async () => ...) without await.',
+ );
+ }
+ });
+
+ it('warns if you try to interleave multiple act calls', async () => {
+ spyOnDevAndProd(console, 'error');
+ // let's try to cheat and spin off a 'thread' with an act call
+ (async () => {
+ await act(async () => {
+ await sleep(50);
+ });
+ })();
+
+ await act(async () => {
+ await sleep(100);
+ });
+
+ await sleep(150);
+ if (__DEV__) {
+ expect(console.error).toHaveBeenCalledTimes(1);
+ }
+ });
+
+ it('commits and effects are guaranteed to be flushed', async () => {
+ function App(props) {
+ let [state, setState] = React.useState(0);
+ async function something() {
+ await null;
+ setState(1);
+ }
+ React.useEffect(() => {
+ something();
+ }, []);
+ React.useEffect(() => {
+ props.callback();
+ });
+ return state;
+ }
+ let ctr = 0;
+ const div = document.createElement('div');
+
+ await act(async () => {
+ act(() => {
+ ReactDOM.render( ctr++} />, div);
+ });
+ expect(div.innerHTML).toBe('0');
+ expect(ctr).toBe(1);
+ });
+ // this may seem odd, but it matches user behaviour -
+ // a flash of "0" followed by "1"
+
+ expect(div.innerHTML).toBe('1');
+ expect(ctr).toBe(2);
+ });
+
+ it('propagates errors', async () => {
+ let err;
+ try {
+ await act(async () => {
+ throw new Error('some error');
+ });
+ } catch (_err) {
+ err = _err;
+ } finally {
+ expect(err instanceof Error).toBe(true);
+ expect(err.message).toBe('some error');
+ }
+ });
+ it('can handle cascading promises', async () => {
+ // this component triggers an effect, that waits a tick,
+ // then sets state. repeats this 5 times.
+ function App() {
+ let [state, setState] = React.useState(0);
+ async function ticker() {
+ await null;
+ setState(x => x + 1);
+ }
+ React.useEffect(
+ () => {
+ ticker();
+ },
+ [Math.min(state, 4)],
+ );
+ return state;
+ }
+ const el = document.createElement('div');
+ await act(async () => {
+ ReactDOM.render(, el);
+ });
+ // all 5 ticks present and accounted for
+ expect(el.innerHTML).toBe('5');
+ });
+ });
+});
diff --git a/packages/react-dom/src/client/ReactDOM.js b/packages/react-dom/src/client/ReactDOM.js
index e226843322f78..728f775adf5e2 100644
--- a/packages/react-dom/src/client/ReactDOM.js
+++ b/packages/react-dom/src/client/ReactDOM.js
@@ -35,6 +35,7 @@ import {
getPublicRootInstance,
findHostInstance,
findHostInstanceWithWarning,
+ flushPassiveEffects,
} from 'react-reconciler/inline.dom';
import {createPortal as createPortalImpl} from 'shared/ReactPortal';
import {canUseDOM} from 'shared/ExecutionEnvironment';
@@ -807,7 +808,7 @@ const ReactDOM: Object = {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: {
// Keep in sync with ReactDOMUnstableNativeDependencies.js
- // and ReactTestUtils.js. This is an array for better minification.
+ // ReactTestUtils.js, and ReactTestUtilsAct.js. This is an array for better minification.
Events: [
getInstanceFromNode,
getNodeFromInstance,
@@ -820,6 +821,7 @@ const ReactDOM: Object = {
restoreStateIfNeeded,
dispatchEvent,
runEventsInBatch,
+ flushPassiveEffects,
],
},
};
diff --git a/packages/react-dom/src/fire/ReactFire.js b/packages/react-dom/src/fire/ReactFire.js
index 525f935882254..e4f555ebd627d 100644
--- a/packages/react-dom/src/fire/ReactFire.js
+++ b/packages/react-dom/src/fire/ReactFire.js
@@ -40,6 +40,7 @@ import {
getPublicRootInstance,
findHostInstance,
findHostInstanceWithWarning,
+ flushPassiveEffects,
} from 'react-reconciler/inline.fire';
import {createPortal as createPortalImpl} from 'shared/ReactPortal';
import {canUseDOM} from 'shared/ExecutionEnvironment';
@@ -826,6 +827,7 @@ const ReactDOM: Object = {
restoreStateIfNeeded,
dispatchEvent,
runEventsInBatch,
+ flushPassiveEffects,
],
},
};
diff --git a/packages/react-dom/src/test-utils/ReactTestUtils.js b/packages/react-dom/src/test-utils/ReactTestUtils.js
index 06c39357fb29e..f463cc4aa6a32 100644
--- a/packages/react-dom/src/test-utils/ReactTestUtils.js
+++ b/packages/react-dom/src/test-utils/ReactTestUtils.js
@@ -4,6 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
+import type {Thenable} from 'react-reconciler/src/ReactFiberScheduler';
import React from 'react';
import ReactDOM from 'react-dom';
@@ -22,15 +23,11 @@ import warningWithoutStack from 'shared/warningWithoutStack';
import {ELEMENT_NODE} from '../shared/HTMLNodeType';
import * as DOMTopLevelEventTypes from '../events/DOMTopLevelEventTypes';
import {PLUGIN_EVENT_SYSTEM} from 'events/EventSystemFlags';
-
-// for .act's return value
-type Thenable = {
- then(resolve: () => mixed, reject?: () => mixed): mixed,
-};
+import act from './ReactTestUtilsAct';
const {findDOMNode} = ReactDOM;
// Keep in sync with ReactDOMUnstableNativeDependencies.js
-// and ReactDOM.js:
+// ReactDOM.js, and ReactTestUtilsAct.js:
const [
getInstanceFromNode,
/* eslint-disable no-unused-vars */
@@ -45,6 +42,8 @@ const [
restoreStateIfNeeded,
dispatchEvent,
runEventsInBatch,
+ // eslint-disable-next-line no-unused-vars
+ flushPassiveEffects,
] = ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Events;
function Event(suffix) {}
@@ -152,9 +151,12 @@ function validateClassInstance(inst, methodName) {
);
}
-// a stub element, lazily initialized, used by act() when flushing effects
+// a plain dom element, lazily initialized, used by act() when flushing effects
let actContainerElement = null;
+// a warning for when you try to use TestUtils.act in a non-browser environment
+let didWarnAboutActInNodejs = false;
+
/**
* Utilities for making it easy to test React components.
*
@@ -391,57 +393,24 @@ const ReactTestUtils = {
Simulate: null,
SimulateNative: {},
- act(callback: () => void): Thenable {
+ act(callback: () => Thenable) {
if (actContainerElement === null) {
- // warn if we can't actually create the stub element
if (__DEV__) {
- warningWithoutStack(
- typeof document !== 'undefined' &&
- document !== null &&
- typeof document.createElement === 'function',
- 'It looks like you called TestUtils.act(...) in a non-browser environment. ' +
- "If you're using TestRenderer for your tests, you should call " +
- 'TestRenderer.act(...) instead of TestUtils.act(...).',
- );
- }
- // then make it
- actContainerElement = document.createElement('div');
- }
-
- const result = ReactDOM.unstable_batchedUpdates(callback);
- // note: keep these warning messages in sync with
- // createReactNoop.js and ReactTestRenderer.js
- if (__DEV__) {
- if (result !== undefined) {
- let addendum;
- if (result !== null && typeof result.then === 'function') {
- addendum =
- '\n\nIt looks like you wrote ReactTestUtils.act(async () => ...), ' +
- 'or returned a Promise from the callback passed to it. ' +
- 'Putting asynchronous logic inside ReactTestUtils.act(...) is not supported.\n';
- } else {
- addendum = ' You returned: ' + result;
- }
- warningWithoutStack(
- false,
- 'The callback passed to ReactTestUtils.act(...) function must not return anything.%s',
- addendum,
- );
- }
- }
- ReactDOM.render(, actContainerElement);
- // we want the user to not expect a return,
- // but we want to warn if they use it like they can await on it.
- return {
- then() {
- if (__DEV__) {
+ // warn if we're trying to use this in something like node (without jsdom)
+ if (didWarnAboutActInNodejs === false) {
+ didWarnAboutActInNodejs = true;
warningWithoutStack(
- false,
- 'Do not await the result of calling ReactTestUtils.act(...), it is not a Promise.',
+ typeof document !== 'undefined' && document !== null,
+ 'It looks like you called ReactTestUtils.act(...) in a non-browser environment. ' +
+ "If you're using TestRenderer for your tests, you should call " +
+ 'ReactTestRenderer.act(...) instead of ReactTestUtils.act(...).',
);
}
- },
- };
+ }
+ // now make the stub element
+ actContainerElement = document.createElement('div');
+ }
+ return act(callback);
},
};
diff --git a/packages/react-dom/src/test-utils/ReactTestUtilsAct.js b/packages/react-dom/src/test-utils/ReactTestUtilsAct.js
new file mode 100644
index 0000000000000..99cb73ede7c10
--- /dev/null
+++ b/packages/react-dom/src/test-utils/ReactTestUtilsAct.js
@@ -0,0 +1,172 @@
+/**
+ * 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 'react-reconciler/src/ReactFiberScheduler';
+
+import warningWithoutStack from 'shared/warningWithoutStack';
+import ReactDOM from 'react-dom';
+import ReactSharedInternals from 'shared/ReactSharedInternals';
+import enqueueTask from 'shared/enqueueTask';
+
+// Keep in sync with ReactDOMUnstableNativeDependencies.js
+// ReactDOM.js, and ReactTestUtils.js:
+const [
+ /* eslint-disable no-unused-vars */
+ getInstanceFromNode,
+ getNodeFromInstance,
+ getFiberCurrentPropsFromNode,
+ injectEventPluginsByName,
+ eventNameDispatchConfigs,
+ accumulateTwoPhaseDispatches,
+ accumulateDirectDispatches,
+ enqueueStateRestore,
+ restoreStateIfNeeded,
+ dispatchEvent,
+ runEventsInBatch,
+ /* eslint-enable no-unused-vars */
+ flushPassiveEffects,
+] = ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.Events;
+
+const batchedUpdates = ReactDOM.unstable_batchedUpdates;
+
+const {ReactShouldWarnActingUpdates} = ReactSharedInternals;
+
+// this implementation should be exactly the same in
+// ReactTestUtilsAct.js, ReactTestRendererAct.js, createReactNoop.js
+
+// 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;
+
+function flushEffectsAndMicroTasks(onDone: (err: ?Error) => void) {
+ try {
+ flushPassiveEffects();
+ enqueueTask(() => {
+ if (flushPassiveEffects()) {
+ flushEffectsAndMicroTasks(onDone);
+ } else {
+ onDone();
+ }
+ });
+ } catch (err) {
+ onDone(err);
+ }
+}
+
+function act(callback: () => Thenable) {
+ let previousActingUpdatesScopeDepth;
+ if (__DEV__) {
+ previousActingUpdatesScopeDepth = actingUpdatesScopeDepth;
+ actingUpdatesScopeDepth++;
+ ReactShouldWarnActingUpdates.current = true;
+ }
+
+ function onDone() {
+ if (__DEV__) {
+ actingUpdatesScopeDepth--;
+ if (actingUpdatesScopeDepth === 0) {
+ ReactShouldWarnActingUpdates.current = false;
+ }
+ if (actingUpdatesScopeDepth > previousActingUpdatesScopeDepth) {
+ // if it's _less than_ previousActingUpdatesScopeDepth, then we can assume the 'other' one has warned
+ warningWithoutStack(
+ null,
+ 'You seem to have overlapping act() calls, this is not supported. ' +
+ 'Be sure to await previous act() calls before making a new one. ',
+ );
+ }
+ }
+ }
+
+ const result = batchedUpdates(callback);
+ 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) {
+ warningWithoutStack(
+ null,
+ '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(
+ () => {
+ flushEffectsAndMicroTasks((err: ?Error) => {
+ onDone();
+ if (err) {
+ reject(err);
+ } else {
+ resolve();
+ }
+ });
+ },
+ err => {
+ onDone();
+ reject(err);
+ },
+ );
+ },
+ };
+ } else {
+ if (__DEV__) {
+ warningWithoutStack(
+ result === undefined,
+ 'The callback passed to act(...) function ' +
+ 'must return undefined, or a Promise. You returned %s',
+ result,
+ );
+ }
+
+ // flush effects until none remain, and cleanup
+ try {
+ while (flushPassiveEffects()) {}
+ onDone();
+ } catch (err) {
+ onDone();
+ throw err;
+ }
+
+ // in the sync case, the returned thenable only warns *if* await-ed
+ return {
+ then(resolve: () => void) {
+ if (__DEV__) {
+ warningWithoutStack(
+ false,
+ 'Do not await the result of calling act(...) with sync logic, it is not a Promise.',
+ );
+ }
+ resolve();
+ },
+ };
+ }
+}
+
+export default act;
diff --git a/packages/react-dom/src/unstable-native-dependencies/ReactDOMUnstableNativeDependencies.js b/packages/react-dom/src/unstable-native-dependencies/ReactDOMUnstableNativeDependencies.js
index f382613ccce72..d53daa4eda107 100644
--- a/packages/react-dom/src/unstable-native-dependencies/ReactDOMUnstableNativeDependencies.js
+++ b/packages/react-dom/src/unstable-native-dependencies/ReactDOMUnstableNativeDependencies.js
@@ -11,7 +11,7 @@ import ResponderEventPlugin from 'events/ResponderEventPlugin';
import ResponderTouchHistoryStore from 'events/ResponderTouchHistoryStore';
// Inject react-dom's ComponentTree into this module.
-// Keep in sync with ReactDOM.js and ReactTestUtils.js:
+// Keep in sync with ReactDOM.js, ReactTestUtils.js, and ReactTestUtilsAct.js:
const [
getInstanceFromNode,
getNodeFromInstance,
diff --git a/packages/react-noop-renderer/src/createReactNoop.js b/packages/react-noop-renderer/src/createReactNoop.js
index 8976372bf6c48..55198b6ba43ad 100644
--- a/packages/react-noop-renderer/src/createReactNoop.js
+++ b/packages/react-noop-renderer/src/createReactNoop.js
@@ -14,6 +14,7 @@
* environment.
*/
+import type {Thenable} from 'react-reconciler/src/ReactFiberScheduler';
import type {Fiber} from 'react-reconciler/src/ReactFiber';
import type {UpdateQueue} from 'react-reconciler/src/ReactUpdateQueue';
import type {ReactNodeList} from 'shared/ReactTypes';
@@ -26,16 +27,12 @@ import {
REACT_ELEMENT_TYPE,
REACT_EVENT_TARGET_TOUCH_HIT,
} from 'shared/ReactSymbols';
-import warningWithoutStack from 'shared/warningWithoutStack';
import warning from 'shared/warning';
-
+import enqueueTask from 'shared/enqueueTask';
+import ReactSharedInternals from 'shared/ReactSharedInternals';
+import warningWithoutStack from 'shared/warningWithoutStack';
import {enableEventAPI} from 'shared/ReactFeatureFlags';
-// for .act's return value
-type Thenable = {
- then(resolve: () => mixed, reject?: () => mixed): mixed,
-};
-
type Container = {
rootID: string,
children: Array,
@@ -59,6 +56,8 @@ type TextInstance = {|
|};
type HostContext = Object;
+const {ReactShouldWarnActingUpdates} = ReactSharedInternals;
+
const NO_CONTEXT = {};
const UPPERCASE_CONTEXT = {};
const EVENT_COMPONENT_CONTEXT = {};
@@ -598,6 +597,140 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
const roots = new Map();
const DEFAULT_ROOT_ID = '';
+ const {flushPassiveEffects, batchedUpdates} = NoopRenderer;
+
+ // this act() implementation should be exactly the same in
+ // ReactTestUtilsAct.js, ReactTestRendererAct.js, createReactNoop.js
+
+ let actingUpdatesScopeDepth = 0;
+
+ function flushEffectsAndMicroTasks(onDone: (err: ?Error) => void) {
+ try {
+ flushPassiveEffects();
+ enqueueTask(() => {
+ if (flushPassiveEffects()) {
+ flushEffectsAndMicroTasks(onDone);
+ } else {
+ onDone();
+ }
+ });
+ } catch (err) {
+ onDone(err);
+ }
+ }
+
+ function act(callback: () => Thenable) {
+ let previousActingUpdatesScopeDepth;
+ if (__DEV__) {
+ previousActingUpdatesScopeDepth = actingUpdatesScopeDepth;
+ actingUpdatesScopeDepth++;
+ ReactShouldWarnActingUpdates.current = true;
+ }
+
+ function onDone() {
+ if (__DEV__) {
+ actingUpdatesScopeDepth--;
+ if (actingUpdatesScopeDepth === 0) {
+ ReactShouldWarnActingUpdates.current = false;
+ }
+ if (actingUpdatesScopeDepth > previousActingUpdatesScopeDepth) {
+ // if it's _less than_ previousActingUpdatesScopeDepth, then we can assume the 'other' one has warned
+ warningWithoutStack(
+ null,
+ 'You seem to have overlapping act() calls, this is not supported. ' +
+ 'Be sure to await previous act() calls before making a new one. ',
+ );
+ }
+ }
+ }
+
+ const result = batchedUpdates(callback);
+ 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) {
+ warningWithoutStack(
+ null,
+ '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(
+ () => {
+ flushEffectsAndMicroTasks((err: ?Error) => {
+ onDone();
+ if (err) {
+ reject(err);
+ } else {
+ resolve();
+ }
+ });
+ },
+ err => {
+ onDone();
+ reject(err);
+ },
+ );
+ },
+ };
+ } else {
+ if (__DEV__) {
+ warningWithoutStack(
+ result === undefined,
+ 'The callback passed to act(...) function ' +
+ 'must return undefined, or a Promise. You returned %s',
+ result,
+ );
+ }
+
+ // flush effects until none remain, and cleanup
+ try {
+ while (flushPassiveEffects()) {}
+ onDone();
+ } catch (err) {
+ onDone();
+ throw err;
+ }
+
+ // in the sync case, the returned thenable only warns *if* await-ed
+ return {
+ then(resolve: () => void) {
+ if (__DEV__) {
+ warningWithoutStack(
+ false,
+ 'Do not await the result of calling act(...) with sync logic, it is not a Promise.',
+ );
+ }
+ resolve();
+ },
+ };
+ }
+ }
+
+ // end act() implementation
+
function childToJSX(child, text) {
if (text !== null) {
return text;
@@ -843,56 +976,13 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
interactiveUpdates: NoopRenderer.interactiveUpdates,
- // maybe this should exist only in the test file
- act(callback: () => void): Thenable {
- // note: keep these warning messages in sync with
- // ReactTestRenderer.js and ReactTestUtils.js
- let result = NoopRenderer.batchedUpdates(callback);
- if (__DEV__) {
- if (result !== undefined) {
- let addendum;
- if (result !== null && typeof result.then === 'function') {
- addendum =
- "\n\nIt looks like you wrote ReactNoop.act(async () => ...) or returned a Promise from it's callback. " +
- 'Putting asynchronous logic inside ReactNoop.act(...) is not supported.\n';
- } else {
- addendum = ' You returned: ' + result;
- }
- warningWithoutStack(
- false,
- 'The callback passed to ReactNoop.act(...) function must not return anything.%s',
- addendum,
- );
- }
- }
- ReactNoop.flushPassiveEffects();
- // we want the user to not expect a return,
- // but we want to warn if they use it like they can await on it.
- return {
- then() {
- if (__DEV__) {
- warningWithoutStack(
- false,
- 'Do not await the result of calling ReactNoop.act(...), it is not a Promise.',
- );
- }
- },
- };
- },
-
flushSync(fn: () => mixed) {
NoopRenderer.flushSync(fn);
},
- flushPassiveEffects() {
- // Trick to flush passive effects without exposing an internal API:
- // Create a throwaway root and schedule a dummy update on it.
- const rootID = 'bloopandthenmoreletterstoavoidaconflict';
- const container = {rootID: rootID, pendingChildren: [], children: []};
- rootContainers.set(rootID, container);
- const root = NoopRenderer.createContainer(container, true, false);
- NoopRenderer.updateContainer(null, root, null, null);
- },
+ flushPassiveEffects: NoopRenderer.flushPassiveEffects,
+
+ act,
// Logs the current state of the tree.
dumpTree(rootID: string = DEFAULT_ROOT_ID) {
diff --git a/packages/react-reconciler/src/ReactFiberHooks.js b/packages/react-reconciler/src/ReactFiberHooks.js
index 57d8a7c4edb71..80707120e95fc 100644
--- a/packages/react-reconciler/src/ReactFiberHooks.js
+++ b/packages/react-reconciler/src/ReactFiberHooks.js
@@ -30,10 +30,10 @@ import {
} from './ReactHookEffectTags';
import {
scheduleWork,
- warnIfNotCurrentlyBatchingInDev,
computeExpirationForFiber,
flushPassiveEffects,
requestCurrentTime,
+ warnIfNotCurrentlyActingUpdatesInDev,
} from './ReactFiberScheduler';
import invariant from 'shared/invariant';
@@ -1046,19 +1046,6 @@ function updateMemo(
return nextValue;
}
-// in a test-like environment, we want to warn if dispatchAction()
-// is called outside of a batchedUpdates/TestUtils.act(...) call.
-let shouldWarnForUnbatchedSetState = false;
-
-if (__DEV__) {
- // jest isn't a 'global', it's just exposed to tests via a wrapped function
- // further, this isn't a test file, so flow doesn't recognize the symbol. So...
- // $FlowExpectedError - because requirements don't give a damn about your type sigs.
- if ('undefined' !== typeof jest) {
- shouldWarnForUnbatchedSetState = true;
- }
-}
-
function dispatchAction(
fiber: Fiber,
queue: UpdateQueue,
@@ -1178,8 +1165,11 @@ function dispatchAction(
}
}
if (__DEV__) {
- if (shouldWarnForUnbatchedSetState === true) {
- warnIfNotCurrentlyBatchingInDev(fiber);
+ // jest isn't a 'global', it's just exposed to tests via a wrapped function
+ // further, this isn't a test file, so flow doesn't recognize the symbol. So...
+ // $FlowExpectedError - because requirements don't give a damn about your type sigs.
+ if ('undefined' !== typeof jest) {
+ warnIfNotCurrentlyActingUpdatesInDev(fiber);
}
}
scheduleWork(fiber, expirationTime);
diff --git a/packages/react-reconciler/src/ReactFiberReconciler.js b/packages/react-reconciler/src/ReactFiberReconciler.js
index 01e303c74bc42..eb98b19ce4f0e 100644
--- a/packages/react-reconciler/src/ReactFiberReconciler.js
+++ b/packages/react-reconciler/src/ReactFiberReconciler.js
@@ -310,6 +310,7 @@ export {
flushInteractiveUpdates,
flushControlled,
flushSync,
+ flushPassiveEffects,
};
export function getPublicRootInstance(
diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js
index 5bea74a78b5ff..df179b766e155 100644
--- a/packages/react-reconciler/src/ReactFiberScheduler.js
+++ b/packages/react-reconciler/src/ReactFiberScheduler.js
@@ -34,7 +34,7 @@ import {
flushInteractiveUpdates as flushInteractiveUpdates_old,
computeUniqueAsyncExpiration as computeUniqueAsyncExpiration_old,
flushPassiveEffects as flushPassiveEffects_old,
- warnIfNotCurrentlyBatchingInDev as warnIfNotCurrentlyBatchingInDev_old,
+ warnIfNotCurrentlyActingUpdatesInDev as warnIfNotCurrentlyActingUpdatesInDev_old,
} from './ReactFiberScheduler.old';
import {
@@ -62,7 +62,7 @@ import {
flushInteractiveUpdates as flushInteractiveUpdates_new,
computeUniqueAsyncExpiration as computeUniqueAsyncExpiration_new,
flushPassiveEffects as flushPassiveEffects_new,
- warnIfNotCurrentlyBatchingInDev as warnIfNotCurrentlyBatchingInDev_new,
+ warnIfNotCurrentlyActingUpdatesInDev as warnIfNotCurrentlyActingUpdatesInDev_new,
} from './ReactFiberScheduler.new';
export let requestCurrentTime = requestCurrentTime_old;
@@ -89,7 +89,7 @@ export let interactiveUpdates = interactiveUpdates_old;
export let flushInteractiveUpdates = flushInteractiveUpdates_old;
export let computeUniqueAsyncExpiration = computeUniqueAsyncExpiration_old;
export let flushPassiveEffects = flushPassiveEffects_old;
-export let warnIfNotCurrentlyBatchingInDev = warnIfNotCurrentlyBatchingInDev_old;
+export let warnIfNotCurrentlyActingUpdatesInDev = warnIfNotCurrentlyActingUpdatesInDev_old;
if (enableNewScheduler) {
requestCurrentTime = requestCurrentTime_new;
@@ -116,9 +116,9 @@ if (enableNewScheduler) {
flushInteractiveUpdates = flushInteractiveUpdates_new;
computeUniqueAsyncExpiration = computeUniqueAsyncExpiration_new;
flushPassiveEffects = flushPassiveEffects_new;
- warnIfNotCurrentlyBatchingInDev = warnIfNotCurrentlyBatchingInDev_new;
+ warnIfNotCurrentlyActingUpdatesInDev = warnIfNotCurrentlyActingUpdatesInDev_new;
}
export type Thenable = {
- then(resolve: () => mixed, reject?: () => mixed): mixed,
+ then(resolve: () => mixed, reject?: () => mixed): void | Thenable,
};
diff --git a/packages/react-reconciler/src/ReactFiberScheduler.new.js b/packages/react-reconciler/src/ReactFiberScheduler.new.js
index e4df9dad4fe72..810cbd8847d8d 100644
--- a/packages/react-reconciler/src/ReactFiberScheduler.new.js
+++ b/packages/react-reconciler/src/ReactFiberScheduler.new.js
@@ -33,4 +33,4 @@ export const interactiveUpdates = notYetImplemented;
export const flushInteractiveUpdates = notYetImplemented;
export const computeUniqueAsyncExpiration = notYetImplemented;
export const flushPassiveEffects = notYetImplemented;
-export const warnIfNotCurrentlyBatchingInDev = notYetImplemented;
+export const warnIfNotCurrentlyActingUpdatesInDev = notYetImplemented;
diff --git a/packages/react-reconciler/src/ReactFiberScheduler.old.js b/packages/react-reconciler/src/ReactFiberScheduler.old.js
index 887305457f1d7..1a4b15b2bee99 100644
--- a/packages/react-reconciler/src/ReactFiberScheduler.old.js
+++ b/packages/react-reconciler/src/ReactFiberScheduler.old.js
@@ -176,10 +176,14 @@ const {
} = Scheduler;
export type Thenable = {
- then(resolve: () => mixed, reject?: () => mixed): mixed,
+ then(resolve: () => mixed, reject?: () => mixed): void | Thenable,
};
-const {ReactCurrentDispatcher, ReactCurrentOwner} = ReactSharedInternals;
+const {
+ ReactCurrentDispatcher,
+ ReactCurrentOwner,
+ ReactShouldWarnActingUpdates,
+} = ReactSharedInternals;
let didWarnAboutStateTransition;
let didWarnSetStateChildContext;
@@ -610,6 +614,7 @@ function markLegacyErrorBoundaryAsFailed(instance: mixed) {
}
function flushPassiveEffects() {
+ const didFlushEffects = passiveEffectCallback !== null;
if (passiveEffectCallbackHandle !== null) {
cancelCallback(passiveEffectCallbackHandle);
}
@@ -618,6 +623,7 @@ function flushPassiveEffects() {
// to ensure tracing works correctly.
passiveEffectCallback();
}
+ return didFlushEffects;
}
function commitRoot(root: FiberRoot, finishedWork: Fiber): void {
@@ -1836,9 +1842,20 @@ function scheduleWorkToRoot(fiber: Fiber, expirationTime): FiberRoot | null {
return root;
}
-export function warnIfNotCurrentlyBatchingInDev(fiber: Fiber): void {
+// in a test-like environment, we want to warn if dispatchAction() is
+// called outside of a TestUtils.act(...)/batchedUpdates/render call.
+// so we have a a step counter for when we descend/ascend from
+// act() calls, and test on it for when to warn
+// It's a tuple with a single value. Look for shared/createAct to
+// see how we change the value inside act() calls
+
+export function warnIfNotCurrentlyActingUpdatesInDev(fiber: Fiber): void {
if (__DEV__) {
- if (isRendering === false && isBatchingUpdates === false) {
+ if (
+ isBatchingUpdates === false &&
+ isRendering === false &&
+ ReactShouldWarnActingUpdates.current === false
+ ) {
warningWithoutStack(
false,
'An update to %s inside a test was not wrapped in act(...).\n\n' +
diff --git a/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js b/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js
index 455df5d557064..f3f00658b5d9d 100644
--- a/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js
@@ -1046,11 +1046,10 @@ describe('ReactHooks', () => {
class Cls extends React.Component {
render() {
- act(() =>
- _setState(() => {
- ReactCurrentDispatcher.current.readContext(ThemeContext);
- }),
+ _setState(() =>
+ ReactCurrentDispatcher.current.readContext(ThemeContext),
);
+
return null;
}
}
@@ -1062,13 +1061,7 @@ describe('ReactHooks', () => {
,
),
- ).toWarnDev(
- [
- 'Context can only be read while React is rendering',
- 'Render methods should be a pure function of props and state',
- ],
- {withoutStack: 1},
- );
+ ).toWarnDev(['Context can only be read while React is rendering']);
});
it('warns when calling hooks inside useReducer', () => {
diff --git a/packages/react-reconciler/src/__tests__/ReactNoopRendererAct-test.js b/packages/react-reconciler/src/__tests__/ReactNoopRendererAct-test.js
new file mode 100644
index 0000000000000..f3aba9b99881d
--- /dev/null
+++ b/packages/react-reconciler/src/__tests__/ReactNoopRendererAct-test.js
@@ -0,0 +1,63 @@
+/**
+ * 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.
+ *
+ * @jest-environment node
+ */
+
+// sanity tests for ReactNoop.act()
+
+jest.useRealTimers();
+const React = require('react');
+const ReactNoop = require('react-noop-renderer');
+const Scheduler = require('scheduler');
+
+describe('ReactNoop.act()', () => {
+ it('can use act to flush effects', async () => {
+ function App(props) {
+ React.useEffect(props.callback);
+ return null;
+ }
+
+ let calledLog = [];
+ ReactNoop.act(() => {
+ ReactNoop.render(
+ {
+ calledLog.push(calledLog.length);
+ }}
+ />,
+ );
+ });
+ expect(Scheduler).toFlushWithoutYielding();
+ expect(calledLog).toEqual([0]);
+ });
+
+ it('should work with async/await', async () => {
+ function App() {
+ let [ctr, setCtr] = React.useState(0);
+ async function someAsyncFunction() {
+ Scheduler.yieldValue('stage 1');
+ await null;
+ Scheduler.yieldValue('stage 2');
+ setCtr(1);
+ }
+ React.useEffect(() => {
+ someAsyncFunction();
+ }, []);
+ return ctr;
+ }
+ await ReactNoop.act(async () => {
+ ReactNoop.act(() => {
+ ReactNoop.render();
+ });
+ await null;
+ expect(Scheduler).toFlushAndYield(['stage 1']);
+ });
+ expect(Scheduler).toHaveYielded(['stage 2']);
+ expect(Scheduler).toFlushWithoutYielding();
+ expect(ReactNoop.getChildren()).toEqual([{text: '1', hidden: false}]);
+ });
+});
diff --git a/packages/react-test-renderer/src/ReactTestRenderer.js b/packages/react-test-renderer/src/ReactTestRenderer.js
index de53adb201935..20553ba821912 100644
--- a/packages/react-test-renderer/src/ReactTestRenderer.js
+++ b/packages/react-test-renderer/src/ReactTestRenderer.js
@@ -40,7 +40,7 @@ import {
} from 'shared/ReactWorkTags';
import invariant from 'shared/invariant';
import ReactVersion from 'shared/ReactVersion';
-import warningWithoutStack from 'shared/warningWithoutStack';
+import act from './ReactTestRendererAct';
import {getPublicInstance} from './ReactTestHostConfig';
@@ -65,11 +65,6 @@ type FindOptions = $Shape<{
export type Predicate = (node: ReactTestInstance) => ?boolean;
-// for .act's return value
-type Thenable = {
- then(resolve: () => mixed, reject?: () => mixed): mixed,
-};
-
const defaultTestOptions = {
createNodeMock: function() {
return null;
@@ -549,63 +544,11 @@ const ReactTestRendererFiber = {
return entry;
},
- /* eslint-disable camelcase */
+ /* eslint-disable-next-line camelcase */
unstable_batchedUpdates: batchedUpdates,
- /* eslint-enable camelcase */
-
- act(callback: () => void): Thenable {
- // note: keep these warning messages in sync with
- // createNoop.js and ReactTestUtils.js
- let result = batchedUpdates(callback);
- if (__DEV__) {
- if (result !== undefined) {
- let addendum;
- if (result !== null && typeof result.then === 'function') {
- addendum =
- "\n\nIt looks like you wrote TestRenderer.act(async () => ...) or returned a Promise from it's callback. " +
- 'Putting asynchronous logic inside TestRenderer.act(...) is not supported.\n';
- } else {
- addendum = ' You returned: ' + result;
- }
- warningWithoutStack(
- false,
- 'The callback passed to TestRenderer.act(...) function must not return anything.%s',
- addendum,
- );
- }
- }
- flushPassiveEffects();
- // we want the user to not expect a return,
- // but we want to warn if they use it like they can await on it.
- return {
- then() {
- if (__DEV__) {
- warningWithoutStack(
- false,
- 'Do not await the result of calling TestRenderer.act(...), it is not a Promise.',
- );
- }
- },
- };
- },
-};
-// root used to flush effects during .act() calls
-const actRoot = createContainer(
- {
- children: [],
- createNodeMock: defaultTestOptions.createNodeMock,
- tag: 'CONTAINER',
- },
- true,
- false,
-);
-
-function flushPassiveEffects() {
- // Trick to flush passive effects without exposing an internal API:
- // Create a throwaway root and schedule a dummy update on it.
- updateContainer(null, actRoot, null, null);
-}
+ act,
+};
const fiberToWrapper = new WeakMap();
function wrapFiber(fiber: Fiber): ReactTestInstance {
diff --git a/packages/react-test-renderer/src/ReactTestRendererAct.js b/packages/react-test-renderer/src/ReactTestRendererAct.js
new file mode 100644
index 0000000000000..37ced3fb04c04
--- /dev/null
+++ b/packages/react-test-renderer/src/ReactTestRendererAct.js
@@ -0,0 +1,153 @@
+/**
+ * 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 'react-reconciler/src/ReactFiberScheduler';
+
+import {
+ batchedUpdates,
+ flushPassiveEffects,
+} from 'react-reconciler/inline.test';
+import ReactSharedInternals from 'shared/ReactSharedInternals';
+import warningWithoutStack from 'shared/warningWithoutStack';
+import enqueueTask from 'shared/enqueueTask';
+
+const {ReactShouldWarnActingUpdates} = ReactSharedInternals;
+
+// this implementation should be exactly the same in
+// ReactTestUtilsAct.js, ReactTestRendererAct.js, createReactNoop.js
+
+// 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;
+
+function flushEffectsAndMicroTasks(onDone: (err: ?Error) => void) {
+ try {
+ flushPassiveEffects();
+ enqueueTask(() => {
+ if (flushPassiveEffects()) {
+ flushEffectsAndMicroTasks(onDone);
+ } else {
+ onDone();
+ }
+ });
+ } catch (err) {
+ onDone(err);
+ }
+}
+
+function act(callback: () => Thenable) {
+ let previousActingUpdatesScopeDepth;
+ if (__DEV__) {
+ previousActingUpdatesScopeDepth = actingUpdatesScopeDepth;
+ actingUpdatesScopeDepth++;
+ ReactShouldWarnActingUpdates.current = true;
+ }
+
+ function onDone() {
+ if (__DEV__) {
+ actingUpdatesScopeDepth--;
+ if (actingUpdatesScopeDepth === 0) {
+ ReactShouldWarnActingUpdates.current = false;
+ }
+ if (actingUpdatesScopeDepth > previousActingUpdatesScopeDepth) {
+ // if it's _less than_ previousActingUpdatesScopeDepth, then we can assume the 'other' one has warned
+ warningWithoutStack(
+ null,
+ 'You seem to have overlapping act() calls, this is not supported. ' +
+ 'Be sure to await previous act() calls before making a new one. ',
+ );
+ }
+ }
+ }
+
+ const result = batchedUpdates(callback);
+ 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) {
+ warningWithoutStack(
+ null,
+ '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(
+ () => {
+ flushEffectsAndMicroTasks((err: ?Error) => {
+ onDone();
+ if (err) {
+ reject(err);
+ } else {
+ resolve();
+ }
+ });
+ },
+ err => {
+ onDone();
+ reject(err);
+ },
+ );
+ },
+ };
+ } else {
+ if (__DEV__) {
+ warningWithoutStack(
+ result === undefined,
+ 'The callback passed to act(...) function ' +
+ 'must return undefined, or a Promise. You returned %s',
+ result,
+ );
+ }
+
+ // flush effects until none remain, and cleanup
+ try {
+ while (flushPassiveEffects()) {}
+ onDone();
+ } catch (err) {
+ onDone();
+ throw err;
+ }
+
+ // in the sync case, the returned thenable only warns *if* await-ed
+ return {
+ then(resolve: () => void) {
+ if (__DEV__) {
+ warningWithoutStack(
+ false,
+ 'Do not await the result of calling act(...) with sync logic, it is not a Promise.',
+ );
+ }
+ resolve();
+ },
+ };
+ }
+}
+
+export default act;
diff --git a/packages/react-test-renderer/src/__tests__/ReactTestRenderer-test.internal.js b/packages/react-test-renderer/src/__tests__/ReactTestRenderer-test.internal.js
index 0f7690e671f32..37820febaf67d 100644
--- a/packages/react-test-renderer/src/__tests__/ReactTestRenderer-test.internal.js
+++ b/packages/react-test-renderer/src/__tests__/ReactTestRenderer-test.internal.js
@@ -1023,40 +1023,19 @@ describe('ReactTestRenderer', () => {
ReactTestRenderer.create();
});
- describe('act', () => {
- it('can use .act() to batch updates and effects', () => {
- function App(props) {
- React.useEffect(() => {
- props.callback();
- });
- return null;
- }
- let called = false;
- ReactTestRenderer.act(() => {
- ReactTestRenderer.create(
- {
- called = true;
- }}
- />,
- );
- });
-
- expect(called).toBe(true);
- });
- it('warns and throws if you use TestUtils.act instead of TestRenderer.act in node', () => {
- // we warn when you try to load 2 renderers in the same 'scope'
- // so as suggested, we call resetModules() to carry on with the test
- jest.resetModules();
- const {act} = require('react-dom/test-utils');
- expect(() => {
- expect(() => act(() => {})).toThrow('document is not defined');
- }).toWarnDev(
- [
- 'It looks like you called TestUtils.act(...) in a non-browser environment',
- ],
- {withoutStack: 1},
- );
- });
+ // we run this test here because we need a dom-less scope
+ it('warns and throws if you use TestUtils.act instead of TestRenderer.act in node', () => {
+ // we warn when you try to load 2 renderers in the same 'scope'
+ // so as suggested, we call resetModules() to carry on with the test
+ jest.resetModules();
+ const {act} = require('react-dom/test-utils');
+ expect(() => {
+ expect(() => act(() => {})).toThrow('document is not defined');
+ }).toWarnDev(
+ [
+ 'It looks like you called ReactTestUtils.act(...) in a non-browser environment',
+ ],
+ {withoutStack: 1},
+ );
});
});
diff --git a/packages/react-test-renderer/src/__tests__/ReactTestRendererAct-test.js b/packages/react-test-renderer/src/__tests__/ReactTestRendererAct-test.js
new file mode 100644
index 0000000000000..9390c4ccfc74d
--- /dev/null
+++ b/packages/react-test-renderer/src/__tests__/ReactTestRendererAct-test.js
@@ -0,0 +1,122 @@
+jest.useRealTimers();
+
+let React;
+let ReactTestRenderer;
+let Scheduler;
+let act;
+
+describe('ReactTestRenderer.act()', () => {
+ beforeEach(() => {
+ jest.resetModules();
+ React = require('react');
+ ReactTestRenderer = require('react-test-renderer');
+ Scheduler = require('scheduler');
+ act = ReactTestRenderer.act;
+ });
+ it('can use .act() to flush effects', () => {
+ function App(props) {
+ let [ctr, setCtr] = React.useState(0);
+ React.useEffect(() => {
+ props.callback();
+ setCtr(1);
+ }, []);
+ return ctr;
+ }
+ let calledLog = [];
+ let root;
+ act(() => {
+ root = ReactTestRenderer.create(
+ {
+ calledLog.push(calledLog.length);
+ }}
+ />,
+ );
+ });
+
+ expect(calledLog).toEqual([0]);
+ expect(root.toJSON()).toEqual('1');
+ });
+
+ it("warns if you don't use .act", () => {
+ let setCtr;
+ function App(props) {
+ let [ctr, _setCtr] = React.useState(0);
+ setCtr = _setCtr;
+ return ctr;
+ }
+
+ ReactTestRenderer.create();
+
+ expect(() => {
+ setCtr(1);
+ }).toWarnDev([
+ 'An update to App inside a test was not wrapped in act(...)',
+ ]);
+ });
+
+ describe('async', () => {
+ it('should work with async/await', async () => {
+ function fetch(url) {
+ return Promise.resolve({
+ details: [1, 2, 3],
+ });
+ }
+ function App() {
+ let [details, setDetails] = React.useState(0);
+
+ React.useEffect(() => {
+ async function fetchDetails() {
+ const response = await fetch();
+ setDetails(response.details);
+ }
+ fetchDetails();
+ }, []);
+ return details;
+ }
+ let root;
+
+ await ReactTestRenderer.act(async () => {
+ root = ReactTestRenderer.create();
+ });
+
+ expect(root.toJSON()).toEqual(['1', '2', '3']);
+ });
+
+ it('should not flush effects without also flushing microtasks', async () => {
+ const {useEffect, useReducer} = React;
+
+ const alreadyResolvedPromise = Promise.resolve();
+
+ function App() {
+ // This component will keep updating itself until step === 3
+ const [step, proceed] = useReducer(s => (s === 3 ? 3 : s + 1), 1);
+ useEffect(() => {
+ Scheduler.yieldValue('Effect');
+ alreadyResolvedPromise.then(() => {
+ Scheduler.yieldValue('Microtask');
+ proceed();
+ });
+ });
+ return step;
+ }
+ const root = ReactTestRenderer.create(null);
+ await act(async () => {
+ root.update();
+ });
+ expect(Scheduler).toHaveYielded([
+ // Should not flush effects without also flushing microtasks
+ // First render:
+ 'Effect',
+ 'Microtask',
+ // Second render:
+ 'Effect',
+ 'Microtask',
+ // Final render:
+ 'Effect',
+ 'Microtask',
+ ]);
+ expect(root).toMatchRenderedOutput('3');
+ });
+ });
+});
diff --git a/packages/react/src/ReactSharedInternals.js b/packages/react/src/ReactSharedInternals.js
index 53dc43d977e51..c6d5010cb977d 100644
--- a/packages/react/src/ReactSharedInternals.js
+++ b/packages/react/src/ReactSharedInternals.js
@@ -15,6 +15,8 @@ import ReactDebugCurrentFrame from './ReactDebugCurrentFrame';
const ReactSharedInternals = {
ReactCurrentDispatcher,
ReactCurrentOwner,
+ // used by act()
+ ReactShouldWarnActingUpdates: {current: false},
// Used by renderers to avoid bundling object-assign twice in UMD bundles:
assign,
};
diff --git a/packages/shared/enqueueTask.js b/packages/shared/enqueueTask.js
new file mode 100644
index 0000000000000..02f4ee220672a
--- /dev/null
+++ b/packages/shared/enqueueTask.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.
+ *
+ * @flow
+ */
+
+import warningWithoutStack from './warningWithoutStack';
+
+let didWarnAboutMessageChannel = false;
+let enqueueTask;
+try {
+ // assuming we're in node, let's try to get node's
+ // version of setImmediate, bypassing fake timers if any
+ let r = require; // trick packagers not to bundle this stuff.
+ enqueueTask = r('timers').setImmediate;
+} catch (_err) {
+ // we're in a browser
+ // we can't use regular timers because they may still be faked
+ // so we try MessageChannel+postMessage instead
+ enqueueTask = function(callback: () => void) {
+ if (__DEV__) {
+ if (didWarnAboutMessageChannel === false) {
+ didWarnAboutMessageChannel = true;
+ warningWithoutStack(
+ typeof MessageChannel !== 'undefined',
+ 'This browser does not have a MessageChannel implementation, ' +
+ 'so enqueuing tasks via await act(async () => ...) will fail. ' +
+ 'Please file an issue at https://github.com/facebook/react/issues ' +
+ 'if you encounter this warning.',
+ );
+ }
+ }
+ const channel = new MessageChannel();
+ channel.port1.onmessage = callback;
+ channel.port2.postMessage(undefined);
+ };
+}
+
+export default enqueueTask;
diff --git a/scripts/rollup/results.json b/scripts/rollup/results.json
index ddb87f522b2e0..c858da46fff87 100644
--- a/scripts/rollup/results.json
+++ b/scripts/rollup/results.json
@@ -4,29 +4,29 @@
"filename": "react.development.js",
"bundleType": "UMD_DEV",
"packageName": "react",
- "size": 102492,
- "gzip": 26625
+ "size": 103860,
+ "gzip": 26948
},
{
"filename": "react.production.min.js",
"bundleType": "UMD_PROD",
"packageName": "react",
- "size": 12609,
- "gzip": 4834
+ "size": 12346,
+ "gzip": 4735
},
{
"filename": "react.development.js",
"bundleType": "NODE_DEV",
"packageName": "react",
- "size": 64139,
- "gzip": 17318
+ "size": 65507,
+ "gzip": 17628
},
{
"filename": "react.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "react",
- "size": 6834,
- "gzip": 2814
+ "size": 6571,
+ "gzip": 2718
},
{
"filename": "React-dev.js",
@@ -46,29 +46,29 @@
"filename": "react-dom.development.js",
"bundleType": "UMD_DEV",
"packageName": "react-dom",
- "size": 832345,
- "gzip": 188603
+ "size": 835612,
+ "gzip": 189016
},
{
"filename": "react-dom.production.min.js",
"bundleType": "UMD_PROD",
"packageName": "react-dom",
- "size": 107683,
- "gzip": 34867
+ "size": 107684,
+ "gzip": 34777
},
{
"filename": "react-dom.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-dom",
- "size": 826372,
- "gzip": 186963
+ "size": 829867,
+ "gzip": 187373
},
{
"filename": "react-dom.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "react-dom",
"size": 107664,
- "gzip": 34301
+ "gzip": 34273
},
{
"filename": "ReactDOM-dev.js",
@@ -88,29 +88,29 @@
"filename": "react-dom-test-utils.development.js",
"bundleType": "UMD_DEV",
"packageName": "react-dom",
- "size": 48620,
- "gzip": 13278
+ "size": 53238,
+ "gzip": 14410
},
{
"filename": "react-dom-test-utils.production.min.js",
"bundleType": "UMD_PROD",
"packageName": "react-dom",
- "size": 10184,
- "gzip": 3732
+ "size": 10659,
+ "gzip": 3913
},
{
"filename": "react-dom-test-utils.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-dom",
- "size": 48334,
- "gzip": 13206
+ "size": 52952,
+ "gzip": 14343
},
{
"filename": "react-dom-test-utils.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "react-dom",
- "size": 9954,
- "gzip": 3660
+ "size": 10441,
+ "gzip": 3835
},
{
"filename": "ReactTestUtils-dev.js",
@@ -123,8 +123,8 @@
"filename": "react-dom-unstable-native-dependencies.development.js",
"bundleType": "UMD_DEV",
"packageName": "react-dom",
- "size": 62190,
- "gzip": 16206
+ "size": 62213,
+ "gzip": 16213
},
{
"filename": "react-dom-unstable-native-dependencies.production.min.js",
@@ -137,8 +137,8 @@
"filename": "react-dom-unstable-native-dependencies.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-dom",
- "size": 61854,
- "gzip": 16078
+ "size": 61877,
+ "gzip": 16085
},
{
"filename": "react-dom-unstable-native-dependencies.production.min.js",
@@ -165,29 +165,29 @@
"filename": "react-dom-server.browser.development.js",
"bundleType": "UMD_DEV",
"packageName": "react-dom",
- "size": 136840,
- "gzip": 36205
+ "size": 137930,
+ "gzip": 36351
},
{
"filename": "react-dom-server.browser.production.min.js",
"bundleType": "UMD_PROD",
"packageName": "react-dom",
- "size": 19363,
- "gzip": 7290
+ "size": 19569,
+ "gzip": 7381
},
{
"filename": "react-dom-server.browser.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-dom",
- "size": 132878,
- "gzip": 35237
+ "size": 133968,
+ "gzip": 35384
},
{
"filename": "react-dom-server.browser.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "react-dom",
- "size": 19287,
- "gzip": 7290
+ "size": 19495,
+ "gzip": 7377
},
{
"filename": "ReactDOMServer-dev.js",
@@ -207,43 +207,43 @@
"filename": "react-dom-server.node.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-dom",
- "size": 134867,
- "gzip": 35791
+ "size": 135957,
+ "gzip": 35941
},
{
"filename": "react-dom-server.node.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "react-dom",
- "size": 20170,
- "gzip": 7598
+ "size": 20377,
+ "gzip": 7690
},
{
"filename": "react-art.development.js",
"bundleType": "UMD_DEV",
"packageName": "react-art",
- "size": 570224,
- "gzip": 123819
+ "size": 582400,
+ "gzip": 125635
},
{
"filename": "react-art.production.min.js",
"bundleType": "UMD_PROD",
"packageName": "react-art",
- "size": 99370,
- "gzip": 30598
+ "size": 99129,
+ "gzip": 30380
},
{
"filename": "react-art.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-art",
- "size": 499479,
- "gzip": 106084
+ "size": 511655,
+ "gzip": 107868
},
{
"filename": "react-art.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "react-art",
- "size": 63450,
- "gzip": 19455
+ "size": 63295,
+ "gzip": 19346
},
{
"filename": "ReactART-dev.js",
@@ -291,29 +291,29 @@
"filename": "react-test-renderer.development.js",
"bundleType": "UMD_DEV",
"packageName": "react-test-renderer",
- "size": 508633,
- "gzip": 107760
+ "size": 525649,
+ "gzip": 110641
},
{
"filename": "react-test-renderer.production.min.js",
"bundleType": "UMD_PROD",
"packageName": "react-test-renderer",
- "size": 64479,
- "gzip": 19791
+ "size": 64570,
+ "gzip": 19714
},
{
"filename": "react-test-renderer.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-test-renderer",
- "size": 504043,
- "gzip": 106587
+ "size": 521059,
+ "gzip": 109459
},
{
"filename": "react-test-renderer.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "react-test-renderer",
- "size": 64119,
- "gzip": 19538
+ "size": 64265,
+ "gzip": 19537
},
{
"filename": "ReactTestRenderer-dev.js",
@@ -326,29 +326,29 @@
"filename": "react-test-renderer-shallow.development.js",
"bundleType": "UMD_DEV",
"packageName": "react-test-renderer",
- "size": 38113,
- "gzip": 9733
+ "size": 39907,
+ "gzip": 10032
},
{
"filename": "react-test-renderer-shallow.production.min.js",
"bundleType": "UMD_PROD",
"packageName": "react-test-renderer",
- "size": 11385,
- "gzip": 3419
+ "size": 11688,
+ "gzip": 3579
},
{
"filename": "react-test-renderer-shallow.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-test-renderer",
- "size": 32275,
- "gzip": 8333
+ "size": 33992,
+ "gzip": 8604
},
{
"filename": "react-test-renderer-shallow.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "react-test-renderer",
- "size": 12046,
- "gzip": 3733
+ "size": 11884,
+ "gzip": 3709
},
{
"filename": "ReactShallowRenderer-dev.js",
@@ -361,57 +361,57 @@
"filename": "react-noop-renderer.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-noop-renderer",
- "size": 26949,
- "gzip": 6362
+ "size": 35270,
+ "gzip": 8615
},
{
"filename": "react-noop-renderer.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "react-noop-renderer",
- "size": 9933,
- "gzip": 3165
+ "size": 10470,
+ "gzip": 3431
},
{
"filename": "react-reconciler.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-reconciler",
- "size": 497879,
- "gzip": 104645
+ "size": 511941,
+ "gzip": 106665
},
{
"filename": "react-reconciler.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "react-reconciler",
- "size": 64551,
- "gzip": 19325
+ "size": 64562,
+ "gzip": 19196
},
{
"filename": "react-reconciler-persistent.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-reconciler",
- "size": 495898,
- "gzip": 103848
+ "size": 509778,
+ "gzip": 105781
},
{
"filename": "react-reconciler-persistent.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "react-reconciler",
- "size": 64562,
- "gzip": 19330
+ "size": 64573,
+ "gzip": 19202
},
{
"filename": "react-reconciler-reflection.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-reconciler",
"size": 16161,
- "gzip": 5096
+ "gzip": 5015
},
{
"filename": "react-reconciler-reflection.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "react-reconciler",
- "size": 2760,
- "gzip": 1244
+ "size": 2423,
+ "gzip": 1082
},
{
"filename": "react-call-return.development.js",
@@ -487,57 +487,57 @@
"filename": "create-subscription.development.js",
"bundleType": "NODE_DEV",
"packageName": "create-subscription",
- "size": 8538,
- "gzip": 2952
+ "size": 8219,
+ "gzip": 2827
},
{
"filename": "create-subscription.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "create-subscription",
- "size": 2889,
- "gzip": 1344
+ "size": 2558,
+ "gzip": 1200
},
{
"filename": "React-dev.js",
"bundleType": "FB_WWW_DEV",
"packageName": "react",
- "size": 61163,
- "gzip": 16239
+ "size": 63801,
+ "gzip": 16915
},
{
"filename": "React-prod.js",
"bundleType": "FB_WWW_PROD",
"packageName": "react",
- "size": 15734,
- "gzip": 4189
+ "size": 15699,
+ "gzip": 4193
},
{
"filename": "ReactDOM-dev.js",
"bundleType": "FB_WWW_DEV",
"packageName": "react-dom",
- "size": 851672,
- "gzip": 188651
+ "size": 855350,
+ "gzip": 189054
},
{
"filename": "ReactDOM-prod.js",
"bundleType": "FB_WWW_PROD",
"packageName": "react-dom",
- "size": 339041,
- "gzip": 62418
+ "size": 339926,
+ "gzip": 62708
},
{
"filename": "ReactTestUtils-dev.js",
"bundleType": "FB_WWW_DEV",
"packageName": "react-dom",
- "size": 46251,
- "gzip": 12476
+ "size": 51170,
+ "gzip": 13698
},
{
"filename": "ReactDOMUnstableNativeDependencies-dev.js",
"bundleType": "FB_WWW_DEV",
"packageName": "react-dom",
- "size": 60296,
- "gzip": 15251
+ "size": 60319,
+ "gzip": 15257
},
{
"filename": "ReactDOMUnstableNativeDependencies-prod.js",
@@ -550,99 +550,99 @@
"filename": "ReactDOMServer-dev.js",
"bundleType": "FB_WWW_DEV",
"packageName": "react-dom",
- "size": 135272,
- "gzip": 35002
+ "size": 136328,
+ "gzip": 35148
},
{
"filename": "ReactDOMServer-prod.js",
"bundleType": "FB_WWW_PROD",
"packageName": "react-dom",
- "size": 46877,
- "gzip": 10879
+ "size": 47596,
+ "gzip": 10982
},
{
"filename": "ReactART-dev.js",
"bundleType": "FB_WWW_DEV",
"packageName": "react-art",
- "size": 509037,
- "gzip": 105217
+ "size": 521820,
+ "gzip": 107152
},
{
"filename": "ReactART-prod.js",
"bundleType": "FB_WWW_PROD",
"packageName": "react-art",
- "size": 201874,
- "gzip": 34245
+ "size": 202109,
+ "gzip": 34176
},
{
"filename": "ReactNativeRenderer-dev.js",
"bundleType": "RN_FB_DEV",
"packageName": "react-native-renderer",
- "size": 637829,
- "gzip": 136280
+ "size": 645983,
+ "gzip": 137694
},
{
"filename": "ReactNativeRenderer-prod.js",
"bundleType": "RN_FB_PROD",
"packageName": "react-native-renderer",
- "size": 251569,
- "gzip": 43973
+ "size": 252030,
+ "gzip": 44064
},
{
"filename": "ReactNativeRenderer-dev.js",
"bundleType": "RN_OSS_DEV",
"packageName": "react-native-renderer",
- "size": 637742,
- "gzip": 136246
+ "size": 645895,
+ "gzip": 137660
},
{
"filename": "ReactNativeRenderer-prod.js",
"bundleType": "RN_OSS_PROD",
"packageName": "react-native-renderer",
- "size": 251583,
- "gzip": 43970
+ "size": 252044,
+ "gzip": 44061
},
{
"filename": "ReactFabric-dev.js",
"bundleType": "RN_FB_DEV",
"packageName": "react-native-renderer",
- "size": 628028,
- "gzip": 133874
+ "size": 634566,
+ "gzip": 134983
},
{
"filename": "ReactFabric-prod.js",
"bundleType": "RN_FB_PROD",
"packageName": "react-native-renderer",
- "size": 245497,
- "gzip": 42746
+ "size": 245276,
+ "gzip": 42773
},
{
"filename": "ReactFabric-dev.js",
"bundleType": "RN_OSS_DEV",
"packageName": "react-native-renderer",
- "size": 627933,
- "gzip": 133834
+ "size": 634470,
+ "gzip": 134930
},
{
"filename": "ReactFabric-prod.js",
"bundleType": "RN_OSS_PROD",
"packageName": "react-native-renderer",
- "size": 245503,
- "gzip": 42741
+ "size": 245282,
+ "gzip": 42767
},
{
"filename": "ReactTestRenderer-dev.js",
"bundleType": "FB_WWW_DEV",
"packageName": "react-test-renderer",
- "size": 514514,
- "gzip": 106185
+ "size": 532823,
+ "gzip": 109328
},
{
"filename": "ReactShallowRenderer-dev.js",
"bundleType": "FB_WWW_DEV",
"packageName": "react-test-renderer",
- "size": 30652,
- "gzip": 7761
+ "size": 33767,
+ "gzip": 8435
},
{
"filename": "ReactIs-dev.js",
@@ -704,36 +704,36 @@
"filename": "react-noop-renderer-persistent.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-noop-renderer",
- "size": 27068,
- "gzip": 6375
+ "size": 35389,
+ "gzip": 8630
},
{
"filename": "react-noop-renderer-persistent.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "react-noop-renderer",
- "size": 9955,
- "gzip": 3170
+ "size": 10492,
+ "gzip": 3436
},
{
"filename": "react-dom.profiling.min.js",
"bundleType": "NODE_PROFILING",
"packageName": "react-dom",
- "size": 110839,
- "gzip": 34944
+ "size": 110841,
+ "gzip": 34901
},
{
"filename": "ReactNativeRenderer-profiling.js",
"bundleType": "RN_OSS_PROFILING",
"packageName": "react-native-renderer",
- "size": 257964,
- "gzip": 45359
+ "size": 258447,
+ "gzip": 45443
},
{
"filename": "ReactFabric-profiling.js",
"bundleType": "RN_OSS_PROFILING",
"packageName": "react-native-renderer",
- "size": 250954,
- "gzip": 44143
+ "size": 250755,
+ "gzip": 44122
},
{
"filename": "Scheduler-dev.js",
@@ -760,50 +760,50 @@
"filename": "React-profiling.js",
"bundleType": "FB_WWW_PROFILING",
"packageName": "react",
- "size": 15734,
- "gzip": 4189
+ "size": 15699,
+ "gzip": 4193
},
{
"filename": "ReactDOM-profiling.js",
"bundleType": "FB_WWW_PROFILING",
"packageName": "react-dom",
- "size": 345581,
- "gzip": 63810
+ "size": 346531,
+ "gzip": 64085
},
{
"filename": "ReactNativeRenderer-profiling.js",
"bundleType": "RN_FB_PROFILING",
"packageName": "react-native-renderer",
- "size": 257945,
- "gzip": 45363
+ "size": 258428,
+ "gzip": 45445
},
{
"filename": "ReactFabric-profiling.js",
"bundleType": "RN_FB_PROFILING",
"packageName": "react-native-renderer",
- "size": 250943,
- "gzip": 44145
+ "size": 250744,
+ "gzip": 44126
},
{
"filename": "react.profiling.min.js",
"bundleType": "UMD_PROFILING",
"packageName": "react",
- "size": 14818,
- "gzip": 5369
+ "size": 14552,
+ "gzip": 5255
},
{
"filename": "react-dom.profiling.min.js",
"bundleType": "UMD_PROFILING",
"packageName": "react-dom",
- "size": 110730,
- "gzip": 35485
+ "size": 110732,
+ "gzip": 35443
},
{
"filename": "scheduler-tracing.development.js",
"bundleType": "NODE_DEV",
"packageName": "scheduler",
- "size": 10878,
- "gzip": 2594
+ "size": 11062,
+ "gzip": 2681
},
{
"filename": "scheduler-tracing.production.min.js",
@@ -823,8 +823,8 @@
"filename": "SchedulerTracing-dev.js",
"bundleType": "FB_WWW_DEV",
"packageName": "scheduler",
- "size": 10267,
- "gzip": 2151
+ "size": 10470,
+ "gzip": 2260
},
{
"filename": "SchedulerTracing-prod.js",
@@ -886,29 +886,29 @@
"filename": "jest-react.development.js",
"bundleType": "NODE_DEV",
"packageName": "jest-react",
- "size": 7455,
- "gzip": 2737
+ "size": 7100,
+ "gzip": 2546
},
{
"filename": "jest-react.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "jest-react",
- "size": 2930,
- "gzip": 1442
+ "size": 2599,
+ "gzip": 1299
},
{
"filename": "JestReact-dev.js",
"bundleType": "FB_WWW_DEV",
"packageName": "jest-react",
- "size": 4208,
- "gzip": 1490
+ "size": 5010,
+ "gzip": 1757
},
{
"filename": "JestReact-prod.js",
"bundleType": "FB_WWW_PROD",
"packageName": "jest-react",
- "size": 3592,
- "gzip": 1315
+ "size": 3492,
+ "gzip": 1287
},
{
"filename": "react-debug-tools.development.js",
@@ -928,15 +928,15 @@
"filename": "eslint-plugin-react-hooks.development.js",
"bundleType": "NODE_DEV",
"packageName": "eslint-plugin-react-hooks",
- "size": 75099,
- "gzip": 17223
+ "size": 77541,
+ "gzip": 17683
},
{
"filename": "eslint-plugin-react-hooks.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "eslint-plugin-react-hooks",
- "size": 19731,
- "gzip": 6811
+ "size": 20485,
+ "gzip": 7082
},
{
"filename": "ReactDOMFizzServer-dev.js",
@@ -1026,71 +1026,71 @@
"filename": "ESLintPluginReactHooks-dev.js",
"bundleType": "FB_WWW_DEV",
"packageName": "eslint-plugin-react-hooks",
- "size": 80483,
- "gzip": 17729
+ "size": 83133,
+ "gzip": 18239
},
{
"filename": "react-dom-unstable-fire.development.js",
"bundleType": "UMD_DEV",
"packageName": "react-dom",
- "size": 832471,
- "gzip": 188731
+ "size": 835944,
+ "gzip": 189154
},
{
"filename": "react-dom-unstable-fire.production.min.js",
"bundleType": "UMD_PROD",
"packageName": "react-dom",
- "size": 107698,
- "gzip": 34877
+ "size": 107699,
+ "gzip": 34786
},
{
"filename": "react-dom-unstable-fire.profiling.min.js",
"bundleType": "UMD_PROFILING",
"packageName": "react-dom",
- "size": 110745,
- "gzip": 35493
+ "size": 110747,
+ "gzip": 35451
},
{
"filename": "react-dom-unstable-fire.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-dom",
- "size": 826725,
- "gzip": 187104
+ "size": 830198,
+ "gzip": 187505
},
{
"filename": "react-dom-unstable-fire.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "react-dom",
"size": 107678,
- "gzip": 34310
+ "gzip": 34282
},
{
"filename": "react-dom-unstable-fire.profiling.min.js",
"bundleType": "NODE_PROFILING",
"packageName": "react-dom",
- "size": 110853,
- "gzip": 34953
+ "size": 110855,
+ "gzip": 34911
},
{
"filename": "ReactFire-dev.js",
"bundleType": "FB_WWW_DEV",
"packageName": "react-dom",
- "size": 850863,
- "gzip": 188551
+ "size": 854519,
+ "gzip": 189023
},
{
"filename": "ReactFire-prod.js",
"bundleType": "FB_WWW_PROD",
"packageName": "react-dom",
- "size": 327881,
- "gzip": 60185
+ "size": 328314,
+ "gzip": 60295
},
{
"filename": "ReactFire-profiling.js",
"bundleType": "FB_WWW_PROFILING",
"packageName": "react-dom",
- "size": 334366,
- "gzip": 61586
+ "size": 334864,
+ "gzip": 61709
},
{
"filename": "jest-mock-scheduler.development.js",
@@ -1152,29 +1152,253 @@
"filename": "react-events.development.js",
"bundleType": "NODE_DEV",
"packageName": "react-events",
- "size": 1135,
- "gzip": 623
+ "size": 990,
+ "gzip": 545
},
{
"filename": "react-events.production.min.js",
"bundleType": "NODE_PROD",
"packageName": "react-events",
- "size": 448,
- "gzip": 328
+ "size": 506,
+ "gzip": 343
},
{
"filename": "ReactEvents-dev.js",
"bundleType": "FB_WWW_DEV",
"packageName": "react-events",
- "size": 1106,
- "gzip": 613
+ "size": 956,
+ "gzip": 536
},
{
"filename": "ReactEvents-prod.js",
"bundleType": "FB_WWW_PROD",
"packageName": "react-events",
- "size": 643,
- "gzip": 377
+ "size": 687,
+ "gzip": 410
+ },
+ {
+ "filename": "react-events.development.js",
+ "bundleType": "UMD_DEV",
+ "packageName": "react-events",
+ "size": 1183,
+ "gzip": 605
+ },
+ {
+ "filename": "react-events.production.min.js",
+ "bundleType": "UMD_PROD",
+ "packageName": "react-events",
+ "size": 676,
+ "gzip": 420
+ },
+ {
+ "filename": "react-events-press.development.js",
+ "bundleType": "UMD_DEV",
+ "packageName": "react-events",
+ "size": 10325,
+ "gzip": 2630
+ },
+ {
+ "filename": "react-events-press.production.min.js",
+ "bundleType": "UMD_PROD",
+ "packageName": "react-events",
+ "size": 4058,
+ "gzip": 1507
+ },
+ {
+ "filename": "react-events-press.development.js",
+ "bundleType": "NODE_DEV",
+ "packageName": "react-events",
+ "size": 10151,
+ "gzip": 2584
+ },
+ {
+ "filename": "react-events-press.production.min.js",
+ "bundleType": "NODE_PROD",
+ "packageName": "react-events",
+ "size": 3892,
+ "gzip": 1451
+ },
+ {
+ "filename": "ReactEventsPress-dev.js",
+ "bundleType": "FB_WWW_DEV",
+ "packageName": "react-events",
+ "size": 10480,
+ "gzip": 2636
+ },
+ {
+ "filename": "ReactEventsPress-prod.js",
+ "bundleType": "FB_WWW_PROD",
+ "packageName": "react-events",
+ "size": 8000,
+ "gzip": 1905
+ },
+ {
+ "filename": "react-events-hover.development.js",
+ "bundleType": "UMD_DEV",
+ "packageName": "react-events",
+ "size": 5271,
+ "gzip": 1416
+ },
+ {
+ "filename": "react-events-hover.production.min.js",
+ "bundleType": "UMD_PROD",
+ "packageName": "react-events",
+ "size": 2312,
+ "gzip": 923
+ },
+ {
+ "filename": "react-events-hover.development.js",
+ "bundleType": "NODE_DEV",
+ "packageName": "react-events",
+ "size": 5097,
+ "gzip": 1372
+ },
+ {
+ "filename": "react-events-hover.production.min.js",
+ "bundleType": "NODE_PROD",
+ "packageName": "react-events",
+ "size": 2147,
+ "gzip": 865
+ },
+ {
+ "filename": "ReactEventsHover-dev.js",
+ "bundleType": "FB_WWW_DEV",
+ "packageName": "react-events",
+ "size": 5113,
+ "gzip": 1386
+ },
+ {
+ "filename": "ReactEventsHover-prod.js",
+ "bundleType": "FB_WWW_PROD",
+ "packageName": "react-events",
+ "size": 4279,
+ "gzip": 1130
+ },
+ {
+ "filename": "react-events-focus.development.js",
+ "bundleType": "UMD_DEV",
+ "packageName": "react-events",
+ "size": 3446,
+ "gzip": 1112
+ },
+ {
+ "filename": "react-events-focus.production.min.js",
+ "bundleType": "UMD_PROD",
+ "packageName": "react-events",
+ "size": 1563,
+ "gzip": 721
+ },
+ {
+ "filename": "react-events-focus.development.js",
+ "bundleType": "NODE_DEV",
+ "packageName": "react-events",
+ "size": 3272,
+ "gzip": 1068
+ },
+ {
+ "filename": "react-events-focus.production.min.js",
+ "bundleType": "NODE_PROD",
+ "packageName": "react-events",
+ "size": 1392,
+ "gzip": 659
+ },
+ {
+ "filename": "ReactEventsFocus-dev.js",
+ "bundleType": "FB_WWW_DEV",
+ "packageName": "react-events",
+ "size": 3242,
+ "gzip": 1058
+ },
+ {
+ "filename": "ReactEventsFocus-prod.js",
+ "bundleType": "FB_WWW_PROD",
+ "packageName": "react-events",
+ "size": 2552,
+ "gzip": 827
+ },
+ {
+ "filename": "react-events-swipe.development.js",
+ "bundleType": "UMD_DEV",
+ "packageName": "react-events",
+ "size": 8479,
+ "gzip": 2604
+ },
+ {
+ "filename": "react-events-swipe.production.min.js",
+ "bundleType": "UMD_PROD",
+ "packageName": "react-events",
+ "size": 3531,
+ "gzip": 1625
+ },
+ {
+ "filename": "react-events-swipe.development.js",
+ "bundleType": "NODE_DEV",
+ "packageName": "react-events",
+ "size": 8305,
+ "gzip": 2571
+ },
+ {
+ "filename": "react-events-swipe.production.min.js",
+ "bundleType": "NODE_PROD",
+ "packageName": "react-events",
+ "size": 3364,
+ "gzip": 1569
+ },
+ {
+ "filename": "ReactEventsSwipe-dev.js",
+ "bundleType": "FB_WWW_DEV",
+ "packageName": "react-events",
+ "size": 6360,
+ "gzip": 1814
+ },
+ {
+ "filename": "ReactEventsSwipe-prod.js",
+ "bundleType": "FB_WWW_PROD",
+ "packageName": "react-events",
+ "size": 6089,
+ "gzip": 1563
+ },
+ {
+ "filename": "react-events-drag.development.js",
+ "bundleType": "UMD_DEV",
+ "packageName": "react-events",
+ "size": 7733,
+ "gzip": 2450
+ },
+ {
+ "filename": "react-events-drag.production.min.js",
+ "bundleType": "UMD_PROD",
+ "packageName": "react-events",
+ "size": 3278,
+ "gzip": 1489
+ },
+ {
+ "filename": "react-events-drag.development.js",
+ "bundleType": "NODE_DEV",
+ "packageName": "react-events",
+ "size": 7560,
+ "gzip": 2415
+ },
+ {
+ "filename": "react-events-drag.production.min.js",
+ "bundleType": "NODE_PROD",
+ "packageName": "react-events",
+ "size": 3112,
+ "gzip": 1429
+ },
+ {
+ "filename": "ReactEventsDrag-dev.js",
+ "bundleType": "FB_WWW_DEV",
+ "packageName": "react-events",
+ "size": 5706,
+ "gzip": 1684
+ },
+ {
+ "filename": "ReactEventsDrag-prod.js",
+ "bundleType": "FB_WWW_PROD",
+ "packageName": "react-events",
+ "size": 5245,
+ "gzip": 1368
}
]
}
\ No newline at end of file