'use strict';
let React;
let ReactDOMClient;
let Suspense;
let SuspenseList;
let ViewTransition;
let act;
let assertLog;
let Scheduler;
let textCache;
describe('ReactDOMViewTransition', () => {
let container;
beforeEach(() => {
jest.resetModules();
React = require('react');
ReactDOMClient = require('react-dom/client');
Scheduler = require('scheduler');
act = require('internal-test-utils').act;
assertLog = require('internal-test-utils').assertLog;
Suspense = React.Suspense;
ViewTransition = React.ViewTransition;
if (gate(flags => flags.enableSuspenseList)) {
SuspenseList = React.unstable_SuspenseList;
}
container = document.createElement('div');
document.body.appendChild(container);
textCache = new Map();
});
afterEach(() => {
document.body.removeChild(container);
});
function resolveText(text) {
const record = textCache.get(text);
if (record === undefined) {
const newRecord = {
status: 'resolved',
value: text,
};
textCache.set(text, newRecord);
} else if (record.status === 'pending') {
const thenable = record.value;
record.status = 'resolved';
record.value = text;
thenable.pings.forEach(t => t());
}
}
function readText(text) {
const record = textCache.get(text);
if (record !== undefined) {
switch (record.status) {
case 'pending':
Scheduler.log(`Suspend! [${text}]`);
throw record.value;
case 'rejected':
throw record.value;
case 'resolved':
return record.value;
}
} else {
Scheduler.log(`Suspend! [${text}]`);
const thenable = {
pings: [],
then(resolve) {
if (newRecord.status === 'pending') {
thenable.pings.push(resolve);
} else {
Promise.resolve().then(() => resolve(newRecord.value));
}
},
};
const newRecord = {
status: 'pending',
value: thenable,
};
textCache.set(text, newRecord);
throw thenable;
}
}
function Text({text}) {
Scheduler.log(text);
return text;
}
function AsyncText({text}) {
readText(text);
Scheduler.log(text);
return text;
}
it('handles ViewTransition wrapping Suspense inside SuspenseList', async () => {
function Card({id}) {
return (
<div>
<AsyncText text={`Card ${id}`} />
</div>
);
}
function CardSkeleton({n}) {
return <Text text={`Skeleton ${n}`} />;
}
function App() {
return (
<div>
<SuspenseList revealOrder="together">
<ViewTransition>
<Suspense fallback={<CardSkeleton n={1} />}>
<Card id={1} />
</Suspense>
</ViewTransition>
<ViewTransition>
<Suspense fallback={<CardSkeleton n={2} />}>
<Card id={2} />
</Suspense>
</ViewTransition>
<ViewTransition>
<Suspense fallback={<CardSkeleton n={3} />}>
<Card id={3} />
</Suspense>
</ViewTransition>
</SuspenseList>
</div>
);
}
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<App />);
});
assertLog([
'Suspend! [Card 1]',
'Skeleton 1',
'Suspend! [Card 2]',
'Skeleton 2',
'Suspend! [Card 3]',
'Skeleton 3',
'Skeleton 1',
'Skeleton 2',
'Skeleton 3',
]);
await act(() => {
resolveText('Card 1');
resolveText('Card 2');
resolveText('Card 3');
});
assertLog(['Card 1', 'Card 2', 'Card 3']);
expect(container.textContent).toContain('Card 1');
expect(container.textContent).toContain('Card 2');
expect(container.textContent).toContain('Card 3');
});
});