mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
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:
@@ -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
@@ -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']);
|
||||
});
|
||||
});
|
||||
|
||||
+22
-57
@@ -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>
|
||||
|
||||
+7
-7
@@ -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']);
|
||||
});
|
||||
});
|
||||
|
||||
+67
-86
@@ -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']);
|
||||
});
|
||||
});
|
||||
|
||||
+37
-31
@@ -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
@@ -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(
|
||||
|
||||
+224
-332
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user