Codemod tests to waitFor pattern (1/?) (#26288)

This converts some of our test suite to use the `waitFor` test pattern,
instead of the `expect(Scheduler).toFlushAndYield` pattern. Most of
these changes are automated with jscodeshift, with some slight manual
cleanup in certain cases.

See #26285 for full context.
This commit is contained in:
Andrew Clark
2023-03-02 22:34:58 -05:00
committed by GitHub
parent e524467338
commit 25a8b9735c
15 changed files with 698 additions and 905 deletions
@@ -12,7 +12,7 @@
describe('DebugTracing', () => {
let React;
let ReactTestRenderer;
let Scheduler;
let waitForPaint;
let logs;
@@ -27,7 +27,8 @@ describe('DebugTracing', () => {
React = require('react');
ReactTestRenderer = require('react-test-renderer');
Scheduler = require('scheduler');
const InternalTestUtils = require('internal-test-utils');
waitForPaint = InternalTestUtils.waitForPaint;
logs = [];
@@ -100,7 +101,7 @@ describe('DebugTracing', () => {
});
// @gate experimental && build === 'development' && enableDebugTracing && enableCPUSuspense
it('should log sync render with CPU suspense', () => {
it('should log sync render with CPU suspense', async () => {
function Example() {
console.log('<Example/>');
return null;
@@ -129,7 +130,7 @@ describe('DebugTracing', () => {
logs.splice(0);
expect(Scheduler).toFlushUntilNextPaint([]);
await waitForPaint([]);
expect(logs).toEqual([
`group: ⚛️ render (${RETRY_LANE_STRING})`,
@@ -9,6 +9,8 @@
let React;
let Scheduler;
let waitForAll;
let assertLog;
let ReactNoop;
let useState;
let act;
@@ -32,6 +34,10 @@ describe('act warnings', () => {
startTransition = React.startTransition;
getCacheForType = React.unstable_getCacheForType;
caches = [];
const InternalTestUtils = require('internal-test-utils');
waitForAll = InternalTestUtils.waitForAll;
assertLog = InternalTestUtils.assertLog;
});
function createTextCache() {
@@ -134,17 +140,17 @@ describe('act warnings', () => {
}
}
function withActEnvironment(value, scope) {
async function withActEnvironment(value, scope) {
const prevValue = global.IS_REACT_ACT_ENVIRONMENT;
global.IS_REACT_ACT_ENVIRONMENT = value;
try {
return scope();
return await scope();
} finally {
global.IS_REACT_ACT_ENVIRONMENT = prevValue;
}
}
test('warns about unwrapped updates only if environment flag is enabled', () => {
test('warns about unwrapped updates only if environment flag is enabled', async () => {
let setState;
function App() {
const [state, _setState] = useState(0);
@@ -154,34 +160,34 @@ describe('act warnings', () => {
const root = ReactNoop.createRoot();
root.render(<App />);
expect(Scheduler).toFlushAndYield([0]);
await waitForAll([0]);
expect(root).toMatchRenderedOutput('0');
// Default behavior. Flag is undefined. No warning.
expect(global.IS_REACT_ACT_ENVIRONMENT).toBe(undefined);
setState(1);
expect(Scheduler).toFlushAndYield([1]);
await waitForAll([1]);
expect(root).toMatchRenderedOutput('1');
// Flag is true. Warn.
withActEnvironment(true, () => {
await withActEnvironment(true, async () => {
expect(() => setState(2)).toErrorDev(
'An update to App inside a test was not wrapped in act',
);
expect(Scheduler).toFlushAndYield([2]);
await waitForAll([2]);
expect(root).toMatchRenderedOutput('2');
});
// Flag is false. No warning.
withActEnvironment(false, () => {
await withActEnvironment(false, async () => {
setState(3);
expect(Scheduler).toFlushAndYield([3]);
await waitForAll([3]);
expect(root).toMatchRenderedOutput('3');
});
});
// @gate __DEV__
test('act warns if the environment flag is not enabled', () => {
test('act warns if the environment flag is not enabled', async () => {
let setState;
function App() {
const [state, _setState] = useState(0);
@@ -191,7 +197,7 @@ describe('act warnings', () => {
const root = ReactNoop.createRoot();
root.render(<App />);
expect(Scheduler).toFlushAndYield([0]);
await waitForAll([0]);
expect(root).toMatchRenderedOutput('0');
// Default behavior. Flag is undefined. Warn.
@@ -204,20 +210,20 @@ describe('act warnings', () => {
'The current testing environment is not configured to support act(...)',
{withoutStack: true},
);
expect(Scheduler).toHaveYielded([1]);
assertLog([1]);
expect(root).toMatchRenderedOutput('1');
// Flag is true. Don't warn.
withActEnvironment(true, () => {
await withActEnvironment(true, () => {
act(() => {
setState(2);
});
expect(Scheduler).toHaveYielded([2]);
assertLog([2]);
expect(root).toMatchRenderedOutput('2');
});
// Flag is false. Warn.
withActEnvironment(false, () => {
await withActEnvironment(false, () => {
expect(() => {
act(() => {
setState(1);
@@ -226,13 +232,13 @@ describe('act warnings', () => {
'The current testing environment is not configured to support act(...)',
{withoutStack: true},
);
expect(Scheduler).toHaveYielded([1]);
assertLog([1]);
expect(root).toMatchRenderedOutput('1');
});
});
test('warns if root update is not wrapped', () => {
withActEnvironment(true, () => {
test('warns if root update is not wrapped', async () => {
await withActEnvironment(true, () => {
const root = ReactNoop.createRoot();
expect(() => root.render('Hi')).toErrorDev(
// TODO: Better error message that doesn't make it look like "Root" is
@@ -244,7 +250,7 @@ describe('act warnings', () => {
});
// @gate __DEV__
test('warns if class update is not wrapped', () => {
test('warns if class update is not wrapped', async () => {
let app;
class App extends React.Component {
state = {count: 0};
@@ -254,7 +260,7 @@ describe('act warnings', () => {
}
}
withActEnvironment(true, () => {
await withActEnvironment(true, () => {
const root = ReactNoop.createRoot();
act(() => {
root.render(<App />);
@@ -266,7 +272,7 @@ describe('act warnings', () => {
});
// @gate __DEV__
test('warns even if update is synchronous', () => {
test('warns even if update is synchronous', async () => {
let setState;
function App() {
const [state, _setState] = useState(0);
@@ -274,10 +280,10 @@ describe('act warnings', () => {
return <Text text={state} />;
}
withActEnvironment(true, () => {
await withActEnvironment(true, () => {
const root = ReactNoop.createRoot();
act(() => root.render(<App />));
expect(Scheduler).toHaveYielded([0]);
assertLog([0]);
expect(root).toMatchRenderedOutput('0');
// Even though this update is synchronous, we should still fire a warning,
@@ -286,14 +292,14 @@ describe('act warnings', () => {
'An update to App inside a test was not wrapped in act(...)',
);
expect(Scheduler).toHaveYielded([1]);
assertLog([1]);
expect(root).toMatchRenderedOutput('1');
});
});
// @gate __DEV__
// @gate enableLegacyCache
test('warns if Suspense retry is not wrapped', () => {
test('warns if Suspense retry is not wrapped', async () => {
function App() {
return (
<Suspense fallback={<Text text="Loading..." />}>
@@ -302,12 +308,12 @@ describe('act warnings', () => {
);
}
withActEnvironment(true, () => {
await withActEnvironment(true, () => {
const root = ReactNoop.createRoot();
act(() => {
root.render(<App />);
});
expect(Scheduler).toHaveYielded(['Suspend! [Async]', 'Loading...']);
assertLog(['Suspend! [Async]', 'Loading...']);
expect(root).toMatchRenderedOutput('Loading...');
// This is a retry, not a ping, because we already showed a fallback.
@@ -321,7 +327,7 @@ describe('act warnings', () => {
// @gate __DEV__
// @gate enableLegacyCache
test('warns if Suspense ping is not wrapped', () => {
test('warns if Suspense ping is not wrapped', async () => {
function App({showMore}) {
return (
<Suspense fallback={<Text text="Loading..." />}>
@@ -330,12 +336,12 @@ describe('act warnings', () => {
);
}
withActEnvironment(true, () => {
await withActEnvironment(true, () => {
const root = ReactNoop.createRoot();
act(() => {
root.render(<App showMore={false} />);
});
expect(Scheduler).toHaveYielded(['(empty)']);
assertLog(['(empty)']);
expect(root).toMatchRenderedOutput('(empty)');
act(() => {
@@ -343,7 +349,7 @@ describe('act warnings', () => {
root.render(<App showMore={true} />);
});
});
expect(Scheduler).toHaveYielded(['Suspend! [Async]', 'Loading...']);
assertLog(['Suspend! [Async]', 'Loading...']);
expect(root).toMatchRenderedOutput('(empty)');
// This is a ping, not a retry, because no fallback is showing.
@@ -2,6 +2,8 @@ let React;
let ReactFeatureFlags;
let ReactNoop;
let Scheduler;
let waitForAll;
let assertLog;
let ReactCache;
let Suspense;
let TextResource;
@@ -18,6 +20,10 @@ describe('ReactBlockingMode', () => {
ReactCache = require('react-cache');
Suspense = React.Suspense;
const InternalTestUtils = require('internal-test-utils');
waitForAll = InternalTestUtils.waitForAll;
assertLog = InternalTestUtils.assertLog;
TextResource = ReactCache.unstable_createResource(
([text, ms = 0]) => {
return new Promise((resolve, reject) =>
@@ -52,7 +58,7 @@ describe('ReactBlockingMode', () => {
}
}
it('updates flush without yielding in the next event', () => {
it('updates flush without yielding in the next event', async () => {
const root = ReactNoop.createRoot();
root.render(
@@ -66,12 +72,11 @@ describe('ReactBlockingMode', () => {
// Nothing should have rendered yet
expect(root).toMatchRenderedOutput(null);
// Everything should render immediately in the next event
expect(Scheduler).toFlushAndYield(['A', 'B', 'C']);
await waitForAll(['A', 'B', 'C']);
expect(root).toMatchRenderedOutput('ABC');
});
it('layout updates flush synchronously in same event', () => {
it('layout updates flush synchronously in same event', async () => {
const {useLayoutEffect} = React;
function App() {
@@ -84,9 +89,9 @@ describe('ReactBlockingMode', () => {
const root = ReactNoop.createRoot();
root.render(<App />);
expect(root).toMatchRenderedOutput(null);
expect(Scheduler).toHaveYielded([]);
assertLog([]);
expect(Scheduler).toFlushAndYield(['Hi', 'Layout effect']);
await waitForAll(['Hi', 'Layout effect']);
expect(root).toMatchRenderedOutput('Hi');
});
@@ -106,15 +111,15 @@ describe('ReactBlockingMode', () => {
</Suspense>,
);
expect(Scheduler).toFlushAndYield(['A', 'Suspend! [B]', 'C', 'Loading...']);
await waitForAll(['A', 'Suspend! [B]', 'C', 'Loading...']);
// In Legacy Mode, A and B would mount in a hidden primary tree. In
// Concurrent Mode, nothing in the primary tree should mount. But the
// fallback should mount immediately.
expect(root).toMatchRenderedOutput('Loading...');
await jest.advanceTimersByTime(1000);
expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
expect(Scheduler).toFlushAndYield(['A', 'B', 'C']);
assertLog(['Promise resolved [B]']);
await waitForAll(['A', 'B', 'C']);
expect(root).toMatchRenderedOutput(
<>
<span>A</span>
@@ -124,7 +129,7 @@ describe('ReactBlockingMode', () => {
);
});
it('flushSync does not flush batched work', () => {
it('flushSync does not flush batched work', async () => {
const {useState, forwardRef, useImperativeHandle} = React;
const root = ReactNoop.createRoot();
@@ -143,8 +148,7 @@ describe('ReactBlockingMode', () => {
</>,
);
// Mount
expect(Scheduler).toFlushAndYield(['A0', 'B0']);
await waitForAll(['A0', 'B0']);
expect(root).toMatchRenderedOutput('A0B0');
// Schedule a batched update to the first sibling
@@ -159,15 +163,13 @@ describe('ReactBlockingMode', () => {
// Now flush the first update
if (gate(flags => flags.enableUnifiedSyncLane)) {
expect(Scheduler).toHaveYielded(['A1', 'B1']);
assertLog(['A1', 'B1']);
expect(root).toMatchRenderedOutput('A1B1');
} else {
// Only the second update should have flushed synchronously
expect(Scheduler).toHaveYielded(['B1']);
assertLog(['B1']);
expect(root).toMatchRenderedOutput('A0B1');
// Now flush the first update
expect(Scheduler).toFlushAndYield(['A1']);
await waitForAll(['A1']);
expect(root).toMatchRenderedOutput('A1B1');
}
});
@@ -10,6 +10,9 @@ let readText;
let resolveText;
// let rejectText;
let assertLog;
let waitForPaint;
describe('ReactSuspenseWithNoopRenderer', () => {
beforeEach(() => {
jest.resetModules();
@@ -21,6 +24,10 @@ describe('ReactSuspenseWithNoopRenderer', () => {
Suspense = React.Suspense;
useState = React.useState;
const InternalTestUtils = require('internal-test-utils');
assertLog = InternalTestUtils.assertLog;
waitForPaint = InternalTestUtils.waitForPaint;
textCache = new Map();
readText = text => {
@@ -128,7 +135,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
const root = ReactNoop.createRoot();
await act(async () => {
root.render(<App />);
expect(Scheduler).toFlushUntilNextPaint(['Outer', 'Loading...']);
await waitForPaint(['Outer', 'Loading...']);
expect(root).toMatchRenderedOutput(
<>
Outer
@@ -136,8 +143,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
</>,
);
});
// Inner contents finish in separate commit from outer
expect(Scheduler).toHaveYielded(['Inner']);
assertLog(['Inner']);
expect(root).toMatchRenderedOutput(
<>
Outer
@@ -172,8 +178,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
await act(async () => {
root.render(<App />);
});
// Inner contents finish in separate commit from outer
expect(Scheduler).toHaveYielded(['Outer', 'Loading...', 'Inner [0]']);
assertLog(['Outer', 'Loading...', 'Inner [0]']);
expect(root).toMatchRenderedOutput(
<>
Outer
@@ -185,8 +190,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
await act(async () => {
setCount(1);
});
// Entire update finishes in a single commit
expect(Scheduler).toHaveYielded(['Outer', 'Inner [1]']);
assertLog(['Outer', 'Inner [1]']);
expect(root).toMatchRenderedOutput(
<>
Outer
@@ -215,7 +219,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
const root = ReactNoop.createRoot();
await act(async () => {
root.render(<App />);
expect(Scheduler).toFlushUntilNextPaint(['Outer', 'Loading...']);
await waitForPaint(['Outer', 'Loading...']);
expect(root).toMatchRenderedOutput(
<>
Outer
@@ -223,8 +227,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
</>,
);
});
// Inner contents suspended, so we continue showing a fallback.
expect(Scheduler).toHaveYielded(['Suspend! [Inner]']);
assertLog(['Suspend! [Inner]']);
expect(root).toMatchRenderedOutput(
<>
Outer
@@ -236,7 +239,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
await act(async () => {
await resolveText('Inner');
});
expect(Scheduler).toHaveYielded(['Inner']);
assertLog(['Inner']);
expect(root).toMatchRenderedOutput(
<>
Outer
@@ -273,14 +276,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
await act(async () => {
root.render(<App />);
});
// Each level commits separately
expect(Scheduler).toHaveYielded([
'A',
'Loading B...',
'B',
'Loading C...',
'C',
]);
assertLog(['A', 'Loading B...', 'B', 'Loading C...', 'C']);
expect(root).toMatchRenderedOutput(
<>
A
+108 -170
View File
@@ -3,6 +3,7 @@ let ReactNoop;
let Cache;
let getCacheSignal;
let Scheduler;
let assertLog;
let act;
let Suspense;
let Offscreen;
@@ -32,6 +33,9 @@ describe('ReactCache', () => {
startTransition = React.startTransition;
useState = React.useState;
const InternalTestUtils = require('internal-test-utils');
assertLog = InternalTestUtils.assertLog;
textCaches = [];
seededCache = null;
@@ -203,20 +207,19 @@ describe('ReactCache', () => {
</Cache>,
);
});
expect(Scheduler).toHaveYielded(['Cache miss! [A]', 'Loading...']);
assertLog(['Cache miss! [A]', 'Loading...']);
expect(root).toMatchRenderedOutput('Loading...');
await act(async () => {
resolveMostRecentTextCache('A');
});
expect(Scheduler).toHaveYielded(['A']);
assertLog(['A']);
expect(root).toMatchRenderedOutput('A');
await act(async () => {
root.render('Bye');
});
// no cleanup: cache is still retained at the root
expect(Scheduler).toHaveYielded([]);
assertLog([]);
expect(root).toMatchRenderedOutput('Bye');
});
@@ -230,20 +233,19 @@ describe('ReactCache', () => {
</Suspense>,
);
});
expect(Scheduler).toHaveYielded(['Cache miss! [A]', 'Loading...']);
assertLog(['Cache miss! [A]', 'Loading...']);
expect(root).toMatchRenderedOutput('Loading...');
await act(async () => {
resolveMostRecentTextCache('A');
});
expect(Scheduler).toHaveYielded(['A']);
assertLog(['A']);
expect(root).toMatchRenderedOutput('A');
await act(async () => {
root.render('Bye');
});
// no cleanup: cache is still retained at the root
expect(Scheduler).toHaveYielded([]);
assertLog([]);
expect(root).toMatchRenderedOutput('Bye');
});
@@ -271,26 +273,19 @@ describe('ReactCache', () => {
root.render(<App showMore={false} />);
});
// Even though there are two new <Cache /> trees, they should share the same
// data cache. So there should be only a single cache miss for A.
expect(Scheduler).toHaveYielded([
'Cache miss! [A]',
'Loading...',
'Loading...',
]);
assertLog(['Cache miss! [A]', 'Loading...', 'Loading...']);
expect(root).toMatchRenderedOutput('Loading...Loading...');
await act(async () => {
resolveMostRecentTextCache('A');
});
expect(Scheduler).toHaveYielded(['A', 'A']);
assertLog(['A', 'A']);
expect(root).toMatchRenderedOutput('AA');
await act(async () => {
root.render('Bye');
});
// no cleanup: cache is still retained at the root
expect(Scheduler).toHaveYielded([]);
assertLog([]);
expect(root).toMatchRenderedOutput('Bye');
});
@@ -319,34 +314,25 @@ describe('ReactCache', () => {
await act(async () => {
root.render(<App showMore={false} />);
});
expect(Scheduler).toHaveYielded([]);
assertLog([]);
expect(root).toMatchRenderedOutput('(empty)');
await act(async () => {
root.render(<App showMore={true} />);
});
// Even though there are two new <Cache /> trees, they should share the same
// data cache. So there should be only a single cache miss for A.
expect(Scheduler).toHaveYielded([
'Cache miss! [A]',
'Loading...',
'Loading...',
]);
assertLog(['Cache miss! [A]', 'Loading...', 'Loading...']);
expect(root).toMatchRenderedOutput('Loading...Loading...');
await act(async () => {
resolveMostRecentTextCache('A');
});
expect(Scheduler).toHaveYielded(['A', 'A']);
assertLog(['A', 'A']);
expect(root).toMatchRenderedOutput('AA');
await act(async () => {
root.render('Bye');
});
// cleanup occurs for the cache shared by the inner cache boundaries (which
// are not shared w the root because they were added in an update)
// note that no cache is created for the root since the cache is never accessed
expect(Scheduler).toHaveYielded(['Cache cleanup: A [v1]']);
assertLog(['Cache cleanup: A [v1]']);
expect(root).toMatchRenderedOutput('Bye');
});
@@ -370,22 +356,19 @@ describe('ReactCache', () => {
await act(async () => {
root.render(<App />);
});
// Even though there is a nested <Cache /> boundary, it should share the same
// data cache as the root. So there should be only a single cache miss for A.
expect(Scheduler).toHaveYielded(['Cache miss! [A]', 'Loading...']);
assertLog(['Cache miss! [A]', 'Loading...']);
expect(root).toMatchRenderedOutput('Loading...');
await act(async () => {
resolveMostRecentTextCache('A');
});
expect(Scheduler).toHaveYielded(['A', 'A']);
assertLog(['A', 'A']);
expect(root).toMatchRenderedOutput('AA');
await act(async () => {
root.render('Bye');
});
// no cleanup: cache is still retained at the root
expect(Scheduler).toHaveYielded([]);
assertLog([]);
expect(root).toMatchRenderedOutput('Bye');
},
);
@@ -412,14 +395,14 @@ describe('ReactCache', () => {
seedNextTextCache('A');
root.render(<App showMore={false} />);
});
expect(Scheduler).toHaveYielded(['A [v1]']);
assertLog(['A [v1]']);
expect(root).toMatchRenderedOutput('A [v1]');
// Add a new cache boundary
await act(async () => {
root.render(<App showMore={true} />);
});
expect(Scheduler).toHaveYielded([
assertLog([
'A [v1]',
// New tree should use already cached data
'A [v1]',
@@ -429,8 +412,7 @@ describe('ReactCache', () => {
await act(async () => {
root.render('Bye');
});
// no cleanup: cache is still retained at the root
expect(Scheduler).toHaveYielded([]);
assertLog([]);
expect(root).toMatchRenderedOutput('Bye');
});
@@ -460,14 +442,14 @@ describe('ReactCache', () => {
seedNextTextCache('A');
root.render(<App showMore={false} />);
});
expect(Scheduler).toHaveYielded(['A [v1]']);
assertLog(['A [v1]']);
expect(root).toMatchRenderedOutput('A [v1]');
// Add a new cache boundary
await act(async () => {
root.render(<App showMore={true} />);
});
expect(Scheduler).toHaveYielded([
assertLog([
'A [v1]',
// New tree should load fresh data.
'Cache miss! [A]',
@@ -477,7 +459,7 @@ describe('ReactCache', () => {
await act(async () => {
resolveMostRecentTextCache('A');
});
expect(Scheduler).toHaveYielded(['A [v2]']);
assertLog(['A [v2]']);
expect(root).toMatchRenderedOutput('A [v1]A [v2]');
// Replace all the children: this should retain the root Cache instance,
@@ -486,9 +468,7 @@ describe('ReactCache', () => {
await act(async () => {
root.render('Bye!');
});
// Cleanup occurs for the *second* cache instance: the first is still
// referenced by the root
expect(Scheduler).toHaveYielded(['Cache cleanup: A [v2]']);
assertLog(['Cache cleanup: A [v2]']);
expect(root).toMatchRenderedOutput('Bye!');
});
@@ -535,13 +515,13 @@ describe('ReactCache', () => {
await act(async () => {
root.render(<App />);
});
expect(Scheduler).toHaveYielded(['Cache miss! [A]', 'Loading shell...']);
assertLog(['Cache miss! [A]', 'Loading shell...']);
expect(root).toMatchRenderedOutput('Loading shell...');
await act(async () => {
resolveMostRecentTextCache('A');
});
expect(Scheduler).toHaveYielded([
assertLog([
'Shell',
// There's a cache miss for B, because it hasn't been read yet. But not
// A, because it was cached when we rendered the shell.
@@ -558,7 +538,7 @@ describe('ReactCache', () => {
await act(async () => {
resolveMostRecentTextCache('B');
});
expect(Scheduler).toHaveYielded(['Content']);
assertLog(['Content']);
expect(root).toMatchRenderedOutput(
<>
<div>Shell</div>
@@ -569,8 +549,7 @@ describe('ReactCache', () => {
await act(async () => {
root.render('Bye');
});
// no cleanup: cache is still retained at the root
expect(Scheduler).toHaveYielded([]);
assertLog([]);
expect(root).toMatchRenderedOutput('Bye');
});
@@ -619,19 +598,19 @@ describe('ReactCache', () => {
await act(async () => {
root.render(<App showMore={false} />);
});
expect(Scheduler).toHaveYielded([]);
assertLog([]);
expect(root).toMatchRenderedOutput('(empty)');
await act(async () => {
root.render(<App showMore={true} />);
});
expect(Scheduler).toHaveYielded(['Cache miss! [A]', 'Loading shell...']);
assertLog(['Cache miss! [A]', 'Loading shell...']);
expect(root).toMatchRenderedOutput('Loading shell...');
await act(async () => {
resolveMostRecentTextCache('A');
});
expect(Scheduler).toHaveYielded([
assertLog([
'Shell',
// There's a cache miss for B, because it hasn't been read yet. But not
// A, because it was cached when we rendered the shell.
@@ -648,7 +627,7 @@ describe('ReactCache', () => {
await act(async () => {
resolveMostRecentTextCache('B');
});
expect(Scheduler).toHaveYielded(['Content']);
assertLog(['Content']);
expect(root).toMatchRenderedOutput(
<>
<div>Shell</div>
@@ -659,10 +638,7 @@ describe('ReactCache', () => {
await act(async () => {
root.render('Bye');
});
expect(Scheduler).toHaveYielded([
'Cache cleanup: A [v1]',
'Cache cleanup: B [v1]',
]);
assertLog(['Cache cleanup: A [v1]', 'Cache cleanup: B [v1]']);
expect(root).toMatchRenderedOutput('Bye');
});
@@ -683,20 +659,20 @@ describe('ReactCache', () => {
</Suspense>,
);
});
expect(Scheduler).toHaveYielded(['Cache miss! [A]', 'Loading...']);
assertLog(['Cache miss! [A]', 'Loading...']);
expect(root).toMatchRenderedOutput('Loading...');
await act(async () => {
resolveMostRecentTextCache('A');
});
expect(Scheduler).toHaveYielded(['A [v1]']);
assertLog(['A [v1]']);
expect(root).toMatchRenderedOutput('A [v1]');
// Refresh for new data.
await act(async () => {
startTransition(() => refresh());
});
expect(Scheduler).toHaveYielded(['Cache miss! [A]', 'Loading...']);
assertLog(['Cache miss! [A]', 'Loading...']);
expect(root).toMatchRenderedOutput('A [v1]');
await act(async () => {
@@ -704,9 +680,9 @@ describe('ReactCache', () => {
});
// Note that the version has updated
if (getCacheSignal) {
expect(Scheduler).toHaveYielded(['A [v2]', 'Cache cleanup: A [v1]']);
assertLog(['A [v2]', 'Cache cleanup: A [v1]']);
} else {
expect(Scheduler).toHaveYielded(['A [v2]']);
assertLog(['A [v2]']);
}
expect(root).toMatchRenderedOutput('A [v2]');
@@ -733,34 +709,32 @@ describe('ReactCache', () => {
</Suspense>,
);
});
expect(Scheduler).toHaveYielded(['Cache miss! [A]', 'Loading...']);
assertLog(['Cache miss! [A]', 'Loading...']);
expect(root).toMatchRenderedOutput('Loading...');
await act(async () => {
resolveMostRecentTextCache('A');
});
expect(Scheduler).toHaveYielded(['A [v1]']);
assertLog(['A [v1]']);
expect(root).toMatchRenderedOutput('A [v1]');
// Refresh for new data.
await act(async () => {
startTransition(() => refresh());
});
expect(Scheduler).toHaveYielded(['Cache miss! [A]', 'Loading...']);
assertLog(['Cache miss! [A]', 'Loading...']);
expect(root).toMatchRenderedOutput('A [v1]');
await act(async () => {
resolveMostRecentTextCache('A');
});
// Note that the version has updated, and the previous cache is cleared
expect(Scheduler).toHaveYielded(['A [v2]', 'Cache cleanup: A [v1]']);
assertLog(['A [v2]', 'Cache cleanup: A [v1]']);
expect(root).toMatchRenderedOutput('A [v2]');
await act(async () => {
root.render('Bye');
});
// the original root cache already cleaned up when the refresh completed
expect(Scheduler).toHaveYielded([]);
assertLog([]);
expect(root).toMatchRenderedOutput('Bye');
});
@@ -781,20 +755,20 @@ describe('ReactCache', () => {
</Suspense>,
);
});
expect(Scheduler).toHaveYielded(['Cache miss! [A]', 'Loading...']);
assertLog(['Cache miss! [A]', 'Loading...']);
expect(root).toMatchRenderedOutput('Loading...');
await act(async () => {
resolveMostRecentTextCache('A');
});
expect(Scheduler).toHaveYielded(['A [v1]']);
assertLog(['A [v1]']);
expect(root).toMatchRenderedOutput('A [v1]');
// Refresh for new data.
await act(async () => {
refresh();
});
expect(Scheduler).toHaveYielded([
assertLog([
'Cache miss! [A]',
'Loading...',
// The v1 cache can be cleaned up since everything that references it has
@@ -807,15 +781,13 @@ describe('ReactCache', () => {
await act(async () => {
resolveMostRecentTextCache('A');
});
// Note that the version has updated, and the previous cache is cleared
expect(Scheduler).toHaveYielded(['A [v2]']);
assertLog(['A [v2]']);
expect(root).toMatchRenderedOutput('A [v2]');
await act(async () => {
root.render('Bye');
});
// the original root cache already cleaned up when the refresh completed
expect(Scheduler).toHaveYielded([]);
assertLog([]);
expect(root).toMatchRenderedOutput('Bye');
});
@@ -847,13 +819,13 @@ describe('ReactCache', () => {
</Cache>,
);
});
expect(Scheduler).toHaveYielded(['Cache miss! [A]', 'Loading...']);
assertLog(['Cache miss! [A]', 'Loading...']);
expect(root).toMatchRenderedOutput('Loading...');
await act(async () => {
resolveMostRecentTextCache('A');
});
expect(Scheduler).toHaveYielded(['A [v1]']);
assertLog(['A [v1]']);
expect(root).toMatchRenderedOutput('A [v1]');
// Refresh for new data.
@@ -869,16 +841,13 @@ describe('ReactCache', () => {
}),
);
});
// The root should re-render without a cache miss.
// The cache is not cleared up yet, since it's still reference by the root
expect(Scheduler).toHaveYielded(['A [v2]']);
assertLog(['A [v2]']);
expect(root).toMatchRenderedOutput('A [v2]');
await act(async () => {
root.render('Bye');
});
// the refreshed cache boundary is unmounted and cleans up
expect(Scheduler).toHaveYielded(['Cache cleanup: A [v2]']);
assertLog(['Cache cleanup: A [v2]']);
expect(root).toMatchRenderedOutput('Bye');
});
@@ -913,7 +882,7 @@ describe('ReactCache', () => {
seedNextTextCache('A');
root.render(<App showMore={false} />);
});
expect(Scheduler).toHaveYielded(['A [v1]']);
assertLog(['A [v1]']);
expect(root).toMatchRenderedOutput('A [v1]');
// Add a new cache boundary
@@ -921,7 +890,7 @@ describe('ReactCache', () => {
seedNextTextCache('A');
root.render(<App showMore={true} />);
});
expect(Scheduler).toHaveYielded([
assertLog([
'A [v1]',
// New tree should load fresh data.
'A [v2]',
@@ -933,17 +902,13 @@ describe('ReactCache', () => {
await act(async () => {
startTransition(() => refreshShell());
});
expect(Scheduler).toHaveYielded([
'Cache miss! [A]',
'Loading...',
'Loading...',
]);
assertLog(['Cache miss! [A]', 'Loading...', 'Loading...']);
expect(root).toMatchRenderedOutput('A [v1]A [v2]');
await act(async () => {
resolveMostRecentTextCache('A');
});
expect(Scheduler).toHaveYielded([
assertLog([
'A [v3]',
'A [v3]',
// once the refresh completes the inner showMore boundary frees its previous
@@ -955,9 +920,7 @@ describe('ReactCache', () => {
await act(async () => {
root.render('Bye!');
});
// Unmounting children releases the refreshed cache instance only; the root
// still retains the original cache instance used for the first render
expect(Scheduler).toHaveYielded(['Cache cleanup: A [v3]']);
assertLog(['Cache cleanup: A [v3]']);
expect(root).toMatchRenderedOutput('Bye!');
});
@@ -1004,19 +967,13 @@ describe('ReactCache', () => {
root.render(<App showMore={true} />);
});
// Even though there are two new <Cache /> trees, they should share the same
// data cache. So there should be only a single cache miss for A.
expect(Scheduler).toHaveYielded([
'Cache miss! [A]',
'Loading...',
'Loading...',
]);
assertLog(['Cache miss! [A]', 'Loading...', 'Loading...']);
expect(root).toMatchRenderedOutput('Loading...Loading...');
await act(async () => {
resolveMostRecentTextCache('A');
});
expect(Scheduler).toHaveYielded(['A [v1]', 'A [v1]']);
assertLog(['A [v1]', 'A [v1]']);
expect(root).toMatchRenderedOutput('A [v1]A [v1]');
// Refresh the first boundary. It should not refresh the second boundary,
@@ -1024,12 +981,12 @@ describe('ReactCache', () => {
await act(async () => {
await refreshFirstBoundary();
});
expect(Scheduler).toHaveYielded(['Cache miss! [A]', 'Loading...']);
assertLog(['Cache miss! [A]', 'Loading...']);
await act(async () => {
resolveMostRecentTextCache('A');
});
expect(Scheduler).toHaveYielded(['A [v2]']);
assertLog(['A [v2]']);
expect(root).toMatchRenderedOutput('A [v2]A [v1]');
// Unmount children: this should clear *both* cache instances:
@@ -1041,10 +998,7 @@ describe('ReactCache', () => {
await act(async () => {
root.render('Bye!');
});
expect(Scheduler).toHaveYielded([
'Cache cleanup: A [v2]',
'Cache cleanup: A [v1]',
]);
assertLog(['Cache cleanup: A [v2]', 'Cache cleanup: A [v1]']);
expect(root).toMatchRenderedOutput('Bye!');
},
);
@@ -1079,11 +1033,7 @@ describe('ReactCache', () => {
await act(async () => {
root.render(<App showMore={false} />);
});
expect(Scheduler).toHaveYielded([
'Cache miss! [A]',
'Cache miss! [B]',
'Loading...',
]);
assertLog(['Cache miss! [A]', 'Cache miss! [B]', 'Loading...']);
expect(root).toMatchRenderedOutput('Loading...');
await act(async () => {
@@ -1093,7 +1043,7 @@ describe('ReactCache', () => {
// And mount the second tree, which includes new content
root.render(<App showMore={true} />);
});
expect(Scheduler).toHaveYielded([
assertLog([
// The new tree should use a fresh cache
'Cache miss! [A]',
'Loading...',
@@ -1108,16 +1058,13 @@ describe('ReactCache', () => {
await act(async () => {
resolveMostRecentTextCache('A');
});
expect(Scheduler).toHaveYielded(['A [v2]']);
assertLog(['A [v2]']);
expect(root).toMatchRenderedOutput('A [v2] A [v1] B [v1]');
await act(async () => {
root.render('Bye!');
});
// Unmounting children releases both cache boundaries, but the original
// cache instance (used by second boundary) is still referenced by the root.
// only the second cache instance is freed.
expect(Scheduler).toHaveYielded(['Cache cleanup: A [v2]']);
assertLog(['Cache cleanup: A [v2]']);
expect(root).toMatchRenderedOutput('Bye!');
},
);
@@ -1138,7 +1085,7 @@ describe('ReactCache', () => {
<Suspense fallback={<Text text="Loading..." />}>(empty)</Suspense>,
);
});
expect(Scheduler).toHaveYielded([]);
assertLog([]);
expect(root).toMatchRenderedOutput('(empty)');
await act(async () => {
@@ -1150,7 +1097,7 @@ describe('ReactCache', () => {
);
});
});
expect(Scheduler).toHaveYielded(['Cache miss! [A]', 'Loading...']);
assertLog(['Cache miss! [A]', 'Loading...']);
expect(root).toMatchRenderedOutput('(empty)');
await act(async () => {
@@ -1163,7 +1110,7 @@ describe('ReactCache', () => {
);
});
});
expect(Scheduler).toHaveYielded([
assertLog([
// No cache miss, because it uses the pooled cache
'Loading...',
]);
@@ -1173,7 +1120,7 @@ describe('ReactCache', () => {
await act(async () => {
resolveMostRecentTextCache('A');
});
expect(Scheduler).toHaveYielded(['A [v1]', 'A [v1]']);
assertLog(['A [v1]', 'A [v1]']);
expect(root).toMatchRenderedOutput('A [v1]A [v1]');
// Now do another transition
@@ -1188,7 +1135,7 @@ describe('ReactCache', () => {
);
});
});
expect(Scheduler).toHaveYielded([
assertLog([
// First two children use the old cache because they already finished
'A [v1]',
'A [v1]',
@@ -1201,7 +1148,7 @@ describe('ReactCache', () => {
await act(async () => {
resolveMostRecentTextCache('A');
});
expect(Scheduler).toHaveYielded(['A [v1]', 'A [v1]', 'A [v2]']);
assertLog(['A [v1]', 'A [v1]', 'A [v2]']);
expect(root).toMatchRenderedOutput('A [v1]A [v1]A [v2]');
// Unmount children: the first text cache instance is created only after the root
@@ -1211,10 +1158,7 @@ describe('ReactCache', () => {
await act(async () => {
root.render('Bye!');
});
expect(Scheduler).toHaveYielded([
'Cache cleanup: A [v1]',
'Cache cleanup: A [v2]',
]);
assertLog(['Cache cleanup: A [v1]', 'Cache cleanup: A [v2]']);
expect(root).toMatchRenderedOutput('Bye!');
});
@@ -1257,7 +1201,7 @@ describe('ReactCache', () => {
await act(async () => {
root.render(<App />);
});
expect(Scheduler).toHaveYielded(['0']);
assertLog(['0']);
expect(root).toMatchRenderedOutput('0');
await act(async () => {
@@ -1265,13 +1209,13 @@ describe('ReactCache', () => {
showMore();
});
});
expect(Scheduler).toHaveYielded(['Cache miss! [A]', 'Loading...']);
assertLog(['Cache miss! [A]', 'Loading...']);
expect(root).toMatchRenderedOutput('0');
await act(async () => {
updateUnrelated(1);
});
expect(Scheduler).toHaveYielded([
assertLog([
'1',
// Happens to re-render the fallback. Doesn't need to, but not relevant
@@ -1283,7 +1227,7 @@ describe('ReactCache', () => {
await act(async () => {
resolveMostRecentTextCache('A');
});
expect(Scheduler).toHaveYielded(['A [v1]']);
assertLog(['A [v1]']);
expect(root).toMatchRenderedOutput('A [v1]1');
// Unmount children: the first text cache instance is created only after initial
@@ -1293,7 +1237,7 @@ describe('ReactCache', () => {
await act(async () => {
root.render('Bye!');
});
expect(Scheduler).toHaveYielded(['Cache cleanup: A [v1]']);
assertLog(['Cache cleanup: A [v1]']);
expect(root).toMatchRenderedOutput('Bye!');
});
@@ -1310,7 +1254,7 @@ describe('ReactCache', () => {
</Suspense>,
);
});
expect(Scheduler).toHaveYielded(['A [v1]']);
assertLog(['A [v1]']);
expect(root).toMatchRenderedOutput('A [v1]');
seedNextTextCache('B');
@@ -1323,7 +1267,7 @@ describe('ReactCache', () => {
</Suspense>,
);
});
expect(Scheduler).toHaveYielded(['B [v2]']);
assertLog(['B [v2]']);
expect(root).toMatchRenderedOutput('B [v2]');
// Unmount children: the fresh cache instance for B cleans up since the cache boundary
@@ -1332,7 +1276,7 @@ describe('ReactCache', () => {
await act(async () => {
root.render('Bye!');
});
expect(Scheduler).toHaveYielded(['Cache cleanup: B [v2]']);
assertLog(['Cache cleanup: B [v2]']);
expect(root).toMatchRenderedOutput('Bye!');
});
@@ -1348,13 +1292,13 @@ describe('ReactCache', () => {
</Suspense>,
);
});
expect(Scheduler).toHaveYielded(['Cache miss! [A]']);
assertLog(['Cache miss! [A]']);
expect(root).toMatchRenderedOutput('Loading...');
await act(async () => {
resolveMostRecentTextCache('A');
});
expect(Scheduler).toHaveYielded(['A [v1]']);
assertLog(['A [v1]']);
expect(root).toMatchRenderedOutput('A [v1]');
// After a mount, subsequent transitions use a fresh cache
@@ -1369,7 +1313,7 @@ describe('ReactCache', () => {
);
});
});
expect(Scheduler).toHaveYielded(['Cache miss! [B]']);
assertLog(['Cache miss! [B]']);
expect(root).toMatchRenderedOutput('A [v1]');
// Update to a different text and with a different key for the cache
@@ -1386,13 +1330,13 @@ describe('ReactCache', () => {
);
});
});
expect(Scheduler).toHaveYielded(['Cache miss! [C]']);
assertLog(['Cache miss! [C]']);
expect(root).toMatchRenderedOutput('A [v1]');
await act(async () => {
resolveMostRecentTextCache('C');
});
expect(Scheduler).toHaveYielded(['C [v2]']);
assertLog(['C [v2]']);
expect(root).toMatchRenderedOutput('C [v2]');
// Unmount children: the fresh cache used for the updates is freed, while the
@@ -1400,10 +1344,7 @@ describe('ReactCache', () => {
await act(async () => {
root.render('Bye!');
});
expect(Scheduler).toHaveYielded([
'Cache cleanup: B [v2]',
'Cache cleanup: C [v2]',
]);
assertLog(['Cache cleanup: B [v2]', 'Cache cleanup: C [v2]']);
expect(root).toMatchRenderedOutput('Bye!');
});
@@ -1419,13 +1360,13 @@ describe('ReactCache', () => {
</Suspense>,
);
});
expect(Scheduler).toHaveYielded(['Cache miss! [A]']);
assertLog(['Cache miss! [A]']);
expect(root).toMatchRenderedOutput('Loading...');
await act(async () => {
resolveMostRecentTextCache('A');
});
expect(Scheduler).toHaveYielded(['A [v1]']);
assertLog(['A [v1]']);
expect(root).toMatchRenderedOutput('A [v1]');
// After a mount, subsequent updates use a fresh cache
@@ -1438,7 +1379,7 @@ describe('ReactCache', () => {
</Suspense>,
);
});
expect(Scheduler).toHaveYielded(['Cache miss! [B]']);
assertLog(['Cache miss! [B]']);
expect(root).toMatchRenderedOutput('Loading...');
// A second update uses the same fresh cache: even though this is a new
@@ -1452,13 +1393,13 @@ describe('ReactCache', () => {
</Suspense>,
);
});
expect(Scheduler).toHaveYielded(['Cache miss! [C]']);
assertLog(['Cache miss! [C]']);
expect(root).toMatchRenderedOutput('Loading...');
await act(async () => {
resolveMostRecentTextCache('C');
});
expect(Scheduler).toHaveYielded(['C [v2]']);
assertLog(['C [v2]']);
expect(root).toMatchRenderedOutput('C [v2]');
// Unmount children: the fresh cache used for the updates is freed, while the
@@ -1466,10 +1407,7 @@ describe('ReactCache', () => {
await act(async () => {
root.render('Bye!');
});
expect(Scheduler).toHaveYielded([
'Cache cleanup: B [v2]',
'Cache cleanup: C [v2]',
]);
assertLog(['Cache cleanup: B [v2]', 'Cache cleanup: C [v2]']);
expect(root).toMatchRenderedOutput('Bye!');
});
@@ -1486,7 +1424,7 @@ describe('ReactCache', () => {
</Suspense>,
);
});
expect(Scheduler).toHaveYielded(['A [v1]']);
assertLog(['A [v1]']);
expect(root).toMatchRenderedOutput('A [v1]');
// Start a transition from A -> B..., which should create a fresh cache
@@ -1502,7 +1440,7 @@ describe('ReactCache', () => {
);
});
});
expect(Scheduler).toHaveYielded(['Cache miss! [B]']);
assertLog(['Cache miss! [B]']);
expect(root).toMatchRenderedOutput('A [v1]');
// ...but cancel by transitioning "back" to A (which we never really left)
@@ -1517,14 +1455,14 @@ describe('ReactCache', () => {
);
});
});
expect(Scheduler).toHaveYielded(['A [v1]', 'Cache cleanup: B [v2]']);
assertLog(['A [v1]', 'Cache cleanup: B [v2]']);
expect(root).toMatchRenderedOutput('A [v1]');
// Unmount children: ...
await act(async () => {
root.render('Bye!');
});
expect(Scheduler).toHaveYielded([]);
assertLog([]);
expect(root).toMatchRenderedOutput('Bye!');
});
@@ -1544,7 +1482,7 @@ describe('ReactCache', () => {
</Suspense>,
);
});
expect(Scheduler).toHaveYielded(['A [v1]']);
assertLog(['A [v1]']);
expect(root).toMatchRenderedOutput('A [v1]');
await act(async () => {
@@ -1552,13 +1490,13 @@ describe('ReactCache', () => {
refresh();
});
});
expect(Scheduler).toHaveYielded(['Cache miss! [A]']);
assertLog(['Cache miss! [A]']);
expect(root).toMatchRenderedOutput('A [v1]');
await act(async () => {
root.render('Bye!');
});
expect(Scheduler).toHaveYielded([
assertLog([
// TODO: the v1 cache should *not* be cleaned up, it is still retained by the root
// The following line is presently yielded but should not be:
// 'Cache cleanup: A [v1]',
@@ -1588,7 +1526,7 @@ describe('ReactCache', () => {
</Suspense>,
);
});
expect(Scheduler).toHaveYielded(['A [v1]']);
assertLog(['A [v1]']);
expect(root).toMatchRenderedOutput('A [v1]');
await act(async () => {
@@ -1596,14 +1534,14 @@ describe('ReactCache', () => {
refresh();
});
});
expect(Scheduler).toHaveYielded(['Cache miss! [A]']);
assertLog(['Cache miss! [A]']);
expect(root).toMatchRenderedOutput('A [v1]');
// Unmount the boundary before the refresh can complete
await act(async () => {
root.render('Bye!');
});
expect(Scheduler).toHaveYielded([
assertLog([
// TODO: the v2 cache *should* be cleaned up, it was created for the abandoned refresh
// The following line is presently not yielded but should be:
'Cache cleanup: A [v2]',
@@ -1632,14 +1570,14 @@ describe('ReactCache', () => {
await act(async () => {
root.render(<App prerenderMore={false} />);
});
expect(Scheduler).toHaveYielded([]);
assertLog([]);
expect(root).toMatchRenderedOutput(<div hidden={true} />);
seedNextTextCache('More');
await act(async () => {
root.render(<App prerenderMore={true} />);
});
expect(Scheduler).toHaveYielded(['More']);
assertLog(['More']);
expect(root).toMatchRenderedOutput(<div hidden={true}>More</div>);
});
@@ -2,6 +2,7 @@ let React;
let ReactNoop;
let Scheduler;
let act;
let assertLog;
describe('ReactClassSetStateCallback', () => {
beforeEach(() => {
@@ -11,6 +12,9 @@ describe('ReactClassSetStateCallback', () => {
ReactNoop = require('react-noop-renderer');
Scheduler = require('scheduler');
act = require('jest-react').act;
const InternalTestUtils = require('internal-test-utils');
assertLog = InternalTestUtils.assertLog;
});
function Text({text}) {
@@ -32,7 +36,7 @@ describe('ReactClassSetStateCallback', () => {
await act(async () => {
root.render(<App />);
});
expect(Scheduler).toHaveYielded([0]);
assertLog([0]);
await act(async () => {
if (gate(flags => flags.enableUnifiedSyncLane)) {
@@ -52,6 +56,6 @@ describe('ReactClassSetStateCallback', () => {
);
});
});
expect(Scheduler).toHaveYielded([2, 'Callback 2', 2, 'Callback 1']);
assertLog([2, 'Callback 2', 2, 'Callback 1']);
});
});
@@ -5,6 +5,7 @@ let act;
let Suspense;
let getCacheForType;
let startTransition;
let assertLog;
let caches;
let seededCache;
@@ -20,6 +21,9 @@ describe('ReactConcurrentErrorRecovery', () => {
Suspense = React.Suspense;
startTransition = React.startTransition;
const InternalTestUtils = require('internal-test-utils');
assertLog = InternalTestUtils.assertLog;
getCacheForType = React.unstable_getCacheForType;
caches = [];
@@ -196,7 +200,7 @@ describe('ReactConcurrentErrorRecovery', () => {
await act(async () => {
root.render(<App step={1} />);
});
expect(Scheduler).toHaveYielded(['A1', 'B1']);
assertLog(['A1', 'B1']);
expect(root).toMatchRenderedOutput('A1B1');
// Start a refresh transition
@@ -205,12 +209,7 @@ describe('ReactConcurrentErrorRecovery', () => {
root.render(<App step={2} />);
});
});
expect(Scheduler).toHaveYielded([
'Suspend! [A2]',
'Loading...',
'Suspend! [B2]',
'Loading...',
]);
assertLog(['Suspend! [A2]', 'Loading...', 'Suspend! [B2]', 'Loading...']);
// Because this is a refresh, we don't switch to a fallback
expect(root).toMatchRenderedOutput('A1B1');
@@ -222,7 +221,7 @@ describe('ReactConcurrentErrorRecovery', () => {
// Because we're still suspended on A, we can't show an error boundary. We
// should wait for A to resolve.
if (gate(flags => flags.replayFailedUnitOfWorkWithInvokeGuardedCallback)) {
expect(Scheduler).toHaveYielded([
assertLog([
'Suspend! [A2]',
'Loading...',
@@ -233,12 +232,7 @@ describe('ReactConcurrentErrorRecovery', () => {
'Oops!',
]);
} else {
expect(Scheduler).toHaveYielded([
'Suspend! [A2]',
'Loading...',
'Error! [B2]',
'Oops!',
]);
assertLog(['Suspend! [A2]', 'Loading...', 'Error! [B2]', 'Oops!']);
}
// Remain on previous screen.
expect(root).toMatchRenderedOutput('A1B1');
@@ -248,7 +242,7 @@ describe('ReactConcurrentErrorRecovery', () => {
resolveText('A2');
});
if (gate(flags => flags.replayFailedUnitOfWorkWithInvokeGuardedCallback)) {
expect(Scheduler).toHaveYielded([
assertLog([
'A2',
'Error! [B2]',
// This extra log happens when we replay the error
@@ -264,15 +258,7 @@ describe('ReactConcurrentErrorRecovery', () => {
'Oops!',
]);
} else {
expect(Scheduler).toHaveYielded([
'A2',
'Error! [B2]',
'Oops!',
'A2',
'Error! [B2]',
'Oops!',
]);
assertLog(['A2', 'Error! [B2]', 'Oops!', 'A2', 'Error! [B2]', 'Oops!']);
}
// Now we can show the error boundary that's wrapped around B.
expect(root).toMatchRenderedOutput('A2Oops!');
@@ -317,7 +303,7 @@ describe('ReactConcurrentErrorRecovery', () => {
await act(async () => {
root.render(<App step={1} />);
});
expect(Scheduler).toHaveYielded(['A1', 'B1']);
assertLog(['A1', 'B1']);
expect(root).toMatchRenderedOutput('A1B1');
// Start a refresh transition
@@ -326,12 +312,7 @@ describe('ReactConcurrentErrorRecovery', () => {
root.render(<App step={2} />);
});
});
expect(Scheduler).toHaveYielded([
'Suspend! [A2]',
'Loading...',
'Suspend! [B2]',
'Loading...',
]);
assertLog(['Suspend! [A2]', 'Loading...', 'Suspend! [B2]', 'Loading...']);
// Because this is a refresh, we don't switch to a fallback
expect(root).toMatchRenderedOutput('A1B1');
@@ -343,7 +324,7 @@ describe('ReactConcurrentErrorRecovery', () => {
// Because we're still suspended on B, we can't show an error boundary. We
// should wait for B to resolve.
if (gate(flags => flags.replayFailedUnitOfWorkWithInvokeGuardedCallback)) {
expect(Scheduler).toHaveYielded([
assertLog([
'Error! [A2]',
// This extra log happens when we replay the error
// in invokeGuardedCallback
@@ -354,12 +335,7 @@ describe('ReactConcurrentErrorRecovery', () => {
'Loading...',
]);
} else {
expect(Scheduler).toHaveYielded([
'Error! [A2]',
'Oops!',
'Suspend! [B2]',
'Loading...',
]);
assertLog(['Error! [A2]', 'Oops!', 'Suspend! [B2]', 'Loading...']);
}
// Remain on previous screen.
expect(root).toMatchRenderedOutput('A1B1');
@@ -369,7 +345,7 @@ describe('ReactConcurrentErrorRecovery', () => {
resolveText('B2');
});
if (gate(flags => flags.replayFailedUnitOfWorkWithInvokeGuardedCallback)) {
expect(Scheduler).toHaveYielded([
assertLog([
'Error! [A2]',
// This extra log happens when we replay the error
// in invokeGuardedCallback
@@ -385,15 +361,7 @@ describe('ReactConcurrentErrorRecovery', () => {
'B2',
]);
} else {
expect(Scheduler).toHaveYielded([
'Error! [A2]',
'Oops!',
'B2',
'Error! [A2]',
'Oops!',
'B2',
]);
assertLog(['Error! [A2]', 'Oops!', 'B2', 'Error! [A2]', 'Oops!', 'B2']);
}
// Now we can show the error boundary that's wrapped around B.
expect(root).toMatchRenderedOutput('Oops!B2');
@@ -422,7 +390,7 @@ describe('ReactConcurrentErrorRecovery', () => {
root.render(<AsyncText text="Async" />);
});
});
expect(Scheduler).toHaveYielded(['Suspend! [Async]']);
assertLog(['Suspend! [Async]']);
expect(root).toMatchRenderedOutput(null);
// This also works if the suspended component is wrapped with an error
@@ -438,14 +406,14 @@ describe('ReactConcurrentErrorRecovery', () => {
);
});
});
expect(Scheduler).toHaveYielded(['Suspend! [Async]']);
assertLog(['Suspend! [Async]']);
expect(root).toMatchRenderedOutput(null);
// Continues rendering once data resolves
await act(async () => {
resolveText('Async');
});
expect(Scheduler).toHaveYielded(['Async']);
assertLog(['Async']);
expect(root).toMatchRenderedOutput('Async');
});
@@ -489,7 +457,7 @@ describe('ReactConcurrentErrorRecovery', () => {
);
});
});
expect(Scheduler).toHaveYielded([
assertLog([
'Suspend! [Async]',
// TODO: Ideally we would skip this second render pass to render the
// error UI, since it's not going to commit anyway. The same goes for
@@ -510,17 +478,14 @@ describe('ReactConcurrentErrorRecovery', () => {
);
});
});
expect(Scheduler).toHaveYielded([
'Suspend! [Async]',
'Caught an error: Oops!',
]);
assertLog(['Suspend! [Async]', 'Caught an error: Oops!']);
expect(root).toMatchRenderedOutput(null);
await act(async () => {
await resolveText('Async');
});
expect(Scheduler).toHaveYielded([
assertLog([
'Async',
'Caught an error: Oops!',
@@ -9,6 +9,7 @@ let SuspenseList;
let getCacheForType;
let caches;
let seededCache;
let assertLog;
describe('ReactLazyContextPropagation', () => {
beforeEach(() => {
@@ -25,6 +26,9 @@ describe('ReactLazyContextPropagation', () => {
SuspenseList = React.SuspenseList;
}
const InternalTestUtils = require('internal-test-utils');
assertLog = InternalTestUtils.assertLog;
getCacheForType = React.unstable_getCacheForType;
caches = [];
@@ -202,13 +206,13 @@ describe('ReactLazyContextPropagation', () => {
await act(async () => {
root.render(<App />);
});
expect(Scheduler).toHaveYielded([0]);
assertLog([0]);
expect(root).toMatchRenderedOutput('0');
await act(async () => {
setValue(1);
});
expect(Scheduler).toHaveYielded([1]);
assertLog([1]);
expect(root).toMatchRenderedOutput('1');
},
);
@@ -244,13 +248,13 @@ describe('ReactLazyContextPropagation', () => {
await act(async () => {
root.render(<App />);
});
expect(Scheduler).toHaveYielded([0]);
assertLog([0]);
expect(root).toMatchRenderedOutput('0');
await act(async () => {
setValue(1);
});
expect(Scheduler).toHaveYielded([1]);
assertLog([1]);
expect(root).toMatchRenderedOutput('1');
});
@@ -287,13 +291,13 @@ describe('ReactLazyContextPropagation', () => {
await act(async () => {
root.render(<App />);
});
expect(Scheduler).toHaveYielded([0]);
assertLog([0]);
expect(root).toMatchRenderedOutput('0');
await act(async () => {
setValue(1);
});
expect(Scheduler).toHaveYielded([1]);
assertLog([1]);
expect(root).toMatchRenderedOutput('1');
});
@@ -325,7 +329,7 @@ describe('ReactLazyContextPropagation', () => {
await act(async () => {
root.render(<App />);
});
expect(Scheduler).toHaveYielded(['Consumer', 0]);
assertLog(['Consumer', 0]);
expect(root).toMatchRenderedOutput('0');
await act(async () => {
@@ -335,12 +339,7 @@ describe('ReactLazyContextPropagation', () => {
setOtherValue(1);
setOtherValue(0);
});
// NOTE: If this didn't yield anything, that indicates that we never visited
// the consumer during the render phase, which probably means the eager
// bailout mechanism kicked in. Because we're testing the _lazy_ bailout
// mechanism, update this test to foil the _eager_ bailout, somehow. Perhaps
// by switching to useReducer.
expect(Scheduler).toHaveYielded(['Consumer']);
assertLog(['Consumer']);
expect(root).toMatchRenderedOutput('0');
});
@@ -387,7 +386,7 @@ describe('ReactLazyContextPropagation', () => {
await act(async () => {
root.render(<App />);
});
expect(Scheduler).toHaveYielded(['A', 'A']);
assertLog(['A', 'A']);
expect(root).toMatchRenderedOutput('AA');
await act(async () => {
@@ -395,13 +394,13 @@ describe('ReactLazyContextPropagation', () => {
// the fallback displays despite this being a refresh.
setContext('B');
});
expect(Scheduler).toHaveYielded(['Suspend! [B]', 'Loading...', 'B']);
assertLog(['Suspend! [B]', 'Loading...', 'B']);
expect(root).toMatchRenderedOutput('Loading...B');
await act(async () => {
await resolveText('B');
});
expect(Scheduler).toHaveYielded(['B']);
assertLog(['B']);
expect(root).toMatchRenderedOutput('BB');
});
@@ -467,7 +466,7 @@ describe('ReactLazyContextPropagation', () => {
await act(async () => {
root.render(<App />);
});
expect(Scheduler).toHaveYielded(['A', 'A', 'A']);
assertLog(['A', 'A', 'A']);
expect(root).toMatchRenderedOutput('AAA');
await act(async () => {
@@ -475,13 +474,13 @@ describe('ReactLazyContextPropagation', () => {
// the fallback displays despite this being a refresh.
setContext('B');
});
expect(Scheduler).toHaveYielded(['Suspend! [B]', 'Loading...', 'B']);
assertLog(['Suspend! [B]', 'Loading...', 'B']);
expect(root).toMatchRenderedOutput('Loading...B');
await act(async () => {
await resolveText('B');
});
expect(Scheduler).toHaveYielded(['B', 'B']);
assertLog(['B', 'B']);
expect(root).toMatchRenderedOutput('BBB');
});
@@ -528,7 +527,7 @@ describe('ReactLazyContextPropagation', () => {
await act(async () => {
root.render(<App />);
});
expect(Scheduler).toHaveYielded(['A', 'A']);
assertLog(['A', 'A']);
expect(root).toMatchRenderedOutput('AA');
await act(async () => {
@@ -536,13 +535,13 @@ describe('ReactLazyContextPropagation', () => {
// the fallback displays despite this being a refresh.
setContext('B');
});
expect(Scheduler).toHaveYielded(['Suspend! [B]', 'Loading...', 'B']);
assertLog(['Suspend! [B]', 'Loading...', 'B']);
expect(root).toMatchRenderedOutput('Loading...B');
await act(async () => {
await resolveText('B');
});
expect(Scheduler).toHaveYielded(['B']);
assertLog(['B']);
expect(root).toMatchRenderedOutput('BB');
});
@@ -582,13 +581,13 @@ describe('ReactLazyContextPropagation', () => {
await act(async () => {
root.render(<App />);
});
expect(Scheduler).toHaveYielded(['A', 'A']);
assertLog(['A', 'A']);
expect(root).toMatchRenderedOutput('AA');
await act(async () => {
setContext('B');
});
expect(Scheduler).toHaveYielded(['B', 'B']);
assertLog(['B', 'B']);
expect(root).toMatchRenderedOutput('BB');
});
@@ -643,13 +642,13 @@ describe('ReactLazyContextPropagation', () => {
await act(async () => {
root.render(<App />);
});
expect(Scheduler).toHaveYielded(['A', 'A', 'A']);
assertLog(['A', 'A', 'A']);
expect(root).toMatchRenderedOutput('AAA');
await act(async () => {
setContext('B');
});
expect(Scheduler).toHaveYielded(['B', 'B', 'B']);
assertLog(['B', 'B', 'B']);
expect(root).toMatchRenderedOutput('BBB');
});
@@ -685,13 +684,13 @@ describe('ReactLazyContextPropagation', () => {
await act(async () => {
root.render(<App />);
});
expect(Scheduler).toHaveYielded(['A', 'A']);
assertLog(['A', 'A']);
expect(root).toMatchRenderedOutput('AA');
await act(async () => {
setContext('B');
});
expect(Scheduler).toHaveYielded(['B', 'B']);
assertLog(['B', 'B']);
expect(root).toMatchRenderedOutput('BB');
});
@@ -739,13 +738,13 @@ describe('ReactLazyContextPropagation', () => {
await act(async () => {
root.render(<App />);
});
expect(Scheduler).toHaveYielded(['A', 'A']);
assertLog(['A', 'A']);
expect(root).toMatchRenderedOutput('AA');
await act(async () => {
setContext('B');
});
expect(Scheduler).toHaveYielded(['B', 'B']);
assertLog(['B', 'B']);
expect(root).toMatchRenderedOutput('BB');
});
@@ -802,19 +801,19 @@ describe('ReactLazyContextPropagation', () => {
await act(async () => {
root.render(<App />);
});
expect(Scheduler).toHaveYielded(['A', 'A']);
assertLog(['A', 'A']);
expect(root).toMatchRenderedOutput('AA');
await act(async () => {
setContext('B');
});
expect(Scheduler).toHaveYielded(['Suspend! [B]', 'Loading...']);
assertLog(['Suspend! [B]', 'Loading...']);
expect(root).toMatchRenderedOutput('Loading...');
await act(async () => {
await resolveText('B');
});
expect(Scheduler).toHaveYielded(['B', 'B']);
assertLog(['B', 'B']);
expect(root).toMatchRenderedOutput('BB');
});
@@ -863,13 +862,13 @@ describe('ReactLazyContextPropagation', () => {
await act(async () => {
root.render(<App />);
});
expect(Scheduler).toHaveYielded(['A', 'A']);
assertLog(['A', 'A']);
expect(root).toMatchRenderedOutput('AA');
await act(async () => {
setContext('B');
});
expect(Scheduler).toHaveYielded(['B', 'B']);
assertLog(['B', 'B']);
expect(root).toMatchRenderedOutput('BB');
});
@@ -912,13 +911,13 @@ describe('ReactLazyContextPropagation', () => {
await act(async () => {
root.render(<App />);
});
expect(Scheduler).toHaveYielded(['A', 'A']);
assertLog(['A', 'A']);
expect(root).toMatchRenderedOutput('AA');
await act(async () => {
setContext('B');
});
expect(Scheduler).toHaveYielded(['B', 'B']);
assertLog(['B', 'B']);
expect(root).toMatchRenderedOutput('BB');
});
});
@@ -15,6 +15,8 @@ let startTransition;
let useDeferredValue;
let useMemo;
let useState;
let assertLog;
let waitForPaint;
describe('ReactDeferredValue', () => {
beforeEach(() => {
@@ -28,6 +30,10 @@ describe('ReactDeferredValue', () => {
useDeferredValue = React.useDeferredValue;
useMemo = React.useMemo;
useState = React.useState;
const InternalTestUtils = require('internal-test-utils');
assertLog = InternalTestUtils.assertLog;
waitForPaint = InternalTestUtils.waitForPaint;
});
function Text({text}) {
@@ -65,15 +71,14 @@ describe('ReactDeferredValue', () => {
await act(async () => {
root.render(<App value={1} />);
});
expect(Scheduler).toHaveYielded(['Original: 1', 'Deferred: 1']);
assertLog(['Original: 1', 'Deferred: 1']);
// If it's an urgent update, the value is deferred
await act(async () => {
root.render(<App value={2} />);
expect(Scheduler).toFlushUntilNextPaint(['Original: 2']);
// The deferred value updates in a separate render
expect(Scheduler).toFlushUntilNextPaint(['Deferred: 2']);
await waitForPaint(['Original: 2']);
await waitForPaint(['Deferred: 2']);
});
expect(root).toMatchRenderedOutput(
<div>
@@ -87,8 +92,7 @@ describe('ReactDeferredValue', () => {
startTransition(() => {
root.render(<App value={3} />);
});
// The deferred value updates in the same render as the original
expect(Scheduler).toFlushUntilNextPaint(['Original: 3', 'Deferred: 3']);
await waitForPaint(['Original: 3', 'Deferred: 3']);
});
expect(root).toMatchRenderedOutput(
<div>
@@ -126,15 +130,14 @@ describe('ReactDeferredValue', () => {
await act(async () => {
root.render(<App value={1} />);
});
expect(Scheduler).toHaveYielded(['Original: 1', 'Deferred: 1']);
assertLog(['Original: 1', 'Deferred: 1']);
// If it's an urgent update, the value is deferred
await act(async () => {
root.render(<App value={2} />);
expect(Scheduler).toFlushUntilNextPaint(['Original: 2']);
// The deferred value updates in a separate render
expect(Scheduler).toFlushUntilNextPaint(['Deferred: 2']);
await waitForPaint(['Original: 2']);
await waitForPaint(['Deferred: 2']);
});
expect(root).toMatchRenderedOutput(
<div>
@@ -148,8 +151,7 @@ describe('ReactDeferredValue', () => {
startTransition(() => {
root.render(<App value={3} />);
});
// The deferred value updates in the same render as the original
expect(Scheduler).toFlushUntilNextPaint(['Original: 3', 'Deferred: 3']);
await waitForPaint(['Original: 3', 'Deferred: 3']);
});
expect(root).toMatchRenderedOutput(
<div>
@@ -192,15 +194,14 @@ describe('ReactDeferredValue', () => {
await act(async () => {
root.render(<App value={1} />);
});
expect(Scheduler).toHaveYielded(['Original: 1', 'Deferred: 1']);
assertLog(['Original: 1', 'Deferred: 1']);
// If it's an urgent update, the value is deferred
await act(async () => {
root.render(<App value={2} />);
expect(Scheduler).toFlushUntilNextPaint(['Original: 2']);
// The deferred value updates in a separate render
expect(Scheduler).toFlushUntilNextPaint(['Deferred: 2']);
await waitForPaint(['Original: 2']);
await waitForPaint(['Deferred: 2']);
});
expect(root).toMatchRenderedOutput(
<div>
@@ -214,8 +215,7 @@ describe('ReactDeferredValue', () => {
startTransition(() => {
root.render(<App value={3} />);
});
// The deferred value updates in the same render as the original
expect(Scheduler).toFlushUntilNextPaint(['Original: 3', 'Deferred: 3']);
await waitForPaint(['Original: 3', 'Deferred: 3']);
});
expect(root).toMatchRenderedOutput(
<div>
@@ -257,7 +257,7 @@ describe('ReactDeferredValue', () => {
// Initial render
await act(async () => {
root.render(<App value={1} />);
expect(Scheduler).toFlushUntilNextPaint(['Original: 1', 'Deferred: 1']);
await waitForPaint(['Original: 1', 'Deferred: 1']);
expect(root).toMatchRenderedOutput(
<div>
<div>Original: 1</div>
@@ -270,7 +270,7 @@ describe('ReactDeferredValue', () => {
startTransition(() => {
root.render(<App value={2} />);
});
expect(Scheduler).toFlushUntilNextPaint(['Original: 2', 'Deferred: 2']);
await waitForPaint(['Original: 2', 'Deferred: 2']);
expect(root).toMatchRenderedOutput(
<div>
<div>Original: 2</div>
@@ -281,17 +281,14 @@ describe('ReactDeferredValue', () => {
await act(async () => {
root.render(<App value={3} />);
// In the regression, the memoized value was not updated during non-urgent
// updates, so this would flip the deferred value back to the initial
// value (1) instead of reusing the current one (2).
expect(Scheduler).toFlushUntilNextPaint(['Original: 3']);
await waitForPaint(['Original: 3']);
expect(root).toMatchRenderedOutput(
<div>
<div>Original: 3</div>
<div>Deferred: 2</div>
</div>,
);
expect(Scheduler).toFlushUntilNextPaint(['Deferred: 3']);
await waitForPaint(['Deferred: 3']);
expect(root).toMatchRenderedOutput(
<div>
<div>Original: 3</div>
@@ -5,6 +5,7 @@ let Scheduler;
let Suspense;
let scheduleCallback;
let NormalPriority;
let waitForAll;
describe('ReactSuspenseList', () => {
beforeEach(() => {
@@ -20,6 +21,9 @@ describe('ReactSuspenseList', () => {
scheduleCallback = Scheduler.unstable_scheduleCallback;
NormalPriority = Scheduler.unstable_NormalPriority;
const InternalTestUtils = require('internal-test-utils');
waitForAll = InternalTestUtils.waitForAll;
});
function Text(props) {
@@ -61,16 +65,12 @@ describe('ReactSuspenseList', () => {
const root = ReactNoop.createRoot(null);
root.render(<App show={false} />);
expect(Scheduler).toFlushAndYield([]);
await waitForAll([]);
React.startTransition(() => {
root.render(<App show={true} />);
});
expect(Scheduler).toFlushAndYield([
'Suspend! [A]',
'Suspend! [B]',
'Loading...',
]);
await waitForAll(['Suspend! [A]', 'Suspend! [B]', 'Loading...']);
expect(root).toMatchRenderedOutput(null);
Scheduler.unstable_advanceTime(2000);
@@ -92,7 +92,7 @@ describe('ReactSuspenseList', () => {
// task should not jump the queue ahead of B.
await expect(Scheduler).toFlushAndYieldThrough(['Resolve B']);
expect(Scheduler).toFlushAndYield(['A', 'B']);
await waitForAll(['A', 'B']);
expect(root).toMatchRenderedOutput('AB');
});
});
@@ -18,8 +18,9 @@ let Scheduler;
let act;
let useEffect;
let useLayoutEffect;
let assertLog;
describe('ReactHooksWithNoopRenderer', () => {
describe('ReactEffectOrdering', () => {
beforeEach(() => {
jest.resetModules();
jest.useFakeTimers();
@@ -30,6 +31,9 @@ describe('ReactHooksWithNoopRenderer', () => {
act = require('jest-react').act;
useEffect = React.useEffect;
useLayoutEffect = React.useLayoutEffect;
const InternalTestUtils = require('internal-test-utils');
assertLog = InternalTestUtils.assertLog;
});
test('layout unmounts on deletion are fired in parent -> child order', async () => {
@@ -56,7 +60,7 @@ describe('ReactHooksWithNoopRenderer', () => {
await act(async () => {
root.render(null);
});
expect(Scheduler).toHaveYielded(['Unmount parent', 'Unmount child']);
assertLog(['Unmount parent', 'Unmount child']);
});
test('passive unmounts on deletion are fired in parent -> child order', async () => {
@@ -83,6 +87,6 @@ describe('ReactHooksWithNoopRenderer', () => {
await act(async () => {
root.render(null);
});
expect(Scheduler).toHaveYielded(['Unmount parent', 'Unmount child']);
assertLog(['Unmount parent', 'Unmount child']);
});
});
@@ -18,6 +18,9 @@ let resolveText;
let startTransition;
let useState;
let useEffect;
let assertLog;
let waitFor;
let waitForAll;
describe('ReactExpiration', () => {
beforeEach(() => {
@@ -31,6 +34,11 @@ describe('ReactExpiration', () => {
useState = React.useState;
useEffect = React.useEffect;
const InternalTestUtils = require('internal-test-utils');
assertLog = InternalTestUtils.assertLog;
waitFor = InternalTestUtils.waitFor;
waitForAll = InternalTestUtils.waitForAll;
const textCache = new Map();
readText = text => {
@@ -136,7 +144,7 @@ describe('ReactExpiration', () => {
expect(ReactNoop).toMatchRenderedOutput(<span prop="done" />);
});
it('two updates of like priority in the same event always flush within the same batch', () => {
it('two updates of like priority in the same event always flush within the same batch', async () => {
class TextClass extends React.Component {
componentDidMount() {
Scheduler.unstable_yieldValue(`${this.props.text} [commit]`);
@@ -163,37 +171,32 @@ describe('ReactExpiration', () => {
});
// Advance the timer.
Scheduler.unstable_advanceTime(2000);
// Partially flush the first update, then interrupt it.
expect(Scheduler).toFlushAndYieldThrough(['A [render]']);
await waitFor(['A [render]']);
interrupt();
// Don't advance time by enough to expire the first update.
expect(Scheduler).toHaveYielded([]);
assertLog([]);
expect(ReactNoop).toMatchRenderedOutput(null);
// Schedule another update.
ReactNoop.render(<TextClass text="B" />);
// Both updates are batched
expect(Scheduler).toFlushAndYield(['B [render]', 'B [commit]']);
await waitForAll(['B [render]', 'B [commit]']);
expect(ReactNoop).toMatchRenderedOutput(<span prop="B" />);
// Now do the same thing again, except this time don't flush any work in
// between the two updates.
ReactNoop.render(<TextClass text="A" />);
Scheduler.unstable_advanceTime(2000);
expect(Scheduler).toHaveYielded([]);
assertLog([]);
expect(ReactNoop).toMatchRenderedOutput(<span prop="B" />);
// Schedule another update.
ReactNoop.render(<TextClass text="B" />);
// The updates should flush in the same batch, since as far as the scheduler
// knows, they may have occurred inside the same event.
expect(Scheduler).toFlushAndYield(['B [render]', 'B [commit]']);
await waitForAll(['B [render]', 'B [commit]']);
});
it(
'two updates of like priority in the same event always flush within the ' +
"same batch, even if there's a sync update in between",
() => {
async () => {
class TextClass extends React.Component {
componentDidMount() {
Scheduler.unstable_yieldValue(`${this.props.text} [commit]`);
@@ -220,25 +223,22 @@ describe('ReactExpiration', () => {
});
// Advance the timer.
Scheduler.unstable_advanceTime(2000);
// Partially flush the first update, then interrupt it.
expect(Scheduler).toFlushAndYieldThrough(['A [render]']);
await waitFor(['A [render]']);
interrupt();
// Don't advance time by enough to expire the first update.
expect(Scheduler).toHaveYielded([]);
assertLog([]);
expect(ReactNoop).toMatchRenderedOutput(null);
// Schedule another update.
ReactNoop.render(<TextClass text="B" />);
// Both updates are batched
expect(Scheduler).toFlushAndYield(['B [render]', 'B [commit]']);
await waitForAll(['B [render]', 'B [commit]']);
expect(ReactNoop).toMatchRenderedOutput(<span prop="B" />);
// Now do the same thing again, except this time don't flush any work in
// between the two updates.
ReactNoop.render(<TextClass text="A" />);
Scheduler.unstable_advanceTime(2000);
expect(Scheduler).toHaveYielded([]);
assertLog([]);
expect(ReactNoop).toMatchRenderedOutput(<span prop="B" />);
// Perform some synchronous work. The scheduler must assume we're inside
@@ -247,13 +247,11 @@ describe('ReactExpiration', () => {
// Schedule another update.
ReactNoop.render(<TextClass text="B" />);
// The updates should flush in the same batch, since as far as the scheduler
// knows, they may have occurred inside the same event.
expect(Scheduler).toFlushAndYield(['B [render]', 'B [commit]']);
await waitForAll(['B [render]', 'B [commit]']);
},
);
it('cannot update at the same expiration time that is already rendering', () => {
it('cannot update at the same expiration time that is already rendering', async () => {
const store = {text: 'initial'};
const subscribers = [];
class Connected extends React.Component {
@@ -292,7 +290,7 @@ describe('ReactExpiration', () => {
React.startTransition(() => {
ReactNoop.render(<App />);
});
expect(Scheduler).toFlushAndYield([
await waitForAll([
'initial [A] [render]',
'initial [B] [render]',
'initial [C] [render]',
@@ -307,10 +305,7 @@ describe('ReactExpiration', () => {
React.startTransition(() => {
subscribers.forEach(s => s.setState({text: '1'}));
});
expect(Scheduler).toFlushAndYieldThrough([
'1 [A] [render]',
'1 [B] [render]',
]);
await waitFor(['1 [A] [render]', '1 [B] [render]']);
// Before the update can finish, update again. Even though no time has
// advanced, this update should be given a different expiration time than
@@ -318,13 +313,10 @@ describe('ReactExpiration', () => {
React.startTransition(() => {
subscribers.forEach(s => s.setState({text: '2'}));
});
expect(Scheduler).toFlushAndYieldThrough([
'1 [C] [render]',
'1 [D] [render]',
]);
await waitFor(['1 [C] [render]', '1 [D] [render]']);
});
it('stops yielding if CPU-bound update takes too long to finish', () => {
it('stops yielding if CPU-bound update takes too long to finish', async () => {
const root = ReactNoop.createRoot();
function App() {
return (
@@ -342,18 +334,18 @@ describe('ReactExpiration', () => {
root.render(<App />);
});
expect(Scheduler).toFlushAndYieldThrough(['A']);
expect(Scheduler).toFlushAndYieldThrough(['B']);
expect(Scheduler).toFlushAndYieldThrough(['C']);
await waitFor(['A']);
await waitFor(['B']);
await waitFor(['C']);
Scheduler.unstable_advanceTime(10000);
flushNextRenderIfExpired();
expect(Scheduler).toHaveYielded(['D', 'E']);
assertLog(['D', 'E']);
expect(root).toMatchRenderedOutput('ABCDE');
});
it('root expiration is measured from the time of the first update', () => {
it('root expiration is measured from the time of the first update', async () => {
Scheduler.unstable_advanceTime(10000);
const root = ReactNoop.createRoot();
@@ -372,14 +364,14 @@ describe('ReactExpiration', () => {
root.render(<App />);
});
expect(Scheduler).toFlushAndYieldThrough(['A']);
expect(Scheduler).toFlushAndYieldThrough(['B']);
expect(Scheduler).toFlushAndYieldThrough(['C']);
await waitFor(['A']);
await waitFor(['B']);
await waitFor(['C']);
Scheduler.unstable_advanceTime(10000);
flushNextRenderIfExpired();
expect(Scheduler).toHaveYielded(['D', 'E']);
assertLog(['D', 'E']);
expect(root).toMatchRenderedOutput('ABCDE');
});
@@ -404,14 +396,14 @@ describe('ReactExpiration', () => {
// The update should not have expired yet.
flushNextRenderIfExpired();
expect(Scheduler).toHaveYielded([]);
assertLog([]);
expect(ReactNoop).toMatchRenderedOutput(null);
// Advance the time some more to expire the update.
Scheduler.unstable_advanceTime(10000);
flushNextRenderIfExpired();
expect(Scheduler).toHaveYielded([]);
assertLog([]);
expect(ReactNoop).toMatchRenderedOutput('Hi');
});
@@ -427,14 +419,14 @@ describe('ReactExpiration', () => {
ReactNoop.render('Hi');
});
flushNextRenderIfExpired();
expect(Scheduler).toHaveYielded([]);
assertLog([]);
expect(ReactNoop).toMatchRenderedOutput(null);
// Advancing by ~5 seconds should be sufficient to expire the update. (I
// used a slightly larger number to allow for possible rounding.)
Scheduler.unstable_advanceTime(6000);
flushNextRenderIfExpired();
expect(Scheduler).toHaveYielded([]);
assertLog([]);
expect(ReactNoop).toMatchRenderedOutput('Hi');
});
@@ -463,7 +455,7 @@ describe('ReactExpiration', () => {
await act(async () => {
root.render(<App />);
});
expect(Scheduler).toHaveYielded(['Sync pri: 0', 'Normal pri: 0']);
assertLog(['Sync pri: 0', 'Normal pri: 0']);
expect(root).toMatchRenderedOutput('Sync pri: 0, Normal pri: 0');
// First demonstrate what happens when there's no starvation
@@ -471,18 +463,16 @@ describe('ReactExpiration', () => {
React.startTransition(() => {
updateNormalPri();
});
expect(Scheduler).toFlushAndYieldThrough(['Sync pri: 0']);
await waitFor(['Sync pri: 0']);
updateSyncPri();
expect(Scheduler).toHaveYielded(['Sync pri: 1', 'Normal pri: 0']);
assertLog(['Sync pri: 1', 'Normal pri: 0']);
// The remaining work hasn't expired, so the render phase is time sliced.
// In other words, we can flush just the first child without flushing
// the rest.
Scheduler.unstable_flushNumberOfYields(1);
// Yield right after first child.
expect(Scheduler).toHaveYielded(['Sync pri: 1']);
// Now do the rest.
expect(Scheduler).toFlushAndYield(['Normal pri: 1']);
assertLog(['Sync pri: 1']);
await waitForAll(['Normal pri: 1']);
});
expect(root).toMatchRenderedOutput('Sync pri: 1, Normal pri: 1');
@@ -491,7 +481,7 @@ describe('ReactExpiration', () => {
React.startTransition(() => {
updateNormalPri();
});
expect(Scheduler).toFlushAndYieldThrough(['Sync pri: 1']);
await waitFor(['Sync pri: 1']);
// This time, a lot of time has elapsed since the normal pri update
// started rendering. (This should advance time by some number that's
@@ -500,12 +490,12 @@ describe('ReactExpiration', () => {
Scheduler.unstable_advanceTime(10000);
updateSyncPri();
expect(Scheduler).toHaveYielded(['Sync pri: 2', 'Normal pri: 1']);
assertLog(['Sync pri: 2', 'Normal pri: 1']);
// The remaining work _has_ expired, so the render phase is _not_ time
// sliced. Attempting to flush just the first child also flushes the rest.
Scheduler.unstable_flushNumberOfYields(1);
expect(Scheduler).toHaveYielded(['Sync pri: 2', 'Normal pri: 2']);
assertLog(['Sync pri: 2', 'Normal pri: 2']);
});
expect(root).toMatchRenderedOutput('Sync pri: 2, Normal pri: 2');
});
@@ -534,16 +524,16 @@ describe('ReactExpiration', () => {
await act(async () => {
root.render(<App />);
});
expect(Scheduler).toHaveYielded(['Sync pri: 0', 'Idle pri: 0']);
assertLog(['Sync pri: 0', 'Idle pri: 0']);
expect(root).toMatchRenderedOutput('Sync pri: 0, Idle pri: 0');
// First demonstrate what happens when there's no starvation
await act(async () => {
updateIdlePri();
expect(Scheduler).toFlushAndYieldThrough(['Sync pri: 0']);
await waitFor(['Sync pri: 0']);
updateSyncPri();
});
expect(Scheduler).toHaveYielded([
assertLog([
// Interrupt idle update to render sync update
'Sync pri: 1',
'Idle pri: 0',
@@ -556,7 +546,7 @@ describe('ReactExpiration', () => {
// Do the same thing, but starve the first update
await act(async () => {
updateIdlePri();
expect(Scheduler).toFlushAndYieldThrough(['Sync pri: 1']);
await waitFor(['Sync pri: 1']);
// Advance a ridiculously large amount of time to demonstrate that the
// idle work never expires
@@ -564,8 +554,7 @@ describe('ReactExpiration', () => {
updateSyncPri();
});
// Same thing should happen as last time
expect(Scheduler).toHaveYielded([
assertLog([
// Interrupt idle update to render sync update
'Sync pri: 2',
'Idle pri: 1',
@@ -597,14 +586,14 @@ describe('ReactExpiration', () => {
await act(async () => {
root.render(<App />);
});
expect(Scheduler).toHaveYielded(['A0', 'B0', 'C']);
assertLog(['A0', 'B0', 'C']);
expect(root).toMatchRenderedOutput('A0B0C');
await act(async () => {
startTransition(() => {
setA(1);
});
expect(Scheduler).toFlushAndYieldThrough(['A1']);
await waitFor(['A1']);
startTransition(() => {
setB(1);
});
@@ -614,12 +603,12 @@ describe('ReactExpiration', () => {
// (entangled), we should be able to finish the in-progress transition
// without also including the next one.
Scheduler.unstable_flushNumberOfYields(1);
expect(Scheduler).toHaveYielded(['B0', 'C']);
assertLog(['B0', 'C']);
expect(root).toMatchRenderedOutput('A1B0C');
// The next transition also finishes without yielding.
Scheduler.unstable_flushNumberOfYields(1);
expect(Scheduler).toHaveYielded(['A1', 'B1', 'C']);
assertLog(['A1', 'B1', 'C']);
expect(root).toMatchRenderedOutput('A1B1C');
});
});
@@ -642,28 +631,21 @@ describe('ReactExpiration', () => {
await resolveText('A0');
root.render(<App step={0} />);
});
expect(Scheduler).toHaveYielded(['A0', 'B', 'C']);
assertLog(['A0', 'B', 'C']);
expect(root).toMatchRenderedOutput('A0BC');
await act(async () => {
React.startTransition(() => {
root.render(<App step={1} />);
});
expect(Scheduler).toFlushAndYield([
'Suspend! [A1]',
'B',
'C',
'Loading...',
]);
await waitForAll(['Suspend! [A1]', 'B', 'C', 'Loading...']);
// Lots of time elapses before the promise resolves
Scheduler.unstable_advanceTime(10000);
await resolveText('A1');
expect(Scheduler).toHaveYielded(['Promise resolved [A1]']);
assertLog(['Promise resolved [A1]']);
// But the update doesn't expire, because it was IO bound. So we can
// partially rendering without finishing.
expect(Scheduler).toFlushAndYieldThrough(['A1']);
await waitFor(['A1']);
expect(root).toMatchRenderedOutput('A0BC');
// Lots more time elapses. We're CPU-bound now, so we should treat this
@@ -672,7 +654,7 @@ describe('ReactExpiration', () => {
// The rest of the update finishes without yielding.
Scheduler.unstable_flushNumberOfYields(1);
expect(Scheduler).toHaveYielded(['B', 'C']);
assertLog(['B', 'C']);
});
});
@@ -696,13 +678,13 @@ describe('ReactExpiration', () => {
await act(async () => {
root.render(<App />);
});
expect(Scheduler).toHaveYielded(['A0', 'B0']);
assertLog(['A0', 'B0']);
await act(async () => {
startTransition(() => {
setA(1);
});
expect(Scheduler).toFlushAndYieldThrough(['A1']);
await waitFor(['A1']);
// Expire the in-progress update
Scheduler.unstable_advanceTime(10000);
@@ -710,12 +692,12 @@ describe('ReactExpiration', () => {
ReactNoop.flushSync(() => {
setB(1);
});
expect(Scheduler).toHaveYielded(['A0', 'B1']);
assertLog(['A0', 'B1']);
// Now flush the original update. Because it expired, it should finish
// without yielding.
Scheduler.unstable_flushNumberOfYields(1);
expect(Scheduler).toHaveYielded(['A1', 'B1']);
assertLog(['A1', 'B1']);
});
});
@@ -737,7 +719,7 @@ describe('ReactExpiration', () => {
await act(async () => {
root.render(<App step={0} />);
});
expect(Scheduler).toHaveYielded(['A0', 'B0', 'C0', 'Effect: 0']);
assertLog(['A0', 'B0', 'C0', 'Effect: 0']);
expect(root).toMatchRenderedOutput('A0B0C0');
await act(async () => {
@@ -749,9 +731,8 @@ describe('ReactExpiration', () => {
// The update finishes without yielding. But it does not flush the effect.
Scheduler.unstable_flushNumberOfYields(1);
expect(Scheduler).toHaveYielded(['A1', 'B1', 'C1']);
assertLog(['A1', 'B1', 'C1']);
});
// The effect flushes after paint.
expect(Scheduler).toHaveYielded(['Effect: 1']);
assertLog(['Effect: 1']);
});
});
@@ -5,6 +5,8 @@ let act;
let useState;
let useEffect;
let startTransition;
let assertLog;
let waitForPaint;
// TODO: Migrate tests to React DOM instead of React Noop
@@ -19,6 +21,10 @@ describe('ReactFlushSync', () => {
useState = React.useState;
useEffect = React.useEffect;
startTransition = React.startTransition;
const InternalTestUtils = require('internal-test-utils');
assertLog = InternalTestUtils.assertLog;
waitForPaint = InternalTestUtils.waitForPaint;
});
function Text({text}) {
@@ -27,6 +33,8 @@ describe('ReactFlushSync', () => {
}
test('changes priority of updates in useEffect', async () => {
spyOnDev(console, 'error').mockImplementation(() => {});
function App() {
const [syncState, setSyncState] = useState(0);
const [state, setState] = useState(0);
@@ -44,29 +52,33 @@ describe('ReactFlushSync', () => {
React.startTransition(() => {
root.render(<App />);
});
// This will yield right before the passive effect fires
expect(Scheduler).toFlushUntilNextPaint(['0, 0']);
await waitForPaint(['0, 0']);
// The passive effect will schedule a sync update and a normal update.
// They should commit in two separate batches. First the sync one.
expect(() => {
expect(Scheduler).toFlushUntilNextPaint(
gate(flags => flags.enableUnifiedSyncLane) ? ['1, 1'] : ['1, 0'],
);
}).toErrorDev('flushSync was called from inside a lifecycle method');
await waitForPaint(
gate(flags => flags.enableUnifiedSyncLane) ? ['1, 1'] : ['1, 0'],
);
// The remaining update is not sync
ReactNoop.flushSync();
expect(Scheduler).toHaveYielded([]);
assertLog([]);
if (gate(flags => flags.enableUnifiedSyncLane)) {
expect(Scheduler).toFlushUntilNextPaint([]);
await waitForPaint([]);
} else {
// Now flush it.
expect(Scheduler).toFlushUntilNextPaint(['1, 1']);
await waitForPaint(['1, 1']);
}
});
expect(root).toMatchRenderedOutput('1, 1');
if (__DEV__) {
expect(console.error.mock.calls[0][0]).toContain(
'flushSync was called from inside a lifecycle method. React ' +
'cannot flush when React is already rendering. Consider moving this ' +
'call to a scheduler task or micro task.%s',
);
}
});
test('nested with startTransition', async () => {
@@ -84,7 +96,7 @@ describe('ReactFlushSync', () => {
await act(async () => {
root.render(<App />);
});
expect(Scheduler).toHaveYielded(['0, 0']);
assertLog(['0, 0']);
expect(root).toMatchRenderedOutput('0, 0');
await act(async () => {
@@ -100,12 +112,10 @@ describe('ReactFlushSync', () => {
});
});
});
// Only the sync update should have flushed
expect(Scheduler).toHaveYielded(['1, 0']);
assertLog(['1, 0']);
expect(root).toMatchRenderedOutput('1, 0');
});
// Now the async update has flushed, too.
expect(Scheduler).toHaveYielded(['1, 1']);
assertLog(['1, 1']);
expect(root).toMatchRenderedOutput('1, 1');
});
@@ -122,7 +132,7 @@ describe('ReactFlushSync', () => {
ReactNoop.flushSync(() => {
root.render(<App />);
});
expect(Scheduler).toHaveYielded([
assertLog([
'Child',
// Because the pending effect was the result of a sync update, calling
// flushSync should flush it.
@@ -145,15 +155,14 @@ describe('ReactFlushSync', () => {
ReactNoop.flushSync(() => {
root.render(<App />);
});
expect(Scheduler).toHaveYielded([
assertLog([
'Child',
// Because we're in legacy mode, we shouldn't have flushed the passive
// effects yet.
]);
expect(root).toMatchRenderedOutput('Child');
});
// Effect flushes after paint.
expect(Scheduler).toHaveYielded(['Effect']);
assertLog(['Effect']);
});
test('flush pending passive effects before scope is called in legacy mode', async () => {
@@ -172,7 +181,7 @@ describe('ReactFlushSync', () => {
ReactNoop.flushSync(() => {
root.render(<App step={1} />);
});
expect(Scheduler).toHaveYielded([
assertLog([
1,
// Because we're in legacy mode, we shouldn't have flushed the passive
// effects yet.
@@ -184,10 +193,10 @@ describe('ReactFlushSync', () => {
// fired, before the scope function is called.
root.render(<App step={currentStep + 1} />);
});
expect(Scheduler).toHaveYielded(['Effect: 1', 2]);
assertLog(['Effect: 1', 2]);
expect(root).toMatchRenderedOutput('2');
});
expect(Scheduler).toHaveYielded(['Effect: 2']);
assertLog(['Effect: 2']);
});
test("do not flush passive effects synchronously when they aren't the result of a sync render", async () => {
@@ -201,15 +210,14 @@ describe('ReactFlushSync', () => {
const root = ReactNoop.createRoot();
await act(async () => {
root.render(<App />);
expect(Scheduler).toFlushUntilNextPaint([
await waitForPaint([
'Child',
// Because the passive effect was not the result of a sync update, it
// should not flush before paint.
]);
expect(root).toMatchRenderedOutput('Child');
});
// Effect flushes after paint.
expect(Scheduler).toHaveYielded(['Effect']);
assertLog(['Effect']);
});
test('does not flush pending passive effects', async () => {
@@ -223,15 +231,13 @@ describe('ReactFlushSync', () => {
const root = ReactNoop.createRoot();
await act(async () => {
root.render(<App />);
expect(Scheduler).toFlushUntilNextPaint(['Child']);
await waitForPaint(['Child']);
expect(root).toMatchRenderedOutput('Child');
// Passive effects are pending. Calling flushSync should not affect them.
ReactNoop.flushSync();
// Effects still haven't fired.
expect(Scheduler).toHaveYielded([]);
assertLog([]);
});
// Now the effects have fired.
expect(Scheduler).toHaveYielded(['Effect']);
assertLog(['Effect']);
});
});
+86 -84
View File
@@ -12,6 +12,7 @@
let React;
let ReactNoop;
let Scheduler;
let waitForAll;
describe('ReactFragment', () => {
beforeEach(function () {
@@ -20,9 +21,12 @@ describe('ReactFragment', () => {
React = require('react');
ReactNoop = require('react-noop-renderer');
Scheduler = require('scheduler');
const InternalTestUtils = require('internal-test-utils');
waitForAll = InternalTestUtils.waitForAll;
});
it('should render a single child via noop renderer', () => {
it('should render a single child via noop renderer', async () => {
const element = (
<>
<span>foo</span>
@@ -30,21 +34,21 @@ describe('ReactFragment', () => {
);
ReactNoop.render(element);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
expect(ReactNoop).toMatchRenderedOutput(<span>foo</span>);
});
it('should render zero children via noop renderer', () => {
it('should render zero children via noop renderer', async () => {
const element = <React.Fragment />;
ReactNoop.render(element);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
expect(ReactNoop).toMatchRenderedOutput(null);
});
it('should render multiple children via noop renderer', () => {
it('should render multiple children via noop renderer', async () => {
const element = (
<>
hello <span>world</span>
@@ -52,7 +56,7 @@ describe('ReactFragment', () => {
);
ReactNoop.render(element);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
expect(ReactNoop).toMatchRenderedOutput(
<>
@@ -61,13 +65,13 @@ describe('ReactFragment', () => {
);
});
it('should render an iterable via noop renderer', () => {
it('should render an iterable via noop renderer', async () => {
const element = (
<>{new Set([<span key="a">hi</span>, <span key="b">bye</span>])}</>
);
ReactNoop.render(element);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
expect(ReactNoop).toMatchRenderedOutput(
<>
@@ -77,7 +81,7 @@ describe('ReactFragment', () => {
);
});
it('should preserve state of children with 1 level nesting', function () {
it('should preserve state of children with 1 level nesting', async function () {
const ops = [];
class Stateful extends React.Component {
@@ -102,10 +106,10 @@ describe('ReactFragment', () => {
}
ReactNoop.render(<Foo condition={true} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
ReactNoop.render(<Foo condition={false} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
expect(ops).toEqual(['Update Stateful']);
expect(ReactNoop).toMatchRenderedOutput(
@@ -116,13 +120,13 @@ describe('ReactFragment', () => {
);
ReactNoop.render(<Foo condition={true} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
expect(ops).toEqual(['Update Stateful', 'Update Stateful']);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
});
it('should preserve state between top-level fragments', function () {
it('should preserve state between top-level fragments', async function () {
const ops = [];
class Stateful extends React.Component {
@@ -148,22 +152,22 @@ describe('ReactFragment', () => {
}
ReactNoop.render(<Foo condition={true} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
ReactNoop.render(<Foo condition={false} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
expect(ops).toEqual(['Update Stateful']);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
ReactNoop.render(<Foo condition={true} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
expect(ops).toEqual(['Update Stateful', 'Update Stateful']);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
});
it('should preserve state of children nested at same level', function () {
it('should preserve state of children nested at same level', async function () {
const ops = [];
class Stateful extends React.Component {
@@ -198,10 +202,10 @@ describe('ReactFragment', () => {
}
ReactNoop.render(<Foo condition={true} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
ReactNoop.render(<Foo condition={false} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
expect(ops).toEqual(['Update Stateful']);
expect(ReactNoop).toMatchRenderedOutput(
@@ -212,13 +216,13 @@ describe('ReactFragment', () => {
);
ReactNoop.render(<Foo condition={true} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
expect(ops).toEqual(['Update Stateful', 'Update Stateful']);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
});
it('should not preserve state in non-top-level fragment nesting', function () {
it('should not preserve state in non-top-level fragment nesting', async function () {
const ops = [];
class Stateful extends React.Component {
@@ -246,22 +250,22 @@ describe('ReactFragment', () => {
}
ReactNoop.render(<Foo condition={true} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
ReactNoop.render(<Foo condition={false} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
ReactNoop.render(<Foo condition={true} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
});
it('should not preserve state of children if nested 2 levels without siblings', function () {
it('should not preserve state of children if nested 2 levels without siblings', async function () {
const ops = [];
class Stateful extends React.Component {
@@ -287,22 +291,22 @@ describe('ReactFragment', () => {
}
ReactNoop.render(<Foo condition={true} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
ReactNoop.render(<Foo condition={false} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
ReactNoop.render(<Foo condition={true} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
});
it('should not preserve state of children if nested 2 levels with siblings', function () {
it('should not preserve state of children if nested 2 levels with siblings', async function () {
const ops = [];
class Stateful extends React.Component {
@@ -329,10 +333,10 @@ describe('ReactFragment', () => {
}
ReactNoop.render(<Foo condition={true} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
ReactNoop.render(<Foo condition={false} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(
@@ -343,13 +347,13 @@ describe('ReactFragment', () => {
);
ReactNoop.render(<Foo condition={true} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
});
it('should preserve state between array nested in fragment and fragment', function () {
it('should preserve state between array nested in fragment and fragment', async function () {
const ops = [];
class Stateful extends React.Component {
@@ -373,22 +377,22 @@ describe('ReactFragment', () => {
}
ReactNoop.render(<Foo condition={true} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
ReactNoop.render(<Foo condition={false} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
expect(ops).toEqual(['Update Stateful']);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
ReactNoop.render(<Foo condition={true} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
expect(ops).toEqual(['Update Stateful', 'Update Stateful']);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
});
it('should preserve state between top level fragment and array', function () {
it('should preserve state between top level fragment and array', async function () {
const ops = [];
class Stateful extends React.Component {
@@ -412,22 +416,22 @@ describe('ReactFragment', () => {
}
ReactNoop.render(<Foo condition={true} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
ReactNoop.render(<Foo condition={false} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
expect(ops).toEqual(['Update Stateful']);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
ReactNoop.render(<Foo condition={true} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
expect(ops).toEqual(['Update Stateful', 'Update Stateful']);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
});
it('should not preserve state between array nested in fragment and double nested fragment', function () {
it('should not preserve state between array nested in fragment and double nested fragment', async function () {
const ops = [];
class Stateful extends React.Component {
@@ -453,22 +457,22 @@ describe('ReactFragment', () => {
}
ReactNoop.render(<Foo condition={true} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
ReactNoop.render(<Foo condition={false} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
ReactNoop.render(<Foo condition={true} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
});
it('should not preserve state between array nested in fragment and double nested array', function () {
it('should not preserve state between array nested in fragment and double nested array', async function () {
const ops = [];
class Stateful extends React.Component {
@@ -490,22 +494,22 @@ describe('ReactFragment', () => {
}
ReactNoop.render(<Foo condition={true} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
ReactNoop.render(<Foo condition={false} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
ReactNoop.render(<Foo condition={true} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
});
it('should preserve state between double nested fragment and double nested array', function () {
it('should preserve state between double nested fragment and double nested array', async function () {
const ops = [];
class Stateful extends React.Component {
@@ -531,22 +535,22 @@ describe('ReactFragment', () => {
}
ReactNoop.render(<Foo condition={true} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
ReactNoop.render(<Foo condition={false} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
expect(ops).toEqual(['Update Stateful']);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
ReactNoop.render(<Foo condition={true} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
expect(ops).toEqual(['Update Stateful', 'Update Stateful']);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
});
it('should not preserve state of children when the keys are different', function () {
it('should not preserve state of children when the keys are different', async function () {
const ops = [];
class Stateful extends React.Component {
@@ -573,10 +577,10 @@ describe('ReactFragment', () => {
}
ReactNoop.render(<Foo condition={true} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
ReactNoop.render(<Foo condition={false} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(
@@ -587,13 +591,13 @@ describe('ReactFragment', () => {
);
ReactNoop.render(<Foo condition={true} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
});
it('should not preserve state between unkeyed and keyed fragment', function () {
it('should not preserve state between unkeyed and keyed fragment', async function () {
const ops = [];
class Stateful extends React.Component {
@@ -619,22 +623,22 @@ describe('ReactFragment', () => {
}
ReactNoop.render(<Foo condition={true} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
ReactNoop.render(<Foo condition={false} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
ReactNoop.render(<Foo condition={true} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
});
it('should preserve state with reordering in multiple levels', function () {
it('should preserve state with reordering in multiple levels', async function () {
const ops = [];
class Stateful extends React.Component {
@@ -672,10 +676,10 @@ describe('ReactFragment', () => {
}
ReactNoop.render(<Foo condition={true} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
ReactNoop.render(<Foo condition={false} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
expect(ops).toEqual(['Update Stateful']);
expect(ReactNoop).toMatchRenderedOutput(
@@ -689,7 +693,7 @@ describe('ReactFragment', () => {
);
ReactNoop.render(<Foo condition={true} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
expect(ops).toEqual(['Update Stateful', 'Update Stateful']);
expect(ReactNoop).toMatchRenderedOutput(
@@ -703,7 +707,7 @@ describe('ReactFragment', () => {
);
});
it('should not preserve state when switching to a keyed fragment to an array', function () {
it('should not preserve state when switching to a keyed fragment to an array', async function () {
const ops = [];
class Stateful extends React.Component {
@@ -735,7 +739,7 @@ describe('ReactFragment', () => {
}
ReactNoop.render(<Foo condition={true} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
ReactNoop.render(<Foo condition={false} />);
expect(() => expect(Scheduler).toFlushWithoutYielding()).toErrorDev(
@@ -751,7 +755,7 @@ describe('ReactFragment', () => {
);
ReactNoop.render(<Foo condition={true} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(
@@ -762,7 +766,7 @@ describe('ReactFragment', () => {
);
});
it('should not preserve state when switching a nested unkeyed fragment to a passthrough component', function () {
it('should not preserve state when switching a nested unkeyed fragment to a passthrough component', async function () {
const ops = [];
function Passthrough({children}) {
@@ -796,22 +800,22 @@ describe('ReactFragment', () => {
}
ReactNoop.render(<Foo condition={true} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
ReactNoop.render(<Foo condition={false} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
ReactNoop.render(<Foo condition={true} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
});
it('should not preserve state when switching a nested keyed fragment to a passthrough component', function () {
it('should not preserve state when switching a nested keyed fragment to a passthrough component', async function () {
const ops = [];
function Passthrough({children}) {
@@ -845,22 +849,22 @@ describe('ReactFragment', () => {
}
ReactNoop.render(<Foo condition={true} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
ReactNoop.render(<Foo condition={false} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
ReactNoop.render(<Foo condition={true} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
});
it('should not preserve state when switching a nested keyed array to a passthrough component', function () {
it('should not preserve state when switching a nested keyed array to a passthrough component', async function () {
const ops = [];
function Passthrough({children}) {
@@ -890,22 +894,22 @@ describe('ReactFragment', () => {
}
ReactNoop.render(<Foo condition={true} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
ReactNoop.render(<Foo condition={false} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
ReactNoop.render(<Foo condition={true} />);
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(<div>Hello</div>);
});
it('should preserve state when it does not change positions', function () {
it('should preserve state when it does not change positions', async function () {
const ops = [];
class Stateful extends React.Component {
@@ -940,8 +944,7 @@ describe('ReactFragment', () => {
);
ReactNoop.render(<Foo condition={false} />);
// The key warning gets deduped because it's in the same component.
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
expect(ops).toEqual(['Update Stateful']);
expect(ReactNoop).toMatchRenderedOutput(
@@ -952,8 +955,7 @@ describe('ReactFragment', () => {
);
ReactNoop.render(<Foo condition={true} />);
// The key warning gets deduped because it's in the same component.
expect(Scheduler).toFlushWithoutYielding();
await waitForAll([]);
expect(ops).toEqual(['Update Stateful', 'Update Stateful']);
expect(ReactNoop).toMatchRenderedOutput(
File diff suppressed because it is too large Load Diff