mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Start prerendering Suspense retries immediately (#30934)
When a component suspends and is replaced by a fallback, we should start prerendering the fallback immediately, even before any new data is received. During the retry, we can enter prerender mode directly if we're sure that no new data was received since we last attempted to render the boundary. To do this, when completing the fallback, we leave behind a pending retry lane on the Suspense boundary. Previously we only did this once a promise resolved, but by assigning a lane during the complete phase, we will know that there's speculative work to be done. Then, upon committing the fallback, we mark the retry lane as suspended — but only if nothing was pinged or updated in the meantime. That allows us to immediately enter prerender mode (i.e. render without skipping any siblings) when performing the retry.
This commit is contained in:
@@ -18,6 +18,7 @@ let Suspense;
|
||||
let TextResource;
|
||||
let textResourceShouldFail;
|
||||
let waitForAll;
|
||||
let waitForPaint;
|
||||
let assertLog;
|
||||
let waitForThrow;
|
||||
let act;
|
||||
@@ -37,6 +38,7 @@ describe('ReactCache', () => {
|
||||
waitForAll = InternalTestUtils.waitForAll;
|
||||
assertLog = InternalTestUtils.assertLog;
|
||||
waitForThrow = InternalTestUtils.waitForThrow;
|
||||
waitForPaint = InternalTestUtils.waitForPaint;
|
||||
act = InternalTestUtils.act;
|
||||
|
||||
TextResource = createResource(
|
||||
@@ -119,7 +121,12 @@ describe('ReactCache', () => {
|
||||
const root = ReactNoop.createRoot();
|
||||
root.render(<App />);
|
||||
|
||||
await waitForAll(['Suspend! [Hi]', 'Loading...']);
|
||||
await waitForAll([
|
||||
'Suspend! [Hi]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [Hi]'] : []),
|
||||
]);
|
||||
|
||||
jest.advanceTimersByTime(100);
|
||||
assertLog(['Promise resolved [Hi]']);
|
||||
@@ -138,7 +145,12 @@ describe('ReactCache', () => {
|
||||
const root = ReactNoop.createRoot();
|
||||
root.render(<App />);
|
||||
|
||||
await waitForAll(['Suspend! [Hi]', 'Loading...']);
|
||||
await waitForAll([
|
||||
'Suspend! [Hi]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [Hi]'] : []),
|
||||
]);
|
||||
|
||||
textResourceShouldFail = true;
|
||||
let error;
|
||||
@@ -148,15 +160,7 @@ describe('ReactCache', () => {
|
||||
error = e;
|
||||
}
|
||||
expect(error.message).toMatch('Failed to load: Hi');
|
||||
assertLog([
|
||||
'Promise rejected [Hi]',
|
||||
'Error! [Hi]',
|
||||
'Error! [Hi]',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Error! [Hi]', 'Error! [Hi]']
|
||||
: []),
|
||||
]);
|
||||
assertLog(['Promise rejected [Hi]', 'Error! [Hi]', 'Error! [Hi]']);
|
||||
|
||||
// Should throw again on a subsequent read
|
||||
root.render(<App />);
|
||||
@@ -187,15 +191,27 @@ describe('ReactCache', () => {
|
||||
|
||||
if (__DEV__) {
|
||||
await expect(async () => {
|
||||
await waitForAll(['App', 'Loading...']);
|
||||
await waitForAll([
|
||||
'App',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['App'] : []),
|
||||
]);
|
||||
}).toErrorDev([
|
||||
'Invalid key type. Expected a string, number, symbol, or ' +
|
||||
"boolean, but instead received: [ 'Hi', 100 ]\n\n" +
|
||||
'To use non-primitive values as keys, you must pass a hash ' +
|
||||
'function as the second argument to createResource().',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Invalid key type'] : []),
|
||||
]);
|
||||
} else {
|
||||
await waitForAll(['App', 'Loading...']);
|
||||
await waitForAll([
|
||||
'App',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['App'] : []),
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -212,13 +228,17 @@ describe('ReactCache', () => {
|
||||
<AsyncText ms={100} text={3} />
|
||||
</Suspense>,
|
||||
);
|
||||
await waitForAll(['Suspend! [1]', 'Loading...']);
|
||||
await waitForPaint(['Suspend! [1]', 'Loading...']);
|
||||
jest.advanceTimersByTime(100);
|
||||
assertLog(['Promise resolved [1]']);
|
||||
await waitForAll([1, 'Suspend! [2]', 1, 'Suspend! [2]', 'Suspend! [3]']);
|
||||
await waitForAll([1, 'Suspend! [2]']);
|
||||
|
||||
jest.advanceTimersByTime(100);
|
||||
assertLog(['Promise resolved [2]', 'Promise resolved [3]']);
|
||||
assertLog(['Promise resolved [2]']);
|
||||
await waitForAll([1, 2, 'Suspend! [3]']);
|
||||
|
||||
jest.advanceTimersByTime(100);
|
||||
assertLog(['Promise resolved [3]']);
|
||||
await waitForAll([1, 2, 3]);
|
||||
|
||||
await act(() => jest.advanceTimersByTime(100));
|
||||
@@ -233,24 +253,17 @@ describe('ReactCache', () => {
|
||||
</Suspense>,
|
||||
);
|
||||
|
||||
await waitForAll([1, 'Suspend! [4]', 'Loading...']);
|
||||
await waitForAll([
|
||||
1,
|
||||
'Suspend! [4]',
|
||||
'Loading...',
|
||||
1,
|
||||
'Suspend! [4]',
|
||||
'Suspend! [5]',
|
||||
]);
|
||||
|
||||
await act(() => jest.advanceTimersByTime(100));
|
||||
assertLog([
|
||||
'Promise resolved [4]',
|
||||
|
||||
1,
|
||||
4,
|
||||
'Suspend! [5]',
|
||||
1,
|
||||
4,
|
||||
'Suspend! [5]',
|
||||
|
||||
'Promise resolved [5]',
|
||||
1,
|
||||
4,
|
||||
5,
|
||||
]);
|
||||
assertLog(['Promise resolved [4]', 'Promise resolved [5]', 1, 4, 5]);
|
||||
|
||||
expect(root).toMatchRenderedOutput('145');
|
||||
|
||||
@@ -271,24 +284,14 @@ describe('ReactCache', () => {
|
||||
// 2 and 3 suspend because they were evicted from the cache
|
||||
'Suspend! [2]',
|
||||
'Loading...',
|
||||
|
||||
1,
|
||||
'Suspend! [2]',
|
||||
'Suspend! [3]',
|
||||
]);
|
||||
|
||||
await act(() => jest.advanceTimersByTime(100));
|
||||
assertLog([
|
||||
'Promise resolved [2]',
|
||||
|
||||
1,
|
||||
2,
|
||||
'Suspend! [3]',
|
||||
1,
|
||||
2,
|
||||
'Suspend! [3]',
|
||||
|
||||
'Promise resolved [3]',
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
]);
|
||||
assertLog(['Promise resolved [2]', 'Promise resolved [3]', 1, 2, 3]);
|
||||
expect(root).toMatchRenderedOutput('123');
|
||||
});
|
||||
|
||||
@@ -363,7 +366,12 @@ describe('ReactCache', () => {
|
||||
</Suspense>,
|
||||
);
|
||||
|
||||
await waitForAll(['Suspend! [Hi]', 'Loading...']);
|
||||
await waitForAll([
|
||||
'Suspend! [Hi]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [Hi]'] : []),
|
||||
]);
|
||||
|
||||
resolveThenable('Hi');
|
||||
// This thenable improperly resolves twice. We should not update the
|
||||
|
||||
@@ -15,6 +15,17 @@ import {
|
||||
normalizeCodeLocInfo,
|
||||
} from './utils';
|
||||
|
||||
import {ReactVersion} from '../../../../ReactVersions';
|
||||
import semver from 'semver';
|
||||
|
||||
// TODO: This is how other DevTools tests access the version but we should find
|
||||
// a better solution for this
|
||||
const ReactVersionTestingAgainst = process.env.REACT_VERSION || ReactVersion;
|
||||
const enableSiblingPrerendering = semver.gte(
|
||||
ReactVersionTestingAgainst,
|
||||
'19.0.0',
|
||||
);
|
||||
|
||||
describe('Timeline profiler', () => {
|
||||
let React;
|
||||
let Scheduler;
|
||||
@@ -1651,7 +1662,11 @@ describe('Timeline profiler', () => {
|
||||
</React.Suspense>,
|
||||
);
|
||||
|
||||
await waitForAll(['suspended']);
|
||||
await waitForAll([
|
||||
'suspended',
|
||||
|
||||
...(enableSiblingPrerendering ? ['suspended'] : []),
|
||||
]);
|
||||
|
||||
Scheduler.unstable_advanceTime(10);
|
||||
resolveFn();
|
||||
@@ -1662,9 +1677,38 @@ describe('Timeline profiler', () => {
|
||||
const timelineData = stopProfilingAndGetTimelineData();
|
||||
|
||||
// Verify the Suspense event and duration was recorded.
|
||||
expect(timelineData.suspenseEvents).toHaveLength(1);
|
||||
const suspenseEvent = timelineData.suspenseEvents[0];
|
||||
expect(suspenseEvent).toMatchInlineSnapshot(`
|
||||
if (enableSiblingPrerendering) {
|
||||
expect(timelineData.suspenseEvents).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"componentName": "Example",
|
||||
"depth": 0,
|
||||
"duration": 10,
|
||||
"id": "0",
|
||||
"phase": "mount",
|
||||
"promiseName": "",
|
||||
"resolution": "resolved",
|
||||
"timestamp": 10,
|
||||
"type": "suspense",
|
||||
"warning": null,
|
||||
},
|
||||
{
|
||||
"componentName": "Example",
|
||||
"depth": 0,
|
||||
"duration": 10,
|
||||
"id": "0",
|
||||
"phase": "mount",
|
||||
"promiseName": "",
|
||||
"resolution": "resolved",
|
||||
"timestamp": 10,
|
||||
"type": "suspense",
|
||||
"warning": null,
|
||||
},
|
||||
]
|
||||
`);
|
||||
} else {
|
||||
const suspenseEvent = timelineData.suspenseEvents[0];
|
||||
expect(suspenseEvent).toMatchInlineSnapshot(`
|
||||
{
|
||||
"componentName": "Example",
|
||||
"depth": 0,
|
||||
@@ -1678,10 +1722,13 @@ describe('Timeline profiler', () => {
|
||||
"warning": null,
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
// There should be two batches of renders: Suspeneded and resolved.
|
||||
expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
|
||||
expect(timelineData.componentMeasures).toHaveLength(2);
|
||||
expect(timelineData.componentMeasures).toHaveLength(
|
||||
enableSiblingPrerendering ? 3 : 2,
|
||||
);
|
||||
});
|
||||
|
||||
it('should mark concurrent render with suspense that rejects', async () => {
|
||||
@@ -1708,7 +1755,11 @@ describe('Timeline profiler', () => {
|
||||
</React.Suspense>,
|
||||
);
|
||||
|
||||
await waitForAll(['suspended']);
|
||||
await waitForAll([
|
||||
'suspended',
|
||||
|
||||
...(enableSiblingPrerendering ? ['suspended'] : []),
|
||||
]);
|
||||
|
||||
Scheduler.unstable_advanceTime(10);
|
||||
rejectFn();
|
||||
@@ -1719,9 +1770,39 @@ describe('Timeline profiler', () => {
|
||||
const timelineData = stopProfilingAndGetTimelineData();
|
||||
|
||||
// Verify the Suspense event and duration was recorded.
|
||||
expect(timelineData.suspenseEvents).toHaveLength(1);
|
||||
const suspenseEvent = timelineData.suspenseEvents[0];
|
||||
expect(suspenseEvent).toMatchInlineSnapshot(`
|
||||
if (enableSiblingPrerendering) {
|
||||
expect(timelineData.suspenseEvents).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"componentName": "Example",
|
||||
"depth": 0,
|
||||
"duration": 10,
|
||||
"id": "0",
|
||||
"phase": "mount",
|
||||
"promiseName": "",
|
||||
"resolution": "rejected",
|
||||
"timestamp": 10,
|
||||
"type": "suspense",
|
||||
"warning": null,
|
||||
},
|
||||
{
|
||||
"componentName": "Example",
|
||||
"depth": 0,
|
||||
"duration": 10,
|
||||
"id": "0",
|
||||
"phase": "mount",
|
||||
"promiseName": "",
|
||||
"resolution": "rejected",
|
||||
"timestamp": 10,
|
||||
"type": "suspense",
|
||||
"warning": null,
|
||||
},
|
||||
]
|
||||
`);
|
||||
} else {
|
||||
expect(timelineData.suspenseEvents).toHaveLength(1);
|
||||
const suspenseEvent = timelineData.suspenseEvents[0];
|
||||
expect(suspenseEvent).toMatchInlineSnapshot(`
|
||||
{
|
||||
"componentName": "Example",
|
||||
"depth": 0,
|
||||
@@ -1735,10 +1816,13 @@ describe('Timeline profiler', () => {
|
||||
"warning": null,
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
// There should be two batches of renders: Suspeneded and resolved.
|
||||
expect(timelineData.batchUIDToMeasuresMap.size).toBe(2);
|
||||
expect(timelineData.componentMeasures).toHaveLength(2);
|
||||
expect(timelineData.componentMeasures).toHaveLength(
|
||||
enableSiblingPrerendering ? 3 : 2,
|
||||
);
|
||||
});
|
||||
|
||||
it('should mark cascading class component state updates', async () => {
|
||||
|
||||
+12
-2
@@ -1459,13 +1459,23 @@ describe('ReactDOMForm', () => {
|
||||
</Suspense>,
|
||||
),
|
||||
);
|
||||
assertLog(['Suspend! [Count: 0]', 'Loading...']);
|
||||
assertLog([
|
||||
'Suspend! [Count: 0]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [Count: 0]'] : []),
|
||||
]);
|
||||
await act(() => resolveText('Count: 0'));
|
||||
assertLog(['Count: 0']);
|
||||
|
||||
// Dispatch outside of a transition. This will trigger a loading state.
|
||||
await act(() => dispatch());
|
||||
assertLog(['Suspend! [Count: 1]', 'Loading...']);
|
||||
assertLog([
|
||||
'Suspend! [Count: 1]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [Count: 1]'] : []),
|
||||
]);
|
||||
expect(container.textContent).toBe('Loading...');
|
||||
|
||||
await act(() => resolveText('Count: 1'));
|
||||
|
||||
@@ -160,7 +160,13 @@ describe('ReactDOMSuspensePlaceholder', () => {
|
||||
});
|
||||
|
||||
expect(container.textContent).toEqual('Loading...');
|
||||
assertLog(['A', 'Suspend! [B]', 'Loading...']);
|
||||
assertLog([
|
||||
'A',
|
||||
'Suspend! [B]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['A', 'Suspend! [B]', 'C'] : []),
|
||||
]);
|
||||
await act(() => {
|
||||
resolveText('B');
|
||||
});
|
||||
|
||||
@@ -192,7 +192,13 @@ test('regression (#20932): return pointer is correct before entering deleted tre
|
||||
await act(() => {
|
||||
root.render(<App />);
|
||||
});
|
||||
assertLog(['Suspend! [0]', 'Loading Async...', 'Loading Tail...']);
|
||||
assertLog([
|
||||
'Suspend! [0]',
|
||||
'Loading Async...',
|
||||
'Loading Tail...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [0]'] : []),
|
||||
]);
|
||||
await act(() => {
|
||||
resolveText(0);
|
||||
});
|
||||
@@ -205,5 +211,7 @@ test('regression (#20932): return pointer is correct before entering deleted tre
|
||||
'Loading Async...',
|
||||
'Suspend! [1]',
|
||||
'Loading Async...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [1]'] : []),
|
||||
]);
|
||||
});
|
||||
|
||||
+23
-19
@@ -155,6 +155,7 @@ import {
|
||||
getRenderTargetTime,
|
||||
getWorkInProgressTransitions,
|
||||
shouldRemainOnPreviousScreen,
|
||||
markSpawnedRetryLane,
|
||||
} from './ReactFiberWorkLoop';
|
||||
import {
|
||||
OffscreenLane,
|
||||
@@ -600,25 +601,28 @@ function scheduleRetryEffect(
|
||||
// Schedule an effect to attach a retry listener to the promise.
|
||||
// TODO: Move to passive phase
|
||||
workInProgress.flags |= Update;
|
||||
} else {
|
||||
// This boundary suspended, but no wakeables were added to the retry
|
||||
// queue. Check if the renderer suspended commit. If so, this means
|
||||
// that once the fallback is committed, we can immediately retry
|
||||
// rendering again, because rendering wasn't actually blocked. Only
|
||||
// the commit phase.
|
||||
// TODO: Consider a model where we always schedule an immediate retry, even
|
||||
// for normal Suspense. That way the retry can partially render up to the
|
||||
// first thing that suspends.
|
||||
if (workInProgress.flags & ScheduleRetry) {
|
||||
const retryLane =
|
||||
// TODO: This check should probably be moved into claimNextRetryLane
|
||||
// I also suspect that we need some further consolidation of offscreen
|
||||
// and retry lanes.
|
||||
workInProgress.tag !== OffscreenComponent
|
||||
? claimNextRetryLane()
|
||||
: OffscreenLane;
|
||||
workInProgress.lanes = mergeLanes(workInProgress.lanes, retryLane);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we need to schedule an immediate retry. This should happen
|
||||
// whenever we unwind a suspended tree without fully rendering its siblings;
|
||||
// we need to begin the retry so we can start prerendering them.
|
||||
//
|
||||
// We also use this mechanism for Suspensey Resources (e.g. stylesheets),
|
||||
// because those don't actually block the render phase, only the commit phase.
|
||||
// So we can start rendering even before the resources are ready.
|
||||
if (workInProgress.flags & ScheduleRetry) {
|
||||
const retryLane =
|
||||
// TODO: This check should probably be moved into claimNextRetryLane
|
||||
// I also suspect that we need some further consolidation of offscreen
|
||||
// and retry lanes.
|
||||
workInProgress.tag !== OffscreenComponent
|
||||
? claimNextRetryLane()
|
||||
: OffscreenLane;
|
||||
workInProgress.lanes = mergeLanes(workInProgress.lanes, retryLane);
|
||||
|
||||
// Track the lanes that have been scheduled for an immediate retry so that
|
||||
// we can mark them as suspended upon committing the root.
|
||||
markSpawnedRetryLane(retryLane);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+71
-7
@@ -26,9 +26,11 @@ import {
|
||||
syncLaneExpirationMs,
|
||||
transitionLaneExpirationMs,
|
||||
retryLaneExpirationMs,
|
||||
disableLegacyMode,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import {isDevToolsPresent} from './ReactFiberDevToolsHook';
|
||||
import {clz32} from './clz32';
|
||||
import {LegacyRoot} from './ReactRootTags';
|
||||
|
||||
// Lane values below should be kept in sync with getLabelForLane(), used by react-devtools-timeline.
|
||||
// If those values are changed that package should be rebuilt and redeployed.
|
||||
@@ -231,6 +233,29 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
|
||||
const pingedLanes = root.pingedLanes;
|
||||
const warmLanes = root.warmLanes;
|
||||
|
||||
// finishedLanes represents a completed tree that is ready to commit.
|
||||
//
|
||||
// It's not worth doing discarding the completed tree in favor of performing
|
||||
// speculative work. So always check this before deciding to warm up
|
||||
// the siblings.
|
||||
//
|
||||
// Note that this is not set in a "suspend indefinitely" scenario, like when
|
||||
// suspending outside of a Suspense boundary, or in the shell during a
|
||||
// transition — only in cases where we are very likely to commit the tree in
|
||||
// a brief amount of time (i.e. below the "Just Noticeable Difference"
|
||||
// threshold).
|
||||
//
|
||||
// TODO: finishedLanes is also set when a Suspensey resource, like CSS or
|
||||
// images, suspends during the commit phase. (We could detect that here by
|
||||
// checking for root.cancelPendingCommit.) These are also expected to resolve
|
||||
// quickly, because of preloading, but theoretically they could block forever
|
||||
// like in a normal "suspend indefinitely" scenario. In the future, we should
|
||||
// consider only blocking for up to some time limit before discarding the
|
||||
// commit in favor of prerendering. If we do discard a pending commit, then
|
||||
// the commit phase callback should act as a ping to try the original
|
||||
// render again.
|
||||
const rootHasPendingCommit = root.finishedLanes !== NoLanes;
|
||||
|
||||
// Do not work on any idle work until all the non-idle work has finished,
|
||||
// even if the work is suspended.
|
||||
const nonIdlePendingLanes = pendingLanes & NonIdleLanes;
|
||||
@@ -246,9 +271,11 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
|
||||
nextLanes = getHighestPriorityLanes(nonIdlePingedLanes);
|
||||
} else {
|
||||
// Nothing has been pinged. Check for lanes that need to be prewarmed.
|
||||
const lanesToPrewarm = nonIdlePendingLanes & ~warmLanes;
|
||||
if (lanesToPrewarm !== NoLanes) {
|
||||
nextLanes = getHighestPriorityLanes(lanesToPrewarm);
|
||||
if (!rootHasPendingCommit) {
|
||||
const lanesToPrewarm = nonIdlePendingLanes & ~warmLanes;
|
||||
if (lanesToPrewarm !== NoLanes) {
|
||||
nextLanes = getHighestPriorityLanes(lanesToPrewarm);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -268,9 +295,11 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
|
||||
nextLanes = getHighestPriorityLanes(pingedLanes);
|
||||
} else {
|
||||
// Nothing has been pinged. Check for lanes that need to be prewarmed.
|
||||
const lanesToPrewarm = pendingLanes & ~warmLanes;
|
||||
if (lanesToPrewarm !== NoLanes) {
|
||||
nextLanes = getHighestPriorityLanes(lanesToPrewarm);
|
||||
if (!rootHasPendingCommit) {
|
||||
const lanesToPrewarm = pendingLanes & ~warmLanes;
|
||||
if (lanesToPrewarm !== NoLanes) {
|
||||
nextLanes = getHighestPriorityLanes(lanesToPrewarm);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -753,10 +782,14 @@ export function markRootPinged(root: FiberRoot, pingedLanes: Lanes) {
|
||||
|
||||
export function markRootFinished(
|
||||
root: FiberRoot,
|
||||
finishedLanes: Lanes,
|
||||
remainingLanes: Lanes,
|
||||
spawnedLane: Lane,
|
||||
updatedLanes: Lanes,
|
||||
suspendedRetryLanes: Lanes,
|
||||
) {
|
||||
const noLongerPendingLanes = root.pendingLanes & ~remainingLanes;
|
||||
const previouslyPendingLanes = root.pendingLanes;
|
||||
const noLongerPendingLanes = previouslyPendingLanes & ~remainingLanes;
|
||||
|
||||
root.pendingLanes = remainingLanes;
|
||||
|
||||
@@ -812,6 +845,37 @@ export function markRootFinished(
|
||||
NoLanes,
|
||||
);
|
||||
}
|
||||
|
||||
// suspendedRetryLanes represents the retry lanes spawned by new Suspense
|
||||
// boundaries during this render that were not later pinged.
|
||||
//
|
||||
// These lanes were marked as pending on their associated Suspense boundary
|
||||
// fiber during the render phase so that we could start rendering them
|
||||
// before new data streams in. As soon as the fallback commits, we can try
|
||||
// to render them again.
|
||||
//
|
||||
// But since we know they're still suspended, we can skip straight to the
|
||||
// "prerender" mode (i.e. don't skip over siblings after something
|
||||
// suspended) instead of the regular mode (i.e. unwind and skip the siblings
|
||||
// as soon as something suspends to unblock the rest of the update).
|
||||
if (
|
||||
suspendedRetryLanes !== NoLanes &&
|
||||
// Note that we only do this if there were no updates since we started
|
||||
// rendering. This mirrors the logic in markRootUpdated — whenever we
|
||||
// receive an update, we reset all the suspended and pinged lanes.
|
||||
updatedLanes === NoLanes &&
|
||||
!(disableLegacyMode && root.tag === LegacyRoot)
|
||||
) {
|
||||
// We also need to avoid marking a retry lane as suspended if it was already
|
||||
// pending before this render. We can't say these are now suspended if they
|
||||
// weren't included in our attempt.
|
||||
const freshlySpawnedRetryLanes =
|
||||
suspendedRetryLanes &
|
||||
// Remove any retry lane that was already pending before our just-finished
|
||||
// attempt, and also wasn't included in that attempt.
|
||||
~(previouslyPendingLanes & ~finishedLanes);
|
||||
root.suspendedLanes |= freshlySpawnedRetryLanes;
|
||||
}
|
||||
}
|
||||
|
||||
function markSpawnedDeferredLane(
|
||||
|
||||
+134
-13
@@ -128,6 +128,7 @@ import {
|
||||
DidDefer,
|
||||
ShouldSuspendCommit,
|
||||
MaySuspendCommit,
|
||||
ScheduleRetry,
|
||||
} from './ReactFiberFlags';
|
||||
import {
|
||||
NoLanes,
|
||||
@@ -365,8 +366,11 @@ let workInProgressRootInterleavedUpdatedLanes: Lanes = NoLanes;
|
||||
let workInProgressRootRenderPhaseUpdatedLanes: Lanes = NoLanes;
|
||||
// Lanes that were pinged (in an interleaved event) during this render.
|
||||
let workInProgressRootPingedLanes: Lanes = NoLanes;
|
||||
// If this lane scheduled deferred work, this is the lane of the deferred task.
|
||||
// If this render scheduled deferred work, this is the lane of the deferred task.
|
||||
let workInProgressDeferredLane: Lane = NoLane;
|
||||
// Represents the retry lanes that were spawned by this render and have not
|
||||
// been pinged since, implying that they are still suspended.
|
||||
let workInProgressSuspendedRetryLanes: Lanes = NoLanes;
|
||||
// Errors that are thrown during the render phase.
|
||||
let workInProgressRootConcurrentErrors: Array<CapturedValue<mixed>> | null =
|
||||
null;
|
||||
@@ -993,8 +997,6 @@ export function performConcurrentWorkOnRoot(
|
||||
|
||||
// We now have a consistent tree. The next step is either to commit it,
|
||||
// or, if something suspended, wait to commit it after a timeout.
|
||||
root.finishedWork = finishedWork;
|
||||
root.finishedLanes = lanes;
|
||||
finishConcurrentRender(root, exitStatus, finishedWork, lanes);
|
||||
}
|
||||
break;
|
||||
@@ -1138,6 +1140,12 @@ function finishConcurrentRender(
|
||||
}
|
||||
}
|
||||
|
||||
// Only set these if we have a complete tree that is ready to be committed.
|
||||
// We use these fields to determine later whether or not the work should be
|
||||
// discarded for a fresh render attempt.
|
||||
root.finishedWork = finishedWork;
|
||||
root.finishedLanes = lanes;
|
||||
|
||||
if (shouldForceFlushFallbacksInDEV()) {
|
||||
// We're inside an `act` scope. Commit immediately.
|
||||
commitRoot(
|
||||
@@ -1146,6 +1154,8 @@ function finishConcurrentRender(
|
||||
workInProgressTransitions,
|
||||
workInProgressRootDidIncludeRecursiveRenderUpdate,
|
||||
workInProgressDeferredLane,
|
||||
workInProgressRootInterleavedUpdatedLanes,
|
||||
workInProgressSuspendedRetryLanes,
|
||||
);
|
||||
} else {
|
||||
if (
|
||||
@@ -1188,6 +1198,8 @@ function finishConcurrentRender(
|
||||
workInProgressRootDidIncludeRecursiveRenderUpdate,
|
||||
lanes,
|
||||
workInProgressDeferredLane,
|
||||
workInProgressRootInterleavedUpdatedLanes,
|
||||
workInProgressSuspendedRetryLanes,
|
||||
workInProgressRootDidSkipSuspendedSiblings,
|
||||
),
|
||||
msUntilTimeout,
|
||||
@@ -1203,6 +1215,8 @@ function finishConcurrentRender(
|
||||
workInProgressRootDidIncludeRecursiveRenderUpdate,
|
||||
lanes,
|
||||
workInProgressDeferredLane,
|
||||
workInProgressRootInterleavedUpdatedLanes,
|
||||
workInProgressSuspendedRetryLanes,
|
||||
workInProgressRootDidSkipSuspendedSiblings,
|
||||
);
|
||||
}
|
||||
@@ -1216,6 +1230,8 @@ function commitRootWhenReady(
|
||||
didIncludeRenderPhaseUpdate: boolean,
|
||||
lanes: Lanes,
|
||||
spawnedLane: Lane,
|
||||
updatedLanes: Lanes,
|
||||
suspendedRetryLanes: Lanes,
|
||||
didSkipSuspendedSiblings: boolean,
|
||||
) {
|
||||
// TODO: Combine retry throttling with Suspensey commits. Right now they run
|
||||
@@ -1254,6 +1270,9 @@ function commitRootWhenReady(
|
||||
recoverableErrors,
|
||||
transitions,
|
||||
didIncludeRenderPhaseUpdate,
|
||||
spawnedLane,
|
||||
updatedLanes,
|
||||
suspendedRetryLanes,
|
||||
),
|
||||
);
|
||||
markRootSuspended(root, lanes, spawnedLane, didSkipSuspendedSiblings);
|
||||
@@ -1261,13 +1280,15 @@ function commitRootWhenReady(
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, commit immediately.
|
||||
// Otherwise, commit immediately.;
|
||||
commitRoot(
|
||||
root,
|
||||
recoverableErrors,
|
||||
transitions,
|
||||
didIncludeRenderPhaseUpdate,
|
||||
spawnedLane,
|
||||
updatedLanes,
|
||||
suspendedRetryLanes,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1277,7 +1298,13 @@ function isRenderConsistentWithExternalStores(finishedWork: Fiber): boolean {
|
||||
// loop instead of recursion so we can exit early.
|
||||
let node: Fiber = finishedWork;
|
||||
while (true) {
|
||||
if (node.flags & StoreConsistency) {
|
||||
const tag = node.tag;
|
||||
if (
|
||||
(tag === FunctionComponent ||
|
||||
tag === ForwardRef ||
|
||||
tag === SimpleMemoComponent) &&
|
||||
node.flags & StoreConsistency
|
||||
) {
|
||||
const updateQueue: FunctionComponentUpdateQueue | null =
|
||||
(node.updateQueue: any);
|
||||
if (updateQueue !== null) {
|
||||
@@ -1464,6 +1491,8 @@ export function performSyncWorkOnRoot(root: FiberRoot, lanes: Lanes): null {
|
||||
workInProgressTransitions,
|
||||
workInProgressRootDidIncludeRecursiveRenderUpdate,
|
||||
workInProgressDeferredLane,
|
||||
workInProgressRootInterleavedUpdatedLanes,
|
||||
workInProgressSuspendedRetryLanes,
|
||||
);
|
||||
|
||||
// Before exiting, make sure there's a callback scheduled for the next
|
||||
@@ -1691,6 +1720,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
|
||||
workInProgressRootRenderPhaseUpdatedLanes = NoLanes;
|
||||
workInProgressRootPingedLanes = NoLanes;
|
||||
workInProgressDeferredLane = NoLane;
|
||||
workInProgressSuspendedRetryLanes = NoLanes;
|
||||
workInProgressRootConcurrentErrors = null;
|
||||
workInProgressRootRecoverableErrors = null;
|
||||
workInProgressRootDidIncludeRecursiveRenderUpdate = false;
|
||||
@@ -2104,9 +2134,10 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) {
|
||||
}
|
||||
default: {
|
||||
// Unwind then continue with the normal work loop.
|
||||
const reason = workInProgressSuspendedReason;
|
||||
workInProgressSuspendedReason = NotSuspended;
|
||||
workInProgressThrownValue = null;
|
||||
throwAndUnwindWorkLoop(root, unitOfWork, thrownValue);
|
||||
throwAndUnwindWorkLoop(root, unitOfWork, thrownValue, reason);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -2199,6 +2230,14 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
|
||||
workInProgressTransitions = getTransitionsForLanes(root, lanes);
|
||||
resetRenderTimer();
|
||||
prepareFreshStack(root, lanes);
|
||||
} else {
|
||||
// This is a continuation of an existing work-in-progress.
|
||||
//
|
||||
// If we were previously in prerendering mode, check if we received any new
|
||||
// data during an interleaved event.
|
||||
if (workInProgressRootIsPrerendering) {
|
||||
workInProgressRootIsPrerendering = checkIfRootIsPrerendering(root, lanes);
|
||||
}
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
@@ -2226,7 +2265,12 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
|
||||
// Unwind then continue with the normal work loop.
|
||||
workInProgressSuspendedReason = NotSuspended;
|
||||
workInProgressThrownValue = null;
|
||||
throwAndUnwindWorkLoop(root, unitOfWork, thrownValue);
|
||||
throwAndUnwindWorkLoop(
|
||||
root,
|
||||
unitOfWork,
|
||||
thrownValue,
|
||||
SuspendedOnError,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case SuspendedOnData: {
|
||||
@@ -2284,7 +2328,12 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
|
||||
// Otherwise, unwind then continue with the normal work loop.
|
||||
workInProgressSuspendedReason = NotSuspended;
|
||||
workInProgressThrownValue = null;
|
||||
throwAndUnwindWorkLoop(root, unitOfWork, thrownValue);
|
||||
throwAndUnwindWorkLoop(
|
||||
root,
|
||||
unitOfWork,
|
||||
thrownValue,
|
||||
SuspendedAndReadyToContinue,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -2347,7 +2396,12 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
|
||||
// Otherwise, unwind then continue with the normal work loop.
|
||||
workInProgressSuspendedReason = NotSuspended;
|
||||
workInProgressThrownValue = null;
|
||||
throwAndUnwindWorkLoop(root, unitOfWork, thrownValue);
|
||||
throwAndUnwindWorkLoop(
|
||||
root,
|
||||
unitOfWork,
|
||||
thrownValue,
|
||||
SuspendedOnInstanceAndReadyToContinue,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case SuspendedOnDeprecatedThrowPromise: {
|
||||
@@ -2357,7 +2411,12 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
|
||||
// always unwind.
|
||||
workInProgressSuspendedReason = NotSuspended;
|
||||
workInProgressThrownValue = null;
|
||||
throwAndUnwindWorkLoop(root, unitOfWork, thrownValue);
|
||||
throwAndUnwindWorkLoop(
|
||||
root,
|
||||
unitOfWork,
|
||||
thrownValue,
|
||||
SuspendedOnDeprecatedThrowPromise,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case SuspendedOnHydration: {
|
||||
@@ -2611,6 +2670,7 @@ function throwAndUnwindWorkLoop(
|
||||
root: FiberRoot,
|
||||
unitOfWork: Fiber,
|
||||
thrownValue: mixed,
|
||||
suspendedReason: SuspendedReason,
|
||||
) {
|
||||
// This is a fork of performUnitOfWork specifcally for unwinding a fiber
|
||||
// that threw an exception.
|
||||
@@ -2658,16 +2718,43 @@ function throwAndUnwindWorkLoop(
|
||||
// The current algorithm for both hydration and error handling assumes
|
||||
// that the tree is rendered sequentially. So we always skip the siblings.
|
||||
getIsHydrating() ||
|
||||
workInProgressSuspendedReason === SuspendedOnError
|
||||
suspendedReason === SuspendedOnError
|
||||
) {
|
||||
skipSiblings = true;
|
||||
// We intentionally don't set workInProgressRootDidSkipSuspendedSiblings,
|
||||
// because we don't want to trigger another prerender attempt.
|
||||
} else if (!workInProgressRootIsPrerendering) {
|
||||
} else if (
|
||||
// Check whether this is a prerender
|
||||
!workInProgressRootIsPrerendering &&
|
||||
// Offscreen rendering is also a form of speculative rendering
|
||||
!includesSomeLane(workInProgressRootRenderLanes, OffscreenLane)
|
||||
) {
|
||||
// This is not a prerender. Skip the siblings during this render. A
|
||||
// separate prerender will be scheduled for later.
|
||||
skipSiblings = true;
|
||||
workInProgressRootDidSkipSuspendedSiblings = true;
|
||||
|
||||
// Because we're skipping the siblings, schedule an immediate retry of
|
||||
// this boundary.
|
||||
//
|
||||
// The reason we do this is because a prerender is only scheduled when
|
||||
// the root is blocked from committing, i.e. RootSuspendedWithDelay.
|
||||
// When the root is not blocked, as in the case when we render a
|
||||
// fallback, the original lane is considered to be finished, and
|
||||
// therefore no longer in need of being prerendered. However, there's
|
||||
// still a pending retry that will happen once the data streams in.
|
||||
// We should start rendering that even before the data streams in so we
|
||||
// can prerender the siblings.
|
||||
if (
|
||||
suspendedReason === SuspendedOnData ||
|
||||
suspendedReason === SuspendedOnImmediate ||
|
||||
suspendedReason === SuspendedOnDeprecatedThrowPromise
|
||||
) {
|
||||
const boundary = getSuspenseHandler();
|
||||
if (boundary !== null && boundary.tag === SuspenseComponent) {
|
||||
boundary.flags |= ScheduleRetry;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// This is a prerender. Don't skip the siblings.
|
||||
skipSiblings = false;
|
||||
@@ -2688,6 +2775,16 @@ function throwAndUnwindWorkLoop(
|
||||
}
|
||||
}
|
||||
|
||||
export function markSpawnedRetryLane(lane: Lane): void {
|
||||
// Keep track of the retry lanes that were spawned by a fallback during the
|
||||
// current render and were not later pinged. This will represent the lanes
|
||||
// that are known to still be suspended.
|
||||
workInProgressSuspendedRetryLanes = mergeLanes(
|
||||
workInProgressSuspendedRetryLanes,
|
||||
lane,
|
||||
);
|
||||
}
|
||||
|
||||
function panicOnRootError(root: FiberRoot, error: mixed) {
|
||||
// There's no ancestor that can handle this exception. This should never
|
||||
// happen because the root is supposed to capture all errors that weren't
|
||||
@@ -2866,6 +2963,8 @@ function commitRoot(
|
||||
transitions: Array<Transition> | null,
|
||||
didIncludeRenderPhaseUpdate: boolean,
|
||||
spawnedLane: Lane,
|
||||
updatedLanes: Lanes,
|
||||
suspendedRetryLanes: Lanes,
|
||||
) {
|
||||
// TODO: This no longer makes any sense. We already wrap the mutation and
|
||||
// layout phases. Should be able to remove.
|
||||
@@ -2881,6 +2980,8 @@ function commitRoot(
|
||||
didIncludeRenderPhaseUpdate,
|
||||
previousUpdateLanePriority,
|
||||
spawnedLane,
|
||||
updatedLanes,
|
||||
suspendedRetryLanes,
|
||||
);
|
||||
} finally {
|
||||
ReactSharedInternals.T = prevTransition;
|
||||
@@ -2897,6 +2998,8 @@ function commitRootImpl(
|
||||
didIncludeRenderPhaseUpdate: boolean,
|
||||
renderPriorityLevel: EventPriority,
|
||||
spawnedLane: Lane,
|
||||
updatedLanes: Lanes,
|
||||
suspendedRetryLanes: Lanes,
|
||||
) {
|
||||
do {
|
||||
// `flushPassiveEffects` will call `flushSyncUpdateQueue` at the end, which
|
||||
@@ -2973,7 +3076,14 @@ function commitRootImpl(
|
||||
const concurrentlyUpdatedLanes = getConcurrentlyUpdatedLanes();
|
||||
remainingLanes = mergeLanes(remainingLanes, concurrentlyUpdatedLanes);
|
||||
|
||||
markRootFinished(root, remainingLanes, spawnedLane);
|
||||
markRootFinished(
|
||||
root,
|
||||
lanes,
|
||||
remainingLanes,
|
||||
spawnedLane,
|
||||
updatedLanes,
|
||||
suspendedRetryLanes,
|
||||
);
|
||||
|
||||
// Reset this before firing side effects so we can detect recursive updates.
|
||||
didIncludeCommitPhaseUpdate = false;
|
||||
@@ -3679,6 +3789,17 @@ function pingSuspendedRoot(
|
||||
pingedLanes,
|
||||
);
|
||||
}
|
||||
|
||||
// If something pings the work-in-progress render, any work that suspended
|
||||
// up to this point may now be unblocked; in other words, no
|
||||
// longer suspended.
|
||||
//
|
||||
// Unlike the broader check above, we only need do this if the lanes match
|
||||
// exactly. If the lanes don't exactly match, that implies the promise
|
||||
// was created by an older render.
|
||||
if (workInProgressSuspendedRetryLanes === workInProgressRootRenderLanes) {
|
||||
workInProgressSuspendedRetryLanes = NoLanes;
|
||||
}
|
||||
}
|
||||
|
||||
ensureRootIsScheduled(root);
|
||||
|
||||
@@ -240,6 +240,11 @@ describe('Activity StrictMode', () => {
|
||||
'Parent mount',
|
||||
'Parent unmount',
|
||||
'Parent mount',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Child rendered', 'Child suspended']
|
||||
: []),
|
||||
|
||||
'------------------------------',
|
||||
'Child rendered',
|
||||
'Child rendered',
|
||||
|
||||
@@ -187,12 +187,26 @@ describe('DebugTracing', () => {
|
||||
`group: ⚛ render (${DEFAULT_LANE_STRING})`,
|
||||
'log: ⚛ Example suspended',
|
||||
`groupEnd: ⚛ render (${DEFAULT_LANE_STRING})`,
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? [
|
||||
`group: ⚛ render (${RETRY_LANE_STRING})`,
|
||||
'log: ⚛ Example suspended',
|
||||
`groupEnd: ⚛ render (${RETRY_LANE_STRING})`,
|
||||
]
|
||||
: []),
|
||||
]);
|
||||
|
||||
logs.splice(0);
|
||||
|
||||
await act(async () => await resolveFakeSuspensePromise());
|
||||
expect(logs).toEqual(['log: ⚛ Example resolved']);
|
||||
expect(logs).toEqual([
|
||||
'log: ⚛ Example resolved',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['log: ⚛ Example resolved']
|
||||
: []),
|
||||
]);
|
||||
});
|
||||
|
||||
// @gate experimental && build === 'development' && enableDebugTracing && enableCPUSuspense
|
||||
|
||||
@@ -313,13 +313,23 @@ describe('act warnings', () => {
|
||||
act(() => {
|
||||
root.render(<App />);
|
||||
});
|
||||
assertLog(['Suspend! [Async]', 'Loading...']);
|
||||
assertLog([
|
||||
'Suspend! [Async]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [Async]'] : []),
|
||||
]);
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
|
||||
// This is a retry, not a ping, because we already showed a fallback.
|
||||
expect(() => resolveText('Async')).toErrorDev(
|
||||
'A suspended resource finished loading inside a test, but the event ' +
|
||||
'was not wrapped in act(...)',
|
||||
[
|
||||
'A suspended resource finished loading inside a test, but the event ' +
|
||||
'was not wrapped in act(...)',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['not wrapped in act'] : []),
|
||||
],
|
||||
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -109,7 +109,13 @@ describe('ReactBlockingMode', () => {
|
||||
</Suspense>,
|
||||
);
|
||||
|
||||
await waitForAll(['A', 'Suspend! [B]', 'Loading...']);
|
||||
await waitForAll([
|
||||
'A',
|
||||
'Suspend! [B]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['A', 'Suspend! [B]', 'C'] : []),
|
||||
]);
|
||||
// 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.
|
||||
|
||||
+1
-10
@@ -292,16 +292,7 @@ describe('ReactConcurrentErrorRecovery', () => {
|
||||
|
||||
// Because we're still suspended on B, we can't show an error boundary. We
|
||||
// should wait for B to resolve.
|
||||
assertLog([
|
||||
'Error! [A2]',
|
||||
'Oops!',
|
||||
'Suspend! [B2]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Error! [A2]', 'Oops!', 'Suspend! [B2]', 'Loading...']
|
||||
: []),
|
||||
]);
|
||||
assertLog(['Error! [A2]', 'Oops!', 'Suspend! [B2]', 'Loading...']);
|
||||
// Remain on previous screen.
|
||||
expect(root).toMatchRenderedOutput('A1B1');
|
||||
|
||||
|
||||
@@ -399,7 +399,13 @@ describe('ReactLazyContextPropagation', () => {
|
||||
// the fallback displays despite this being a refresh.
|
||||
setContext('B');
|
||||
});
|
||||
assertLog(['Suspend! [B]', 'Loading...', 'B']);
|
||||
assertLog([
|
||||
'Suspend! [B]',
|
||||
'Loading...',
|
||||
'B',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [B]'] : []),
|
||||
]);
|
||||
expect(root).toMatchRenderedOutput('Loading...B');
|
||||
|
||||
await act(async () => {
|
||||
@@ -479,7 +485,13 @@ describe('ReactLazyContextPropagation', () => {
|
||||
// the fallback displays despite this being a refresh.
|
||||
setContext('B');
|
||||
});
|
||||
assertLog(['Suspend! [B]', 'Loading...', 'B']);
|
||||
assertLog([
|
||||
'Suspend! [B]',
|
||||
'Loading...',
|
||||
'B',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [B]'] : []),
|
||||
]);
|
||||
expect(root).toMatchRenderedOutput('Loading...B');
|
||||
|
||||
await act(async () => {
|
||||
@@ -812,7 +824,12 @@ describe('ReactLazyContextPropagation', () => {
|
||||
await act(() => {
|
||||
setContext('B');
|
||||
});
|
||||
assertLog(['Suspend! [B]', 'Loading...']);
|
||||
assertLog([
|
||||
'Suspend! [B]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [B]'] : []),
|
||||
]);
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
|
||||
await act(async () => {
|
||||
|
||||
@@ -499,6 +499,8 @@ describe('ReactDeferredValue', () => {
|
||||
// The initial value suspended, so we attempt the final value, which
|
||||
// also suspends.
|
||||
'Suspend! [Final]',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [Final]'] : []),
|
||||
]);
|
||||
expect(root).toMatchRenderedOutput('Fallback');
|
||||
|
||||
@@ -630,6 +632,8 @@ describe('ReactDeferredValue', () => {
|
||||
// go straight to attempting the final value.
|
||||
'Suspend! [Content]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [Content]'] : []),
|
||||
]);
|
||||
// The content suspended, so we show a Suspense fallback
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
|
||||
@@ -1864,13 +1864,15 @@ describe('ReactHooks', () => {
|
||||
it('does not fire a false positive warning when suspending memo', async () => {
|
||||
const {Suspense, useState} = React;
|
||||
|
||||
let wasSuspended = false;
|
||||
let isSuspended = true;
|
||||
let resolve;
|
||||
function trySuspend() {
|
||||
if (!wasSuspended) {
|
||||
throw new Promise(r => {
|
||||
wasSuspended = true;
|
||||
resolve = r;
|
||||
if (isSuspended) {
|
||||
throw new Promise(res => {
|
||||
resolve = () => {
|
||||
isSuspended = false;
|
||||
res();
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1900,13 +1902,15 @@ describe('ReactHooks', () => {
|
||||
it('does not fire a false positive warning when suspending forwardRef', async () => {
|
||||
const {Suspense, useState} = React;
|
||||
|
||||
let wasSuspended = false;
|
||||
let isSuspended = true;
|
||||
let resolve;
|
||||
function trySuspend() {
|
||||
if (!wasSuspended) {
|
||||
throw new Promise(r => {
|
||||
wasSuspended = true;
|
||||
resolve = r;
|
||||
if (isSuspended) {
|
||||
throw new Promise(res => {
|
||||
resolve = () => {
|
||||
isSuspended = false;
|
||||
res();
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1936,13 +1940,15 @@ describe('ReactHooks', () => {
|
||||
it('does not fire a false positive warning when suspending memo(forwardRef)', async () => {
|
||||
const {Suspense, useState} = React;
|
||||
|
||||
let wasSuspended = false;
|
||||
let isSuspended = true;
|
||||
let resolve;
|
||||
function trySuspend() {
|
||||
if (!wasSuspended) {
|
||||
throw new Promise(r => {
|
||||
wasSuspended = true;
|
||||
resolve = r;
|
||||
if (isSuspended) {
|
||||
throw new Promise(res => {
|
||||
resolve = () => {
|
||||
isSuspended = false;
|
||||
res();
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3544,7 +3544,13 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||
ReactNoop.render(<App />);
|
||||
});
|
||||
|
||||
assertLog(['A', 'Suspend! [A]', 'Loading']);
|
||||
assertLog([
|
||||
'A',
|
||||
'Suspend! [A]',
|
||||
'Loading',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [A]'] : []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="A" />
|
||||
@@ -4201,13 +4207,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||
await act(async () => {
|
||||
await resolveText('A');
|
||||
});
|
||||
assertLog([
|
||||
'Promise resolved [A]',
|
||||
'A',
|
||||
'Suspend! [B]',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['A', 'Suspend! [B]'] : []),
|
||||
]);
|
||||
assertLog(['Promise resolved [A]', 'A', 'Suspend! [B]']);
|
||||
|
||||
await act(() => {
|
||||
root.render(null);
|
||||
|
||||
@@ -198,11 +198,7 @@ describe('ReactLazy', () => {
|
||||
|
||||
await resolveFakeImport(Foo);
|
||||
|
||||
await waitForAll([
|
||||
'Foo',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Foo'] : []),
|
||||
]);
|
||||
await waitForAll(['Foo']);
|
||||
expect(root).not.toMatchRenderedOutput('FooBar');
|
||||
|
||||
await act(() => resolveFakeImport(Bar));
|
||||
@@ -239,13 +235,6 @@ describe('ReactLazy', () => {
|
||||
assertConsoleErrorDev([
|
||||
'Expected the result of a dynamic import() call',
|
||||
'Expected the result of a dynamic import() call',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? [
|
||||
'Expected the result of a dynamic import() call',
|
||||
'Expected the result of a dynamic import() call',
|
||||
]
|
||||
: []),
|
||||
]);
|
||||
expect(root).not.toMatchRenderedOutput('Hi');
|
||||
});
|
||||
@@ -320,7 +309,12 @@ describe('ReactLazy', () => {
|
||||
unstable_isConcurrent: true,
|
||||
});
|
||||
|
||||
await waitForAll(['Suspend! [LazyChildA]', 'Loading...']);
|
||||
await waitForAll([
|
||||
'Suspend! [LazyChildA]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [LazyChildB]'] : []),
|
||||
]);
|
||||
expect(root).not.toMatchRenderedOutput('AB');
|
||||
|
||||
await act(async () => {
|
||||
@@ -329,9 +323,23 @@ describe('ReactLazy', () => {
|
||||
// B suspends even though it happens to share the same import as A.
|
||||
// TODO: React.lazy should implement the `status` and `value` fields, so
|
||||
// we can unwrap the result synchronously if it already loaded. Like `use`.
|
||||
await waitFor(['A', 'Suspend! [LazyChildB]']);
|
||||
await waitFor([
|
||||
'A',
|
||||
|
||||
// When enableSiblingPrerendering is on, LazyChildB was already
|
||||
// initialized. So it also already resolved when we called
|
||||
// resolveFakeImport above. So it doesn't suspend again.
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['B']
|
||||
: ['Suspend! [LazyChildB]']),
|
||||
]);
|
||||
});
|
||||
assertLog(['A', 'B', 'Did mount: A', 'Did mount: B']);
|
||||
assertLog([
|
||||
...(gate('enableSiblingPrerendering') ? [] : ['A', 'B']),
|
||||
|
||||
'Did mount: A',
|
||||
'Did mount: B',
|
||||
]);
|
||||
expect(root).toMatchRenderedOutput('AB');
|
||||
|
||||
// Swap the position of A and B
|
||||
@@ -1395,15 +1403,20 @@ describe('ReactLazy', () => {
|
||||
unstable_isConcurrent: true,
|
||||
});
|
||||
|
||||
await waitForAll(['Init A', 'Loading...']);
|
||||
await waitForAll([
|
||||
'Init A',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Init B'] : []),
|
||||
]);
|
||||
expect(root).not.toMatchRenderedOutput('AB');
|
||||
|
||||
await act(() => resolveFakeImport(ChildA));
|
||||
assertLog([
|
||||
'A',
|
||||
'Init B',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['A'] : []),
|
||||
// When enableSiblingPrerendering is on, B was already initialized.
|
||||
...(gate('enableSiblingPrerendering') ? ['A'] : ['Init B']),
|
||||
]);
|
||||
|
||||
await act(() => resolveFakeImport(ChildB));
|
||||
|
||||
@@ -0,0 +1,472 @@
|
||||
let React;
|
||||
let ReactNoop;
|
||||
let Scheduler;
|
||||
let act;
|
||||
let assertLog;
|
||||
let waitFor;
|
||||
let waitForPaint;
|
||||
let waitForAll;
|
||||
let textCache;
|
||||
let startTransition;
|
||||
let Suspense;
|
||||
let Activity;
|
||||
|
||||
describe('ReactSiblingPrerendering', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
|
||||
React = require('react');
|
||||
ReactNoop = require('react-noop-renderer');
|
||||
Scheduler = require('scheduler');
|
||||
act = require('internal-test-utils').act;
|
||||
assertLog = require('internal-test-utils').assertLog;
|
||||
waitFor = require('internal-test-utils').waitFor;
|
||||
waitForPaint = require('internal-test-utils').waitForPaint;
|
||||
waitForAll = require('internal-test-utils').waitForAll;
|
||||
startTransition = React.startTransition;
|
||||
Suspense = React.Suspense;
|
||||
Activity = React.unstable_Activity;
|
||||
|
||||
textCache = new Map();
|
||||
});
|
||||
|
||||
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 getText(text) {
|
||||
// const record = textCache.get(text);
|
||||
// if (record === undefined) {
|
||||
// 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);
|
||||
// return thenable;
|
||||
// } else {
|
||||
// switch (record.status) {
|
||||
// case 'pending':
|
||||
// return record.value;
|
||||
// case 'rejected':
|
||||
// return Promise.reject(record.value);
|
||||
// case 'resolved':
|
||||
// return Promise.resolve(record.value);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
function Text({text}) {
|
||||
Scheduler.log(text);
|
||||
return text;
|
||||
}
|
||||
|
||||
function AsyncText({text}) {
|
||||
readText(text);
|
||||
Scheduler.log(text);
|
||||
return text;
|
||||
}
|
||||
|
||||
it("don't prerender siblings when something errors", async () => {
|
||||
class ErrorBoundary extends React.Component {
|
||||
state = {error: null};
|
||||
static getDerivedStateFromError(error) {
|
||||
return {error};
|
||||
}
|
||||
render() {
|
||||
if (this.state.error) {
|
||||
return <Text text={this.state.error.message} />;
|
||||
}
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
function Oops() {
|
||||
throw new Error('Oops!');
|
||||
}
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<ErrorBoundary>
|
||||
<Oops />
|
||||
<AsyncText text="A" />
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
<div>
|
||||
<AsyncText text="B" />
|
||||
<AsyncText text="C" />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const root = ReactNoop.createRoot();
|
||||
await act(() => startTransition(() => root.render(<App />)));
|
||||
assertLog([
|
||||
'Oops!',
|
||||
|
||||
// A is skipped because we don't prerender siblings when
|
||||
// something errors.
|
||||
|
||||
'Suspend! [B]',
|
||||
|
||||
// After B suspends, we're still able to prerender C without starting
|
||||
// over because there's no fallback, so the root is blocked from
|
||||
// committing anyway.
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [C]'] : []),
|
||||
]);
|
||||
});
|
||||
|
||||
// @gate enableActivity
|
||||
it("don't skip siblings when rendering inside a hidden tree", async () => {
|
||||
function App() {
|
||||
return (
|
||||
<>
|
||||
<Text text="A" />
|
||||
<Activity mode="hidden">
|
||||
<Suspense fallback={<Text text="Loading..." />}>
|
||||
<AsyncText text="B" />
|
||||
<AsyncText text="C" />
|
||||
</Suspense>
|
||||
</Activity>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const root = ReactNoop.createRoot();
|
||||
await act(async () => {
|
||||
startTransition(async () => root.render(<App />));
|
||||
|
||||
// The first render includes only the visible part of the tree. The
|
||||
// hidden content is deferred until later.
|
||||
await waitForPaint(['A']);
|
||||
expect(root).toMatchRenderedOutput('A');
|
||||
|
||||
// The second render is a prerender of the hidden content.
|
||||
await waitForPaint([
|
||||
'Suspend! [B]',
|
||||
|
||||
// If B and C were visible, C would not have been attempted
|
||||
// during this pass, because it would prevented the fallback
|
||||
// from showing.
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [C]'] : []),
|
||||
|
||||
'Loading...',
|
||||
]);
|
||||
expect(root).toMatchRenderedOutput('A');
|
||||
});
|
||||
});
|
||||
|
||||
it('start prerendering retries right after the fallback commits', async () => {
|
||||
function App() {
|
||||
return (
|
||||
<Suspense fallback={<Text text="Loading..." />}>
|
||||
<AsyncText text="A" />
|
||||
<AsyncText text="B" />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
const root = ReactNoop.createRoot();
|
||||
await act(async () => {
|
||||
startTransition(() => root.render(<App />));
|
||||
|
||||
// On the first attempt, A suspends. Unwind and show a fallback, without
|
||||
// attempting B.
|
||||
await waitForPaint(['Suspend! [A]', 'Loading...']);
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
|
||||
// Immediately after the fallback commits, retry the boundary again. This
|
||||
// time we include B, since we're not blocking the fallback from showing.
|
||||
if (gate('enableSiblingPrerendering')) {
|
||||
await waitForPaint(['Suspend! [A]', 'Suspend! [B]']);
|
||||
}
|
||||
});
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
});
|
||||
|
||||
it('switch back to normal rendering mode if a ping occurs during prerendering', async () => {
|
||||
function App() {
|
||||
return (
|
||||
<div>
|
||||
<Suspense fallback={<Text text="Loading outer..." />}>
|
||||
<div>
|
||||
<Text text="A" />
|
||||
<AsyncText text="B" />
|
||||
</div>
|
||||
<div>
|
||||
<Suspense fallback={<Text text="Loading inner..." />}>
|
||||
<AsyncText text="C" />
|
||||
<AsyncText text="D" />
|
||||
</Suspense>
|
||||
</div>
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const root = ReactNoop.createRoot();
|
||||
await act(async () => {
|
||||
startTransition(() => root.render(<App />));
|
||||
|
||||
// On the first attempt, B suspends. Unwind and show a fallback, without
|
||||
// attempting the siblings.
|
||||
await waitForPaint(['A', 'Suspend! [B]', 'Loading outer...']);
|
||||
expect(root).toMatchRenderedOutput(<div>Loading outer...</div>);
|
||||
|
||||
// Now that the fallback is visible, we can prerender the siblings. Start
|
||||
// prerendering, then yield to simulate an interleaved event.
|
||||
if (gate('enableSiblingPrerendering')) {
|
||||
await waitFor(['A']);
|
||||
} else {
|
||||
await waitForAll([]);
|
||||
}
|
||||
|
||||
// To avoid the Suspense throttling mechanism, let's pretend there's been
|
||||
// more than a Just Noticeable Difference since we rendered the
|
||||
// outer fallback.
|
||||
Scheduler.unstable_advanceTime(500);
|
||||
|
||||
// During the render phase, but before we get to B again, resolve its
|
||||
// promise. We should re-enter normal rendering mode, but we also
|
||||
// shouldn't unwind and lose our work-in-progress.
|
||||
await resolveText('B');
|
||||
await waitForPaint([
|
||||
// When sibling prerendering is not enabled, we weren't already rendering
|
||||
// when the data for B came in, so A doesn't get rendered until now.
|
||||
...(gate('enableSiblingPrerendering') ? [] : ['A']),
|
||||
|
||||
'B',
|
||||
'Suspend! [C]',
|
||||
|
||||
// If we were still in prerendering mode, then we would have attempted
|
||||
// to render D here. But since we received new data, we will skip the
|
||||
// remaining siblings to unblock the inner fallback.
|
||||
'Loading inner...',
|
||||
]);
|
||||
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<div>
|
||||
<div>AB</div>
|
||||
<div>Loading inner...</div>
|
||||
</div>,
|
||||
);
|
||||
});
|
||||
|
||||
// Now that the inner fallback is showing, we can prerender the rest of
|
||||
// the tree.
|
||||
assertLog(
|
||||
gate('enableSiblingPrerendering')
|
||||
? [
|
||||
// NOTE: C renders twice instead of once because when B resolved, it
|
||||
// was treated like a retry update, not just a ping. So first it
|
||||
// regular renders, then it prerenders. TODO: We should be able to
|
||||
// optimize this by detecting inside the retry listener that the
|
||||
// outer boundary is no longer suspended, and therefore doesn't need
|
||||
// to be updated.
|
||||
'Suspend! [C]',
|
||||
|
||||
// Now we're in prerender mode, so D is incuded in this attempt.
|
||||
'Suspend! [C]',
|
||||
'Suspend! [D]',
|
||||
]
|
||||
: [],
|
||||
);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<div>
|
||||
<div>AB</div>
|
||||
<div>Loading inner...</div>
|
||||
</div>,
|
||||
);
|
||||
});
|
||||
|
||||
it("don't throw out completed work in order to prerender", async () => {
|
||||
function App() {
|
||||
return (
|
||||
<div>
|
||||
<Suspense fallback={<Text text="Loading outer..." />}>
|
||||
<div>
|
||||
<AsyncText text="A" />
|
||||
</div>
|
||||
<div>
|
||||
<Suspense fallback={<Text text="Loading inner..." />}>
|
||||
<AsyncText text="B" />
|
||||
</Suspense>
|
||||
</div>
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const root = ReactNoop.createRoot();
|
||||
await act(async () => {
|
||||
startTransition(() => root.render(<App />));
|
||||
|
||||
await waitForPaint(['Suspend! [A]', 'Loading outer...']);
|
||||
expect(root).toMatchRenderedOutput(<div>Loading outer...</div>);
|
||||
|
||||
// Before the prerendering of the inner boundary starts, the data for A
|
||||
// resolves, so we try rendering that again.
|
||||
await resolveText('A');
|
||||
// This produces a new tree that we can show. However, the commit phase
|
||||
// is throttled because it's been less than a Just Noticeable Difference
|
||||
// since the outer fallback was committed.
|
||||
//
|
||||
// In the meantime, we could choose to start prerendering B, but instead
|
||||
// we wait for a JND to elapse and the commit to finish — it's not
|
||||
// worth discarding the work we've already done.
|
||||
await waitForAll(['A', 'Suspend! [B]', 'Loading inner...']);
|
||||
expect(root).toMatchRenderedOutput(<div>Loading outer...</div>);
|
||||
|
||||
// Fire the timer to commit the outer fallback.
|
||||
jest.runAllTimers();
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<div>
|
||||
<div>A</div>
|
||||
<div>Loading inner...</div>
|
||||
</div>,
|
||||
);
|
||||
});
|
||||
// Once the outer fallback is committed, we can start prerendering B.
|
||||
assertLog(gate('enableSiblingPrerendering') ? ['Suspend! [B]'] : []);
|
||||
});
|
||||
|
||||
it(
|
||||
"don't skip siblings during the retry if there was a ping since the " +
|
||||
'first attempt',
|
||||
async () => {
|
||||
function App() {
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<Suspense fallback={<Text text="Loading outer..." />}>
|
||||
<div>
|
||||
<AsyncText text="A" />
|
||||
</div>
|
||||
<div>
|
||||
<Suspense fallback={<Text text="Loading inner..." />}>
|
||||
<AsyncText text="B" />
|
||||
<AsyncText text="C" />
|
||||
</Suspense>
|
||||
</div>
|
||||
</Suspense>
|
||||
</div>
|
||||
<div>
|
||||
<Text text="D" />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const root = ReactNoop.createRoot();
|
||||
await act(async () => {
|
||||
startTransition(() => root.render(<App />));
|
||||
|
||||
// On the first attempt, A suspends. Unwind and show a fallback, without
|
||||
// attempting B or C.
|
||||
await waitFor([
|
||||
'Suspend! [A]',
|
||||
'Loading outer...',
|
||||
|
||||
// Yield to simulate an interleaved event
|
||||
]);
|
||||
|
||||
// Ping the promise for A before the render phase has finished, as might
|
||||
// happen in an interleaved network event
|
||||
await resolveText('A');
|
||||
|
||||
// Now continue rendering the rest of the tree.
|
||||
await waitForPaint(['D']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<div>Loading outer...</div>
|
||||
<div>D</div>
|
||||
</>,
|
||||
);
|
||||
|
||||
// Immediately after the fallback commits, retry the boundary again.
|
||||
// Because the promise for A resolved, this is a normal render, _not_
|
||||
// a prerender. So when we proceed to B, and B suspends, we unwind again
|
||||
// without attempting C. The practical benefit of this is that we don't
|
||||
// block the inner Suspense fallback from appearing.
|
||||
await waitForPaint(['A', 'Suspend! [B]', 'Loading inner...']);
|
||||
// (Since this is a retry, the commit phase is throttled by a timer.)
|
||||
jest.runAllTimers();
|
||||
// The inner fallback is now visible.
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<div>
|
||||
<div>A</div>
|
||||
<div>Loading inner...</div>
|
||||
</div>
|
||||
<div>D</div>
|
||||
</>,
|
||||
);
|
||||
|
||||
// Now we can proceed to prerendering C.
|
||||
if (gate('enableSiblingPrerendering')) {
|
||||
await waitForPaint(['Suspend! [B]', 'Suspend! [C]']);
|
||||
}
|
||||
});
|
||||
assertLog([]);
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -169,13 +169,23 @@ describe('ReactSuspense', () => {
|
||||
'Loading A...',
|
||||
'Suspend! [B]',
|
||||
'Loading B...',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Suspend! [A]', 'Suspend! [B]']
|
||||
: []),
|
||||
]);
|
||||
expect(container.innerHTML).toEqual('Loading A...Loading B...');
|
||||
|
||||
// Resolve first Suspense's promise and switch back to the normal view. The
|
||||
// second Suspense should still show the placeholder
|
||||
await act(() => resolveText('A'));
|
||||
assertLog(['A']);
|
||||
assertLog([
|
||||
'A',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Suspend! [B]', 'Suspend! [B]']
|
||||
: []),
|
||||
]);
|
||||
expect(container.textContent).toEqual('ALoading B...');
|
||||
|
||||
// Resolve the second Suspense's promise resolves and switche back to the
|
||||
@@ -274,19 +284,19 @@ describe('ReactSuspense', () => {
|
||||
root.render(<Foo />);
|
||||
});
|
||||
|
||||
assertLog(['Foo', 'Suspend! [A]', 'Loading...']);
|
||||
assertLog([
|
||||
'Foo',
|
||||
'Suspend! [A]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Suspend! [A]', 'Suspend! [B]', 'Loading more...']
|
||||
: []),
|
||||
]);
|
||||
expect(container.textContent).toEqual('Loading...');
|
||||
|
||||
await resolveText('A');
|
||||
await waitForAll([
|
||||
'A',
|
||||
'Suspend! [B]',
|
||||
'Loading more...',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['A', 'Suspend! [B]', 'Loading more...']
|
||||
: []),
|
||||
]);
|
||||
await waitForAll(['A', 'Suspend! [B]', 'Loading more...']);
|
||||
|
||||
// By this point, we have enough info to show "A" and "Loading more..."
|
||||
// However, we've just shown the outer fallback. So we'll delay
|
||||
@@ -327,7 +337,15 @@ describe('ReactSuspense', () => {
|
||||
// Render an empty shell
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
root.render(<Foo />);
|
||||
await waitForAll(['Foo', 'Suspend! [A]', 'Loading...']);
|
||||
await waitForAll([
|
||||
'Foo',
|
||||
'Suspend! [A]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Suspend! [A]', 'Suspend! [B]', 'Loading more...']
|
||||
: []),
|
||||
]);
|
||||
expect(container.textContent).toEqual('Loading...');
|
||||
|
||||
// Now resolve A
|
||||
@@ -338,14 +356,7 @@ describe('ReactSuspense', () => {
|
||||
// B starts loading. Parent boundary is in throttle.
|
||||
// Still shows parent loading under throttle
|
||||
jest.advanceTimersByTime(10);
|
||||
await waitForAll([
|
||||
'Suspend! [B]',
|
||||
'Loading more...',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['A', 'Suspend! [B]', 'Loading more...']
|
||||
: []),
|
||||
]);
|
||||
await waitForAll(['Suspend! [B]', 'Loading more...']);
|
||||
expect(container.textContent).toEqual('Loading...');
|
||||
|
||||
// !! B could have finished before the throttle, but we show a fallback.
|
||||
@@ -375,19 +386,19 @@ describe('ReactSuspense', () => {
|
||||
await act(() => {
|
||||
root.render(<Foo />);
|
||||
});
|
||||
assertLog(['Foo', 'Suspend! [A]', 'Loading...']);
|
||||
assertLog([
|
||||
'Foo',
|
||||
'Suspend! [A]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Suspend! [A]', 'Suspend! [B]', 'Loading more...']
|
||||
: []),
|
||||
]);
|
||||
expect(container.textContent).toEqual('Loading...');
|
||||
|
||||
await resolveText('A');
|
||||
await waitForAll([
|
||||
'A',
|
||||
'Suspend! [B]',
|
||||
'Loading more...',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['A', 'Suspend! [B]', 'Loading more...']
|
||||
: []),
|
||||
]);
|
||||
await waitForAll(['A', 'Suspend! [B]', 'Loading more...']);
|
||||
|
||||
// By this point, we have enough info to show "A" and "Loading more..."
|
||||
// However, we've just shown the outer fallback. So we'll delay
|
||||
@@ -468,14 +479,24 @@ describe('ReactSuspense', () => {
|
||||
await act(() => {
|
||||
root.render(<App />);
|
||||
});
|
||||
assertLog(['Suspend! [default]', 'Loading...']);
|
||||
assertLog([
|
||||
'Suspend! [default]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [default]'] : []),
|
||||
]);
|
||||
|
||||
await act(() => resolveText('default'));
|
||||
assertLog(['default']);
|
||||
expect(container.textContent).toEqual('default');
|
||||
|
||||
await act(() => setValue('new value'));
|
||||
assertLog(['Suspend! [new value]', 'Loading...']);
|
||||
assertLog([
|
||||
'Suspend! [new value]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [new value]'] : []),
|
||||
]);
|
||||
|
||||
await act(() => resolveText('new value'));
|
||||
assertLog(['new value']);
|
||||
@@ -515,14 +536,24 @@ describe('ReactSuspense', () => {
|
||||
await act(() => {
|
||||
root.render(<App />);
|
||||
});
|
||||
assertLog(['Suspend! [default]', 'Loading...']);
|
||||
assertLog([
|
||||
'Suspend! [default]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [default]'] : []),
|
||||
]);
|
||||
|
||||
await act(() => resolveText('default'));
|
||||
assertLog(['default']);
|
||||
expect(container.textContent).toEqual('default');
|
||||
|
||||
await act(() => setValue('new value'));
|
||||
assertLog(['Suspend! [new value]', 'Loading...']);
|
||||
assertLog([
|
||||
'Suspend! [new value]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [new value]'] : []),
|
||||
]);
|
||||
|
||||
await act(() => resolveText('new value'));
|
||||
assertLog(['new value']);
|
||||
@@ -559,14 +590,24 @@ describe('ReactSuspense', () => {
|
||||
</App>,
|
||||
);
|
||||
});
|
||||
assertLog(['Suspend! [default]', 'Loading...']);
|
||||
assertLog([
|
||||
'Suspend! [default]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [default]'] : []),
|
||||
]);
|
||||
|
||||
await act(() => resolveText('default'));
|
||||
assertLog(['default']);
|
||||
expect(container.textContent).toEqual('default');
|
||||
|
||||
await act(() => setValue('new value'));
|
||||
assertLog(['Suspend! [new value]', 'Loading...']);
|
||||
assertLog([
|
||||
'Suspend! [new value]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [new value]'] : []),
|
||||
]);
|
||||
|
||||
await act(() => resolveText('new value'));
|
||||
assertLog(['new value']);
|
||||
@@ -603,14 +644,24 @@ describe('ReactSuspense', () => {
|
||||
</App>,
|
||||
);
|
||||
});
|
||||
assertLog(['Suspend! [default]', 'Loading...']);
|
||||
assertLog([
|
||||
'Suspend! [default]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [default]'] : []),
|
||||
]);
|
||||
|
||||
await act(() => resolveText('default'));
|
||||
assertLog(['default']);
|
||||
expect(container.textContent).toEqual('default');
|
||||
|
||||
await act(() => setValue('new value'));
|
||||
assertLog(['Suspend! [new value]', 'Loading...']);
|
||||
assertLog([
|
||||
'Suspend! [new value]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [new value]'] : []),
|
||||
]);
|
||||
|
||||
await act(() => resolveText('new value'));
|
||||
assertLog(['new value']);
|
||||
@@ -657,6 +708,10 @@ describe('ReactSuspense', () => {
|
||||
'Suspend! [Child 2]',
|
||||
'Loading...',
|
||||
'destroy layout',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Child 1', 'Suspend! [Child 2]']
|
||||
: []),
|
||||
]);
|
||||
|
||||
await act(() => resolveText('Child 2'));
|
||||
@@ -679,16 +734,16 @@ describe('ReactSuspense', () => {
|
||||
root.render(<App />);
|
||||
});
|
||||
|
||||
assertLog(['Suspend! [Child 1]', 'Loading...']);
|
||||
await resolveText('Child 1');
|
||||
await waitForAll([
|
||||
'Child 1',
|
||||
'Suspend! [Child 2]',
|
||||
assertLog([
|
||||
'Suspend! [Child 1]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Child 1', 'Suspend! [Child 2]']
|
||||
? ['Suspend! [Child 1]', 'Suspend! [Child 2]']
|
||||
: []),
|
||||
]);
|
||||
await resolveText('Child 1');
|
||||
await waitForAll(['Child 1', 'Suspend! [Child 2]']);
|
||||
|
||||
jest.advanceTimersByTime(6000);
|
||||
|
||||
|
||||
@@ -136,7 +136,11 @@ describe('ReactSuspense', () => {
|
||||
ReactNoop.render(element);
|
||||
await waitForAll([]);
|
||||
expect(ReactNoop).toMatchRenderedOutput('Waiting Tier 1');
|
||||
expect(ops).toEqual([new Set([promise2])]);
|
||||
expect(ops).toEqual([
|
||||
new Set([promise2]),
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? new Set([promise2]) : []),
|
||||
]);
|
||||
ops = [];
|
||||
|
||||
await act(() => resolve2());
|
||||
@@ -224,7 +228,11 @@ describe('ReactSuspense', () => {
|
||||
await act(() => resolve1());
|
||||
expect(ReactNoop).toMatchRenderedOutput('Waiting Tier 2Done');
|
||||
expect(ops1).toEqual([]);
|
||||
expect(ops2).toEqual([new Set([promise2])]);
|
||||
expect(ops2).toEqual([
|
||||
new Set([promise2]),
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? new Set([promise2]) : []),
|
||||
]);
|
||||
ops1 = [];
|
||||
ops2 = [];
|
||||
|
||||
|
||||
+193
-15
@@ -274,6 +274,14 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
'Text:Fallback create passive',
|
||||
'Text:Outside create passive',
|
||||
'App create passive',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? [
|
||||
'Text:Inside:Before render',
|
||||
'Suspend:Async',
|
||||
'ClassText:Inside:After render',
|
||||
]
|
||||
: []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
@@ -646,7 +654,17 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
'Text:Inside:After destroy layout',
|
||||
'Text:Fallback create layout',
|
||||
]);
|
||||
await waitForAll(['Text:Fallback create passive']);
|
||||
await waitForAll([
|
||||
'Text:Fallback create passive',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? [
|
||||
'Text:Inside:Before render',
|
||||
'Suspend:Async',
|
||||
'Text:Inside:After render',
|
||||
]
|
||||
: []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Inside:Before" hidden={true} />
|
||||
@@ -797,6 +815,13 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
</>,
|
||||
);
|
||||
});
|
||||
if (gate('enableSiblingPrerendering')) {
|
||||
assertLog([
|
||||
'ClassText:Inside:Before render',
|
||||
'Suspend:Async',
|
||||
'ClassText:Inside:After render',
|
||||
]);
|
||||
}
|
||||
|
||||
// Resolving the suspended resource should re-create inner layout effects.
|
||||
await act(async () => {
|
||||
@@ -896,7 +921,13 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
'Text:Inner destroy layout',
|
||||
'Text:Fallback create layout',
|
||||
]);
|
||||
await waitForAll(['Text:Fallback create passive']);
|
||||
await waitForAll([
|
||||
'Text:Fallback create passive',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Suspend:Async', 'Text:Outer render', 'Text:Inner render']
|
||||
: []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span hidden={true} prop="Outer">
|
||||
@@ -1011,7 +1042,13 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
'Text:MemoizedInner destroy layout',
|
||||
'Text:Fallback create layout',
|
||||
]);
|
||||
await waitForAll(['Text:Fallback create passive']);
|
||||
await waitForAll([
|
||||
'Text:Fallback create passive',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Suspend:Async', 'Text:Outer render']
|
||||
: []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span hidden={true} prop="Outer">
|
||||
@@ -1108,6 +1145,10 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
'Text:Inner destroy layout',
|
||||
'Text:InnerFallback create layout',
|
||||
'Text:InnerFallback create passive',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Text:Inner render', 'Suspend:InnerAsync_1']
|
||||
: []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
@@ -1136,6 +1177,16 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
'Text:InnerFallback destroy layout',
|
||||
'Text:OuterFallback create layout',
|
||||
'Text:OuterFallback create passive',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? [
|
||||
'Text:Outer render',
|
||||
'Suspend:OuterAsync_1',
|
||||
'Text:Inner render',
|
||||
'Suspend:InnerAsync_1',
|
||||
'Text:InnerFallback render',
|
||||
]
|
||||
: []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
@@ -1186,6 +1237,16 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
'Text:Outer render',
|
||||
'Suspend:OuterAsync_1',
|
||||
'Text:OuterFallback render',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? [
|
||||
'Text:Outer render',
|
||||
'Suspend:OuterAsync_1',
|
||||
'Text:Inner render',
|
||||
'Suspend:InnerAsync_2',
|
||||
'Text:InnerFallback render',
|
||||
]
|
||||
: []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
@@ -1207,22 +1268,16 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
'Suspend:InnerAsync_2',
|
||||
'Text:InnerFallback render',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? [
|
||||
'Text:Outer render',
|
||||
'AsyncText:OuterAsync_1 render',
|
||||
'Text:Inner render',
|
||||
'Suspend:InnerAsync_2',
|
||||
'Text:InnerFallback render',
|
||||
]
|
||||
: []),
|
||||
|
||||
'Text:OuterFallback destroy layout',
|
||||
'Text:Outer create layout',
|
||||
'AsyncText:OuterAsync_1 create layout',
|
||||
'Text:InnerFallback create layout',
|
||||
'Text:OuterFallback destroy passive',
|
||||
'AsyncText:OuterAsync_1 create passive',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Text:Inner render', 'Suspend:InnerAsync_2']
|
||||
: []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
@@ -1274,6 +1329,15 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
'AsyncText:InnerAsync_2 destroy layout',
|
||||
'Text:OuterFallback create layout',
|
||||
'Text:OuterFallback create passive',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? [
|
||||
'Text:Outer render',
|
||||
'Suspend:OuterAsync_2',
|
||||
'Text:Inner render',
|
||||
'AsyncText:InnerAsync_2 render',
|
||||
]
|
||||
: []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
@@ -1359,6 +1423,10 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
'Text:Inner destroy layout',
|
||||
'Text:InnerFallback create layout',
|
||||
'Text:InnerFallback create passive',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Text:Inner render', 'Suspend:InnerAsync_1']
|
||||
: []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
@@ -1386,6 +1454,16 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
'Text:InnerFallback destroy layout',
|
||||
'Text:OuterFallback create layout',
|
||||
'Text:OuterFallback create passive',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? [
|
||||
'Text:Outer render',
|
||||
'Suspend:OuterAsync_1',
|
||||
'Text:Inner render',
|
||||
'Suspend:InnerAsync_1',
|
||||
'Text:InnerFallback render',
|
||||
]
|
||||
: []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
@@ -1486,6 +1564,10 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await waitForAll([
|
||||
'Text:Fallback:Inside create passive',
|
||||
'Text:Fallback:Outside create passive',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Text:Inside render', 'Suspend:OutsideAsync']
|
||||
: []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
@@ -1516,7 +1598,18 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
'Text:Fallback:Inside destroy layout',
|
||||
'Text:Fallback:Fallback create layout',
|
||||
]);
|
||||
await waitForAll(['Text:Fallback:Fallback create passive']);
|
||||
await waitForAll([
|
||||
'Text:Fallback:Fallback create passive',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? [
|
||||
'Text:Inside render',
|
||||
'Suspend:OutsideAsync',
|
||||
'Text:Fallback:Inside render',
|
||||
'Suspend:FallbackAsync',
|
||||
]
|
||||
: []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Inside" hidden={true} />
|
||||
@@ -1618,6 +1711,15 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
'Text:Fallback:Outside create layout',
|
||||
'Text:Fallback:Fallback create passive',
|
||||
'Text:Fallback:Outside create passive',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? [
|
||||
'Text:Inside render',
|
||||
'Suspend:OutsideAsync',
|
||||
'Text:Fallback:Inside render',
|
||||
'Suspend:FallbackAsync',
|
||||
]
|
||||
: []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
@@ -1728,7 +1830,11 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
'Text:Inside destroy layout',
|
||||
'Text:Fallback create layout',
|
||||
]);
|
||||
await waitForAll(['Text:Fallback create passive']);
|
||||
await waitForAll([
|
||||
'Text:Fallback create passive',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend:Suspend'] : []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Inside" hidden={true} />
|
||||
@@ -1845,6 +1951,10 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
'Text:Inside destroy layout',
|
||||
'Text:Fallback create layout',
|
||||
'Text:Fallback create passive',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Suspend:Async', 'ThrowsInDidMount render', 'Text:Inside render']
|
||||
: []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
@@ -2088,6 +2198,14 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
'Text:Inside destroy layout',
|
||||
'Text:Fallback create layout',
|
||||
'Text:Fallback create passive',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? [
|
||||
'Suspend:Async',
|
||||
'ThrowsInLayoutEffect render',
|
||||
'Text:Inside render',
|
||||
]
|
||||
: []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
@@ -2320,6 +2438,15 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
);
|
||||
});
|
||||
|
||||
if (gate('enableSiblingPrerendering')) {
|
||||
assertLog([
|
||||
'Text:Function render',
|
||||
'Suspend:Async_1',
|
||||
'Suspend:Async_2',
|
||||
'ClassText:Class render',
|
||||
]);
|
||||
}
|
||||
|
||||
// Resolving the suspended resource should re-create inner layout effects.
|
||||
await act(async () => {
|
||||
await resolveText('Async_1');
|
||||
@@ -2469,6 +2596,14 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
</>,
|
||||
);
|
||||
});
|
||||
if (gate('enableSiblingPrerendering')) {
|
||||
assertLog([
|
||||
'Text:Function render',
|
||||
'Suspender "A" render',
|
||||
'Suspend:A',
|
||||
'ClassText:Class render',
|
||||
]);
|
||||
}
|
||||
|
||||
// Resolving the suspended resource should re-create inner layout effects.
|
||||
textToRead = 'B';
|
||||
@@ -2719,6 +2854,15 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
'RefCheckerInner:refCallback destroy layout ref? false',
|
||||
'Text:Fallback create layout',
|
||||
'Text:Fallback create passive',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? [
|
||||
'Suspend:Async',
|
||||
'RefCheckerOuter render',
|
||||
'RefCheckerInner:refObject render',
|
||||
'RefCheckerInner:refCallback render',
|
||||
]
|
||||
: []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
@@ -2820,6 +2964,17 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
'RefCheckerInner:refCallback destroy layout ref? false',
|
||||
'Text:Fallback create layout',
|
||||
'Text:Fallback create passive',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? [
|
||||
'Suspend:Async',
|
||||
'RefCheckerOuter render',
|
||||
'ClassComponent:refObject render',
|
||||
'RefCheckerInner:refObject render',
|
||||
'ClassComponent:refCallback render',
|
||||
'RefCheckerInner:refCallback render',
|
||||
]
|
||||
: []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Fallback" />);
|
||||
|
||||
@@ -2917,6 +3072,17 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
'RefCheckerInner:refCallback destroy layout ref? false',
|
||||
'Text:Fallback create layout',
|
||||
'Text:Fallback create passive',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? [
|
||||
'Suspend:Async',
|
||||
'RefCheckerOuter render',
|
||||
'FunctionComponent render',
|
||||
'RefCheckerInner:refObject render',
|
||||
'FunctionComponent render',
|
||||
'RefCheckerInner:refCallback render',
|
||||
]
|
||||
: []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Fallback" />);
|
||||
|
||||
@@ -3016,6 +3182,10 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
'RefChecker destroy layout ref? true',
|
||||
'Text:Fallback create layout',
|
||||
'Text:Fallback create passive',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Suspend:Async', 'RefChecker render']
|
||||
: []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Fallback" />);
|
||||
|
||||
@@ -3130,6 +3300,14 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
'Text:Inside destroy layout',
|
||||
'Text:Fallback create layout',
|
||||
'Text:Fallback create passive',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? [
|
||||
'Suspend:Async',
|
||||
'ThrowsInRefCallback render',
|
||||
'Text:Inside render',
|
||||
]
|
||||
: []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
|
||||
@@ -139,7 +139,12 @@ describe('ReactSuspenseFallback', () => {
|
||||
</Suspense>,
|
||||
);
|
||||
|
||||
await waitForAll(['Suspend! [A]', 'Loading...']);
|
||||
await waitForAll([
|
||||
'Suspend! [A]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [A]'] : []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Loading..." />);
|
||||
});
|
||||
|
||||
@@ -154,6 +159,8 @@ describe('ReactSuspenseFallback', () => {
|
||||
await waitForAll([
|
||||
'Suspend! [A]',
|
||||
// null
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [A]'] : []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(null);
|
||||
});
|
||||
@@ -169,6 +176,8 @@ describe('ReactSuspenseFallback', () => {
|
||||
await waitForAll([
|
||||
'Suspend! [A]',
|
||||
// null
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [A]'] : []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(null);
|
||||
});
|
||||
@@ -183,7 +192,12 @@ describe('ReactSuspenseFallback', () => {
|
||||
</Suspense>,
|
||||
);
|
||||
|
||||
await waitForAll(['Suspend! [A]', 'Loading...']);
|
||||
await waitForAll([
|
||||
'Suspend! [A]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [A]'] : []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Loading..." />);
|
||||
});
|
||||
|
||||
@@ -200,6 +214,8 @@ describe('ReactSuspenseFallback', () => {
|
||||
await waitForAll([
|
||||
'Suspend! [A]',
|
||||
// null
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [A]'] : []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(null);
|
||||
});
|
||||
@@ -217,6 +233,8 @@ describe('ReactSuspenseFallback', () => {
|
||||
await waitForAll([
|
||||
'Suspend! [A]',
|
||||
// null
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [A]'] : []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(null);
|
||||
});
|
||||
|
||||
+115
-148
@@ -239,6 +239,9 @@ describe('ReactSuspenseList', () => {
|
||||
'Loading B',
|
||||
'Suspend! [C]',
|
||||
'Loading C',
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Suspend! [B]', 'Suspend! [C]']
|
||||
: []),
|
||||
]);
|
||||
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
@@ -250,7 +253,11 @@ describe('ReactSuspenseList', () => {
|
||||
);
|
||||
|
||||
await act(() => C.resolve());
|
||||
assertLog(['C']);
|
||||
assertLog(
|
||||
gate('enableSiblingPrerendering')
|
||||
? ['Suspend! [B]', 'C', 'Suspend! [B]']
|
||||
: ['C'],
|
||||
);
|
||||
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
@@ -383,13 +390,7 @@ describe('ReactSuspenseList', () => {
|
||||
);
|
||||
|
||||
await act(() => B.resolve());
|
||||
assertLog([
|
||||
'A',
|
||||
'B',
|
||||
'Suspend! [C]',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['A', 'B', 'Suspend! [C]'] : []),
|
||||
]);
|
||||
assertLog(['A', 'B', 'Suspend! [C]']);
|
||||
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
@@ -465,13 +466,7 @@ describe('ReactSuspenseList', () => {
|
||||
);
|
||||
|
||||
await act(() => B.resolve());
|
||||
assertLog([
|
||||
'A',
|
||||
'B',
|
||||
'Suspend! [C]',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['A', 'B', 'Suspend! [C]'] : []),
|
||||
]);
|
||||
assertLog(['A', 'B', 'Suspend! [C]']);
|
||||
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
@@ -742,7 +737,11 @@ describe('ReactSuspenseList', () => {
|
||||
|
||||
ReactNoop.render(<Foo />);
|
||||
|
||||
await waitForAll(['Suspend! [A]', 'Loading']);
|
||||
await waitForAll([
|
||||
'Suspend! [A]',
|
||||
'Loading',
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [A]'] : []),
|
||||
]);
|
||||
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span>Loading</span>);
|
||||
|
||||
@@ -779,12 +778,7 @@ describe('ReactSuspenseList', () => {
|
||||
);
|
||||
|
||||
await act(() => B.resolve());
|
||||
assertLog([
|
||||
'B',
|
||||
'Suspend! [C]',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['B', 'Suspend! [C]'] : []),
|
||||
]);
|
||||
assertLog(['B', 'Suspend! [C]']);
|
||||
|
||||
// Even though we could now show B, we're still waiting on C.
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
@@ -871,12 +865,7 @@ describe('ReactSuspenseList', () => {
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span>A</span>);
|
||||
|
||||
await act(() => B.resolve());
|
||||
assertLog([
|
||||
'B',
|
||||
'Suspend! [C]',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['B', 'Suspend! [C]'] : []),
|
||||
]);
|
||||
assertLog(['B', 'Suspend! [C]']);
|
||||
|
||||
// Even though we could now show B, we're still waiting on C.
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span>A</span>);
|
||||
@@ -919,7 +908,14 @@ describe('ReactSuspenseList', () => {
|
||||
|
||||
ReactNoop.render(<Foo />);
|
||||
|
||||
await waitForAll(['Suspend! [A]', 'Loading A', 'Loading B', 'Loading C']);
|
||||
await waitForAll([
|
||||
'Suspend! [A]',
|
||||
'Loading A',
|
||||
'Loading B',
|
||||
'Loading C',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [A]'] : []),
|
||||
]);
|
||||
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
@@ -934,7 +930,7 @@ describe('ReactSuspenseList', () => {
|
||||
'A',
|
||||
'Suspend! [B]',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['A', 'Suspend! [B]'] : []),
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [B]'] : []),
|
||||
]);
|
||||
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
@@ -983,7 +979,14 @@ describe('ReactSuspenseList', () => {
|
||||
|
||||
ReactNoop.render(<Foo />);
|
||||
|
||||
await waitForAll(['Suspend! [C]', 'Loading C', 'Loading B', 'Loading A']);
|
||||
await waitForAll([
|
||||
'Suspend! [C]',
|
||||
'Loading C',
|
||||
'Loading B',
|
||||
'Loading A',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [C]'] : []),
|
||||
]);
|
||||
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
@@ -998,7 +1001,7 @@ describe('ReactSuspenseList', () => {
|
||||
'C',
|
||||
'Suspend! [B]',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['C', 'Suspend! [B]'] : []),
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [B]'] : []),
|
||||
]);
|
||||
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
@@ -1104,12 +1107,7 @@ describe('ReactSuspenseList', () => {
|
||||
);
|
||||
|
||||
await act(() => A.resolve());
|
||||
assertLog([
|
||||
'A',
|
||||
'Suspend! [C]',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['A', 'Suspend! [C]'] : []),
|
||||
]);
|
||||
assertLog(['A', 'Suspend! [C]']);
|
||||
|
||||
// Even though we could show A, it is still in a fallback state because
|
||||
// C is not yet resolved. We need to resolve everything in the head first.
|
||||
@@ -1130,7 +1128,7 @@ describe('ReactSuspenseList', () => {
|
||||
'C',
|
||||
'Suspend! [E]',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['A', 'C', 'Suspend! [E]'] : []),
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [E]'] : []),
|
||||
]);
|
||||
|
||||
// We can now resolve the full head.
|
||||
@@ -1150,7 +1148,7 @@ describe('ReactSuspenseList', () => {
|
||||
'E',
|
||||
'Suspend! [F]',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['E', 'Suspend! [F]'] : []),
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [F]'] : []),
|
||||
]);
|
||||
|
||||
// In the tail we can resolve one-by-one.
|
||||
@@ -1292,6 +1290,10 @@ describe('ReactSuspenseList', () => {
|
||||
'E',
|
||||
'Suspend! [F]',
|
||||
'Loading F',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Suspend! [D]', 'Suspend! [F]']
|
||||
: []),
|
||||
]);
|
||||
|
||||
// This will suspend, since the boundaries are avoided. Give them
|
||||
@@ -1315,12 +1317,7 @@ describe('ReactSuspenseList', () => {
|
||||
|
||||
await F.resolve();
|
||||
|
||||
await waitForAll([
|
||||
'Suspend! [D]',
|
||||
'F',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [D]', 'F'] : []),
|
||||
]);
|
||||
await waitForAll(['Suspend! [D]', 'F']);
|
||||
|
||||
// Even though we could show F, it is still in a fallback state because
|
||||
// E is not yet resolved. We need to resolve everything in the head first.
|
||||
@@ -1345,7 +1342,7 @@ describe('ReactSuspenseList', () => {
|
||||
'F',
|
||||
'Suspend! [B]',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['D', 'F', 'Suspend! [B]'] : []),
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [B]'] : []),
|
||||
]);
|
||||
|
||||
// We can now resolve the full head.
|
||||
@@ -1367,7 +1364,7 @@ describe('ReactSuspenseList', () => {
|
||||
'B',
|
||||
'Suspend! [A]',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['B', 'Suspend! [A]'] : []),
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [A]'] : []),
|
||||
]);
|
||||
|
||||
// In the tail we can resolve one-by-one.
|
||||
@@ -1487,21 +1484,18 @@ describe('ReactSuspenseList', () => {
|
||||
|
||||
ReactNoop.render(<Foo />);
|
||||
|
||||
await waitForAll(['Suspend! [A]', 'Loading A']);
|
||||
await waitForAll([
|
||||
'Suspend! [A]',
|
||||
'Loading A',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [A]'] : []),
|
||||
]);
|
||||
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span>Loading A</span>);
|
||||
|
||||
await A.resolve();
|
||||
|
||||
await waitForAll([
|
||||
'A',
|
||||
'Suspend! [B]',
|
||||
'Loading B',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['A', 'Suspend! [B]', 'Loading B']
|
||||
: []),
|
||||
]);
|
||||
await waitForAll(['A', 'Suspend! [B]', 'Loading B']);
|
||||
|
||||
// Incremental loading is suspended.
|
||||
jest.advanceTimersByTime(500);
|
||||
@@ -1515,15 +1509,7 @@ describe('ReactSuspenseList', () => {
|
||||
|
||||
await B.resolve();
|
||||
|
||||
await waitForAll([
|
||||
'B',
|
||||
'Suspend! [C]',
|
||||
'Loading C',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['B', 'Suspend! [C]', 'Loading C']
|
||||
: []),
|
||||
]);
|
||||
await waitForAll(['B', 'Suspend! [C]', 'Loading C']);
|
||||
|
||||
// Incremental loading is suspended.
|
||||
jest.advanceTimersByTime(500);
|
||||
@@ -1747,12 +1733,7 @@ describe('ReactSuspenseList', () => {
|
||||
|
||||
await B.resolve();
|
||||
|
||||
await waitForAll([
|
||||
'B',
|
||||
'Suspend! [C]',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['B', 'Suspend! [C]'] : []),
|
||||
]);
|
||||
await waitForAll(['B', 'Suspend! [C]']);
|
||||
|
||||
// Incremental loading is suspended.
|
||||
jest.advanceTimersByTime(500);
|
||||
@@ -1772,17 +1753,7 @@ describe('ReactSuspenseList', () => {
|
||||
await C.resolve();
|
||||
await E.resolve();
|
||||
|
||||
await waitForAll([
|
||||
'B',
|
||||
'C',
|
||||
'E',
|
||||
'Suspend! [F]',
|
||||
'Loading F',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['B', 'C', 'E', 'Suspend! [F]', 'Loading F']
|
||||
: []),
|
||||
]);
|
||||
await waitForAll(['B', 'C', 'E', 'Suspend! [F]', 'Loading F']);
|
||||
|
||||
jest.advanceTimersByTime(500);
|
||||
|
||||
@@ -1899,12 +1870,7 @@ describe('ReactSuspenseList', () => {
|
||||
|
||||
await D.resolve();
|
||||
|
||||
await waitForAll([
|
||||
'D',
|
||||
'Suspend! [E]',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['D', 'Suspend! [E]'] : []),
|
||||
]);
|
||||
await waitForAll(['D', 'Suspend! [E]']);
|
||||
|
||||
// Incremental loading is suspended.
|
||||
jest.advanceTimersByTime(500);
|
||||
@@ -1929,17 +1895,7 @@ describe('ReactSuspenseList', () => {
|
||||
await D.resolve();
|
||||
await E.resolve();
|
||||
|
||||
await waitForAll([
|
||||
'D',
|
||||
'E',
|
||||
'B',
|
||||
'Suspend! [A]',
|
||||
'Loading A',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['D', 'E', 'B', 'Suspend! [A]', 'Loading A']
|
||||
: []),
|
||||
]);
|
||||
await waitForAll(['D', 'E', 'B', 'Suspend! [A]', 'Loading A']);
|
||||
|
||||
jest.advanceTimersByTime(500);
|
||||
|
||||
@@ -2047,6 +2003,8 @@ describe('ReactSuspenseList', () => {
|
||||
'Suspend! [D]',
|
||||
'Loading D',
|
||||
'Loading E',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [B]'] : []),
|
||||
]);
|
||||
|
||||
// This is suspended due to the update to D causing a loading state.
|
||||
@@ -2068,12 +2026,7 @@ describe('ReactSuspenseList', () => {
|
||||
|
||||
await B.resolve();
|
||||
|
||||
await waitForAll([
|
||||
'B',
|
||||
'Suspend! [C]',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['B', 'Suspend! [C]'] : []),
|
||||
]);
|
||||
await waitForAll(['B', 'Suspend! [C]']);
|
||||
|
||||
// Incremental loading is suspended.
|
||||
jest.advanceTimersByTime(500);
|
||||
@@ -2096,17 +2049,7 @@ describe('ReactSuspenseList', () => {
|
||||
await D.resolve();
|
||||
await E.resolve();
|
||||
|
||||
await waitForAll([
|
||||
'C',
|
||||
'D',
|
||||
'E',
|
||||
'Suspend! [F]',
|
||||
'Loading F',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['C', 'D', 'E', 'Suspend! [F]', 'Loading F']
|
||||
: []),
|
||||
]);
|
||||
await waitForAll(['C', 'D', 'E', 'Suspend! [F]', 'Loading F']);
|
||||
|
||||
jest.advanceTimersByTime(500);
|
||||
|
||||
@@ -2169,15 +2112,7 @@ describe('ReactSuspenseList', () => {
|
||||
|
||||
await A.resolve();
|
||||
|
||||
await waitForAll([
|
||||
'A',
|
||||
'Suspend! [B]',
|
||||
'Loading B',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['A', 'Suspend! [B]', 'Loading B']
|
||||
: []),
|
||||
]);
|
||||
await waitForAll(['A', 'Suspend! [B]', 'Loading B']);
|
||||
|
||||
// Incremental loading is suspended.
|
||||
jest.advanceTimersByTime(500);
|
||||
@@ -2185,15 +2120,7 @@ describe('ReactSuspenseList', () => {
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span>A</span>);
|
||||
|
||||
await act(() => B.resolve());
|
||||
assertLog([
|
||||
'B',
|
||||
'Suspend! [C]',
|
||||
'Loading C',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['B', 'Suspend! [C]', 'Loading C']
|
||||
: []),
|
||||
]);
|
||||
assertLog(['B', 'Suspend! [C]', 'Loading C']);
|
||||
|
||||
// Incremental loading is suspended.
|
||||
jest.advanceTimersByTime(500);
|
||||
@@ -2490,7 +2417,13 @@ describe('ReactSuspenseList', () => {
|
||||
// This should leave the tree intact.
|
||||
await act(() => ReactNoop.render(<Foo updateList={true} />));
|
||||
|
||||
assertLog(['A', 'Suspend! [B]', 'Loading B']);
|
||||
assertLog([
|
||||
'A',
|
||||
'Suspend! [B]',
|
||||
'Loading B',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [B]'] : []),
|
||||
]);
|
||||
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
@@ -2571,7 +2504,12 @@ describe('ReactSuspenseList', () => {
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span>Loading A</span>);
|
||||
|
||||
// Try again on low-pri.
|
||||
await waitForAll(['Suspend! [A]', 'Loading A']);
|
||||
await waitForAll([
|
||||
'Suspend! [A]',
|
||||
'Loading A',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [A]'] : []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span>Loading A</span>);
|
||||
});
|
||||
|
||||
@@ -2847,7 +2785,15 @@ describe('ReactSuspenseList', () => {
|
||||
|
||||
ReactNoop.render(<App suspendTail={true} />);
|
||||
|
||||
await waitForAll(['App', 'A', 'B', 'Suspend! [C]', 'Fallback']);
|
||||
await waitForAll([
|
||||
'App',
|
||||
'A',
|
||||
'B',
|
||||
'Suspend! [C]',
|
||||
'Fallback',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [C]'] : []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span>A</span>
|
||||
@@ -2900,6 +2846,8 @@ describe('ReactSuspenseList', () => {
|
||||
'Fallback',
|
||||
// Lastly we render the tail.
|
||||
'Fallback',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [C]'] : []),
|
||||
]);
|
||||
|
||||
// Flush suspended time.
|
||||
@@ -2914,7 +2862,9 @@ describe('ReactSuspenseList', () => {
|
||||
<span>Loading...</span>
|
||||
</>,
|
||||
);
|
||||
expect(onRender).toHaveBeenCalledTimes(3);
|
||||
expect(onRender).toHaveBeenCalledTimes(
|
||||
gate('enableSiblingPrerendering') ? 4 : 3,
|
||||
);
|
||||
|
||||
// The treeBaseDuration should be the time to render the first two
|
||||
// children and then two fallbacks.
|
||||
@@ -2932,7 +2882,7 @@ describe('ReactSuspenseList', () => {
|
||||
'C',
|
||||
'Suspend! [D]',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['C', 'Suspend! [D]'] : []),
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [D]'] : []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
@@ -2942,12 +2892,22 @@ describe('ReactSuspenseList', () => {
|
||||
<span>Loading...</span>
|
||||
</>,
|
||||
);
|
||||
expect(onRender).toHaveBeenCalledTimes(4);
|
||||
|
||||
// actualDuration
|
||||
expect(onRender.mock.calls[3][2]).toBe(5 + 12);
|
||||
// treeBaseDuration
|
||||
expect(onRender.mock.calls[3][3]).toBe(1 + 4 + 5 + 3);
|
||||
if (gate('enableSiblingPrerendering')) {
|
||||
expect(onRender).toHaveBeenCalledTimes(6);
|
||||
|
||||
// actualDuration
|
||||
expect(onRender.mock.calls[5][2]).toBe(12);
|
||||
// treeBaseDuration
|
||||
expect(onRender.mock.calls[5][3]).toBe(1 + 4 + 5 + 3);
|
||||
} else {
|
||||
expect(onRender).toHaveBeenCalledTimes(4);
|
||||
|
||||
// actualDuration
|
||||
expect(onRender.mock.calls[3][2]).toBe(5 + 12);
|
||||
// treeBaseDuration
|
||||
expect(onRender.mock.calls[3][3]).toBe(1 + 4 + 5 + 3);
|
||||
}
|
||||
});
|
||||
|
||||
// @gate enableSuspenseList
|
||||
@@ -3008,7 +2968,14 @@ describe('ReactSuspenseList', () => {
|
||||
|
||||
ReactNoop.render(<Foo />);
|
||||
|
||||
await waitForAll(['Suspend! [A]', 'Loading A', 'Loading B', 'Loading C']);
|
||||
await waitForAll([
|
||||
'Suspend! [A]',
|
||||
'Loading A',
|
||||
'Loading B',
|
||||
'Loading C',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [A]'] : []),
|
||||
]);
|
||||
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
@@ -3023,7 +2990,7 @@ describe('ReactSuspenseList', () => {
|
||||
'A',
|
||||
'Suspend! [B]',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['A', 'Suspend! [B]'] : []),
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [B]'] : []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
|
||||
@@ -135,7 +135,13 @@ describe('ReactSuspensePlaceholder', () => {
|
||||
// Initial mount
|
||||
ReactNoop.render(<App middleText="B" />);
|
||||
|
||||
await waitForAll(['A', 'Suspend! [B]', 'Loading...']);
|
||||
await waitForAll([
|
||||
'A',
|
||||
'Suspend! [B]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['A', 'Suspend! [B]', 'C'] : []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput('Loading...');
|
||||
|
||||
await act(() => jest.advanceTimersByTime(1000));
|
||||
@@ -151,7 +157,12 @@ describe('ReactSuspensePlaceholder', () => {
|
||||
|
||||
// Update
|
||||
ReactNoop.render(<App middleText="B2" />);
|
||||
await waitForAll(['Suspend! [B2]', 'Loading...']);
|
||||
await waitForAll([
|
||||
'Suspend! [B2]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [B2]', 'C'] : []),
|
||||
]);
|
||||
|
||||
// Time out the update
|
||||
jest.advanceTimersByTime(750);
|
||||
@@ -194,7 +205,13 @@ describe('ReactSuspensePlaceholder', () => {
|
||||
// Initial mount
|
||||
ReactNoop.render(<App middleText="B" />);
|
||||
|
||||
await waitForAll(['A', 'Suspend! [B]', 'Loading...']);
|
||||
await waitForAll([
|
||||
'A',
|
||||
'Suspend! [B]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['A', 'Suspend! [B]', 'C'] : []),
|
||||
]);
|
||||
|
||||
expect(ReactNoop).not.toMatchRenderedOutput('ABC');
|
||||
|
||||
@@ -204,7 +221,13 @@ describe('ReactSuspensePlaceholder', () => {
|
||||
|
||||
// Update
|
||||
ReactNoop.render(<App middleText="B2" />);
|
||||
await waitForAll(['A', 'Suspend! [B2]', 'Loading...']);
|
||||
await waitForAll([
|
||||
'A',
|
||||
'Suspend! [B2]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['A', 'Suspend! [B2]', 'C'] : []),
|
||||
]);
|
||||
// Time out the update
|
||||
jest.advanceTimersByTime(750);
|
||||
await waitForAll([]);
|
||||
@@ -237,7 +260,13 @@ describe('ReactSuspensePlaceholder', () => {
|
||||
// Initial mount
|
||||
ReactNoop.render(<App middleText="b" />);
|
||||
|
||||
await waitForAll(['a', 'Suspend! [b]', 'Loading...']);
|
||||
await waitForAll([
|
||||
'a',
|
||||
'Suspend! [b]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['a', 'Suspend! [b]', 'c'] : []),
|
||||
]);
|
||||
|
||||
expect(ReactNoop).toMatchRenderedOutput(<uppercase>LOADING...</uppercase>);
|
||||
|
||||
@@ -247,7 +276,13 @@ describe('ReactSuspensePlaceholder', () => {
|
||||
|
||||
// Update
|
||||
ReactNoop.render(<App middleText="b2" />);
|
||||
await waitForAll(['a', 'Suspend! [b2]', 'Loading...']);
|
||||
await waitForAll([
|
||||
'a',
|
||||
'Suspend! [b2]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['a', 'Suspend! [b2]', 'c'] : []),
|
||||
]);
|
||||
// Time out the update
|
||||
jest.advanceTimersByTime(750);
|
||||
await waitForAll([]);
|
||||
@@ -340,6 +375,10 @@ describe('ReactSuspensePlaceholder', () => {
|
||||
'Suspending',
|
||||
'Suspend! [Loaded]',
|
||||
'Fallback',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Suspending', 'Suspend! [Loaded]', 'Text']
|
||||
: []),
|
||||
]);
|
||||
// Since this is initial render we immediately commit the fallback. Another test below
|
||||
// deals with the update case where this suspends.
|
||||
@@ -361,12 +400,22 @@ describe('ReactSuspensePlaceholder', () => {
|
||||
'Text',
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput('LoadedText');
|
||||
expect(onRender).toHaveBeenCalledTimes(2);
|
||||
|
||||
// When the suspending data is resolved and our final UI is rendered,
|
||||
// both times should include the 8ms re-rendering Suspending and AsyncText.
|
||||
expect(onRender.mock.calls[1][2]).toBe(8);
|
||||
expect(onRender.mock.calls[1][3]).toBe(8);
|
||||
if (gate('enableSiblingPrerendering')) {
|
||||
expect(onRender).toHaveBeenCalledTimes(3);
|
||||
|
||||
// When the suspending data is resolved and our final UI is rendered,
|
||||
// both times should include the 8ms re-rendering Suspending and AsyncText.
|
||||
expect(onRender.mock.calls[2][2]).toBe(8);
|
||||
expect(onRender.mock.calls[2][3]).toBe(8);
|
||||
} else {
|
||||
expect(onRender).toHaveBeenCalledTimes(2);
|
||||
|
||||
// When the suspending data is resolved and our final UI is rendered,
|
||||
// both times should include the 8ms re-rendering Suspending and AsyncText.
|
||||
expect(onRender.mock.calls[1][2]).toBe(8);
|
||||
expect(onRender.mock.calls[1][3]).toBe(8);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -487,6 +536,10 @@ describe('ReactSuspensePlaceholder', () => {
|
||||
'Suspending',
|
||||
'Suspend! [Loaded]',
|
||||
'Fallback',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Suspending', 'Suspend! [Loaded]', 'Text']
|
||||
: []),
|
||||
]);
|
||||
// Show the fallback UI.
|
||||
expect(ReactNoop).toMatchRenderedOutput('Loading...');
|
||||
@@ -526,9 +579,16 @@ describe('ReactSuspensePlaceholder', () => {
|
||||
'Suspend! [Loaded]',
|
||||
'Fallback',
|
||||
'Suspend! [Sibling]',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Suspending', 'Suspend! [Loaded]', 'New', 'Suspend! [Sibling]']
|
||||
: []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput('Loading...');
|
||||
expect(onRender).toHaveBeenCalledTimes(3);
|
||||
|
||||
expect(onRender).toHaveBeenCalledTimes(
|
||||
gate('enableSiblingPrerendering') ? 4 : 3,
|
||||
);
|
||||
|
||||
// Resolve the pending promise.
|
||||
await act(async () => {
|
||||
@@ -539,13 +599,24 @@ describe('ReactSuspensePlaceholder', () => {
|
||||
]);
|
||||
await waitForAll(['Suspending', 'Loaded', 'New', 'Sibling']);
|
||||
});
|
||||
expect(onRender).toHaveBeenCalledTimes(4);
|
||||
|
||||
// When the suspending data is resolved and our final UI is rendered,
|
||||
// both times should include the 6ms rendering Text,
|
||||
// the 2ms rendering Suspending, and the 1ms rendering AsyncText.
|
||||
expect(onRender.mock.calls[3][2]).toBe(9);
|
||||
expect(onRender.mock.calls[3][3]).toBe(9);
|
||||
if (gate('enableSiblingPrerendering')) {
|
||||
expect(onRender).toHaveBeenCalledTimes(5);
|
||||
|
||||
// When the suspending data is resolved and our final UI is rendered,
|
||||
// both times should include the 6ms rendering Text,
|
||||
// the 2ms rendering Suspending, and the 1ms rendering AsyncText.
|
||||
expect(onRender.mock.calls[4][2]).toBe(9);
|
||||
expect(onRender.mock.calls[4][3]).toBe(9);
|
||||
} else {
|
||||
expect(onRender).toHaveBeenCalledTimes(4);
|
||||
|
||||
// When the suspending data is resolved and our final UI is rendered,
|
||||
// both times should include the 6ms rendering Text,
|
||||
// the 2ms rendering Suspending, and the 1ms rendering AsyncText.
|
||||
expect(onRender.mock.calls[3][2]).toBe(9);
|
||||
expect(onRender.mock.calls[3][3]).toBe(9);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+245
-57
@@ -334,6 +334,10 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
'Loading A...',
|
||||
'Suspend! [B]',
|
||||
'Loading B...',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Suspend! [A]', 'Suspend! [B]']
|
||||
: []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
@@ -345,7 +349,13 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
// Resolve first Suspense's promise so that it switches switches back to the
|
||||
// normal view. The second Suspense should still show the placeholder.
|
||||
await act(() => resolveText('A'));
|
||||
assertLog(['A']);
|
||||
assertLog([
|
||||
'A',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Suspend! [B]', 'Suspend! [B]']
|
||||
: []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="A" />
|
||||
@@ -496,7 +506,12 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
}
|
||||
|
||||
ReactNoop.render(<App />);
|
||||
await waitForAll(['Suspend! [Result]', 'Loading...']);
|
||||
await waitForAll([
|
||||
'Suspend! [Result]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [Result]'] : []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Loading..." />);
|
||||
|
||||
await act(() => rejectText('Result', new Error('Failed to load: Result')));
|
||||
@@ -506,10 +521,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
// React retries one more time
|
||||
'Error! [Result]',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Error! [Result]', 'Error! [Result]']
|
||||
: []),
|
||||
|
||||
// Errored again on retry. Now handle it.
|
||||
'Caught error: Failed to load: Result',
|
||||
]);
|
||||
@@ -547,7 +558,13 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
|
||||
// Initial mount
|
||||
await act(() => ReactNoop.render(<App />));
|
||||
assertLog(['A', 'Suspend! [1]', 'Loading...']);
|
||||
assertLog([
|
||||
'A',
|
||||
'Suspend! [1]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [1]'] : []),
|
||||
]);
|
||||
|
||||
await act(() => resolveText('1'));
|
||||
assertLog(['1']);
|
||||
@@ -748,6 +765,14 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
// The async content suspends
|
||||
'Suspend! [Outer content]',
|
||||
'Loading outer...',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? [
|
||||
'Suspend! [Outer content]',
|
||||
'Suspend! [Inner content]',
|
||||
'Loading inner...',
|
||||
]
|
||||
: []),
|
||||
]);
|
||||
// The outer loading state finishes immediately.
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
@@ -763,10 +788,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
'Outer content',
|
||||
'Suspend! [Inner content]',
|
||||
'Loading inner...',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Outer content', 'Suspend! [Inner content]', 'Loading inner...']
|
||||
: []),
|
||||
]);
|
||||
// Don't commit the inner placeholder yet.
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
@@ -928,7 +949,14 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
<AsyncText text="B" />
|
||||
</Suspense>,
|
||||
);
|
||||
await waitForAll(['Suspend! [A]', 'Loading...']);
|
||||
await waitForAll([
|
||||
'Suspend! [A]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Suspend! [A]', 'Suspend! [B]']
|
||||
: []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Loading..." />);
|
||||
|
||||
await act(() => {
|
||||
@@ -1038,7 +1066,13 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
}
|
||||
|
||||
ReactNoop.render(<App />);
|
||||
await waitForAll(['Suspend! [A]']);
|
||||
await waitForAll([
|
||||
'Suspend! [A]',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Suspend! [A]', 'Suspend! [B]', 'Suspend! [C]']
|
||||
: []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput('Loading...');
|
||||
|
||||
await resolveText('A');
|
||||
@@ -1701,6 +1735,10 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
// A suspends
|
||||
'Suspend! [A]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Suspend! [A]', 'Suspend! [B]', 'Loading more...']
|
||||
: []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Loading..." />);
|
||||
|
||||
@@ -1715,6 +1753,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
// B suspends
|
||||
'Suspend! [B]',
|
||||
'Loading more...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [B]'] : []),
|
||||
]);
|
||||
|
||||
// Because we've already been waiting for so long we've exceeded
|
||||
@@ -1759,6 +1799,10 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
// A suspends
|
||||
'Suspend! [A]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Suspend! [A]', 'Suspend! [B]', 'Loading more...']
|
||||
: []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Loading..." />);
|
||||
|
||||
@@ -1771,10 +1815,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
// B suspends
|
||||
'Suspend! [B]',
|
||||
'Loading more...',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['A', 'Suspend! [B]', 'Loading more...']
|
||||
: []),
|
||||
]);
|
||||
// Because we've already been waiting for so long we can
|
||||
// wait a bit longer. Still nothing...
|
||||
@@ -1841,6 +1881,10 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
'Loading A...',
|
||||
'Suspend! [B]',
|
||||
'Loading B...',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Suspend! [A]', 'Suspend! [B]']
|
||||
: []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
@@ -1985,14 +2029,24 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
ReactNoop.render(<App />);
|
||||
});
|
||||
|
||||
assertLog(['Suspend! [A]']);
|
||||
assertLog([
|
||||
'Suspend! [A]',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [A]'] : []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput('Loading...');
|
||||
|
||||
await act(() => {
|
||||
ReactNoop.flushSync(() => showB());
|
||||
});
|
||||
|
||||
assertLog(['Suspend! [A]']);
|
||||
assertLog([
|
||||
'Suspend! [A]',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Suspend! [A]', 'Suspend! [B]']
|
||||
: []),
|
||||
]);
|
||||
});
|
||||
|
||||
// TODO: flip to "warns" when this is implemented again.
|
||||
@@ -2048,7 +2102,11 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
// also make sure lowpriority is okay
|
||||
await act(() => show(true));
|
||||
|
||||
assertLog(['Suspend! [A]']);
|
||||
assertLog([
|
||||
'Suspend! [A]',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [A]'] : []),
|
||||
]);
|
||||
await resolveText('A');
|
||||
|
||||
expect(ReactNoop).toMatchRenderedOutput('Loading...');
|
||||
@@ -2074,7 +2132,11 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
// also make sure lowpriority is okay
|
||||
await act(() => _setShow(true));
|
||||
|
||||
assertLog(['Suspend! [A]']);
|
||||
assertLog([
|
||||
'Suspend! [A]',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [A]'] : []),
|
||||
]);
|
||||
await resolveText('A');
|
||||
|
||||
expect(ReactNoop).toMatchRenderedOutput('Loading...');
|
||||
@@ -2098,7 +2160,13 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
}
|
||||
|
||||
ReactNoop.render(<Foo />);
|
||||
await waitForAll(['Foo', 'Suspend! [A]', 'Initial load...']);
|
||||
await waitForAll([
|
||||
'Foo',
|
||||
'Suspend! [A]',
|
||||
'Initial load...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [A]', 'B'] : []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Initial load..." />);
|
||||
|
||||
// Eventually we resolve and show the data.
|
||||
@@ -2113,7 +2181,15 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
|
||||
// Update to show C
|
||||
ReactNoop.render(<Foo showC={true} />);
|
||||
await waitForAll(['Foo', 'A', 'Suspend! [C]', 'Updating...', 'B']);
|
||||
await waitForAll([
|
||||
'Foo',
|
||||
'A',
|
||||
'Suspend! [C]',
|
||||
'Updating...',
|
||||
'B',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['A', 'Suspend! [C]'] : []),
|
||||
]);
|
||||
// Flush to skip suspended time.
|
||||
Scheduler.unstable_advanceTime(600);
|
||||
await advanceTimers(600);
|
||||
@@ -2160,6 +2236,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
'Suspend! [A]',
|
||||
'B',
|
||||
// null
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [A]'] : []),
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="B" />);
|
||||
|
||||
@@ -2181,6 +2259,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
'Suspend! [C]',
|
||||
// null
|
||||
'B',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['A', 'Suspend! [C]'] : []),
|
||||
]);
|
||||
// Flush to skip suspended time.
|
||||
Scheduler.unstable_advanceTime(600);
|
||||
@@ -2223,7 +2303,14 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
}
|
||||
|
||||
ReactNoop.render(<Foo />);
|
||||
await waitForAll(['Foo', 'A', 'Suspend! [B]', 'Loading B...']);
|
||||
await waitForAll([
|
||||
'Foo',
|
||||
'A',
|
||||
'Suspend! [B]',
|
||||
'Loading B...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [B]'] : []),
|
||||
]);
|
||||
// Flush to skip suspended time.
|
||||
Scheduler.unstable_advanceTime(600);
|
||||
await advanceTimers(600);
|
||||
@@ -2303,6 +2390,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
'A',
|
||||
'Suspend! [B]',
|
||||
// Null
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [B]'] : []),
|
||||
]);
|
||||
// Still suspended.
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="A" />);
|
||||
@@ -2328,7 +2417,12 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
// Initial render.
|
||||
React.startTransition(() => ReactNoop.render(<App page="A" />));
|
||||
|
||||
await waitForAll(['Suspend! [A]', 'Loading...']);
|
||||
await waitForAll([
|
||||
'Suspend! [A]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [A]'] : []),
|
||||
]);
|
||||
// Only a short time is needed to unsuspend the initial loading state.
|
||||
Scheduler.unstable_advanceTime(400);
|
||||
await advanceTimers(400);
|
||||
@@ -2377,7 +2471,12 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
await act(async () => {
|
||||
React.startTransition(() => transitionToPage('A'));
|
||||
|
||||
await waitForAll(['Suspend! [A]', 'Loading...']);
|
||||
await waitForAll([
|
||||
'Suspend! [A]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [A]'] : []),
|
||||
]);
|
||||
// Only a short time is needed to unsuspend the initial loading state.
|
||||
Scheduler.unstable_advanceTime(400);
|
||||
await advanceTimers(400);
|
||||
@@ -2432,7 +2531,12 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
await act(async () => {
|
||||
React.startTransition(() => transitionToPage('A'));
|
||||
|
||||
await waitForAll(['Suspend! [A]', 'Loading...']);
|
||||
await waitForAll([
|
||||
'Suspend! [A]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [A]'] : []),
|
||||
]);
|
||||
// Only a short time is needed to unsuspend the initial loading state.
|
||||
Scheduler.unstable_advanceTime(400);
|
||||
await advanceTimers(400);
|
||||
@@ -2476,7 +2580,12 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
// Initial render.
|
||||
React.startTransition(() => ReactNoop.render(<App page="A" />));
|
||||
|
||||
await waitForAll(['Suspend! [A]', 'Loading...']);
|
||||
await waitForAll([
|
||||
'Suspend! [A]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [A]'] : []),
|
||||
]);
|
||||
// Only a short time is needed to unsuspend the initial loading state.
|
||||
Scheduler.unstable_advanceTime(400);
|
||||
await advanceTimers(400);
|
||||
@@ -2536,7 +2645,12 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
await act(async () => {
|
||||
React.startTransition(() => transitionToPage('A'));
|
||||
|
||||
await waitForAll(['Suspend! [A]', 'Loading...']);
|
||||
await waitForAll([
|
||||
'Suspend! [A]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [A]'] : []),
|
||||
]);
|
||||
// Only a short time is needed to unsuspend the initial loading state.
|
||||
Scheduler.unstable_advanceTime(400);
|
||||
await advanceTimers(400);
|
||||
@@ -2606,7 +2720,12 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
await act(async () => {
|
||||
React.startTransition(() => transitionToPage('A'));
|
||||
|
||||
await waitForAll(['Suspend! [A]', 'Loading...']);
|
||||
await waitForAll([
|
||||
'Suspend! [A]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [A]'] : []),
|
||||
]);
|
||||
// Only a short time is needed to unsuspend the initial loading state.
|
||||
Scheduler.unstable_advanceTime(400);
|
||||
await advanceTimers(400);
|
||||
@@ -2667,7 +2786,13 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
|
||||
// Initial render.
|
||||
ReactNoop.render(<App page="A" />);
|
||||
await waitForAll(['Hi!', 'Suspend! [A]', 'Loading...']);
|
||||
await waitForAll([
|
||||
'Hi!',
|
||||
'Suspend! [A]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Hi!', 'Suspend! [A]'] : []),
|
||||
]);
|
||||
await act(() => resolveText('A'));
|
||||
assertLog(['Hi!', 'A']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
@@ -2988,6 +3113,14 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
},
|
||||
);
|
||||
|
||||
// TODO: This test is substantially different when sibling prerendering is
|
||||
// enabled because we never work on Idle updates if there are pending retries.
|
||||
// This was already an issue before the enableSiblingPrerendering change but
|
||||
// it's exacerbated by the fact that we schedule a retry immediately. I'm not
|
||||
// going to bother to update this test for now, though, because Idle updates
|
||||
// aren't actually used and should probably just be deleted unless/until we
|
||||
// finish the feature. Feel free to delete if needed.
|
||||
// @gate !enableSiblingPrerendering
|
||||
// @gate enableLegacyCache
|
||||
it(
|
||||
'multiple updates originating inside a Suspense boundary at different ' +
|
||||
@@ -3086,7 +3219,12 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
await act(async () => {
|
||||
// Schedule an update inside the Suspense boundary that suspends.
|
||||
setAppText('B');
|
||||
await waitForAll(['Suspend! [B]', 'Loading...']);
|
||||
await waitForAll([
|
||||
'Suspend! [B]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [B]'] : []),
|
||||
]);
|
||||
});
|
||||
|
||||
expect(root).toMatchRenderedOutput(
|
||||
@@ -3122,6 +3260,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
|
||||
// Then complete the update to the fallback.
|
||||
'Still loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [C]'] : []),
|
||||
]);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
@@ -3182,7 +3322,12 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
await act(() => {
|
||||
setText('C');
|
||||
});
|
||||
assertLog(['Suspend! [C]', 'Loading...']);
|
||||
assertLog([
|
||||
'Suspend! [C]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [C]'] : []),
|
||||
]);
|
||||
|
||||
// Commit. This will insert a fragment fiber to wrap around the component
|
||||
// that triggered the update.
|
||||
@@ -3259,7 +3404,12 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
await act(() => {
|
||||
setText('C');
|
||||
});
|
||||
assertLog(['Suspend! [C]', 'Loading...']);
|
||||
assertLog([
|
||||
'Suspend! [C]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [C]'] : []),
|
||||
]);
|
||||
|
||||
// Commit. This will insert a fragment fiber to wrap around the component
|
||||
// that triggered the update.
|
||||
@@ -3288,7 +3438,13 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
});
|
||||
// Even though the fragment fiber is not part of the return path, we should
|
||||
// be able to finish rendering.
|
||||
assertLog(['Suspend! [D]', 'E']);
|
||||
assertLog([
|
||||
'Suspend! [D]',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [D]'] : []),
|
||||
|
||||
'E',
|
||||
]);
|
||||
expect(root).toMatchRenderedOutput(<span prop="E" />);
|
||||
},
|
||||
);
|
||||
@@ -3374,6 +3530,10 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
'Outer step: 0',
|
||||
'Suspend! [Inner text: B]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Suspend! [Inner text: B]', 'Inner step: 0']
|
||||
: []),
|
||||
]);
|
||||
// Commit the placeholder
|
||||
await advanceTimers(250);
|
||||
@@ -3402,6 +3562,10 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
'Outer step: 1',
|
||||
'Suspend! [Inner text: B]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Suspend! [Inner text: B]', 'Inner step: 1']
|
||||
: []),
|
||||
]);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
@@ -3492,7 +3656,13 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
await act(() => {
|
||||
setText('B');
|
||||
});
|
||||
assertLog(['Outer: B0', 'Suspend! [Inner: B0]', 'Loading...']);
|
||||
assertLog([
|
||||
'Outer: B0',
|
||||
'Suspend! [Inner: B0]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [Inner: B0]'] : []),
|
||||
]);
|
||||
// Commit the placeholder
|
||||
await advanceTimers(250);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
@@ -3520,10 +3690,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
);
|
||||
});
|
||||
|
||||
// This regression test relies on subtle implementation details that happen to
|
||||
// rely on sibling prerendering being disabled. Not going to bother to rewrite
|
||||
// it for now; maybe once we land the experiment.
|
||||
// @gate !enableSiblingPrerendering
|
||||
// @gate enableLegacyCache
|
||||
it('regression: ping at high priority causes update to be dropped', async () => {
|
||||
const {useState, useTransition} = React;
|
||||
@@ -3737,8 +3903,16 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: This test is substantially different when sibling prerendering is
|
||||
// enabled because we never work on Idle updates if there are pending retries.
|
||||
// This was already an issue before the enableSiblingPrerendering change but
|
||||
// it's exacerbated by the fact that we schedule a retry immediately. I'm not
|
||||
// going to bother to update this test for now, though, because Idle updates
|
||||
// aren't actually used and should probably just be deleted unless/until we
|
||||
// finish the feature. Feel free to delete if needed.
|
||||
// @gate !enableSiblingPrerendering
|
||||
// @gate enableLegacyCache
|
||||
it('regression: #18657', async () => {
|
||||
it('regression related to Idle updates (outdated experiment): #18657', async () => {
|
||||
const {useState} = React;
|
||||
|
||||
let setText;
|
||||
@@ -3822,7 +3996,13 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
</>,
|
||||
);
|
||||
});
|
||||
assertLog(['A', 'Suspend! [Async]', 'Loading...']);
|
||||
assertLog([
|
||||
'A',
|
||||
'Suspend! [Async]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [Async]'] : []),
|
||||
]);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="A" />
|
||||
@@ -3889,7 +4069,12 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
await act(() => {
|
||||
root.render(<App show={true} />);
|
||||
});
|
||||
assertLog(['Suspend! [Async]', 'Loading...']);
|
||||
assertLog([
|
||||
'Suspend! [Async]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend! [Async]'] : []),
|
||||
]);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<span hidden={true} prop="Child" />
|
||||
@@ -3960,7 +4145,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
// used to cause a crash.
|
||||
const thenable = {
|
||||
then(resolve) {
|
||||
resolve('hi');
|
||||
resolve('B');
|
||||
},
|
||||
status: 'pending',
|
||||
};
|
||||
@@ -3970,21 +4155,21 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
thenable.status = 'fulfilled';
|
||||
throw thenable;
|
||||
}
|
||||
return <Text text="Hi" />;
|
||||
return <Text text="B" />;
|
||||
}
|
||||
|
||||
function App({showMore}) {
|
||||
return (
|
||||
<div>
|
||||
<Suspense fallback={<Text text="Loading..." />}>
|
||||
<Suspense fallback={<Text text="Loading A..." />}>
|
||||
{showMore ? (
|
||||
<>
|
||||
<AsyncText text="Async" />
|
||||
<AsyncText text="A" />
|
||||
</>
|
||||
) : null}
|
||||
</Suspense>
|
||||
{showMore ? (
|
||||
<Suspense>
|
||||
<Suspense fallback={<Text text="Loading B..." />}>
|
||||
<ImmediatelyPings />
|
||||
</Suspense>
|
||||
) : null}
|
||||
@@ -4015,15 +4200,10 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
// The fix was to check if we're in the render phase before calling
|
||||
// `prepareFreshStack`.
|
||||
await act(() => {
|
||||
root.render(<App showMore={true} />);
|
||||
startTransition(() => root.render(<App showMore={true} />));
|
||||
});
|
||||
assertLog(['Suspend! [Async]', 'Loading...', 'Hi']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<div>
|
||||
<span prop="Loading..." />
|
||||
<span prop="Hi" />
|
||||
</div>,
|
||||
);
|
||||
assertLog(['Suspend! [A]', 'Loading A...', 'Loading B...']);
|
||||
expect(root).toMatchRenderedOutput(<div />);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -4060,7 +4240,15 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
|
||||
const root = ReactNoop.createRoot();
|
||||
root.render(<App />);
|
||||
await waitForAll(['1', 'Suspend! [Async]', 'Loading...']);
|
||||
await waitForAll([
|
||||
'1',
|
||||
'Suspend! [Async]',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Suspend! [Async]', 'A', 'B', 'C']
|
||||
: []),
|
||||
]);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="1" />
|
||||
|
||||
@@ -441,6 +441,7 @@ describe('ReactInteractionTracing', () => {
|
||||
await waitForAll([
|
||||
'Suspend [Page Two]',
|
||||
'Loading...',
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend [Page Two]'] : []),
|
||||
'onTransitionStart(page transition, 1000)',
|
||||
'onTransitionProgress(page transition, 1000, 2000, [suspense page])',
|
||||
]);
|
||||
@@ -531,6 +532,7 @@ describe('ReactInteractionTracing', () => {
|
||||
await waitForAll([
|
||||
'Suspend [Page Two]',
|
||||
'Loading...',
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend [Page Two]'] : []),
|
||||
'onTransitionStart(page transition, 1000)',
|
||||
'onTransitionProgress(page transition, 1000, 1000, [suspense page])',
|
||||
]);
|
||||
@@ -549,6 +551,7 @@ describe('ReactInteractionTracing', () => {
|
||||
'Suspend [Show Text]',
|
||||
'Show Text Loading...',
|
||||
'Page Two',
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend [Show Text]'] : []),
|
||||
'onTransitionStart(text transition, 2000)',
|
||||
'onTransitionProgress(text transition, 2000, 2000, [show text])',
|
||||
]);
|
||||
@@ -638,6 +641,7 @@ describe('ReactInteractionTracing', () => {
|
||||
await waitForAll([
|
||||
'Suspend [Page Two]',
|
||||
'Loading...',
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend [Page Two]'] : []),
|
||||
'onTransitionStart(page transition, 1000)',
|
||||
'onTransitionProgress(page transition, 1000, 2000, [suspense page])',
|
||||
]);
|
||||
@@ -651,6 +655,9 @@ describe('ReactInteractionTracing', () => {
|
||||
'Show Text Loading...',
|
||||
'Suspend [Page Two]',
|
||||
'Loading...',
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Suspend [Show Text]', 'Suspend [Page Two]']
|
||||
: []),
|
||||
'onTransitionStart(show text, 2000)',
|
||||
'onTransitionProgress(show text, 2000, 2000, [show text])',
|
||||
]);
|
||||
@@ -753,6 +760,15 @@ describe('ReactInteractionTracing', () => {
|
||||
await waitForAll([
|
||||
'Suspend [Page Two]',
|
||||
'Loading...',
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? [
|
||||
'Suspend [Page Two]',
|
||||
'Suspend [Show Text One]',
|
||||
'Show Text One Loading...',
|
||||
'Suspend [Show Text Two]',
|
||||
'Show Text Two Loading...',
|
||||
]
|
||||
: []),
|
||||
'onTransitionStart(page transition, 1000)',
|
||||
'onTransitionProgress(page transition, 1000, 2000, [suspense page])',
|
||||
]);
|
||||
@@ -767,6 +783,9 @@ describe('ReactInteractionTracing', () => {
|
||||
'Show Text One Loading...',
|
||||
'Suspend [Show Text Two]',
|
||||
'Show Text Two Loading...',
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Suspend [Show Text One]', 'Suspend [Show Text Two]']
|
||||
: []),
|
||||
'onTransitionProgress(page transition, 1000, 3000, [show text one, show text two])',
|
||||
]);
|
||||
|
||||
@@ -879,6 +898,15 @@ describe('ReactInteractionTracing', () => {
|
||||
await waitForAll([
|
||||
'Suspend [Page Two]',
|
||||
'Loading...',
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? [
|
||||
'Suspend [Page Two]',
|
||||
'Suspend [Show Text One]',
|
||||
'Show Text One Loading...',
|
||||
'Suspend [Show Text]',
|
||||
'Show Text Loading...',
|
||||
]
|
||||
: []),
|
||||
'onTransitionStart(navigate, 1000)',
|
||||
'onTransitionStart(show text one, 1000)',
|
||||
'onTransitionProgress(navigate, 1000, 2000, [suspense page])',
|
||||
@@ -894,6 +922,9 @@ describe('ReactInteractionTracing', () => {
|
||||
'Show Text One Loading...',
|
||||
'Suspend [Show Text]',
|
||||
'Show Text Loading...',
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Suspend [Show Text One]', 'Suspend [Show Text]']
|
||||
: []),
|
||||
'onTransitionProgress(navigate, 1000, 3000, [show text one, <null>])',
|
||||
'onTransitionProgress(show text one, 1000, 3000, [show text one, <null>])',
|
||||
]);
|
||||
@@ -910,6 +941,13 @@ describe('ReactInteractionTracing', () => {
|
||||
'Show Text Loading...',
|
||||
'Suspend [Show Text Two]',
|
||||
'Show Text Two Loading...',
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? [
|
||||
'Suspend [Show Text One]',
|
||||
'Suspend [Show Text]',
|
||||
'Suspend [Show Text Two]',
|
||||
]
|
||||
: []),
|
||||
'onTransitionStart(show text two, 3000)',
|
||||
'onTransitionProgress(show text two, 3000, 4000, [show text two])',
|
||||
]);
|
||||
@@ -1114,6 +1152,9 @@ describe('ReactInteractionTracing', () => {
|
||||
await waitForAll([
|
||||
'Suspend [Page Two]',
|
||||
'Loading...',
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Suspend [Page Two]', 'Suspend [Marker Text]', 'Loading...']
|
||||
: []),
|
||||
'onTransitionStart(page transition, 1000)',
|
||||
]);
|
||||
|
||||
@@ -1125,6 +1166,7 @@ describe('ReactInteractionTracing', () => {
|
||||
'Page Two',
|
||||
'Suspend [Marker Text]',
|
||||
'Loading...',
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend [Marker Text]'] : []),
|
||||
'onMarkerProgress(page transition, async marker, 1000, 3000, [marker suspense])',
|
||||
'onMarkerComplete(page transition, sync marker, 1000, 3000)',
|
||||
]);
|
||||
@@ -1230,6 +1272,15 @@ describe('ReactInteractionTracing', () => {
|
||||
await waitForAll([
|
||||
'Suspend [Outer Text]',
|
||||
'Outer...',
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? [
|
||||
'Suspend [Outer Text]',
|
||||
'Suspend [Inner Text One]',
|
||||
'Inner One...',
|
||||
'Suspend [Inner Text Two]',
|
||||
'Inner Two...',
|
||||
]
|
||||
: []),
|
||||
'onTransitionStart(page transition, 1000)',
|
||||
'onMarkerProgress(page transition, outer marker, 1000, 2000, [outer])',
|
||||
]);
|
||||
@@ -1247,6 +1298,9 @@ describe('ReactInteractionTracing', () => {
|
||||
'Suspend [Inner Text One]',
|
||||
'Inner One...',
|
||||
'Inner Text Two',
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Suspend [Inner Text One]']
|
||||
: []),
|
||||
'onMarkerProgress(page transition, outer marker, 1000, 4000, [inner one])',
|
||||
'onMarkerComplete(page transition, marker two, 1000, 4000)',
|
||||
]);
|
||||
@@ -1484,6 +1538,9 @@ describe('ReactInteractionTracing', () => {
|
||||
'Loading...',
|
||||
'Suspend [Sibling Text]',
|
||||
'Sibling Loading...',
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Suspend [Page Two]', 'Suspend [Sibling Text]']
|
||||
: []),
|
||||
'onTransitionStart(transition one, 1000)',
|
||||
'onMarkerProgress(transition one, parent, 1000, 2000, [suspense page, suspense sibling])',
|
||||
'onMarkerProgress(transition one, marker one, 1000, 2000, [suspense page])',
|
||||
@@ -1499,6 +1556,9 @@ describe('ReactInteractionTracing', () => {
|
||||
'Loading...',
|
||||
'Suspend [Sibling Text]',
|
||||
'Sibling Loading...',
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Suspend [Page Two]', 'Suspend [Sibling Text]']
|
||||
: []),
|
||||
'onMarkerProgress(transition one, parent, 1000, 3000, [suspense sibling])',
|
||||
'onMarkerIncomplete(transition one, marker one, 1000, [{endTime: 3000, name: marker one, type: marker}, {endTime: 3000, name: suspense page, type: suspense}])',
|
||||
'onMarkerIncomplete(transition one, parent, 1000, [{endTime: 3000, name: marker one, type: marker}, {endTime: 3000, name: suspense page, type: suspense}])',
|
||||
@@ -1512,6 +1572,9 @@ describe('ReactInteractionTracing', () => {
|
||||
'Loading...',
|
||||
'Suspend [Sibling Text]',
|
||||
'Sibling Loading...',
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Suspend [Page Two]', 'Suspend [Sibling Text]']
|
||||
: []),
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -1633,6 +1696,9 @@ describe('ReactInteractionTracing', () => {
|
||||
'Loading One...',
|
||||
'Suspend [Page Two]',
|
||||
'Loading Two...',
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Suspend [Page One]', 'Suspend [Page Two]']
|
||||
: []),
|
||||
'onTransitionStart(transition, 1000)',
|
||||
'onMarkerProgress(transition, parent, 1000, 2000, [suspense one, suspense two])',
|
||||
'onMarkerProgress(transition, one, 1000, 2000, [suspense one])',
|
||||
@@ -1646,6 +1712,7 @@ describe('ReactInteractionTracing', () => {
|
||||
await waitForAll([
|
||||
'Suspend [Page Two]',
|
||||
'Loading Two...',
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend [Page Two]'] : []),
|
||||
'onMarkerProgress(transition, parent, 1000, 3000, [suspense two])',
|
||||
'onMarkerIncomplete(transition, one, 1000, [{endTime: 3000, name: one, type: marker}, {endTime: 3000, name: suspense one, type: suspense}])',
|
||||
'onMarkerIncomplete(transition, parent, 1000, [{endTime: 3000, name: one, type: marker}, {endTime: 3000, name: suspense one, type: suspense}])',
|
||||
@@ -1772,6 +1839,14 @@ describe('ReactInteractionTracing', () => {
|
||||
'Loading One...',
|
||||
'Suspend [Page Two]',
|
||||
'Loading Two...',
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? [
|
||||
'Suspend [Page One]',
|
||||
'Suspend [Child]',
|
||||
'Loading Child...',
|
||||
'Suspend [Page Two]',
|
||||
]
|
||||
: []),
|
||||
'onTransitionStart(transition, 1000)',
|
||||
'onMarkerProgress(transition, parent, 1000, 2000, [suspense one, suspense two])',
|
||||
'onMarkerProgress(transition, one, 1000, 2000, [suspense one])',
|
||||
@@ -1786,6 +1861,7 @@ describe('ReactInteractionTracing', () => {
|
||||
'Page One',
|
||||
'Suspend [Child]',
|
||||
'Loading Child...',
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend [Child]'] : []),
|
||||
'onMarkerProgress(transition, parent, 1000, 3000, [suspense two, suspense child])',
|
||||
'onMarkerProgress(transition, one, 1000, 3000, [suspense child])',
|
||||
'onMarkerComplete(transition, page one, 1000, 3000)',
|
||||
@@ -1798,6 +1874,7 @@ describe('ReactInteractionTracing', () => {
|
||||
await waitForAll([
|
||||
'Suspend [Page Two]',
|
||||
'Loading Two...',
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend [Page Two]'] : []),
|
||||
// "suspense one" has unsuspended so shouldn't be included
|
||||
// tracing marker "page one" has completed so shouldn't be included
|
||||
// all children of "suspense child" haven't yet been rendered so shouldn't be included
|
||||
@@ -1895,6 +1972,7 @@ describe('ReactInteractionTracing', () => {
|
||||
|
||||
await waitForAll([
|
||||
'Suspend [Child]',
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend [Child]'] : []),
|
||||
'onTransitionStart(transition, 0)',
|
||||
'onMarkerProgress(transition, parent, 0, 1000, [child])',
|
||||
'onTransitionProgress(transition, 0, 1000, [child])',
|
||||
@@ -1905,14 +1983,23 @@ describe('ReactInteractionTracing', () => {
|
||||
await advanceTimers(1000);
|
||||
// This appended child isn't part of the transition so we
|
||||
// don't call any callback
|
||||
await waitForAll(['Suspend [Appended child]', 'Suspend [Child]']);
|
||||
await waitForAll([
|
||||
'Suspend [Appended child]',
|
||||
'Suspend [Child]',
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Suspend [Appended child]', 'Suspend [Child]']
|
||||
: []),
|
||||
]);
|
||||
|
||||
// This deleted child isn't part of the transition so we
|
||||
// don't call any callbacks
|
||||
root.render(<App show={false} />);
|
||||
ReactNoop.expire(1000);
|
||||
await advanceTimers(1000);
|
||||
await waitForAll(['Suspend [Child]']);
|
||||
await waitForAll([
|
||||
'Suspend [Child]',
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend [Child]'] : []),
|
||||
]);
|
||||
|
||||
await resolveText('Child');
|
||||
ReactNoop.expire(1000);
|
||||
@@ -2013,6 +2100,7 @@ describe('ReactInteractionTracing', () => {
|
||||
|
||||
assertLog([
|
||||
'Suspend [Child]',
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend [Child]'] : []),
|
||||
'onTransitionStart(transition one, 0)',
|
||||
'onMarkerProgress(transition one, parent, 0, 1000, [child])',
|
||||
'onTransitionProgress(transition one, 0, 1000, [child])',
|
||||
@@ -2033,6 +2121,9 @@ describe('ReactInteractionTracing', () => {
|
||||
assertLog([
|
||||
'Suspend [Appended child]',
|
||||
'Suspend [Child]',
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Suspend [Appended child]', 'Suspend [Child]']
|
||||
: []),
|
||||
'onTransitionStart(transition two, 1000)',
|
||||
'onMarkerProgress(transition two, appended child, 1000, 2000, [appended child])',
|
||||
'onTransitionProgress(transition two, 1000, 2000, [appended child])',
|
||||
@@ -2046,6 +2137,7 @@ describe('ReactInteractionTracing', () => {
|
||||
|
||||
assertLog([
|
||||
'Suspend [Child]',
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend [Child]'] : []),
|
||||
'onMarkerProgress(transition two, appended child, 1000, 3000, [])',
|
||||
'onMarkerIncomplete(transition two, appended child, 1000, [{endTime: 3000, name: appended child, type: suspense}])',
|
||||
]);
|
||||
@@ -2201,9 +2293,20 @@ describe('ReactInteractionTracing', () => {
|
||||
assertLog([
|
||||
'Suspend [Text]',
|
||||
'Loading...',
|
||||
'Suspend [Hidden Text]',
|
||||
'Hidden Loading...',
|
||||
'onTransitionStart(transition, 0)',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? [
|
||||
'Suspend [Text]',
|
||||
'onTransitionStart(transition, 0)',
|
||||
|
||||
'Suspend [Hidden Text]',
|
||||
'Hidden Loading...',
|
||||
]
|
||||
: [
|
||||
'Suspend [Hidden Text]',
|
||||
'Hidden Loading...',
|
||||
'onTransitionStart(transition, 0)',
|
||||
]),
|
||||
]);
|
||||
|
||||
await act(() => {
|
||||
@@ -2269,6 +2372,7 @@ describe('ReactInteractionTracing', () => {
|
||||
assertLog([
|
||||
'Suspend [Page Two]',
|
||||
'Loading...',
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend [Page Two]'] : []),
|
||||
'onTransitionStart(page transition, 0)',
|
||||
'onTransitionProgress(page transition, 0, 1000, [suspense page])',
|
||||
]);
|
||||
@@ -2342,8 +2446,10 @@ describe('ReactInteractionTracing', () => {
|
||||
'Text',
|
||||
'Suspend [Text Two]',
|
||||
'Loading Two...',
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend [Text Two]'] : []),
|
||||
'onTransitionStart(transition, 0)',
|
||||
'onTransitionProgress(transition, 0, 1000, [two])',
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend [Text Two]'] : []),
|
||||
]);
|
||||
|
||||
await act(() => {
|
||||
@@ -2417,6 +2523,9 @@ describe('ReactInteractionTracing', () => {
|
||||
'Loading one...',
|
||||
'Suspend [Text two]',
|
||||
'Loading two...',
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Suspend [Text one]', 'Suspend [Text two]']
|
||||
: []),
|
||||
'onTransitionStart(transition one, 0) /root one/',
|
||||
'onTransitionProgress(transition one, 0, 1000, [one]) /root one/',
|
||||
'onTransitionStart(transition two, 0) /root two/',
|
||||
|
||||
+15
-16
@@ -191,7 +191,12 @@ describe('ReactUse', () => {
|
||||
await act(() => {
|
||||
root.render(<App />);
|
||||
});
|
||||
assertLog(['Suspend!', 'Loading...']);
|
||||
assertLog([
|
||||
'Suspend!',
|
||||
'Loading...',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['Suspend!'] : []),
|
||||
]);
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
});
|
||||
|
||||
@@ -1060,31 +1065,25 @@ describe('ReactUse', () => {
|
||||
</Suspense>,
|
||||
);
|
||||
});
|
||||
assertLog(['(Loading A...)']);
|
||||
assertLog([
|
||||
'(Loading A...)',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['(Loading C...)', '(Loading B...)']
|
||||
: []),
|
||||
]);
|
||||
expect(root).toMatchRenderedOutput('(Loading A...)');
|
||||
|
||||
await act(() => {
|
||||
resolveTextRequests('A');
|
||||
});
|
||||
assertLog([
|
||||
'A',
|
||||
'(Loading B...)',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['A', '(Loading C...)', '(Loading B...)']
|
||||
: []),
|
||||
]);
|
||||
assertLog(['A', '(Loading B...)']);
|
||||
expect(root).toMatchRenderedOutput('A(Loading B...)');
|
||||
|
||||
await act(() => {
|
||||
resolveTextRequests('B');
|
||||
});
|
||||
assertLog([
|
||||
'B',
|
||||
'(Loading C...)',
|
||||
|
||||
...(gate('enableSiblingPrerendering') ? ['B', '(Loading C...)'] : []),
|
||||
]);
|
||||
assertLog(['B', '(Loading C...)']);
|
||||
expect(root).toMatchRenderedOutput('AB(Loading C...)');
|
||||
|
||||
await act(() => {
|
||||
|
||||
@@ -907,6 +907,10 @@ describe('StrictEffectsMode', () => {
|
||||
'Child suspended',
|
||||
'Fallback',
|
||||
'Fallback',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Child rendered', 'Child suspended']
|
||||
: []),
|
||||
]);
|
||||
|
||||
log = [];
|
||||
@@ -928,6 +932,10 @@ describe('StrictEffectsMode', () => {
|
||||
'Fallback',
|
||||
'Parent dep destroy',
|
||||
'Parent dep create',
|
||||
|
||||
...(gate('enableSiblingPrerendering')
|
||||
? ['Child rendered', 'Child suspended']
|
||||
: []),
|
||||
]);
|
||||
|
||||
log = [];
|
||||
|
||||
Reference in New Issue
Block a user