diff --git a/packages/react-dom/src/__tests__/utils/ReactDOMServerIntegrationTestUtils.js b/packages/react-dom/src/__tests__/utils/ReactDOMServerIntegrationTestUtils.js
index 5aa5cad24842a..868c59be61a09 100644
--- a/packages/react-dom/src/__tests__/utils/ReactDOMServerIntegrationTestUtils.js
+++ b/packages/react-dom/src/__tests__/utils/ReactDOMServerIntegrationTestUtils.js
@@ -48,21 +48,16 @@ module.exports = function (initModules) {
// ====================================
// promisified version of ReactDOM.render()
- function asyncReactDOMRender(reactElement, domElement, forceHydrate) {
- return new Promise(resolve => {
- if (forceHydrate) {
- act(() => {
- ReactDOM.hydrate(reactElement, domElement);
- });
- } else {
- act(() => {
- ReactDOM.render(reactElement, domElement);
- });
- }
- // We can't use the callback for resolution because that will not catch
- // errors. They're thrown.
- resolve();
- });
+ async function asyncReactDOMRender(reactElement, domElement, forceHydrate) {
+ if (forceHydrate) {
+ await act(async () => {
+ ReactDOM.hydrate(reactElement, domElement);
+ });
+ } else {
+ await act(async () => {
+ ReactDOM.render(reactElement, domElement);
+ });
+ }
}
// performs fn asynchronously and expects count errors logged to console.error.
// will fail the test if the count of errors logged is not equal to count.
diff --git a/packages/react-dom/src/events/__tests__/DOMPluginEventSystem-test.internal.js b/packages/react-dom/src/events/__tests__/DOMPluginEventSystem-test.internal.js
index 4f0b03f42143e..8a81d5582f6e0 100644
--- a/packages/react-dom/src/events/__tests__/DOMPluginEventSystem-test.internal.js
+++ b/packages/react-dom/src/events/__tests__/DOMPluginEventSystem-test.internal.js
@@ -2520,7 +2520,7 @@ describe('DOMPluginEventSystem', () => {
});
// @gate www
- it('beforeblur and afterblur are called after a focused element is suspended', () => {
+ it('beforeblur and afterblur are called after a focused element is suspended', async () => {
const log = [];
// We have to persist here because we want to read relatedTarget later.
const onAfterBlur = jest.fn(e => {
@@ -2575,7 +2575,7 @@ describe('DOMPluginEventSystem', () => {
const root = ReactDOMClient.createRoot(container2);
- act(() => {
+ await act(async () => {
root.render();
});
jest.runAllTimers();
@@ -2587,7 +2587,7 @@ describe('DOMPluginEventSystem', () => {
expect(onAfterBlur).toHaveBeenCalledTimes(0);
suspend = true;
- act(() => {
+ await act(async () => {
root.render();
});
jest.runAllTimers();
@@ -2604,7 +2604,7 @@ describe('DOMPluginEventSystem', () => {
});
// @gate www
- it('beforeblur should skip handlers from a deleted subtree after the focused element is suspended', () => {
+ it('beforeblur should skip handlers from a deleted subtree after the focused element is suspended', async () => {
const onBeforeBlur = jest.fn();
const innerRef = React.createRef();
const innerRef2 = React.createRef();
@@ -2661,7 +2661,7 @@ describe('DOMPluginEventSystem', () => {
const root = ReactDOMClient.createRoot(container2);
- act(() => {
+ await act(async () => {
root.render();
});
jest.runAllTimers();
@@ -2672,7 +2672,7 @@ describe('DOMPluginEventSystem', () => {
expect(onBeforeBlur).toHaveBeenCalledTimes(0);
suspend = true;
- act(() => {
+ await act(async () => {
root.render();
});
jest.runAllTimers();
@@ -2684,17 +2684,17 @@ describe('DOMPluginEventSystem', () => {
});
// @gate www
- it('regression: does not fire beforeblur/afterblur if target is already hidden', () => {
+ it('regression: does not fire beforeblur/afterblur if target is already hidden', async () => {
const Suspense = React.Suspense;
let suspend = false;
- const promise = Promise.resolve();
+ const fakePromise = {then() {}};
const setBeforeBlurHandle =
ReactDOM.unstable_createEventHandle('beforeblur');
const innerRef = React.createRef();
function Child() {
if (suspend) {
- throw promise;
+ throw fakePromise;
}
return ;
}
@@ -2726,7 +2726,7 @@ describe('DOMPluginEventSystem', () => {
document.body.appendChild(container2);
const root = ReactDOMClient.createRoot(container2);
- act(() => {
+ await act(async () => {
root.render();
});
@@ -2737,7 +2737,7 @@ describe('DOMPluginEventSystem', () => {
// Suspend. This hides the input node, causing it to lose focus.
suspend = true;
- act(() => {
+ await act(async () => {
root.render();
});
diff --git a/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js b/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js
index df5af903a7216..dc4ef2135f281 100644
--- a/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js
@@ -20,6 +20,7 @@ let ReactDOMServer;
let act;
let assertLog;
let waitForAll;
+let waitForThrow;
describe('ReactHooks', () => {
beforeEach(() => {
@@ -36,6 +37,7 @@ describe('ReactHooks', () => {
const InternalTestUtils = require('internal-test-utils');
assertLog = InternalTestUtils.assertLog;
waitForAll = InternalTestUtils.waitForAll;
+ waitForThrow = InternalTestUtils.waitForThrow;
});
if (__DEV__) {
@@ -90,7 +92,7 @@ describe('ReactHooks', () => {
expect(root).toMatchRenderedOutput('0, 0');
// Normal update
- act(() => {
+ await act(async () => {
setCounter1(1);
setCounter2(1);
});
@@ -98,12 +100,12 @@ describe('ReactHooks', () => {
assertLog(['Parent: 1, 1', 'Child: 1, 1', 'Effect: 1, 1']);
// Update that bails out.
- act(() => setCounter1(1));
+ await act(async () => setCounter1(1));
assertLog(['Parent: 1, 1']);
// This time, one of the state updates but the other one doesn't. So we
// can't bail out.
- act(() => {
+ await act(async () => {
setCounter1(1);
setCounter2(2);
});
@@ -111,7 +113,7 @@ describe('ReactHooks', () => {
assertLog(['Parent: 1, 2', 'Child: 1, 2', 'Effect: 1, 2']);
// Lots of updates that eventually resolve to the current values.
- act(() => {
+ await act(async () => {
setCounter1(9);
setCounter2(3);
setCounter1(4);
@@ -125,7 +127,7 @@ describe('ReactHooks', () => {
assertLog(['Parent: 1, 2']);
// prepare to check SameValue
- act(() => {
+ await act(async () => {
setCounter1(0 / -1);
setCounter2(NaN);
});
@@ -133,7 +135,7 @@ describe('ReactHooks', () => {
assertLog(['Parent: 0, NaN', 'Child: 0, NaN', 'Effect: 0, NaN']);
// check if re-setting to negative 0 / NaN still bails out
- act(() => {
+ await act(async () => {
setCounter1(0 / -1);
setCounter2(NaN);
setCounter2(Infinity);
@@ -143,7 +145,7 @@ describe('ReactHooks', () => {
assertLog(['Parent: 0, NaN']);
// check if changing negative 0 to positive 0 does not bail out
- act(() => {
+ await act(async () => {
setCounter1(0);
});
assertLog(['Parent: 0, NaN', 'Child: 0, NaN', 'Effect: 0, NaN']);
@@ -178,7 +180,7 @@ describe('ReactHooks', () => {
expect(root).toMatchRenderedOutput('0, 0 (light)');
// Normal update
- act(() => {
+ await act(async () => {
setCounter1(1);
setCounter2(1);
});
@@ -186,12 +188,12 @@ describe('ReactHooks', () => {
assertLog(['Parent: 1, 1 (light)', 'Child: 1, 1 (light)']);
// Update that bails out.
- act(() => setCounter1(1));
+ await act(async () => setCounter1(1));
assertLog(['Parent: 1, 1 (light)']);
// This time, one of the state updates but the other one doesn't. So we
// can't bail out.
- act(() => {
+ await act(async () => {
setCounter1(1);
setCounter2(2);
});
@@ -200,7 +202,7 @@ describe('ReactHooks', () => {
// Updates bail out, but component still renders because props
// have changed
- act(() => {
+ await act(async () => {
setCounter1(1);
setCounter2(2);
root.update();
@@ -209,7 +211,7 @@ describe('ReactHooks', () => {
assertLog(['Parent: 1, 2 (dark)', 'Child: 1, 2 (dark)']);
// Both props and state bail out
- act(() => {
+ await act(async () => {
setCounter1(1);
setCounter2(2);
root.update();
@@ -235,8 +237,8 @@ describe('ReactHooks', () => {
await waitForAll(['Count: 0']);
expect(root).toMatchRenderedOutput('0');
- expect(() => {
- act(() =>
+ await expect(async () => {
+ await act(async () =>
setCounter(1, () => {
throw new Error('Expected to ignore the callback.');
}),
@@ -269,8 +271,8 @@ describe('ReactHooks', () => {
await waitForAll(['Count: 0']);
expect(root).toMatchRenderedOutput('0');
- expect(() => {
- act(() =>
+ await expect(async () => {
+ await act(async () =>
dispatch(1, () => {
throw new Error('Expected to ignore the callback.');
}),
@@ -321,7 +323,7 @@ describe('ReactHooks', () => {
return ;
}
const root = ReactTestRenderer.create(null, {unstable_isConcurrent: true});
- act(() => {
+ await act(async () => {
root.update(
@@ -344,18 +346,18 @@ describe('ReactHooks', () => {
expect(root).toMatchRenderedOutput('0 (light)');
// Normal update
- act(() => setCounter(1));
+ await act(async () => setCounter(1));
assertLog(['Parent: 1 (light)', 'Child: 1 (light)', 'Effect: 1 (light)']);
expect(root).toMatchRenderedOutput('1 (light)');
// Update that doesn't change state, so it bails out
- act(() => setCounter(1));
+ await act(async () => setCounter(1));
assertLog(['Parent: 1 (light)']);
expect(root).toMatchRenderedOutput('1 (light)');
// Update that doesn't change state, but the context changes, too, so it
// can't bail out
- act(() => {
+ await act(async () => {
setCounter(1);
setTheme('dark');
});
@@ -394,7 +396,7 @@ describe('ReactHooks', () => {
expect(root).toMatchRenderedOutput('0');
// Normal update
- act(() => setCounter(1));
+ await act(async () => setCounter(1));
assertLog(['Parent: 1', 'Child: 1', 'Effect: 1']);
expect(root).toMatchRenderedOutput('1');
@@ -402,30 +404,30 @@ describe('ReactHooks', () => {
// because the alternate fiber has pending update priority, so we have to
// enter the render phase before we can bail out. But we bail out before
// rendering the child, and we don't fire any effects.
- act(() => setCounter(1));
+ await act(async () => setCounter(1));
assertLog(['Parent: 1']);
expect(root).toMatchRenderedOutput('1');
// Update to the same state again. This times, neither fiber has pending
// update priority, so we can bail out before even entering the render phase.
- act(() => setCounter(1));
+ await act(async () => setCounter(1));
await waitForAll([]);
expect(root).toMatchRenderedOutput('1');
// This changes the state to something different so it renders normally.
- act(() => setCounter(2));
+ await act(async () => setCounter(2));
assertLog(['Parent: 2', 'Child: 2', 'Effect: 2']);
expect(root).toMatchRenderedOutput('2');
// prepare to check SameValue
- act(() => {
+ await act(async () => {
setCounter(0);
});
assertLog(['Parent: 0', 'Child: 0', 'Effect: 0']);
expect(root).toMatchRenderedOutput('0');
// Update to the same state for the first time to flush the queue
- act(() => {
+ await act(async () => {
setCounter(0);
});
@@ -433,14 +435,14 @@ describe('ReactHooks', () => {
expect(root).toMatchRenderedOutput('0');
// Update again to the same state. Should bail out.
- act(() => {
+ await act(async () => {
setCounter(0);
});
await waitForAll([]);
expect(root).toMatchRenderedOutput('0');
// Update to a different state (positive 0 to negative 0)
- act(() => {
+ await act(async () => {
setCounter(0 / -1);
});
assertLog(['Parent: 0', 'Child: 0', 'Effect: 0']);
@@ -615,7 +617,7 @@ describe('ReactHooks', () => {
]);
});
- it('warns if deps is not an array', () => {
+ it('warns if deps is not an array', async () => {
const {useEffect, useLayoutEffect, useMemo, useCallback} = React;
function App(props) {
@@ -626,8 +628,8 @@ describe('ReactHooks', () => {
return null;
}
- expect(() => {
- act(() => {
+ await expect(async () => {
+ await act(async () => {
ReactTestRenderer.create();
});
}).toErrorDev([
@@ -640,8 +642,8 @@ describe('ReactHooks', () => {
'Warning: useCallback received a final argument that is not an array (instead, received `string`). ' +
'When specified, the final argument must be an array.',
]);
- expect(() => {
- act(() => {
+ await expect(async () => {
+ await act(async () => {
ReactTestRenderer.create();
});
}).toErrorDev([
@@ -654,8 +656,8 @@ describe('ReactHooks', () => {
'Warning: useCallback received a final argument that is not an array (instead, received `number`). ' +
'When specified, the final argument must be an array.',
]);
- expect(() => {
- act(() => {
+ await expect(async () => {
+ await act(async () => {
ReactTestRenderer.create();
});
}).toErrorDev([
@@ -669,7 +671,7 @@ describe('ReactHooks', () => {
'When specified, the final argument must be an array.',
]);
- act(() => {
+ await act(async () => {
ReactTestRenderer.create();
ReactTestRenderer.create();
ReactTestRenderer.create();
@@ -695,7 +697,7 @@ describe('ReactHooks', () => {
ReactTestRenderer.create();
});
- it('does not forget render phase useState updates inside an effect', () => {
+ it('does not forget render phase useState updates inside an effect', async () => {
const {useState, useEffect} = React;
function Counter() {
@@ -712,13 +714,13 @@ describe('ReactHooks', () => {
}
const root = ReactTestRenderer.create(null);
- act(() => {
+ await act(async () => {
root.update();
});
expect(root).toMatchRenderedOutput('4');
});
- it('does not forget render phase useReducer updates inside an effect with hoisted reducer', () => {
+ it('does not forget render phase useReducer updates inside an effect with hoisted reducer', async () => {
const {useReducer, useEffect} = React;
const reducer = x => x + 1;
@@ -736,13 +738,13 @@ describe('ReactHooks', () => {
}
const root = ReactTestRenderer.create(null);
- act(() => {
+ await act(async () => {
root.update();
});
expect(root).toMatchRenderedOutput('4');
});
- it('does not forget render phase useReducer updates inside an effect with inline reducer', () => {
+ it('does not forget render phase useReducer updates inside an effect with inline reducer', async () => {
const {useReducer, useEffect} = React;
function Counter() {
@@ -759,7 +761,7 @@ describe('ReactHooks', () => {
}
const root = ReactTestRenderer.create(null);
- act(() => {
+ await act(async () => {
root.update();
});
expect(root).toMatchRenderedOutput('4');
@@ -914,7 +916,7 @@ describe('ReactHooks', () => {
});
// Throws because there's no runtime cost for being strict here.
- it('throws when reading context inside useEffect', () => {
+ it('throws when reading context inside useEffect', async () => {
const {useEffect, createContext} = React;
const ReactCurrentDispatcher =
React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
@@ -928,14 +930,11 @@ describe('ReactHooks', () => {
return null;
}
- expect(() => {
- act(() => {
- ReactTestRenderer.create();
- });
- }).toThrow(
+ await act(async () => {
+ ReactTestRenderer.create();
// The exact message doesn't matter, just make sure we don't allow this
- 'Context can only be read while React is rendering',
- );
+ await waitForThrow('Context can only be read while React is rendering');
+ });
});
// Throws because there's no runtime cost for being strict here.
@@ -1070,7 +1069,7 @@ describe('ReactHooks', () => {
);
});
- it('resets warning internal state when interrupted by an error', () => {
+ it('resets warning internal state when interrupted by an error', async () => {
const ReactCurrentDispatcher =
React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
.ReactCurrentDispatcher;
@@ -1132,7 +1131,7 @@ describe('ReactHooks', () => {
}
// Verify it doesn't think we're still inside a Hook.
// Should have no warnings.
- act(() => {
+ await act(async () => {
ReactTestRenderer.create();
});
@@ -1473,7 +1472,7 @@ describe('ReactHooks', () => {
.replace('use', '')
.replace('Helper', '');
- it(`warns on using differently ordered hooks (${hookNameA}, ${hookNameB}) on subsequent renders`, () => {
+ it(`warns on using differently ordered hooks (${hookNameA}, ${hookNameB}) on subsequent renders`, async () => {
function App(props) {
/* eslint-disable no-unused-vars */
if (props.update) {
@@ -1489,12 +1488,12 @@ describe('ReactHooks', () => {
/* eslint-enable no-unused-vars */
}
let root;
- act(() => {
+ await act(async () => {
root = ReactTestRenderer.create();
});
- expect(() => {
+ await expect(async () => {
try {
- act(() => {
+ await act(async () => {
root.update();
});
} catch (error) {
@@ -1515,7 +1514,7 @@ describe('ReactHooks', () => {
// further warnings for this component are silenced
try {
- act(() => {
+ await act(async () => {
root.update();
});
} catch (error) {
@@ -1525,7 +1524,7 @@ describe('ReactHooks', () => {
}
});
- it(`warns when more hooks (${hookNameA}, ${hookNameB}) are used during update than mount`, () => {
+ it(`warns when more hooks (${hookNameA}, ${hookNameB}) are used during update than mount`, async () => {
function App(props) {
/* eslint-disable no-unused-vars */
if (props.update) {
@@ -1538,13 +1537,13 @@ describe('ReactHooks', () => {
/* eslint-enable no-unused-vars */
}
let root;
- act(() => {
+ await act(async () => {
root = ReactTestRenderer.create();
});
- expect(() => {
+ await expect(async () => {
try {
- act(() => {
+ await act(async () => {
root.update();
});
} catch (error) {
@@ -1579,7 +1578,7 @@ describe('ReactHooks', () => {
.replace('use', '')
.replace('Helper', '');
- it(`warns when fewer hooks (${hookNameA}, ${hookNameB}) are used during update than mount`, () => {
+ it(`warns when fewer hooks (${hookNameA}, ${hookNameB}) are used during update than mount`, async () => {
function App(props) {
/* eslint-disable no-unused-vars */
if (props.update) {
@@ -1592,15 +1591,15 @@ describe('ReactHooks', () => {
/* eslint-enable no-unused-vars */
}
let root;
- act(() => {
+ await act(async () => {
root = ReactTestRenderer.create();
});
- expect(() => {
- act(() => {
+ await act(async () => {
+ expect(() => {
root.update();
- });
- }).toThrow('Rendered fewer hooks than expected.');
+ }).toThrow('Rendered fewer hooks than expected. ');
+ });
});
});
@@ -1726,7 +1725,7 @@ describe('ReactHooks', () => {
});
// Regression test for https://github.com/facebook/react/issues/15057
- it('does not fire a false positive warning when previous effect unmounts the component', () => {
+ it('does not fire a false positive warning when previous effect unmounts the component', async () => {
const {useState, useEffect} = React;
let globalListener;
@@ -1762,7 +1761,7 @@ describe('ReactHooks', () => {
return null;
}
- act(() => {
+ await act(async () => {
ReactTestRenderer.create();
});
@@ -1912,7 +1911,7 @@ describe('ReactHooks', () => {
}
let root;
- act(() => {
+ await act(async () => {
root = ReactTestRenderer.create(
@@ -1921,7 +1920,7 @@ describe('ReactHooks', () => {
});
expect(root).toMatchRenderedOutput('Throw!');
- act(() => setShouldThrow(true));
+ await act(async () => setShouldThrow(true));
expect(root).toMatchRenderedOutput('Error!');
});
});
diff --git a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js
index 7325a3cf79b32..9010c8da5670a 100644
--- a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js
@@ -294,11 +294,11 @@ describe('ReactHooksWithNoopRenderer', () => {
await waitForAll(['Count: 0']);
expect(ReactNoop).toMatchRenderedOutput();
- act(() => counter.current.updateCount(1));
+ await act(async () => counter.current.updateCount(1));
assertLog(['Count: 1']);
expect(ReactNoop).toMatchRenderedOutput();
- act(() => counter.current.updateCount(count => count + 10));
+ await act(async () => counter.current.updateCount(count => count + 10));
assertLog(['Count: 11']);
expect(ReactNoop).toMatchRenderedOutput();
});
@@ -318,7 +318,7 @@ describe('ReactHooksWithNoopRenderer', () => {
await waitForAll(['getInitialState', 'Count: 42']);
expect(ReactNoop).toMatchRenderedOutput();
- act(() => counter.current.updateCount(7));
+ await act(async () => counter.current.updateCount(7));
assertLog(['Count: 7']);
expect(ReactNoop).toMatchRenderedOutput();
});
@@ -336,10 +336,10 @@ describe('ReactHooksWithNoopRenderer', () => {
await waitForAll(['Count: 0']);
expect(ReactNoop).toMatchRenderedOutput();
- act(() => counter.current.updateCount(7));
+ await act(async () => counter.current.updateCount(7));
assertLog(['Count: 7']);
- act(() => counter.current.updateLabel('Total'));
+ await act(async () => counter.current.updateLabel('Total'));
assertLog(['Total: 7']);
});
@@ -356,13 +356,13 @@ describe('ReactHooksWithNoopRenderer', () => {
const firstUpdater = updater;
- act(() => firstUpdater(1));
+ await act(async () => firstUpdater(1));
assertLog(['Count: 1']);
expect(ReactNoop).toMatchRenderedOutput();
const secondUpdater = updater;
- act(() => firstUpdater(count => count + 10));
+ await act(async () => firstUpdater(count => count + 10));
assertLog(['Count: 11']);
expect(ReactNoop).toMatchRenderedOutput();
@@ -381,7 +381,7 @@ describe('ReactHooksWithNoopRenderer', () => {
await waitForAll([]);
ReactNoop.render(null);
await waitForAll([]);
- act(() => _updateCount(1));
+ await act(async () => _updateCount(1));
});
it('works with memo', async () => {
@@ -401,7 +401,7 @@ describe('ReactHooksWithNoopRenderer', () => {
await waitForAll([]);
expect(ReactNoop).toMatchRenderedOutput();
- act(() => _updateCount(1));
+ await act(async () => _updateCount(1));
assertLog(['Count: 1']);
expect(ReactNoop).toMatchRenderedOutput();
});
@@ -637,7 +637,7 @@ describe('ReactHooksWithNoopRenderer', () => {
// Test that it works on update, too. This time the log is a bit different
// because we started with reducerB instead of reducerA.
- act(() => {
+ await act(async () => {
counter.current.dispatch('reset');
});
ReactNoop.render();
@@ -851,10 +851,10 @@ describe('ReactHooksWithNoopRenderer', () => {
await waitForAll(['Count: 0']);
expect(ReactNoop).toMatchRenderedOutput();
- act(() => counter.current.dispatch(INCREMENT));
+ await act(async () => counter.current.dispatch(INCREMENT));
assertLog(['Count: 1']);
expect(ReactNoop).toMatchRenderedOutput();
- act(() => {
+ await act(async () => {
counter.current.dispatch(DECREMENT);
counter.current.dispatch(DECREMENT);
counter.current.dispatch(DECREMENT);
@@ -893,11 +893,11 @@ describe('ReactHooksWithNoopRenderer', () => {
await waitForAll(['Init', 'Count: 10']);
expect(ReactNoop).toMatchRenderedOutput();
- act(() => counter.current.dispatch(INCREMENT));
+ await act(async () => counter.current.dispatch(INCREMENT));
assertLog(['Count: 11']);
expect(ReactNoop).toMatchRenderedOutput();
- act(() => {
+ await act(async () => {
counter.current.dispatch(DECREMENT);
counter.current.dispatch(DECREMENT);
counter.current.dispatch(DECREMENT);
@@ -1427,7 +1427,7 @@ describe('ReactHooksWithNoopRenderer', () => {
return state;
}
- act(() => {
+ await act(async () => {
ReactNoop.renderToRootWithID(, 'root', () =>
Scheduler.log('Sync effect'),
);
@@ -1437,7 +1437,7 @@ describe('ReactHooksWithNoopRenderer', () => {
ReactNoop.unmountRootWithID('root');
await waitForAll(['passive destroy']);
- act(() => {
+ await act(async () => {
updaterFunction(true);
});
});
@@ -1624,7 +1624,7 @@ describe('ReactHooksWithNoopRenderer', () => {
expect(ReactNoop).toMatchRenderedOutput();
// A flush sync doesn't cause the passive effects to fire.
// So we haven't added the other update yet.
- act(() => {
+ await act(async () => {
ReactNoop.flushSync(() => {
_updateCount(2);
});
@@ -2256,7 +2256,7 @@ describe('ReactHooksWithNoopRenderer', () => {
// @gate skipUnmountedBoundaries
it('should use the nearest still-mounted boundary if there are no unmounted boundaries', async () => {
- act(() => {
+ await act(async () => {
ReactNoop.render(
@@ -2269,7 +2269,7 @@ describe('ReactHooksWithNoopRenderer', () => {
'BrokenUseEffectCleanup useEffect',
]);
- act(() => {
+ await act(async () => {
ReactNoop.render();
});
@@ -2294,7 +2294,7 @@ describe('ReactHooksWithNoopRenderer', () => {
}
}
- act(() => {
+ await act(async () => {
ReactNoop.render(
@@ -2308,7 +2308,7 @@ describe('ReactHooksWithNoopRenderer', () => {
'BrokenUseEffectCleanup useEffect',
]);
- act(() => {
+ await act(async () => {
ReactNoop.render(
@@ -2333,7 +2333,7 @@ describe('ReactHooksWithNoopRenderer', () => {
}
}
- act(() => {
+ await act(async () => {
ReactNoop.render(
@@ -2346,7 +2346,7 @@ describe('ReactHooksWithNoopRenderer', () => {
'BrokenUseEffectCleanup useEffect',
]);
- act(() => {
+ await act(async () => {
ReactNoop.render(
@@ -2381,7 +2381,7 @@ describe('ReactHooksWithNoopRenderer', () => {
}
}
- act(() => {
+ await act(async () => {
ReactNoop.render();
});
@@ -2390,11 +2390,10 @@ describe('ReactHooksWithNoopRenderer', () => {
'BrokenUseEffectCleanup useEffect',
]);
- expect(() => {
- act(() => {
- ReactNoop.render();
- });
- }).toThrow('Expected error');
+ await act(async () => {
+ ReactNoop.render();
+ await waitForThrow('Expected error');
+ });
assertLog(['BrokenUseEffectCleanup useEffect destroy']);
@@ -2425,7 +2424,7 @@ describe('ReactHooksWithNoopRenderer', () => {
prevProps.prop === nextProps.prop;
const MemoizedChild = React.memo(Child, isEqual);
- act(() => {
+ await act(async () => {
ReactNoop.render(
@@ -2435,7 +2434,7 @@ describe('ReactHooksWithNoopRenderer', () => {
assertLog(['render', 'layout create', 'passive create']);
// Include at least one no-op (memoized) update to trigger original bug.
- act(() => {
+ await act(async () => {
ReactNoop.render(
@@ -2444,7 +2443,7 @@ describe('ReactHooksWithNoopRenderer', () => {
});
assertLog([]);
- act(() => {
+ await act(async () => {
ReactNoop.render(
@@ -2459,7 +2458,7 @@ describe('ReactHooksWithNoopRenderer', () => {
'passive create',
]);
- act(() => {
+ await act(async () => {
ReactNoop.render(null);
});
assertLog(['layout destroy', 'passive destroy']);
@@ -2492,7 +2491,7 @@ describe('ReactHooksWithNoopRenderer', () => {
prevProps.prop === nextProps.prop;
const MemoizedChild = React.memo(Child, isEqual);
- act(() => {
+ await act(async () => {
ReactNoop.render(
@@ -2502,7 +2501,7 @@ describe('ReactHooksWithNoopRenderer', () => {
assertLog(['render', 'layout create', 'passive create']);
// Include at least one no-op (memoized) update to trigger original bug.
- act(() => {
+ await act(async () => {
ReactNoop.render(
@@ -2511,7 +2510,7 @@ describe('ReactHooksWithNoopRenderer', () => {
});
assertLog([]);
- act(() => {
+ await act(async () => {
ReactNoop.render(
@@ -2526,12 +2525,16 @@ describe('ReactHooksWithNoopRenderer', () => {
'passive create',
]);
- act(() => {
+ await act(async () => {
ReactNoop.render(null);
});
assertLog(['layout destroy', 'passive destroy']);
});
+ // TODO: This test fails when skipUnmountedBoundaries is disabled. However,
+ // it's also rolled out to open source already and partially to www. So
+ // we should probably just land it.
+ // @gate skipUnmountedBoundaries
it('assumes passive effect destroy function is either a function or undefined', async () => {
function App(props) {
useEffect(() => {
@@ -2541,8 +2544,8 @@ describe('ReactHooksWithNoopRenderer', () => {
}
const root1 = ReactNoop.createRoot();
- expect(() => {
- act(() => {
+ await expect(async () => {
+ await act(async () => {
root1.render();
});
}).toErrorDev([
@@ -2551,8 +2554,8 @@ describe('ReactHooksWithNoopRenderer', () => {
]);
const root2 = ReactNoop.createRoot();
- expect(() => {
- act(() => {
+ await expect(async () => {
+ await act(async () => {
root2.render();
});
}).toErrorDev([
@@ -2562,8 +2565,8 @@ describe('ReactHooksWithNoopRenderer', () => {
]);
const root3 = ReactNoop.createRoot();
- expect(() => {
- act(() => {
+ await expect(async () => {
+ await act(async () => {
root3.render();
});
}).toErrorDev([
@@ -2573,11 +2576,10 @@ describe('ReactHooksWithNoopRenderer', () => {
]);
// Error on unmount because React assumes the value is a function
- expect(() =>
- act(() => {
- root3.unmount();
- }),
- ).toThrow('is not a function');
+ await act(async () => {
+ root3.render(null);
+ await waitForThrow('is not a function');
+ });
});
});
@@ -2921,8 +2923,8 @@ describe('ReactHooksWithNoopRenderer', () => {
}
const root1 = ReactNoop.createRoot();
- expect(() => {
- act(() => {
+ await expect(async () => {
+ await act(async () => {
root1.render();
});
}).toErrorDev([
@@ -2931,8 +2933,8 @@ describe('ReactHooksWithNoopRenderer', () => {
]);
const root2 = ReactNoop.createRoot();
- expect(() => {
- act(() => {
+ await expect(async () => {
+ await act(async () => {
root2.render();
});
}).toErrorDev([
@@ -2942,8 +2944,8 @@ describe('ReactHooksWithNoopRenderer', () => {
]);
const root3 = ReactNoop.createRoot();
- expect(() => {
- act(() => {
+ await expect(async () => {
+ await act(async () => {
root3.render();
});
}).toErrorDev([
@@ -2953,11 +2955,10 @@ describe('ReactHooksWithNoopRenderer', () => {
]);
// Error on unmount because React assumes the value is a function
- expect(() =>
- act(() => {
- root3.unmount();
- }),
- ).toThrow('is not a function');
+ await act(async () => {
+ root3.render(null);
+ await waitForThrow('is not a function');
+ });
});
it('warns when setState is called from insertion effect setup', async () => {
@@ -2973,17 +2974,16 @@ describe('ReactHooksWithNoopRenderer', () => {
}
const root = ReactNoop.createRoot();
- expect(() => {
- act(() => {
+ await expect(async () => {
+ await act(async () => {
root.render();
});
}).toErrorDev(['Warning: useInsertionEffect must not schedule updates.']);
- expect(() => {
- act(() => {
- root.render();
- });
- }).toThrow('No');
+ await act(async () => {
+ root.render();
+ await waitForThrow('No');
+ });
// Should not warn for regular effects after throw.
function NotInsertion() {
@@ -2993,7 +2993,7 @@ describe('ReactHooksWithNoopRenderer', () => {
}, []);
return null;
}
- act(() => {
+ await act(async () => {
root.render();
});
});
@@ -3013,20 +3013,19 @@ describe('ReactHooksWithNoopRenderer', () => {
}
const root = ReactNoop.createRoot();
- act(() => {
+ await act(async () => {
root.render();
});
- expect(() => {
- act(() => {
+ await expect(async () => {
+ await act(async () => {
root.render();
});
}).toErrorDev(['Warning: useInsertionEffect must not schedule updates.']);
- expect(() => {
- act(() => {
- root.render();
- });
- }).toThrow('No');
+ await act(async () => {
+ root.render();
+ await waitForThrow('No');
+ });
// Should not warn for regular effects after throw.
function NotInsertion() {
@@ -3036,7 +3035,7 @@ describe('ReactHooksWithNoopRenderer', () => {
}, []);
return null;
}
- act(() => {
+ await act(async () => {
root.render();
});
});
@@ -3204,8 +3203,8 @@ describe('ReactHooksWithNoopRenderer', () => {
}
const root1 = ReactNoop.createRoot();
- expect(() => {
- act(() => {
+ await expect(async () => {
+ await act(async () => {
root1.render();
});
}).toErrorDev([
@@ -3214,8 +3213,8 @@ describe('ReactHooksWithNoopRenderer', () => {
]);
const root2 = ReactNoop.createRoot();
- expect(() => {
- act(() => {
+ await expect(async () => {
+ await act(async () => {
root2.render();
});
}).toErrorDev([
@@ -3225,8 +3224,8 @@ describe('ReactHooksWithNoopRenderer', () => {
]);
const root3 = ReactNoop.createRoot();
- expect(() => {
- act(() => {
+ await expect(async () => {
+ await act(async () => {
root3.render();
});
}).toErrorDev([
@@ -3236,11 +3235,10 @@ describe('ReactHooksWithNoopRenderer', () => {
]);
// Error on unmount because React assumes the value is a function
- expect(() =>
- act(() => {
- root3.unmount();
- }),
- ).toThrow('is not a function');
+ await act(async () => {
+ root3.render(null);
+ await waitForThrow('is not a function');
+ });
});
});
@@ -3425,7 +3423,7 @@ describe('ReactHooksWithNoopRenderer', () => {
expect(ReactNoop).toMatchRenderedOutput();
expect(counter.current.count).toBe(0);
- act(() => {
+ await act(async () => {
counter.current.dispatch(INCREMENT);
});
assertLog(['Count: 1']);
@@ -3455,7 +3453,7 @@ describe('ReactHooksWithNoopRenderer', () => {
expect(ReactNoop).toMatchRenderedOutput();
expect(counter.current.count).toBe(0);
- act(() => {
+ await act(async () => {
counter.current.dispatch(INCREMENT);
});
assertLog(['Count: 1']);
@@ -3492,7 +3490,7 @@ describe('ReactHooksWithNoopRenderer', () => {
expect(counter.current.count).toBe(0);
expect(totalRefUpdates).toBe(1);
- act(() => {
+ await act(async () => {
counter.current.dispatch(INCREMENT);
});
assertLog(['Count: 1']);
@@ -3591,7 +3589,7 @@ describe('ReactHooksWithNoopRenderer', () => {
);
}
- act(() => {
+ await act(async () => {
ReactNoop.render();
});
@@ -3689,7 +3687,7 @@ describe('ReactHooksWithNoopRenderer', () => {
,
);
- act(() => {
+ await act(async () => {
updateA(2);
updateB(3);
});
@@ -3751,7 +3749,7 @@ describe('ReactHooksWithNoopRenderer', () => {
ReactNoop.render();
await waitForAll(['A: 0, B: 0, C: 0']);
expect(ReactNoop).toMatchRenderedOutput();
- act(() => {
+ await act(async () => {
updateA(2);
updateB(3);
updateC(4);
@@ -3857,7 +3855,7 @@ describe('ReactHooksWithNoopRenderer', () => {
expect(ReactNoop).toMatchRenderedOutput('1');
});
- act(() => {
+ await act(async () => {
setCounter(2);
});
assertLog(['Render: 1', 'Effect: 2', 'Reducer: 2', 'Render: 2']);
@@ -3892,7 +3890,7 @@ describe('ReactHooksWithNoopRenderer', () => {
await waitForAll(['Render disabled: true', 'Render count: 0']);
expect(ReactNoop).toMatchRenderedOutput('0');
- act(() => {
+ await act(async () => {
// These increments should have no effect, since disabled=true
increment();
increment();
@@ -3901,7 +3899,7 @@ describe('ReactHooksWithNoopRenderer', () => {
assertLog(['Render disabled: true', 'Render count: 0']);
expect(ReactNoop).toMatchRenderedOutput('0');
- act(() => {
+ await act(async () => {
// Enabling the updater should *not* replay the previous increment() actions
setDisabled(false);
});
@@ -3941,7 +3939,7 @@ describe('ReactHooksWithNoopRenderer', () => {
await waitForAll(['Render disabled: true', 'Render count: 0']);
expect(ReactNoop).toMatchRenderedOutput('0');
- act(() => {
+ await act(async () => {
// These increments should have no effect, since disabled=true
increment();
increment();
@@ -3950,7 +3948,7 @@ describe('ReactHooksWithNoopRenderer', () => {
assertLog(['Render count: 0']);
expect(ReactNoop).toMatchRenderedOutput('0');
- act(() => {
+ await act(async () => {
// Enabling the updater should *not* replay the previous increment() actions
setDisabled(false);
});
@@ -3990,7 +3988,7 @@ describe('ReactHooksWithNoopRenderer', () => {
await waitForAll(['Render disabled: true', 'Render count: 0']);
expect(ReactNoop).toMatchRenderedOutput('0');
- act(() => {
+ await act(async () => {
// Although the increment happens first (and would seem to do nothing since disabled=true),
// because these calls are in a batch the parent updates first. This should cause the child
// to re-render with disabled=false and *then* process the increment action, which now
@@ -4085,7 +4083,7 @@ describe('ReactHooksWithNoopRenderer', () => {
]);
expect(ReactNoop).toMatchRenderedOutput('0');
- act(() => dispatch());
+ await act(async () => dispatch());
assertLog(['Step: 5, Shadow: 5']);
expect(ReactNoop).toMatchRenderedOutput('5');
});
@@ -4110,10 +4108,10 @@ describe('ReactHooksWithNoopRenderer', () => {
return `${a ? 'A' : 'a'}${b ? 'B' : 'b'}${c ? 'C' : 'c'}`;
}
- act(() => ReactNoop.render());
+ await act(async () => ReactNoop.render());
expect(ReactNoop).toMatchRenderedOutput('abc');
- act(() => {
+ await act(async () => {
updateA(true);
// This update should not get dropped.
updateC(true);
@@ -4282,25 +4280,25 @@ describe('ReactHooksWithNoopRenderer', () => {
return ;
}
- act(() => {
+ await act(async () => {
ReactNoop.render();
});
assertLog(['Render: 0', 'Effect: 0']);
- act(() => {
+ await act(async () => {
handleClick();
});
assertLog(['Render: 0']);
- act(() => {
+ await act(async () => {
handleClick();
});
assertLog(['Render: 0']);
- act(() => {
+ await act(async () => {
handleClick();
});
diff --git a/packages/react-reconciler/src/__tests__/ReactOffscreenStrictMode-test.js b/packages/react-reconciler/src/__tests__/ReactOffscreenStrictMode-test.js
index a3f45d8aa1ac6..49d0e0c4b8959 100644
--- a/packages/react-reconciler/src/__tests__/ReactOffscreenStrictMode-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactOffscreenStrictMode-test.js
@@ -32,8 +32,8 @@ describe('ReactOffscreenStrictMode', () => {
}
// @gate __DEV__ && enableOffscreen
- it('should trigger strict effects when offscreen is visible', () => {
- act(() => {
+ it('should trigger strict effects when offscreen is visible', async () => {
+ await act(async () => {
ReactNoop.render(
@@ -56,8 +56,8 @@ describe('ReactOffscreenStrictMode', () => {
});
// @gate __DEV__ && enableOffscreen && useModernStrictMode
- it('should not trigger strict effects when offscreen is hidden', () => {
- act(() => {
+ it('should not trigger strict effects when offscreen is hidden', async () => {
+ await act(async () => {
ReactNoop.render(
@@ -71,7 +71,7 @@ describe('ReactOffscreenStrictMode', () => {
log = [];
- act(() => {
+ await act(async () => {
ReactNoop.render(
@@ -86,7 +86,7 @@ describe('ReactOffscreenStrictMode', () => {
log = [];
- act(() => {
+ await act(async () => {
ReactNoop.render(
@@ -109,7 +109,7 @@ describe('ReactOffscreenStrictMode', () => {
log = [];
- act(() => {
+ await act(async () => {
ReactNoop.render(
@@ -127,7 +127,7 @@ describe('ReactOffscreenStrictMode', () => {
]);
});
- it('should not cause infinite render loop when StrictMode is used with Suspense and synchronous set states', () => {
+ it('should not cause infinite render loop when StrictMode is used with Suspense and synchronous set states', async () => {
// This is a regression test, see https://github.com/facebook/react/pull/25179 for more details.
function App() {
const [state, setState] = React.useState(false);
@@ -143,7 +143,7 @@ describe('ReactOffscreenStrictMode', () => {
return state;
}
- act(() => {
+ await act(async () => {
ReactNoop.render(
@@ -193,7 +193,7 @@ describe('ReactOffscreenStrictMode', () => {
return null;
}
- act(() => {
+ await act(async () => {
ReactNoop.render(
diff --git a/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js b/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js
index e46d6deb51f73..ee3ea935ace91 100644
--- a/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js
@@ -2,12 +2,9 @@ let React;
let ReactTestRenderer;
let ReactFeatureFlags;
let Scheduler;
-let ReactCache;
let Suspense;
let act;
-
-let TextResource;
-let textResourceShouldFail;
+let textCache;
let assertLog;
let waitForPaint;
@@ -24,7 +21,6 @@ describe('ReactSuspense', () => {
ReactTestRenderer = require('react-test-renderer');
act = require('jest-react').act;
Scheduler = require('scheduler');
- ReactCache = require('react-cache');
Suspense = React.Suspense;
@@ -34,73 +30,71 @@ describe('ReactSuspense', () => {
assertLog = InternalTestUtils.assertLog;
waitFor = InternalTestUtils.waitFor;
- TextResource = ReactCache.unstable_createResource(
- ([text, ms = 0]) => {
- let listeners = null;
- let status = 'pending';
- let value = null;
- return {
- then(resolve, reject) {
- switch (status) {
- case 'pending': {
- if (listeners === null) {
- listeners = [{resolve, reject}];
- setTimeout(() => {
- if (textResourceShouldFail) {
- Scheduler.log(`Promise rejected [${text}]`);
- status = 'rejected';
- value = new Error('Failed to load: ' + text);
- listeners.forEach(listener => listener.reject(value));
- } else {
- Scheduler.log(`Promise resolved [${text}]`);
- status = 'resolved';
- value = text;
- listeners.forEach(listener => listener.resolve(value));
- }
- }, ms);
- } else {
- listeners.push({resolve, reject});
- }
- break;
- }
- case 'resolved': {
- resolve(value);
- break;
- }
- case 'rejected': {
- reject(value);
- break;
- }
- }
- },
- };
- },
- ([text, ms]) => text,
- );
- textResourceShouldFail = false;
+ textCache = new Map();
});
- function Text(props) {
- Scheduler.log(props.text);
- return props.text;
+ function resolveText(text) {
+ const record = textCache.get(text);
+ if (record === undefined) {
+ const newRecord = {
+ status: 'resolved',
+ value: text,
+ };
+ textCache.set(text, newRecord);
+ } else if (record.status === 'pending') {
+ const thenable = record.value;
+ record.status = 'resolved';
+ record.value = text;
+ thenable.pings.forEach(t => t());
+ }
}
- function AsyncText(props) {
- const text = props.text;
- try {
- TextResource.read([props.text, props.ms]);
- Scheduler.log(text);
- return text;
- } catch (promise) {
- if (typeof promise.then === 'function') {
- Scheduler.log(`Suspend! [${text}]`);
- } else {
- Scheduler.log(`Error! [${text}]`);
+ function readText(text) {
+ const record = textCache.get(text);
+ if (record !== undefined) {
+ switch (record.status) {
+ case 'pending':
+ Scheduler.log(`Suspend! [${text}]`);
+ throw record.value;
+ case 'rejected':
+ throw record.value;
+ case 'resolved':
+ return record.value;
}
- throw promise;
+ } else {
+ Scheduler.log(`Suspend! [${text}]`);
+ const thenable = {
+ pings: [],
+ then(resolve) {
+ if (newRecord.status === 'pending') {
+ thenable.pings.push(resolve);
+ } else {
+ Promise.resolve().then(() => resolve(newRecord.value));
+ }
+ },
+ };
+
+ const newRecord = {
+ status: 'pending',
+ value: thenable,
+ };
+ textCache.set(text, newRecord);
+
+ throw thenable;
}
}
+ function Text({text}) {
+ Scheduler.log(text);
+ return text;
+ }
+
+ function AsyncText({text}) {
+ readText(text);
+ Scheduler.log(text);
+ return text;
+ }
+
it('suspends rendering and continues later', async () => {
function Bar(props) {
Scheduler.log('Bar');
@@ -146,16 +140,10 @@ describe('ReactSuspense', () => {
]);
expect(root).toMatchRenderedOutput(null);
- // Flush some of the time
- jest.advanceTimersByTime(50);
- // Still nothing...
await waitForAll([]);
expect(root).toMatchRenderedOutput(null);
- // Flush the promise completely
- jest.advanceTimersByTime(50);
- // Renders successfully
- assertLog(['Promise resolved [A]']);
+ await resolveText('A');
await waitForAll(['Foo', 'Bar', 'A', 'B']);
expect(root).toMatchRenderedOutput('AB');
});
@@ -184,19 +172,15 @@ describe('ReactSuspense', () => {
]);
expect(root).toMatchRenderedOutput('Loading A...Loading B...');
- // Advance time by enough that the first Suspense's promise resolves and
- // switches back to the normal view. The second Suspense should still
- // show the placeholder
- jest.advanceTimersByTime(5000);
- // TODO: Should we throw if you forget to call toHaveYielded?
- assertLog(['Promise resolved [A]']);
+ // Resolve first Suspense's promise and switch back to the normal view. The
+ // second Suspense should still show the placeholder
+ await resolveText('A');
await waitForAll(['A']);
expect(root).toMatchRenderedOutput('ALoading B...');
- // Advance time by enough that the second Suspense's promise resolves
- // and switches back to the normal view
- jest.advanceTimersByTime(1000);
- assertLog(['Promise resolved [B]']);
+ // Resolve the second Suspense's promise resolves and switche back to the
+ // normal view
+ await resolveText('B');
await waitForAll(['B']);
expect(root).toMatchRenderedOutput('AB');
});
@@ -284,11 +268,6 @@ describe('ReactSuspense', () => {
);
}
- // Committing fallbacks should be throttled.
- // First, advance some time to skip the first threshold.
- jest.advanceTimersByTime(600);
- Scheduler.unstable_advanceTime(600);
-
const root = ReactTestRenderer.create(, {
unstable_isConcurrent: true,
});
@@ -302,10 +281,7 @@ describe('ReactSuspense', () => {
]);
expect(root).toMatchRenderedOutput('Loading...');
- // Resolve A.
- jest.advanceTimersByTime(200);
- Scheduler.unstable_advanceTime(200);
- assertLog(['Promise resolved [A]']);
+ await resolveText('A');
await waitForAll(['A', 'Suspend! [B]', 'Loading more...']);
// By this point, we have enough info to show "A" and "Loading more..."
@@ -313,15 +289,12 @@ describe('ReactSuspense', () => {
// showing the inner fallback hoping that B will resolve soon enough.
expect(root).toMatchRenderedOutput('Loading...');
- // Resolve B.
- jest.advanceTimersByTime(100);
- Scheduler.unstable_advanceTime(100);
- assertLog(['Promise resolved [B]']);
-
// By this point, B has resolved.
// We're still showing the outer fallback.
+ await resolveText('B');
expect(root).toMatchRenderedOutput('Loading...');
await waitForAll(['A', 'B']);
+
// Then contents of both should pop in together.
expect(root).toMatchRenderedOutput('AB');
});
@@ -339,11 +312,6 @@ describe('ReactSuspense', () => {
);
}
- // Committing fallbacks should be throttled.
- // First, advance some time to skip the first threshold.
- jest.advanceTimersByTime(600);
- Scheduler.unstable_advanceTime(600);
-
const root = ReactTestRenderer.create(, {
unstable_isConcurrent: true,
});
@@ -357,27 +325,20 @@ describe('ReactSuspense', () => {
]);
expect(root).toMatchRenderedOutput('Loading...');
- // Resolve A.
- jest.advanceTimersByTime(200);
- Scheduler.unstable_advanceTime(200);
- assertLog(['Promise resolved [A]']);
+ await resolveText('A');
await waitForAll(['A', 'Suspend! [B]', 'Loading more...']);
// By this point, we have enough info to show "A" and "Loading more..."
// However, we've just shown the outer fallback. So we'll delay
// showing the inner fallback hoping that B will resolve soon enough.
expect(root).toMatchRenderedOutput('Loading...');
-
- // Wait some more. B is still not resolving.
+ // But if we wait a bit longer, eventually we'll give up and show a
+ // fallback. The exact value here isn't important. It's a JND ("Just
+ // Noticeable Difference").
jest.advanceTimersByTime(500);
- Scheduler.unstable_advanceTime(500);
- // Give up and render A with a spinner for B.
expect(root).toMatchRenderedOutput('ALoading more...');
- // Resolve B.
- jest.advanceTimersByTime(500);
- Scheduler.unstable_advanceTime(500);
- assertLog(['Promise resolved [B]']);
+ await resolveText('B');
await waitForAll(['B']);
expect(root).toMatchRenderedOutput('AB');
});
@@ -423,18 +384,7 @@ describe('ReactSuspense', () => {
const MemoizedChild = memo(function MemoizedChild() {
const text = useContext(ValueContext);
- try {
- TextResource.read([text, 1000]);
- Scheduler.log(text);
- return text;
- } catch (promise) {
- if (typeof promise.then === 'function') {
- Scheduler.log(`Suspend! [${text}]`);
- } else {
- Scheduler.log(`Error! [${text}]`);
- }
- throw promise;
- }
+ return ;
});
let setValue;
@@ -455,17 +405,15 @@ describe('ReactSuspense', () => {
unstable_isConcurrent: true,
});
await waitForAll(['Suspend! [default]', 'Loading...']);
- jest.advanceTimersByTime(1000);
- assertLog(['Promise resolved [default]']);
+ await resolveText('default');
await waitForAll(['default']);
expect(root).toMatchRenderedOutput('default');
- act(() => setValue('new value'));
+ await act(async () => setValue('new value'));
assertLog(['Suspend! [new value]', 'Loading...']);
- jest.advanceTimersByTime(1000);
- assertLog(['Promise resolved [new value]']);
+ await resolveText('new value');
await waitForAll(['new value']);
expect(root).toMatchRenderedOutput('new value');
});
@@ -478,18 +426,7 @@ describe('ReactSuspense', () => {
const MemoizedChild = memo(
function MemoizedChild() {
const text = useContext(ValueContext);
- try {
- TextResource.read([text, 1000]);
- Scheduler.log(text);
- return text;
- } catch (promise) {
- if (typeof promise.then === 'function') {
- Scheduler.log(`Suspend! [${text}]`);
- } else {
- Scheduler.log(`Error! [${text}]`);
- }
- throw promise;
- }
+ return ;
},
function areEqual(prevProps, nextProps) {
return true;
@@ -514,17 +451,15 @@ describe('ReactSuspense', () => {
unstable_isConcurrent: true,
});
await waitForAll(['Suspend! [default]', 'Loading...']);
- jest.advanceTimersByTime(1000);
- assertLog(['Promise resolved [default]']);
+ await resolveText('default');
await waitForAll(['default']);
expect(root).toMatchRenderedOutput('default');
- act(() => setValue('new value'));
+ await act(async () => setValue('new value'));
assertLog(['Suspend! [new value]', 'Loading...']);
- jest.advanceTimersByTime(1000);
- assertLog(['Promise resolved [new value]']);
+ await resolveText('new value');
await waitForAll(['new value']);
expect(root).toMatchRenderedOutput('new value');
});
@@ -536,18 +471,7 @@ describe('ReactSuspense', () => {
function MemoizedChild() {
const text = useContext(ValueContext);
- try {
- TextResource.read([text, 1000]);
- Scheduler.log(text);
- return text;
- } catch (promise) {
- if (typeof promise.then === 'function') {
- Scheduler.log(`Suspend! [${text}]`);
- } else {
- Scheduler.log(`Error! [${text}]`);
- }
- throw promise;
- }
+ return ;
}
let setValue;
@@ -571,17 +495,15 @@ describe('ReactSuspense', () => {
},
);
await waitForAll(['Suspend! [default]', 'Loading...']);
- jest.advanceTimersByTime(1000);
- assertLog(['Promise resolved [default]']);
+ await resolveText('default');
await waitForAll(['default']);
expect(root).toMatchRenderedOutput('default');
- act(() => setValue('new value'));
+ await act(async () => setValue('new value'));
assertLog(['Suspend! [new value]', 'Loading...']);
- jest.advanceTimersByTime(1000);
- assertLog(['Promise resolved [new value]']);
+ await resolveText('new value');
await waitForAll(['new value']);
expect(root).toMatchRenderedOutput('new value');
});
@@ -593,18 +515,7 @@ describe('ReactSuspense', () => {
const MemoizedChild = forwardRef(() => {
const text = useContext(ValueContext);
- try {
- TextResource.read([text, 1000]);
- Scheduler.log(text);
- return text;
- } catch (promise) {
- if (typeof promise.then === 'function') {
- Scheduler.log(`Suspend! [${text}]`);
- } else {
- Scheduler.log(`Error! [${text}]`);
- }
- throw promise;
- }
+ return ;
});
let setValue;
@@ -628,17 +539,15 @@ describe('ReactSuspense', () => {
},
);
await waitForAll(['Suspend! [default]', 'Loading...']);
- jest.advanceTimersByTime(1000);
- assertLog(['Promise resolved [default]']);
+ await resolveText('default');
await waitForAll(['default']);
expect(root).toMatchRenderedOutput('default');
- act(() => setValue('new value'));
+ await act(async () => setValue('new value'));
assertLog(['Suspend! [new value]', 'Loading...']);
- jest.advanceTimersByTime(1000);
- assertLog(['Promise resolved [new value]']);
+ await resolveText('new value');
await waitForAll(['new value']);
expect(root).toMatchRenderedOutput('new value');
});
@@ -674,12 +583,17 @@ describe('ReactSuspense', () => {
await waitForAll(['Child 1', 'create layout']);
expect(root).toMatchRenderedOutput('Child 1');
- act(() => {
+ await act(async () => {
_setShow(true);
});
- assertLog(['Child 1', 'Suspend! [Child 2]', 'Loading...']);
- jest.advanceTimersByTime(1000);
- assertLog(['destroy layout', 'Promise resolved [Child 2]']);
+ assertLog([
+ 'Child 1',
+ 'Suspend! [Child 2]',
+ 'Loading...',
+ 'destroy layout',
+ ]);
+
+ await resolveText('Child 2');
await waitForAll(['Child 1', 'Child 2', 'create layout']);
expect(root).toMatchRenderedOutput(['Child 1', 'Child 2'].join(''));
});
@@ -715,20 +629,8 @@ describe('ReactSuspense', () => {
}
render() {
instance = this;
- const text = `${this.props.text}:${this.state.step}`;
- const ms = this.props.ms;
- try {
- TextResource.read([text, ms]);
- Scheduler.log(text);
- return text;
- } catch (promise) {
- if (typeof promise.then === 'function') {
- Scheduler.log(`Suspend! [${text}]`);
- } else {
- Scheduler.log(`Error! [${text}]`);
- }
- throw promise;
- }
+ const text = readText(`${this.props.text}:${this.state.step}`);
+ return ;
}
}
@@ -758,9 +660,7 @@ describe('ReactSuspense', () => {
]);
expect(root).toMatchRenderedOutput('Loading...');
- jest.advanceTimersByTime(100);
-
- assertLog(['Promise resolved [B:1]']);
+ await resolveText('B:1');
await waitForPaint([
'B:1',
'Unmount [Loading...]',
@@ -773,9 +673,7 @@ describe('ReactSuspense', () => {
assertLog(['Suspend! [B:2]', 'Loading...', 'Mount [Loading...]']);
expect(root).toMatchRenderedOutput('Loading...');
- jest.advanceTimersByTime(100);
-
- assertLog(['Promise resolved [B:2]']);
+ await resolveText('B:2');
await waitForPaint(['B:2', 'Unmount [Loading...]', 'Update [B:2]']);
expect(root).toMatchRenderedOutput('AB:2C');
});
@@ -803,9 +701,7 @@ describe('ReactSuspense', () => {
assertLog(['Stateful: 1', 'Suspend! [A]', 'Loading...']);
- jest.advanceTimersByTime(1000);
-
- assertLog(['Promise resolved [A]']);
+ await resolveText('A');
await waitForPaint(['A']);
expect(root).toMatchRenderedOutput('Stateful: 1A');
@@ -817,9 +713,7 @@ describe('ReactSuspense', () => {
assertLog(['Stateful: 2', 'Suspend! [B]']);
expect(root).toMatchRenderedOutput('Loading...');
- jest.advanceTimersByTime(1000);
-
- assertLog(['Promise resolved [B]']);
+ await resolveText('B');
await waitForPaint(['B']);
expect(root).toMatchRenderedOutput('Stateful: 2B');
});
@@ -855,9 +749,7 @@ describe('ReactSuspense', () => {
assertLog(['Stateful: 1', 'Suspend! [A]', 'Loading...']);
- jest.advanceTimersByTime(1000);
-
- assertLog(['Promise resolved [A]']);
+ await resolveText('A');
await waitForPaint(['A']);
expect(root).toMatchRenderedOutput('Stateful: 1A');
@@ -876,9 +768,7 @@ describe('ReactSuspense', () => {
]);
expect(root).toMatchRenderedOutput('Loading...');
- jest.advanceTimersByTime(1000);
-
- assertLog(['Promise resolved [B]']);
+ await resolveText('B');
await waitForPaint(['B']);
expect(root).toMatchRenderedOutput('Stateful: 2B');
});
@@ -889,20 +779,7 @@ describe('ReactSuspense', () => {
Scheduler.log('will unmount');
}
render() {
- const text = this.props.text;
- const ms = this.props.ms;
- try {
- TextResource.read([text, ms]);
- Scheduler.log(text);
- return text;
- } catch (promise) {
- if (typeof promise.then === 'function') {
- Scheduler.log(`Suspend! [${text}]`);
- } else {
- Scheduler.log(`Error! [${text}]`);
- }
- throw promise;
- }
+ return ;
}
}
@@ -932,18 +809,7 @@ describe('ReactSuspense', () => {
Scheduler.log('Did commit: ' + text);
}, [text]);
- try {
- TextResource.read([props.text, props.ms]);
- Scheduler.log(text);
- return text;
- } catch (promise) {
- if (typeof promise.then === 'function') {
- Scheduler.log(`Suspend! [${text}]`);
- } else {
- Scheduler.log(`Error! [${text}]`);
- }
- throw promise;
- }
+ return ;
}
function App({text}) {
@@ -956,9 +822,7 @@ describe('ReactSuspense', () => {
ReactTestRenderer.create();
assertLog(['Suspend! [A]', 'Loading...']);
- jest.advanceTimersByTime(500);
-
- assertLog(['Promise resolved [A]']);
+ await resolveText('A');
await waitForPaint(['A', 'Did commit: A']);
});
@@ -986,15 +850,16 @@ describe('ReactSuspense', () => {
// Initial render
await waitForAll(['Suspend! [Step: 1]', 'Loading...']);
- jest.advanceTimersByTime(1000);
- assertLog(['Promise resolved [Step: 1]']);
+
+ await resolveText('Step: 1');
await waitForAll(['Step: 1']);
expect(root).toMatchRenderedOutput('Step: 1');
// Update that suspends
- instance.setState({step: 2});
- await waitForAll(['Suspend! [Step: 2]', 'Loading...']);
- jest.advanceTimersByTime(500);
+ await act(async () => {
+ instance.setState({step: 2});
+ });
+ assertLog(['Suspend! [Step: 2]', 'Loading...']);
expect(root).toMatchRenderedOutput('Loading...');
// Update while still suspended
@@ -1002,8 +867,8 @@ describe('ReactSuspense', () => {
await waitForAll(['Suspend! [Step: 3]']);
expect(root).toMatchRenderedOutput('Loading...');
- jest.advanceTimersByTime(1000);
- assertLog(['Promise resolved [Step: 2]', 'Promise resolved [Step: 3]']);
+ await resolveText('Step: 2');
+ await resolveText('Step: 3');
await waitForAll(['Step: 3']);
expect(root).toMatchRenderedOutput('Step: 3');
});
@@ -1040,23 +905,17 @@ describe('ReactSuspense', () => {
]);
await waitForAll([]);
- jest.advanceTimersByTime(1000);
-
- assertLog(['Promise resolved [Child 1]']);
+ await resolveText('Child 1');
await waitForPaint([
'Child 1',
'Suspend! [Child 2]',
'Suspend! [Child 3]',
]);
- jest.advanceTimersByTime(1000);
-
- assertLog(['Promise resolved [Child 2]']);
+ await resolveText('Child 2');
await waitForPaint(['Child 2', 'Suspend! [Child 3]']);
- jest.advanceTimersByTime(1000);
-
- assertLog(['Promise resolved [Child 3]']);
+ await resolveText('Child 3');
await waitForPaint(['Child 3']);
expect(root).toMatchRenderedOutput(
['Child 1', 'Child 2', 'Child 3'].join(''),
@@ -1083,11 +942,12 @@ describe('ReactSuspense', () => {
'Suspend! [Child 2]',
'Loading...',
]);
- jest.advanceTimersByTime(1000);
- assertLog(['Promise resolved [Child 1]']);
+ await resolveText('Child 1');
await waitForAll(['Child 1', 'Suspend! [Child 2]']);
+
jest.advanceTimersByTime(6000);
- assertLog(['Promise resolved [Child 2]']);
+
+ await resolveText('Child 2');
await waitForAll(['Child 1', 'Child 2']);
expect(root).toMatchRenderedOutput(['Child 1', 'Child 2'].join(''));
});
@@ -1111,32 +971,29 @@ describe('ReactSuspense', () => {
const root = ReactTestRenderer.create();
assertLog(['Suspend! [Tab: 0]', ' + sibling', 'Loading...']);
expect(root).toMatchRenderedOutput('Loading...');
- jest.advanceTimersByTime(1000);
- assertLog(['Promise resolved [Tab: 0]']);
+ await resolveText('Tab: 0');
await waitForPaint(['Tab: 0']);
expect(root).toMatchRenderedOutput('Tab: 0 + sibling');
- act(() => setTab(1));
+ await act(async () => setTab(1));
assertLog(['Suspend! [Tab: 1]', ' + sibling', 'Loading...']);
expect(root).toMatchRenderedOutput('Loading...');
- jest.advanceTimersByTime(1000);
- assertLog(['Promise resolved [Tab: 1]']);
+ await resolveText('Tab: 1');
await waitForPaint(['Tab: 1']);
expect(root).toMatchRenderedOutput('Tab: 1 + sibling');
- act(() => setTab(2));
+ await act(async () => setTab(2));
assertLog(['Suspend! [Tab: 2]', ' + sibling', 'Loading...']);
expect(root).toMatchRenderedOutput('Loading...');
- jest.advanceTimersByTime(1000);
- assertLog(['Promise resolved [Tab: 2]']);
+ await resolveText('Tab: 2');
await waitForPaint(['Tab: 2']);
expect(root).toMatchRenderedOutput('Tab: 2 + sibling');
});
- it('does not warn if an mounted component is pinged', async () => {
+ it('does not warn if a mounted component is pinged', async () => {
const {useState} = React;
const root = ReactTestRenderer.create(null);
@@ -1146,18 +1003,7 @@ describe('ReactSuspense', () => {
const [step, _setStep] = useState(0);
setStep = _setStep;
const fullText = `${text}:${step}`;
- try {
- TextResource.read([fullText, ms]);
- Scheduler.log(fullText);
- return fullText;
- } catch (promise) {
- if (typeof promise.then === 'function') {
- Scheduler.log(`Suspend! [${fullText}]`);
- } else {
- Scheduler.log(`Error! [${fullText}]`);
- }
- throw promise;
- }
+ return ;
}
root.update(
@@ -1167,19 +1013,18 @@ describe('ReactSuspense', () => {
);
assertLog(['Suspend! [A:0]', 'Loading...']);
- jest.advanceTimersByTime(1000);
- assertLog(['Promise resolved [A:0]']);
+ await resolveText('A:0');
await waitForPaint(['A:0']);
expect(root).toMatchRenderedOutput('A:0');
- act(() => setStep(1));
+ await act(async () => setStep(1));
assertLog(['Suspend! [A:1]', 'Loading...']);
expect(root).toMatchRenderedOutput('Loading...');
- root.update(null);
- await waitForAll([]);
- jest.advanceTimersByTime(1000);
+ await act(async () => {
+ root.update(null);
+ });
});
it('memoizes promise listeners per thread ID to prevent redundant renders', async () => {
@@ -1199,10 +1044,7 @@ describe('ReactSuspense', () => {
assertLog(['Suspend! [A]', 'Suspend! [B]', 'Suspend! [C]', 'Loading...']);
- // Resolve A
- jest.advanceTimersByTime(1000);
-
- assertLog(['Promise resolved [A]']);
+ await resolveText('A');
await waitForPaint([
'A',
// The promises for B and C have now been thrown twice
@@ -1210,10 +1052,7 @@ describe('ReactSuspense', () => {
'Suspend! [C]',
]);
- // Resolve B
- jest.advanceTimersByTime(1000);
-
- assertLog(['Promise resolved [B]']);
+ await resolveText('B');
await waitForPaint([
// Even though the promise for B was thrown twice, we should only
// re-render once.
@@ -1222,10 +1061,7 @@ describe('ReactSuspense', () => {
'Suspend! [C]',
]);
- // Resolve C
- jest.advanceTimersByTime(1000);
-
- assertLog(['Promise resolved [C]']);
+ await resolveText('C');
await waitForPaint([
// Even though the promise for C was thrown three times, we should only
// re-render once.
@@ -1233,7 +1069,7 @@ describe('ReactSuspense', () => {
]);
});
- it('#14162', () => {
+ it('#14162', async () => {
const {lazy} = React;
function Hello() {
@@ -1267,8 +1103,9 @@ describe('ReactSuspense', () => {
const root = ReactTestRenderer.create(null);
- root.update();
- jest.advanceTimersByTime(1000);
+ await act(async () => {
+ root.update();
+ });
});
it('updates memoized child of suspense component when context updates (simple memo)', async () => {
@@ -1278,18 +1115,7 @@ describe('ReactSuspense', () => {
const MemoizedChild = memo(function MemoizedChild() {
const text = useContext(ValueContext);
- try {
- TextResource.read([text, 1000]);
- Scheduler.log(text);
- return text;
- } catch (promise) {
- if (typeof promise.then === 'function') {
- Scheduler.log(`Suspend! [${text}]`);
- } else {
- Scheduler.log(`Error! [${text}]`);
- }
- throw promise;
- }
+ return ;
});
let setValue;
@@ -1308,17 +1134,15 @@ describe('ReactSuspense', () => {
const root = ReactTestRenderer.create();
assertLog(['Suspend! [default]', 'Loading...']);
- jest.advanceTimersByTime(1000);
- assertLog(['Promise resolved [default]']);
+ await resolveText('default');
await waitForPaint(['default']);
expect(root).toMatchRenderedOutput('default');
- act(() => setValue('new value'));
+ await act(async () => setValue('new value'));
assertLog(['Suspend! [new value]', 'Loading...']);
- jest.advanceTimersByTime(1000);
- assertLog(['Promise resolved [new value]']);
+ await resolveText('new value');
await waitForPaint(['new value']);
expect(root).toMatchRenderedOutput('new value');
});
@@ -1331,18 +1155,7 @@ describe('ReactSuspense', () => {
const MemoizedChild = memo(
function MemoizedChild() {
const text = useContext(ValueContext);
- try {
- TextResource.read([text, 1000]);
- Scheduler.log(text);
- return text;
- } catch (promise) {
- if (typeof promise.then === 'function') {
- Scheduler.log(`Suspend! [${text}]`);
- } else {
- Scheduler.log(`Error! [${text}]`);
- }
- throw promise;
- }
+ return ;
},
function areEqual(prevProps, nextProps) {
return true;
@@ -1365,17 +1178,15 @@ describe('ReactSuspense', () => {
const root = ReactTestRenderer.create();
assertLog(['Suspend! [default]', 'Loading...']);
- jest.advanceTimersByTime(1000);
- assertLog(['Promise resolved [default]']);
+ await resolveText('default');
await waitForPaint(['default']);
expect(root).toMatchRenderedOutput('default');
- act(() => setValue('new value'));
+ await act(async () => setValue('new value'));
assertLog(['Suspend! [new value]', 'Loading...']);
- jest.advanceTimersByTime(1000);
- assertLog(['Promise resolved [new value]']);
+ await resolveText('new value');
await waitForPaint(['new value']);
expect(root).toMatchRenderedOutput('new value');
});
@@ -1387,18 +1198,7 @@ describe('ReactSuspense', () => {
function MemoizedChild() {
const text = useContext(ValueContext);
- try {
- TextResource.read([text, 1000]);
- Scheduler.log(text);
- return text;
- } catch (promise) {
- if (typeof promise.then === 'function') {
- Scheduler.log(`Suspend! [${text}]`);
- } else {
- Scheduler.log(`Error! [${text}]`);
- }
- throw promise;
- }
+ return ;
}
let setValue;
@@ -1421,17 +1221,15 @@ describe('ReactSuspense', () => {
,
);
assertLog(['Suspend! [default]', 'Loading...']);
- jest.advanceTimersByTime(1000);
- assertLog(['Promise resolved [default]']);
+ await resolveText('default');
await waitForPaint(['default']);
expect(root).toMatchRenderedOutput('default');
- act(() => setValue('new value'));
+ await act(async () => setValue('new value'));
assertLog(['Suspend! [new value]', 'Loading...']);
- jest.advanceTimersByTime(1000);
- assertLog(['Promise resolved [new value]']);
+ await resolveText('new value');
await waitForPaint(['new value']);
expect(root).toMatchRenderedOutput('new value');
});
@@ -1443,18 +1241,7 @@ describe('ReactSuspense', () => {
const MemoizedChild = forwardRef(function MemoizedChild() {
const text = useContext(ValueContext);
- try {
- TextResource.read([text, 1000]);
- Scheduler.log(text);
- return text;
- } catch (promise) {
- if (typeof promise.then === 'function') {
- Scheduler.log(`Suspend! [${text}]`);
- } else {
- Scheduler.log(`Error! [${text}]`);
- }
- throw promise;
- }
+ return ;
});
let setValue;
@@ -1473,22 +1260,20 @@ describe('ReactSuspense', () => {
const root = ReactTestRenderer.create();
assertLog(['Suspend! [default]', 'Loading...']);
- jest.advanceTimersByTime(1000);
- assertLog(['Promise resolved [default]']);
+ await resolveText('default');
await waitForPaint(['default']);
expect(root).toMatchRenderedOutput('default');
- act(() => setValue('new value'));
+ await act(async () => setValue('new value'));
assertLog(['Suspend! [new value]', 'Loading...']);
- jest.advanceTimersByTime(1000);
- assertLog(['Promise resolved [new value]']);
+ await resolveText('new value');
await waitForPaint(['new value']);
expect(root).toMatchRenderedOutput('new value');
});
- it('updates context consumer within child of suspended suspense component when context updates', () => {
+ it('updates context consumer within child of suspended suspense component when context updates', async () => {
const {createContext, useState} = React;
const ValueContext = createContext(null);
@@ -1531,11 +1316,11 @@ describe('ReactSuspense', () => {
assertLog(['Received context value [default]', 'default']);
expect(root).toMatchRenderedOutput('default');
- act(() => setValue('new value'));
+ await act(async () => setValue('new value'));
assertLog(['Received context value [new value]', 'Loading...']);
expect(root).toMatchRenderedOutput('Loading...');
- act(() => setValue('default'));
+ await act(async () => setValue('default'));
assertLog(['Received context value [default]', 'default']);
expect(root).toMatchRenderedOutput('default');
});
diff --git a/packages/react-reconciler/src/__tests__/ReactSuspenseEffectsSemantics-test.js b/packages/react-reconciler/src/__tests__/ReactSuspenseEffectsSemantics-test.js
index 71cb898077f36..914f58c2bdb23 100644
--- a/packages/react-reconciler/src/__tests__/ReactSuspenseEffectsSemantics-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactSuspenseEffectsSemantics-test.js
@@ -17,6 +17,7 @@ let caches;
let seededCache;
let ErrorBoundary;
let waitForAll;
+let waitFor;
let assertLog;
// TODO: These tests don't pass in persistent mode yet. Need to implement.
@@ -35,6 +36,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
const InternalTestUtils = require('internal-test-utils');
waitForAll = InternalTestUtils.waitForAll;
+ waitFor = InternalTestUtils.waitFor;
assertLog = InternalTestUtils.assertLog;
caches = [];
@@ -372,7 +374,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
}
// Mount and suspend.
- act(() => {
+ await act(async () => {
ReactNoop.renderLegacySyncRoot(
@@ -473,7 +475,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
}
// Mount
- act(() => {
+ await act(async () => {
ReactNoop.renderLegacySyncRoot();
});
assertLog([
@@ -499,7 +501,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
);
// Schedule an update that causes React to suspend.
- act(() => {
+ await act(async () => {
ReactNoop.renderLegacySyncRoot(
@@ -629,46 +631,46 @@ describe('ReactSuspenseEffectsSemantics', () => {
);
// Schedule an update that causes React to suspend.
- act(() => {
+ await act(async () => {
ReactNoop.render(
,
);
- });
- assertLog([
- 'App render',
- 'Text:Inside:Before render',
- 'Suspend:Async',
- 'Text:Inside:After render',
- 'Text:Fallback render',
- 'Text:Outside render',
- ]);
- expect(ReactNoop).toMatchRenderedOutput(
- <>
-
-
-
- >,
- );
+ await waitFor([
+ 'App render',
+ 'Text:Inside:Before render',
+ 'Suspend:Async',
+ 'Text:Inside:After render',
+ 'Text:Fallback render',
+ 'Text:Outside render',
+ ]);
+ expect(ReactNoop).toMatchRenderedOutput(
+ <>
+
+
+
+ >,
+ );
- await advanceTimers(1000);
+ await jest.runAllTimers();
- // Timing out should commit the fallback and destroy inner layout effects.
- assertLog([
- 'Text:Inside:Before destroy layout',
- 'Text:Inside:After destroy layout',
- 'Text:Fallback create layout',
- ]);
- await waitForAll(['Text:Fallback create passive']);
- expect(ReactNoop).toMatchRenderedOutput(
- <>
-
-
-
-
- >,
- );
+ // Timing out should commit the fallback and destroy inner layout effects.
+ assertLog([
+ 'Text:Inside:Before destroy layout',
+ 'Text:Inside:After destroy layout',
+ 'Text:Fallback create layout',
+ ]);
+ await waitForAll(['Text:Fallback create passive']);
+ expect(ReactNoop).toMatchRenderedOutput(
+ <>
+
+
+
+
+ >,
+ );
+ });
// Resolving the suspended resource should re-create inner layout effects.
await act(async () => {
@@ -783,46 +785,47 @@ describe('ReactSuspenseEffectsSemantics', () => {
);
// Schedule an update that causes React to suspend.
- act(() => {
+ await act(async () => {
ReactNoop.render(
,
);
- });
- assertLog([
- 'App render',
- 'ClassText:Inside:Before render',
- 'Suspend:Async',
- 'ClassText:Inside:After render',
- 'ClassText:Fallback render',
- 'ClassText:Outside render',
- ]);
- expect(ReactNoop).toMatchRenderedOutput(
- <>
-
-
-
- >,
- );
- await advanceTimers(1000);
+ await waitFor([
+ 'App render',
+ 'ClassText:Inside:Before render',
+ 'Suspend:Async',
+ 'ClassText:Inside:After render',
+ 'ClassText:Fallback render',
+ 'ClassText:Outside render',
+ ]);
+ expect(ReactNoop).toMatchRenderedOutput(
+ <>
+
+
+
+ >,
+ );
- // Timing out should commit the fallback and destroy inner layout effects.
- assertLog([
- 'ClassText:Inside:Before componentWillUnmount',
- 'ClassText:Inside:After componentWillUnmount',
- 'ClassText:Fallback componentDidMount',
- 'ClassText:Outside componentDidUpdate',
- ]);
- expect(ReactNoop).toMatchRenderedOutput(
- <>
-
-
-
-
- >,
- );
+ await jest.runAllTimers();
+
+ // Timing out should commit the fallback and destroy inner layout effects.
+ assertLog([
+ 'ClassText:Inside:Before componentWillUnmount',
+ 'ClassText:Inside:After componentWillUnmount',
+ 'ClassText:Fallback componentDidMount',
+ 'ClassText:Outside componentDidUpdate',
+ ]);
+ expect(ReactNoop).toMatchRenderedOutput(
+ <>
+
+
+
+
+ >,
+ );
+ });
// Resolving the suspended resource should re-create inner layout effects.
await act(async () => {
@@ -908,43 +911,43 @@ describe('ReactSuspenseEffectsSemantics', () => {
);
// Schedule an update that causes React to suspend.
- act(() => {
+ await act(async () => {
ReactNoop.render(
,
);
- });
- assertLog([
- 'App render',
- 'Suspend:Async',
- 'Text:Outer render',
- 'Text:Inner render',
- 'Text:Fallback render',
- ]);
- expect(ReactNoop).toMatchRenderedOutput(
-
-
- ,
- );
+ await waitFor([
+ 'App render',
+ 'Suspend:Async',
+ 'Text:Outer render',
+ 'Text:Inner render',
+ 'Text:Fallback render',
+ ]);
+ expect(ReactNoop).toMatchRenderedOutput(
+
+
+ ,
+ );
- await advanceTimers(1000);
+ await jest.runAllTimers();
- // Timing out should commit the fallback and destroy inner layout effects.
- assertLog([
- 'Text:Outer destroy layout',
- 'Text:Inner destroy layout',
- 'Text:Fallback create layout',
- ]);
- await waitForAll(['Text:Fallback create passive']);
- expect(ReactNoop).toMatchRenderedOutput(
- <>
-
-
-
-
- >,
- );
+ // Timing out should commit the fallback and destroy inner layout effects.
+ assertLog([
+ 'Text:Outer destroy layout',
+ 'Text:Inner destroy layout',
+ 'Text:Fallback create layout',
+ ]);
+ await waitForAll(['Text:Fallback create passive']);
+ expect(ReactNoop).toMatchRenderedOutput(
+ <>
+
+
+
+
+ >,
+ );
+ });
// Resolving the suspended resource should re-create inner layout effects.
await act(async () => {
@@ -1035,44 +1038,44 @@ describe('ReactSuspenseEffectsSemantics', () => {
);
// Schedule an update that causes React to suspend.
- act(() => {
+ await act(async () => {
ReactNoop.render(
,
);
- });
- assertLog([
- 'App render',
- 'Suspend:Async',
- 'Text:Outer render',
- // Text:MemoizedInner is memoized
- 'Text:Fallback render',
- ]);
- expect(ReactNoop).toMatchRenderedOutput(
-
-
- ,
- );
+ await waitFor([
+ 'App render',
+ 'Suspend:Async',
+ 'Text:Outer render',
+ // Text:MemoizedInner is memoized
+ 'Text:Fallback render',
+ ]);
+ expect(ReactNoop).toMatchRenderedOutput(
+
+
+ ,
+ );
- await advanceTimers(1000);
+ await jest.runAllTimers();
- // Timing out should commit the fallback and destroy inner layout effects.
- // Even though the innermost layout effects are beneath a hidden HostComponent.
- assertLog([
- 'Text:Outer destroy layout',
- 'Text:MemoizedInner destroy layout',
- 'Text:Fallback create layout',
- ]);
- await waitForAll(['Text:Fallback create passive']);
- expect(ReactNoop).toMatchRenderedOutput(
- <>
-
-
-
-
- >,
- );
+ // Timing out should commit the fallback and destroy inner layout effects.
+ // Even though the innermost layout effects are beneath a hidden HostComponent.
+ assertLog([
+ 'Text:Outer destroy layout',
+ 'Text:MemoizedInner destroy layout',
+ 'Text:Fallback create layout',
+ ]);
+ await waitForAll(['Text:Fallback create passive']);
+ expect(ReactNoop).toMatchRenderedOutput(
+ <>
+
+
+
+
+ >,
+ );
+ });
// Resolving the suspended resource should re-create inner layout effects.
await act(async () => {
@@ -1147,12 +1150,11 @@ describe('ReactSuspenseEffectsSemantics', () => {
);
// Suspend the inner Suspense subtree (only inner effects should be destroyed)
- act(() => {
+ await act(async () => {
ReactNoop.render(
} />,
);
});
- await advanceTimers(1000);
assertLog([
'Text:Outer render',
'Text:Inner render',
@@ -1160,8 +1162,8 @@ describe('ReactSuspenseEffectsSemantics', () => {
'Text:InnerFallback render',
'Text:Inner destroy layout',
'Text:InnerFallback create layout',
+ 'Text:InnerFallback create passive',
]);
- await waitForAll(['Text:InnerFallback create passive']);
expect(ReactNoop).toMatchRenderedOutput(
<>
@@ -1172,7 +1174,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
// Suspend the outer Suspense subtree (outer effects and inner fallback effects should be destroyed)
// (This check also ensures we don't destroy effects for mounted inner fallback.)
- act(() => {
+ await act(async () => {
ReactNoop.render(
}
@@ -1191,8 +1193,8 @@ describe('ReactSuspenseEffectsSemantics', () => {
'Text:Outer destroy layout',
'Text:InnerFallback destroy layout',
'Text:OuterFallback create layout',
+ 'Text:OuterFallback create passive',
]);
- await waitForAll(['Text:OuterFallback create passive']);
expect(ReactNoop).toMatchRenderedOutput(
<>
@@ -1222,7 +1224,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
);
// Suspend the inner Suspense subtree (no effects should be destroyed)
- act(() => {
+ await act(async () => {
ReactNoop.render(
}
@@ -1297,7 +1299,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
);
// Suspend the outer Suspense subtree (all effects should be destroyed)
- act(() => {
+ await act(async () => {
ReactNoop.render(
}
@@ -1305,7 +1307,6 @@ describe('ReactSuspenseEffectsSemantics', () => {
/>,
);
});
- await advanceTimers(1000);
assertLog([
'Text:Outer render',
'Suspend:OuterAsync_2',
@@ -1317,6 +1318,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
'Text:Inner destroy layout',
'AsyncText:InnerAsync_2 destroy layout',
'Text:OuterFallback create layout',
+ 'Text:OuterFallback create passive',
]);
expect(ReactNoop).toMatchRenderedOutput(
<>
@@ -1333,7 +1335,6 @@ describe('ReactSuspenseEffectsSemantics', () => {
await resolveText('OuterAsync_2');
});
assertLog([
- 'Text:OuterFallback create passive',
'Text:Outer render',
'AsyncText:OuterAsync_2 render',
'Text:Inner render',
@@ -1390,12 +1391,11 @@ describe('ReactSuspenseEffectsSemantics', () => {
);
// Suspend the inner Suspense subtree (only inner effects should be destroyed)
- act(() => {
+ await act(async () => {
ReactNoop.render(
} />,
);
});
- await advanceTimers(1000);
assertLog([
'Text:Outer render',
'Text:Inner render',
@@ -1403,8 +1403,8 @@ describe('ReactSuspenseEffectsSemantics', () => {
'Text:InnerFallback render',
'Text:Inner destroy layout',
'Text:InnerFallback create layout',
+ 'Text:InnerFallback create passive',
]);
- await waitForAll(['Text:InnerFallback create passive']);
expect(ReactNoop).toMatchRenderedOutput(
<>
@@ -1415,7 +1415,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
// Suspend the outer Suspense subtree (outer effects and inner fallback effects should be destroyed)
// (This check also ensures we don't destroy effects for mounted inner fallback.)
- act(() => {
+ await act(async () => {
ReactNoop.render(
}
@@ -1423,7 +1423,6 @@ describe('ReactSuspenseEffectsSemantics', () => {
/>,
);
});
- await advanceTimers(1000);
assertLog([
'Text:Outer render',
'Suspend:OuterAsync_1',
@@ -1434,8 +1433,8 @@ describe('ReactSuspenseEffectsSemantics', () => {
'Text:Outer destroy layout',
'Text:InnerFallback destroy layout',
'Text:OuterFallback create layout',
+ 'Text:OuterFallback create passive',
]);
- await waitForAll(['Text:OuterFallback create passive']);
expect(ReactNoop).toMatchRenderedOutput(
<>
@@ -1518,88 +1517,88 @@ describe('ReactSuspenseEffectsSemantics', () => {
);
// Suspend the outer shell
- act(() => {
+ await act(async () => {
ReactNoop.render(
} />,
);
- });
- assertLog([
- 'Text:Inside render',
- 'Suspend:OutsideAsync',
- 'Text:Fallback:Inside render',
- 'Text:Fallback:Outside render',
- 'Text:Outside render',
- ]);
- expect(ReactNoop).toMatchRenderedOutput(
- <>
-
-
- >,
- );
+ await waitFor([
+ 'Text:Inside render',
+ 'Suspend:OutsideAsync',
+ 'Text:Fallback:Inside render',
+ 'Text:Fallback:Outside render',
+ 'Text:Outside render',
+ ]);
+ expect(ReactNoop).toMatchRenderedOutput(
+ <>
+
+
+ >,
+ );
- // Timing out should commit the fallback and destroy inner layout effects.
- await advanceTimers(1000);
- assertLog([
- 'Text:Inside destroy layout',
- 'Text:Fallback:Inside create layout',
- 'Text:Fallback:Outside create layout',
- ]);
- await waitForAll([
- 'Text:Fallback:Inside create passive',
- 'Text:Fallback:Outside create passive',
- ]);
- expect(ReactNoop).toMatchRenderedOutput(
- <>
-
-
-
-
- >,
- );
+ // Timing out should commit the fallback and destroy inner layout effects.
+ await jest.runAllTimers();
+ assertLog([
+ 'Text:Inside destroy layout',
+ 'Text:Fallback:Inside create layout',
+ 'Text:Fallback:Outside create layout',
+ ]);
+ await waitForAll([
+ 'Text:Fallback:Inside create passive',
+ 'Text:Fallback:Outside create passive',
+ ]);
+ expect(ReactNoop).toMatchRenderedOutput(
+ <>
+
+
+
+
+ >,
+ );
+ });
// Suspend the fallback and verify that it's effects get cleaned up as well
- act(() => {
+ await act(async () => {
ReactNoop.render(
}
outerChildren={}
/>,
);
- });
- assertLog([
- 'Text:Inside render',
- 'Suspend:OutsideAsync',
- 'Text:Fallback:Inside render',
- 'Suspend:FallbackAsync',
- 'Text:Fallback:Fallback render',
- 'Text:Fallback:Outside render',
- 'Text:Outside render',
- ]);
- expect(ReactNoop).toMatchRenderedOutput(
- <>
-
-
-
-
- >,
- );
+ await waitFor([
+ 'Text:Inside render',
+ 'Suspend:OutsideAsync',
+ 'Text:Fallback:Inside render',
+ 'Suspend:FallbackAsync',
+ 'Text:Fallback:Fallback render',
+ 'Text:Fallback:Outside render',
+ 'Text:Outside render',
+ ]);
+ expect(ReactNoop).toMatchRenderedOutput(
+ <>
+
+
+
+
+ >,
+ );
- // Timing out should commit the inner fallback and destroy outer fallback layout effects.
- await advanceTimers(1000);
- assertLog([
- 'Text:Fallback:Inside destroy layout',
- 'Text:Fallback:Fallback create layout',
- ]);
- await waitForAll(['Text:Fallback:Fallback create passive']);
- expect(ReactNoop).toMatchRenderedOutput(
- <>
-
-
-
-
-
- >,
- );
+ // Timing out should commit the inner fallback and destroy outer fallback layout effects.
+ await jest.runAllTimers();
+ assertLog([
+ 'Text:Fallback:Inside destroy layout',
+ 'Text:Fallback:Fallback create layout',
+ ]);
+ await waitForAll(['Text:Fallback:Fallback create passive']);
+ expect(ReactNoop).toMatchRenderedOutput(
+ <>
+
+
+
+
+
+ >,
+ );
+ });
// Resolving both resources should cleanup fallback effects and recreate main effects
await act(async () => {
@@ -1670,7 +1669,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
);
// Suspend both the outer boundary and the fallback
- act(() => {
+ await act(async () => {
ReactNoop.render(
}
@@ -1678,7 +1677,6 @@ describe('ReactSuspenseEffectsSemantics', () => {
/>,
);
});
- await advanceTimers(1000);
assertLog([
'Text:Inside render',
'Suspend:OutsideAsync',
@@ -1690,8 +1688,6 @@ describe('ReactSuspenseEffectsSemantics', () => {
'Text:Inside destroy layout',
'Text:Fallback:Fallback create layout',
'Text:Fallback:Outside create layout',
- ]);
- await waitForAll([
'Text:Fallback:Fallback create passive',
'Text:Fallback:Outside create passive',
]);
@@ -1795,32 +1791,35 @@ describe('ReactSuspenseEffectsSemantics', () => {
// Suspending a component in the middle of the tree
// should still properly cleanup effects deeper in the tree
- act(() => {
+ await act(async () => {
ReactNoop.render();
- });
- assertLog([
- 'Suspend:Suspend',
- 'Text:Fallback render',
- 'Text:Outside render',
- ]);
- expect(ReactNoop).toMatchRenderedOutput(
- <>
-
-
- >,
- );
+ await waitFor([
+ 'Suspend:Suspend',
+ 'Text:Fallback render',
+ 'Text:Outside render',
+ ]);
+ expect(ReactNoop).toMatchRenderedOutput(
+ <>
+
+
+ >,
+ );
- // Timing out should commit the inner fallback and destroy outer fallback layout effects.
- await advanceTimers(1000);
- assertLog(['Text:Inside destroy layout', 'Text:Fallback create layout']);
- await waitForAll(['Text:Fallback create passive']);
- expect(ReactNoop).toMatchRenderedOutput(
- <>
-
-
-
- >,
- );
+ // Timing out should commit the inner fallback and destroy outer fallback layout effects.
+ await jest.runAllTimers();
+ assertLog([
+ 'Text:Inside destroy layout',
+ 'Text:Fallback create layout',
+ ]);
+ await waitForAll(['Text:Fallback create passive']);
+ expect(ReactNoop).toMatchRenderedOutput(
+ <>
+
+
+
+ >,
+ );
+ });
// Resolving should cleanup.
await act(async () => {
@@ -2390,43 +2389,43 @@ describe('ReactSuspenseEffectsSemantics', () => {
);
// Schedule an update that causes React to suspend.
- act(() => {
+ await act(async () => {
ReactNoop.render(
,
);
- });
- assertLog([
- 'Text:Function render',
- 'Suspend:Async_1',
- 'Suspend:Async_2',
- 'ClassText:Class render',
- 'ClassText:Fallback render',
- ]);
- expect(ReactNoop).toMatchRenderedOutput(
- <>
-
-
- >,
- );
+ await waitFor([
+ 'Text:Function render',
+ 'Suspend:Async_1',
+ 'Suspend:Async_2',
+ 'ClassText:Class render',
+ 'ClassText:Fallback render',
+ ]);
+ expect(ReactNoop).toMatchRenderedOutput(
+ <>
+
+
+ >,
+ );
- await advanceTimers(1000);
+ await jest.runAllTimers();
- // Timing out should commit the fallback and destroy inner layout effects.
- assertLog([
- 'Text:Function destroy layout',
- 'ClassText:Class componentWillUnmount',
- 'ClassText:Fallback componentDidMount',
- ]);
- expect(ReactNoop).toMatchRenderedOutput(
- <>
-
-
-
- >,
- );
+ // Timing out should commit the fallback and destroy inner layout effects.
+ assertLog([
+ 'Text:Function destroy layout',
+ 'ClassText:Class componentWillUnmount',
+ 'ClassText:Fallback componentDidMount',
+ ]);
+ expect(ReactNoop).toMatchRenderedOutput(
+ <>
+
+
+
+ >,
+ );
+ });
// Resolving the suspended resource should re-create inner layout effects.
await act(async () => {
@@ -2549,40 +2548,40 @@ describe('ReactSuspenseEffectsSemantics', () => {
// Schedule an update that causes React to suspend.
textToRead = 'A';
- act(() => {
+ await act(async () => {
ReactNoop.render();
- });
- assertLog([
- 'Text:Function render',
- 'Suspender "A" render',
- 'Suspend:A',
- 'ClassText:Class render',
- 'ClassText:Fallback render',
- ]);
- expect(ReactNoop).toMatchRenderedOutput(
- <>
-
-
-
- >,
- );
+ await waitFor([
+ 'Text:Function render',
+ 'Suspender "A" render',
+ 'Suspend:A',
+ 'ClassText:Class render',
+ 'ClassText:Fallback render',
+ ]);
+ expect(ReactNoop).toMatchRenderedOutput(
+ <>
+
+
+
+ >,
+ );
- await advanceTimers(1000);
+ await jest.runAllTimers();
- // Timing out should commit the fallback and destroy inner layout effects.
- assertLog([
- 'Text:Function destroy layout',
- 'ClassText:Class componentWillUnmount',
- 'ClassText:Fallback componentDidMount',
- ]);
- expect(ReactNoop).toMatchRenderedOutput(
- <>
-
-
-
-
- >,
- );
+ // Timing out should commit the fallback and destroy inner layout effects.
+ assertLog([
+ 'Text:Function destroy layout',
+ 'ClassText:Class componentWillUnmount',
+ 'ClassText:Fallback componentDidMount',
+ ]);
+ expect(ReactNoop).toMatchRenderedOutput(
+ <>
+
+
+
+
+ >,
+ );
+ });
// Resolving the suspended resource should re-create inner layout effects.
textToRead = 'B';
@@ -2712,7 +2711,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
);
}
- act(() => {
+ await act(async () => {
ReactNoop.renderLegacySyncRoot();
});
assertLog([
@@ -2730,7 +2729,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
expect(ReactNoop).toMatchRenderedOutput(null);
// Suspend the inner Suspense subtree (only inner effects should be destroyed)
- act(() => {
+ await act(async () => {
ReactNoop.renderLegacySyncRoot(
} />,
);
@@ -2811,7 +2810,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
);
// Suspend the inner Suspense subtree (only inner effects should be destroyed)
- act(() => {
+ await act(async () => {
ReactNoop.render(
} />,
);
@@ -2829,6 +2828,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
'RefCheckerOuter refCallback value? false',
'RefCheckerInner:refCallback destroy layout ref? false',
'Text:Fallback create layout',
+ 'Text:Fallback create passive',
]);
expect(ReactNoop).toMatchRenderedOutput(
<>
@@ -2843,7 +2843,6 @@ describe('ReactSuspenseEffectsSemantics', () => {
await resolveText('Async');
});
assertLog([
- 'Text:Fallback create passive',
'AsyncText:Async render',
'RefCheckerOuter render',
'RefCheckerInner:refObject render',
@@ -2917,7 +2916,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
expect(ReactNoop).toMatchRenderedOutput(null);
// Suspend the inner Suspense subtree (only inner effects should be destroyed)
- act(() => {
+ await act(async () => {
ReactNoop.render(
} />,
);
@@ -2937,6 +2936,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
'RefCheckerOuter refCallback value? false',
'RefCheckerInner:refCallback destroy layout ref? false',
'Text:Fallback create layout',
+ 'Text:Fallback create passive',
]);
expect(ReactNoop).toMatchRenderedOutput();
@@ -2945,7 +2945,6 @@ describe('ReactSuspenseEffectsSemantics', () => {
await resolveText('Async');
});
assertLog([
- 'Text:Fallback create passive',
'AsyncText:Async render',
'RefCheckerOuter render',
'ClassComponent:refObject render',
@@ -3021,7 +3020,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
expect(ReactNoop).toMatchRenderedOutput(null);
// Suspend the inner Suspense subtree (only inner effects should be destroyed)
- act(() => {
+ await act(async () => {
ReactNoop.render(
} />,
);
@@ -3041,6 +3040,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
'RefCheckerOuter refCallback value? false',
'RefCheckerInner:refCallback destroy layout ref? false',
'Text:Fallback create layout',
+ 'Text:Fallback create passive',
]);
expect(ReactNoop).toMatchRenderedOutput();
@@ -3049,7 +3049,6 @@ describe('ReactSuspenseEffectsSemantics', () => {
await resolveText('Async');
});
assertLog([
- 'Text:Fallback create passive',
'AsyncText:Async render',
'RefCheckerOuter render',
'FunctionComponent render',
@@ -3130,7 +3129,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
expect(ReactNoop).toMatchRenderedOutput(null);
// Suspend the inner Suspense subtree (only inner effects should be destroyed)
- act(() => {
+ await act(async () => {
ReactNoop.render(
} />,
);
@@ -3143,6 +3142,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
'Text:Fallback render',
'RefChecker destroy layout ref? true',
'Text:Fallback create layout',
+ 'Text:Fallback create passive',
]);
expect(ReactNoop).toMatchRenderedOutput();
@@ -3151,7 +3151,6 @@ describe('ReactSuspenseEffectsSemantics', () => {
await resolveText('Async');
});
assertLog([
- 'Text:Fallback create passive',
'AsyncText:Async render',
'RefChecker render',
'Text:Fallback destroy layout',
diff --git a/packages/react-reconciler/src/__tests__/ReactSuspenseEffectsSemanticsDOM-test.js b/packages/react-reconciler/src/__tests__/ReactSuspenseEffectsSemanticsDOM-test.js
index 3fefcaad56153..2eb1dae06f142 100644
--- a/packages/react-reconciler/src/__tests__/ReactSuspenseEffectsSemanticsDOM-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactSuspenseEffectsSemanticsDOM-test.js
@@ -17,6 +17,7 @@ let act;
let container;
let waitForAll;
let assertLog;
+let fakeModuleCache;
describe('ReactSuspenseEffectsSemanticsDOM', () => {
beforeEach(() => {
@@ -34,14 +35,53 @@ describe('ReactSuspenseEffectsSemanticsDOM', () => {
container = document.createElement('div');
document.body.appendChild(container);
+
+ fakeModuleCache = new Map();
});
afterEach(() => {
document.body.removeChild(container);
});
- async function fakeImport(result) {
- return {default: result};
+ async function fakeImport(Component) {
+ const record = fakeModuleCache.get(Component);
+ if (record === undefined) {
+ const newRecord = {
+ status: 'pending',
+ value: {default: Component},
+ pings: [],
+ then(ping) {
+ switch (newRecord.status) {
+ case 'pending': {
+ newRecord.pings.push(ping);
+ return;
+ }
+ case 'resolved': {
+ ping(newRecord.value);
+ return;
+ }
+ case 'rejected': {
+ throw newRecord.value;
+ }
+ }
+ },
+ };
+ fakeModuleCache.set(Component, newRecord);
+ return newRecord;
+ }
+ return record;
+ }
+
+ function resolveFakeImport(moduleName) {
+ const record = fakeModuleCache.get(moduleName);
+ if (record === undefined) {
+ throw new Error('Module not found');
+ }
+ if (record.status !== 'pending') {
+ throw new Error('Module already resolved');
+ }
+ record.status = 'resolved';
+ record.pings.forEach(ping => ping(record.value));
}
function Text(props) {
@@ -49,7 +89,7 @@ describe('ReactSuspenseEffectsSemanticsDOM', () => {
return props.text;
}
- it('should not cause a cycle when combined with a render phase update', () => {
+ it('should not cause a cycle when combined with a render phase update', async () => {
let scheduleSuspendingUpdate;
function App() {
@@ -79,22 +119,22 @@ describe('ReactSuspenseEffectsSemanticsDOM', () => {
return ;
}
- const promise = Promise.resolve();
+ const neverResolves = {then() {}};
function ComponentThatSuspendsOnUpdate({shouldSuspend}) {
if (shouldSuspend) {
// Fake Suspend
- throw promise;
+ throw neverResolves;
}
return null;
}
- act(() => {
+ await act(async () => {
const root = ReactDOMClient.createRoot(container);
root.render();
});
- act(() => {
+ await act(async () => {
scheduleSuspendingUpdate();
});
});
@@ -142,12 +182,12 @@ describe('ReactSuspenseEffectsSemanticsDOM', () => {
}
const root = ReactDOMClient.createRoot(container);
- act(() => {
+ await act(async () => {
root.render();
});
assertLog(['Loading...']);
- await LazyChildA;
+ await resolveFakeImport(ChildA);
await waitForAll(['A', 'Ref mount: A']);
expect(container.innerHTML).toBe('A');
@@ -160,7 +200,7 @@ describe('ReactSuspenseEffectsSemanticsDOM', () => {
'ALoading...',
);
- await LazyChildB;
+ await resolveFakeImport(ChildB);
await waitForAll(['B', 'Ref mount: B']);
expect(container.innerHTML).toBe('B');
});
@@ -202,12 +242,12 @@ describe('ReactSuspenseEffectsSemanticsDOM', () => {
}
const root = ReactDOMClient.createRoot(container);
- act(() => {
+ await act(async () => {
root.render();
});
assertLog(['Loading...']);
- await LazyChildA;
+ await resolveFakeImport(ChildA);
await waitForAll(['A', 'Did mount: A']);
expect(container.innerHTML).toBe('A');
@@ -218,7 +258,7 @@ describe('ReactSuspenseEffectsSemanticsDOM', () => {
assertLog(['Loading...', 'Will unmount: A']);
expect(container.innerHTML).toBe('Loading...');
- await LazyChildB;
+ await resolveFakeImport(ChildB);
await waitForAll(['B', 'Did mount: B']);
expect(container.innerHTML).toBe('B');
});
@@ -254,12 +294,12 @@ describe('ReactSuspenseEffectsSemanticsDOM', () => {
}
const root = ReactDOMClient.createRoot(container);
- act(() => {
+ await act(async () => {
root.render();
});
assertLog(['Loading...']);
- await LazyChildA;
+ await resolveFakeImport(ChildA);
await waitForAll(['A', 'Did mount: A']);
expect(container.innerHTML).toBe('A');
@@ -321,12 +361,12 @@ describe('ReactSuspenseEffectsSemanticsDOM', () => {
}
const root = ReactDOMClient.createRoot(container);
- act(() => {
+ await act(async () => {
root.render();
});
assertLog(['Loading...']);
- await LazyChildA;
+ await resolveFakeImport(ChildA);
await waitForAll(['A', 'Ref mount: A']);
expect(container.innerHTML).toBe('A');
@@ -384,12 +424,12 @@ describe('ReactSuspenseEffectsSemanticsDOM', () => {
}
const root = ReactDOMClient.createRoot(container);
- act(() => {
+ await act(async () => {
root.render();
});
assertLog(['Loading...']);
- await LazyChildA;
+ await resolveFakeImport(ChildA);
await waitForAll(['A', 'Did mount: A']);
expect(container.innerHTML).toBe('A');
diff --git a/packages/react-reconciler/src/__tests__/ReactSuspenseFuzz-test.internal.js b/packages/react-reconciler/src/__tests__/ReactSuspenseFuzz-test.internal.js
index 7dc6e8116fac1..2bb0b4f67a6f0 100644
--- a/packages/react-reconciler/src/__tests__/ReactSuspenseFuzz-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactSuspenseFuzz-test.internal.js
@@ -138,14 +138,14 @@ describe('ReactSuspenseFuzz', () => {
return resolvedText;
}
- function resolveAllTasks() {
+ async function resolveAllTasks() {
Scheduler.unstable_flushAllWithoutAsserting();
let elapsedTime = 0;
while (pendingTasks && pendingTasks.size > 0) {
if ((elapsedTime += 1000) > 1000000) {
throw new Error('Something did not resolve properly.');
}
- act(() => {
+ await act(async () => {
ReactNoop.batchedUpdates(() => {
jest.advanceTimersByTime(1000);
});
@@ -154,7 +154,7 @@ describe('ReactSuspenseFuzz', () => {
}
}
- function testResolvedOutput(unwrappedChildren) {
+ async function testResolvedOutput(unwrappedChildren) {
const children = (
{unwrappedChildren}
);
@@ -166,17 +166,15 @@ describe('ReactSuspenseFuzz', () => {
{children}
,
);
- resolveAllTasks();
+ await resolveAllTasks();
const expectedOutput = expectedRoot.getChildrenAsJSX();
- gate(flags => {
- resetCache();
- ReactNoop.renderLegacySyncRoot(children);
- resolveAllTasks();
- const legacyOutput = ReactNoop.getChildrenAsJSX();
- expect(legacyOutput).toEqual(expectedOutput);
- ReactNoop.renderLegacySyncRoot(null);
- });
+ resetCache();
+ ReactNoop.renderLegacySyncRoot(children);
+ await resolveAllTasks();
+ const legacyOutput = ReactNoop.getChildrenAsJSX();
+ expect(legacyOutput).toEqual(expectedOutput);
+ ReactNoop.renderLegacySyncRoot(null);
}
function pickRandomWeighted(rand, options) {
@@ -298,10 +296,10 @@ describe('ReactSuspenseFuzz', () => {
return {Container, Text, testResolvedOutput, generateTestCase};
}
- it('basic cases', () => {
+ it('basic cases', async () => {
// This demonstrates that the testing primitives work
const {Container, Text, testResolvedOutput} = createFuzzer();
- testResolvedOutput(
+ await testResolvedOutput(
{
);
});
- it(`generative tests (random seed: ${SEED})`, () => {
+ it(`generative tests (random seed: ${SEED})`, async () => {
const {generateTestCase, testResolvedOutput} = createFuzzer();
const rand = Random.create(SEED);
@@ -323,7 +321,7 @@ describe('ReactSuspenseFuzz', () => {
for (let i = 0; i < NUMBER_OF_TEST_CASES; i++) {
const randomTestCase = generateTestCase(rand, ELEMENTS_PER_CASE);
try {
- testResolvedOutput(randomTestCase);
+ await testResolvedOutput(randomTestCase);
} catch (e) {
console.log(`
Failed fuzzy test case:
@@ -339,9 +337,9 @@ Random seed is ${SEED}
});
describe('hard-coded cases', () => {
- it('1', () => {
+ it('1', async () => {
const {Text, testResolvedOutput} = createFuzzer();
- testResolvedOutput(
+ await testResolvedOutput(
<>
{
+ it('2', async () => {
const {Text, Container, testResolvedOutput} = createFuzzer();
- testResolvedOutput(
+ await testResolvedOutput(
<>
@@ -378,9 +376,9 @@ Random seed is ${SEED}
);
});
- it('3', () => {
+ it('3', async () => {
const {Text, Container, testResolvedOutput} = createFuzzer();
- testResolvedOutput(
+ await testResolvedOutput(
<>
{
+ it('4', async () => {
const {Text, testResolvedOutput} = createFuzzer();
- testResolvedOutput(
+ await testResolvedOutput(
diff --git a/packages/react-reconciler/src/__tests__/ReactSuspenseList-test.js b/packages/react-reconciler/src/__tests__/ReactSuspenseList-test.js
index 6989d33abff4e..e24e44a22f917 100644
--- a/packages/react-reconciler/src/__tests__/ReactSuspenseList-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactSuspenseList-test.js
@@ -2275,7 +2275,7 @@ describe('ReactSuspenseList', () => {
);
// Update the row adjacent to the list
- act(() => updateAdjacent('C'));
+ await act(async () => updateAdjacent('C'));
assertLog(['C']);
@@ -2332,7 +2332,7 @@ describe('ReactSuspenseList', () => {
const previousInst = setAsyncB;
// During an update we suspend on B.
- act(() => setAsyncB(true));
+ await act(async () => setAsyncB(true));
assertLog([
'Suspend! [B]',
@@ -2350,7 +2350,7 @@ describe('ReactSuspenseList', () => {
// Before we resolve we'll rerender the whole list.
// This should leave the tree intact.
- act(() => ReactNoop.render());
+ await act(async () => ReactNoop.render());
assertLog(['A', 'Suspend! [B]', 'Loading B']);
@@ -2421,7 +2421,7 @@ describe('ReactSuspenseList', () => {
const previousInst = setAsyncB;
// During an update we suspend on B.
- act(() => setAsyncB(true));
+ await act(async () => setAsyncB(true));
assertLog([
'Suspend! [B]',
@@ -2439,7 +2439,7 @@ describe('ReactSuspenseList', () => {
// Before we resolve we'll rerender the whole list.
// This should leave the tree intact.
- act(() => ReactNoop.render());
+ await act(async () => ReactNoop.render());
assertLog(['A', 'Suspend! [B]', 'Loading B']);
diff --git a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js
index 175d7c1fe8e1a..25e37f9d60e94 100644
--- a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js
@@ -2086,7 +2086,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
});
// TODO: assert toErrorDev() when the warning is implemented again.
- act(() => {
+ await act(async () => {
ReactNoop.flushSync(() => _setShow(true));
});
});
@@ -2113,7 +2113,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
});
// TODO: assert toErrorDev() when the warning is implemented again.
- act(() => {
+ await act(async () => {
ReactNoop.flushSync(() => show());
});
});
@@ -2142,7 +2142,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
assertLog(['Suspend! [A]']);
expect(ReactNoop).toMatchRenderedOutput('Loading...');
- act(() => {
+ await act(async () => {
ReactNoop.flushSync(() => showB());
});
@@ -2173,7 +2173,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
});
// TODO: assert toErrorDev() when the warning is implemented again.
- act(() => {
+ await act(async () => {
ReactNoop.flushSync(() => _setShow(true));
});
},
diff --git a/packages/react-reconciler/src/__tests__/ReactUpdaters-test.internal.js b/packages/react-reconciler/src/__tests__/ReactUpdaters-test.internal.js
index c5645040df673..ba7e5c23afb15 100644
--- a/packages/react-reconciler/src/__tests__/ReactUpdaters-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactUpdaters-test.internal.js
@@ -308,7 +308,7 @@ describe('updaters', () => {
expect(allSchedulerTypes).toEqual([[null], [Suspender]]);
expect(resolver).not.toBeNull();
- await act(() => {
+ await act(async () => {
resolver('abc');
return promise;
});