'use strict';
let React;
let ReactNoop;
let Scheduler;
let Suspense;
let startTransition;
let waitForAll;
let textCache;
describe('ReactSuspenseThrottledRetry', () => {
beforeEach(() => {
jest.resetModules();
React = require('react');
ReactNoop = require('react-noop-renderer');
Scheduler = require('scheduler');
Suspense = React.Suspense;
startTransition = React.startTransition;
const InternalTestUtils = require('internal-test-utils');
waitForAll = InternalTestUtils.waitForAll;
textCache = new Map();
});
function resolveText(text) {
const record = textCache.get(text);
if (record === undefined) {
textCache.set(text, {status: 'resolved', value: text});
} else if (record.status === 'pending') {
record.status = 'resolved';
record.resolve();
}
}
function readText(text) {
let record = textCache.get(text);
if (record === undefined) {
let resolve;
const promise = new Promise(r => (resolve = r));
record = {status: 'pending', value: promise, resolve};
textCache.set(text, record);
}
if (record.status === 'pending') {
Scheduler.log(`Suspend! [${text}]`);
throw record.value;
}
return text;
}
function Text({text}) {
Scheduler.log(text);
return text;
}
function AsyncText({text}) {
readText(text);
Scheduler.log(text);
return text;
}
let setOtherText;
function Other({readMore}) {
const [text, setText] = React.useState('O1');
setOtherText = setText;
readText(text);
if (readMore && text !== 'O1') {
readText(text + ' (more)');
}
Scheduler.log(text);
return text;
}
function App({readMore}) {
return (
<>
<Suspense fallback={<Text text="Loading A..." />}>
<AsyncText text="A" />
</Suspense>
<Suspense fallback={<Text text="Loading other..." />}>
<Other readMore={readMore} />
</Suspense>
</>
);
}
async function renderUntilRetryIsThrottled(readMore) {
resolveText('O1');
const root = ReactNoop.createRoot();
React.startTransition(() => {
root.render(<App readMore={readMore} />);
});
await waitForAll([
'Suspend! [A]',
'Loading A...',
'O1',
'Suspend! [A]',
]);
expect(root).toMatchRenderedOutput('Loading A...O1');
startTransition(() => setOtherText('O2'));
await waitForAll([
'Suspend! [O2]',
'Loading other...',
'Suspend! [A]',
]);
expect(root).toMatchRenderedOutput('Loading A...O1');
resolveText('A');
await waitForAll(['Suspend! [O2]', 'Loading other...', 'A']);
expect(root).toMatchRenderedOutput('Loading A...O1');
return root;
}
it('commits a throttled retry after an unrelated transition interrupts it and commits', async () => {
const root = await renderUntilRetryIsThrottled(false);
resolveText('O2');
await waitForAll(['O2', 'A']);
jest.advanceTimersByTime(5000);
await waitForAll([]);
expect(root).toMatchRenderedOutput('AO2');
});
it('commits a throttled retry after an unrelated transition interrupts it and suspends again', async () => {
const root = await renderUntilRetryIsThrottled(true);
resolveText('O2');
await waitForAll(['Suspend! [O2 (more)]', 'Loading other...', 'A']);
expect(root).toMatchRenderedOutput('Loading A...O1');
jest.advanceTimersByTime(5000);
await waitForAll(['Suspend! [O2 (more)]', 'Loading other...']);
expect(root).toMatchRenderedOutput('AO1');
resolveText('O2 (more)');
await waitForAll(['O2']);
expect(root).toMatchRenderedOutput('AO2');
});
});