diff --git a/packages/react-cache/src/__tests__/ReactCacheOld-test.internal.js b/packages/react-cache/src/__tests__/ReactCacheOld-test.internal.js
index c448a25b83179..7589463c49e7b 100644
--- a/packages/react-cache/src/__tests__/ReactCacheOld-test.internal.js
+++ b/packages/react-cache/src/__tests__/ReactCacheOld-test.internal.js
@@ -148,7 +148,15 @@ describe('ReactCache', () => {
error = e;
}
expect(error.message).toMatch('Failed to load: Hi');
- assertLog(['Promise rejected [Hi]', 'Error! [Hi]', 'Error! [Hi]']);
+ assertLog([
+ 'Promise rejected [Hi]',
+ 'Error! [Hi]',
+ 'Error! [Hi]',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Error! [Hi]', 'Error! [Hi]']
+ : []),
+ ]);
// Should throw again on a subsequent read
root.render();
@@ -191,6 +199,7 @@ describe('ReactCache', () => {
}
});
+ // @gate enableSiblingPrerendering
it('evicts least recently used values', async () => {
ReactCache.unstable_setGlobalCacheLimit(3);
@@ -206,15 +215,13 @@ describe('ReactCache', () => {
await waitForAll(['Suspend! [1]', 'Loading...']);
jest.advanceTimersByTime(100);
assertLog(['Promise resolved [1]']);
- await waitForAll([1, 'Suspend! [2]']);
+ await waitForAll([1, 'Suspend! [2]', 1, 'Suspend! [2]', 'Suspend! [3]']);
jest.advanceTimersByTime(100);
- assertLog(['Promise resolved [2]']);
- await waitForAll([1, 2, 'Suspend! [3]']);
+ assertLog(['Promise resolved [2]', 'Promise resolved [3]']);
+ await waitForAll([1, 2, 3]);
await act(() => jest.advanceTimersByTime(100));
- assertLog(['Promise resolved [3]', 1, 2, 3]);
-
expect(root).toMatchRenderedOutput('123');
// Render 1, 4, 5
@@ -234,6 +241,9 @@ describe('ReactCache', () => {
1,
4,
'Suspend! [5]',
+ 1,
+ 4,
+ 'Suspend! [5]',
'Promise resolved [5]',
1,
4,
@@ -267,6 +277,9 @@ describe('ReactCache', () => {
1,
2,
'Suspend! [3]',
+ 1,
+ 2,
+ 'Suspend! [3]',
'Promise resolved [3]',
1,
2,
diff --git a/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js b/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js
index ee843996bef1c..027099d54707c 100644
--- a/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js
@@ -744,7 +744,7 @@ describe('ReactDOMFiberAsync', () => {
// Because it suspended, it remains on the current path
expect(div.textContent).toBe('/path/a');
});
- assertLog([]);
+ assertLog(gate('enableSiblingPrerendering') ? ['Suspend! [/path/b]'] : []);
await act(async () => {
resolvePromise();
diff --git a/packages/react-dom/src/__tests__/ReactDOMForm-test.js b/packages/react-dom/src/__tests__/ReactDOMForm-test.js
index 7ba4bea06bacf..b2a45a4c71b1b 100644
--- a/packages/react-dom/src/__tests__/ReactDOMForm-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMForm-test.js
@@ -699,7 +699,15 @@ describe('ReactDOMForm', () => {
// This should suspend because form actions are implicitly wrapped
// in startTransition.
await submit(formRef.current);
- assertLog(['Pending...', 'Suspend! [Updated]', 'Loading...']);
+ assertLog([
+ 'Pending...',
+ 'Suspend! [Updated]',
+ 'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Suspend! [Updated]', 'Loading...']
+ : []),
+ ]);
expect(container.textContent).toBe('Pending...Initial');
await act(() => resolveText('Updated'));
@@ -736,7 +744,15 @@ describe('ReactDOMForm', () => {
// Update
await submit(formRef.current);
- assertLog(['Pending...', 'Suspend! [Count: 1]', 'Loading...']);
+ assertLog([
+ 'Pending...',
+ 'Suspend! [Count: 1]',
+ 'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Suspend! [Count: 1]', 'Loading...']
+ : []),
+ ]);
expect(container.textContent).toBe('Pending...Count: 0');
await act(() => resolveText('Count: 1'));
@@ -745,7 +761,15 @@ describe('ReactDOMForm', () => {
// Update again
await submit(formRef.current);
- assertLog(['Pending...', 'Suspend! [Count: 2]', 'Loading...']);
+ assertLog([
+ 'Pending...',
+ 'Suspend! [Count: 2]',
+ 'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Suspend! [Count: 2]', 'Loading...']
+ : []),
+ ]);
expect(container.textContent).toBe('Pending...Count: 1');
await act(() => resolveText('Count: 2'));
@@ -789,7 +813,14 @@ describe('ReactDOMForm', () => {
assertLog(['Async action started', 'Pending...']);
await act(() => resolveText('Wait'));
- assertLog(['Suspend! [Updated]', 'Loading...']);
+ assertLog([
+ 'Suspend! [Updated]',
+ 'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Suspend! [Updated]', 'Loading...']
+ : []),
+ ]);
expect(container.textContent).toBe('Pending...Initial');
await act(() => resolveText('Updated'));
@@ -1475,7 +1506,15 @@ describe('ReactDOMForm', () => {
// Now dispatch inside of a transition. This one does not trigger a
// loading state.
await act(() => startTransition(() => dispatch()));
- assertLog(['Count: 1', 'Suspend! [Count: 2]', 'Loading...']);
+ assertLog([
+ 'Count: 1',
+ 'Suspend! [Count: 2]',
+ 'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Suspend! [Count: 2]', 'Loading...']
+ : []),
+ ]);
expect(container.textContent).toBe('Count: 1');
await act(() => resolveText('Count: 2'));
@@ -1495,7 +1534,11 @@ describe('ReactDOMForm', () => {
const root = ReactDOMClient.createRoot(container);
await act(() => root.render());
- assertLog(['Suspend! [Count: 0]']);
+ assertLog([
+ 'Suspend! [Count: 0]',
+
+ ...(gate('enableSiblingPrerendering') ? ['Suspend! [Count: 0]'] : []),
+ ]);
await act(() => resolveText('Count: 0'));
assertLog(['Count: 0']);
@@ -1508,7 +1551,11 @@ describe('ReactDOMForm', () => {
{withoutStack: true},
],
]);
- assertLog(['Suspend! [Count: 1]']);
+ assertLog([
+ 'Suspend! [Count: 1]',
+
+ ...(gate('enableSiblingPrerendering') ? ['Suspend! [Count: 1]'] : []),
+ ]);
expect(container.textContent).toBe('Count: 0');
});
diff --git a/packages/react-reconciler/src/ReactFiberLane.js b/packages/react-reconciler/src/ReactFiberLane.js
index 6ed4caae70974..2995548fbbee2 100644
--- a/packages/react-reconciler/src/ReactFiberLane.js
+++ b/packages/react-reconciler/src/ReactFiberLane.js
@@ -229,28 +229,49 @@ export function getNextLanes(root: FiberRoot, wipLanes: Lanes): Lanes {
const suspendedLanes = root.suspendedLanes;
const pingedLanes = root.pingedLanes;
+ const warmLanes = root.warmLanes;
// 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;
if (nonIdlePendingLanes !== NoLanes) {
+ // First check for fresh updates.
const nonIdleUnblockedLanes = nonIdlePendingLanes & ~suspendedLanes;
if (nonIdleUnblockedLanes !== NoLanes) {
nextLanes = getHighestPriorityLanes(nonIdleUnblockedLanes);
} else {
+ // No fresh updates. Check if suspended work has been pinged.
const nonIdlePingedLanes = nonIdlePendingLanes & pingedLanes;
if (nonIdlePingedLanes !== NoLanes) {
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);
+ }
}
}
} else {
// The only remaining work is Idle.
+ // TODO: Idle isn't really used anywhere, and the thinking around
+ // speculative rendering has evolved since this was implemented. Consider
+ // removing until we've thought about this again.
+
+ // First check for fresh updates.
const unblockedLanes = pendingLanes & ~suspendedLanes;
if (unblockedLanes !== NoLanes) {
nextLanes = getHighestPriorityLanes(unblockedLanes);
} else {
+ // No fresh updates. Check if suspended work has been pinged.
if (pingedLanes !== NoLanes) {
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);
+ }
}
}
}
@@ -335,6 +356,21 @@ export function getNextLanesToFlushSync(
return NoLanes;
}
+export function checkIfRootIsPrerendering(
+ root: FiberRoot,
+ renderLanes: Lanes,
+): boolean {
+ const pendingLanes = root.pendingLanes;
+ const suspendedLanes = root.suspendedLanes;
+ const pingedLanes = root.pingedLanes;
+ // Remove lanes that are suspended (but not pinged)
+ const unblockedLanes = pendingLanes & ~(suspendedLanes & ~pingedLanes);
+
+ // If there are no unsuspended or pinged lanes, that implies that we're
+ // performing a prerender.
+ return (unblockedLanes & renderLanes) === 0;
+}
+
export function getEntangledLanes(root: FiberRoot, renderLanes: Lanes): Lanes {
let entangledLanes = renderLanes;
@@ -670,6 +706,7 @@ export function markRootUpdated(root: FiberRoot, updateLane: Lane) {
if (updateLane !== IdleLane) {
root.suspendedLanes = NoLanes;
root.pingedLanes = NoLanes;
+ root.warmLanes = NoLanes;
}
}
@@ -677,10 +714,19 @@ export function markRootSuspended(
root: FiberRoot,
suspendedLanes: Lanes,
spawnedLane: Lane,
+ didSkipSuspendedSiblings: boolean,
) {
root.suspendedLanes |= suspendedLanes;
root.pingedLanes &= ~suspendedLanes;
+ if (!didSkipSuspendedSiblings) {
+ // Mark these lanes as warm so we know there's nothing else to work on.
+ root.warmLanes |= suspendedLanes;
+ } else {
+ // Render unwound without attempting all the siblings. Do no mark the lanes
+ // as warm. This will cause a prewarm render to be scheduled.
+ }
+
// The suspended lanes are no longer CPU-bound. Clear their expiration times.
const expirationTimes = root.expirationTimes;
let lanes = suspendedLanes;
@@ -700,6 +746,9 @@ export function markRootSuspended(
export function markRootPinged(root: FiberRoot, pingedLanes: Lanes) {
root.pingedLanes |= root.suspendedLanes & pingedLanes;
+ // The data that just resolved could have unblocked additional children, which
+ // will also need to be prewarmed if something suspends again.
+ root.warmLanes &= ~pingedLanes;
}
export function markRootFinished(
@@ -714,6 +763,7 @@ export function markRootFinished(
// Let's try everything again
root.suspendedLanes = NoLanes;
root.pingedLanes = NoLanes;
+ root.warmLanes = NoLanes;
root.expiredLanes &= remainingLanes;
diff --git a/packages/react-reconciler/src/ReactFiberRoot.js b/packages/react-reconciler/src/ReactFiberRoot.js
index 27590743ad79f..f62a5d7158db1 100644
--- a/packages/react-reconciler/src/ReactFiberRoot.js
+++ b/packages/react-reconciler/src/ReactFiberRoot.js
@@ -75,6 +75,7 @@ function FiberRootNode(
this.pendingLanes = NoLanes;
this.suspendedLanes = NoLanes;
this.pingedLanes = NoLanes;
+ this.warmLanes = NoLanes;
this.expiredLanes = NoLanes;
this.finishedLanes = NoLanes;
this.errorRecoveryDisabledLanes = NoLanes;
diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js
index 3190cd0035b33..bdba919c79243 100644
--- a/packages/react-reconciler/src/ReactFiberWorkLoop.js
+++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js
@@ -161,6 +161,7 @@ import {
SyncUpdateLanes,
UpdateLanes,
claimNextTransitionLane,
+ checkIfRootIsPrerendering,
} from './ReactFiberLane';
import {
DiscreteEventPriority,
@@ -329,6 +330,15 @@ const SuspendedOnHydration: SuspendedReason = 8;
let workInProgressSuspendedReason: SuspendedReason = NotSuspended;
let workInProgressThrownValue: mixed = null;
+// Tracks whether any siblings were skipped during the unwind phase after
+// something suspends. Used to determine whether to schedule another render
+// to prewarm the skipped siblings.
+let workInProgressRootDidSkipSuspendedSiblings: boolean = false;
+// Whether the work-in-progress render is the result of a prewarm/prerender.
+// This tells us whether or not we should render the siblings after
+// something suspends.
+let workInProgressRootIsPrerendering: boolean = false;
+
// Whether a ping listener was attached during this render. This is slightly
// different that whether something suspended, because we don't add multiple
// listeners to a promise we've already seen (per root and lane).
@@ -731,6 +741,7 @@ export function scheduleUpdateOnFiber(
root,
workInProgressRootRenderLanes,
workInProgressDeferredLane,
+ workInProgressRootDidSkipSuspendedSiblings,
);
}
@@ -797,6 +808,7 @@ export function scheduleUpdateOnFiber(
root,
workInProgressRootRenderLanes,
workInProgressDeferredLane,
+ workInProgressRootDidSkipSuspendedSiblings,
);
}
}
@@ -909,7 +921,12 @@ export function performConcurrentWorkOnRoot(
// The render unwound without completing the tree. This happens in special
// cases where need to exit the current render without producing a
// consistent tree or committing.
- markRootSuspended(root, lanes, NoLane);
+ markRootSuspended(
+ root,
+ lanes,
+ NoLane,
+ workInProgressRootDidSkipSuspendedSiblings,
+ );
} else {
// The render completed.
@@ -965,7 +982,12 @@ export function performConcurrentWorkOnRoot(
}
if (exitStatus === RootFatalErrored) {
prepareFreshStack(root, NoLanes);
- markRootSuspended(root, lanes, NoLane);
+ markRootSuspended(
+ root,
+ lanes,
+ NoLane,
+ workInProgressRootDidSkipSuspendedSiblings,
+ );
break;
}
@@ -1088,7 +1110,12 @@ function finishConcurrentRender(
// This is a transition, so we should exit without committing a
// placeholder and without scheduling a timeout. Delay indefinitely
// until we receive more data.
- markRootSuspended(root, lanes, workInProgressDeferredLane);
+ markRootSuspended(
+ root,
+ lanes,
+ workInProgressDeferredLane,
+ workInProgressRootDidSkipSuspendedSiblings,
+ );
return;
}
// Commit the placeholder.
@@ -1132,7 +1159,12 @@ function finishConcurrentRender(
// Don't bother with a very short suspense time.
if (msUntilTimeout > 10) {
- markRootSuspended(root, lanes, workInProgressDeferredLane);
+ markRootSuspended(
+ root,
+ lanes,
+ workInProgressDeferredLane,
+ workInProgressRootDidSkipSuspendedSiblings,
+ );
const nextLanes = getNextLanes(root, NoLanes);
if (nextLanes !== NoLanes) {
@@ -1156,6 +1188,7 @@ function finishConcurrentRender(
workInProgressRootDidIncludeRecursiveRenderUpdate,
lanes,
workInProgressDeferredLane,
+ workInProgressRootDidSkipSuspendedSiblings,
),
msUntilTimeout,
);
@@ -1170,6 +1203,7 @@ function finishConcurrentRender(
workInProgressRootDidIncludeRecursiveRenderUpdate,
lanes,
workInProgressDeferredLane,
+ workInProgressRootDidSkipSuspendedSiblings,
);
}
}
@@ -1182,6 +1216,7 @@ function commitRootWhenReady(
didIncludeRenderPhaseUpdate: boolean,
lanes: Lanes,
spawnedLane: Lane,
+ didSkipSuspendedSiblings: boolean,
) {
// TODO: Combine retry throttling with Suspensey commits. Right now they run
// one after the other.
@@ -1221,7 +1256,7 @@ function commitRootWhenReady(
didIncludeRenderPhaseUpdate,
),
);
- markRootSuspended(root, lanes, spawnedLane);
+ markRootSuspended(root, lanes, spawnedLane, didSkipSuspendedSiblings);
return;
}
}
@@ -1333,6 +1368,7 @@ function markRootSuspended(
root: FiberRoot,
suspendedLanes: Lanes,
spawnedLane: Lane,
+ didSkipSuspendedSiblings: boolean,
) {
// When suspending, we should always exclude lanes that were pinged or (more
// rarely, since we try to avoid it) updated during the render phase.
@@ -1341,7 +1377,12 @@ function markRootSuspended(
suspendedLanes,
workInProgressRootInterleavedUpdatedLanes,
);
- _markRootSuspended(root, suspendedLanes, spawnedLane);
+ _markRootSuspended(
+ root,
+ suspendedLanes,
+ spawnedLane,
+ didSkipSuspendedSiblings,
+ );
}
// This is the entry point for synchronous tasks that don't go
@@ -1393,7 +1434,7 @@ export function performSyncWorkOnRoot(root: FiberRoot, lanes: Lanes): null {
if (exitStatus === RootFatalErrored) {
prepareFreshStack(root, NoLanes);
- markRootSuspended(root, lanes, NoLane);
+ markRootSuspended(root, lanes, NoLane, false);
ensureRootIsScheduled(root);
return null;
}
@@ -1402,7 +1443,12 @@ export function performSyncWorkOnRoot(root: FiberRoot, lanes: Lanes): null {
// The render unwound without completing the tree. This happens in special
// cases where need to exit the current render without producing a
// consistent tree or committing.
- markRootSuspended(root, lanes, workInProgressDeferredLane);
+ markRootSuspended(
+ root,
+ lanes,
+ workInProgressDeferredLane,
+ workInProgressRootDidSkipSuspendedSiblings,
+ );
ensureRootIsScheduled(root);
return null;
}
@@ -1636,6 +1682,8 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
workInProgressRootRenderLanes = lanes;
workInProgressSuspendedReason = NotSuspended;
workInProgressThrownValue = null;
+ workInProgressRootDidSkipSuspendedSiblings = false;
+ workInProgressRootIsPrerendering = checkIfRootIsPrerendering(root, lanes);
workInProgressRootDidAttachPingListener = false;
workInProgressRootExitStatus = RootInProgress;
workInProgressRootSkippedLanes = NoLanes;
@@ -1940,6 +1988,7 @@ export function renderDidSuspendDelayIfPossible(): void {
workInProgressRoot,
workInProgressRootRenderLanes,
workInProgressDeferredLane,
+ workInProgressRootDidSkipSuspendedSiblings,
);
}
}
@@ -2589,7 +2638,30 @@ function throwAndUnwindWorkLoop(
if (unitOfWork.flags & Incomplete) {
// Unwind the stack until we reach the nearest boundary.
- unwindUnitOfWork(unitOfWork);
+ let skipSiblings;
+ if (!enableSiblingPrerendering) {
+ skipSiblings = true;
+ } else {
+ if (workInProgressRootIsPrerendering) {
+ // This is a prerender. Don't skip the siblings.
+ skipSiblings = false;
+ } else if (
+ // The currenty algorithm for both hydration and error handling assumes
+ // that the tree is rendered sequentially. So we always skip the siblings.
+ getIsHydrating() ||
+ workInProgressSuspendedReason === SuspendedOnError
+ ) {
+ skipSiblings = true;
+ // We intentionally don't set workInProgressRootDidSkipSuspendedSiblings,
+ // because we don't want to trigger another prerender attempt.
+ } else {
+ // This is not a prerender. Skip the siblings during this render. A
+ // separate prerender will be scheduled for later.
+ skipSiblings = true;
+ workInProgressRootDidSkipSuspendedSiblings = true;
+ }
+ }
+ unwindUnitOfWork(unitOfWork, skipSiblings);
} else {
// Although the fiber suspended, we're intentionally going to commit it in
// an inconsistent state. We can do this safely in cases where we know the
@@ -2625,15 +2697,16 @@ function completeUnitOfWork(unitOfWork: Fiber): void {
// sibling. If there are no more siblings, return to the parent fiber.
let completedWork: Fiber = unitOfWork;
do {
- if (__DEV__) {
- if ((completedWork.flags & Incomplete) !== NoFlags) {
- // NOTE: If we re-enable sibling prerendering in some cases, this branch
- // is where we would switch to the unwinding path.
- console.error(
- 'Internal React error: Expected this fiber to be complete, but ' +
- "it isn't. It should have been unwound. This is a bug in React.",
- );
- }
+ if ((completedWork.flags & Incomplete) !== NoFlags) {
+ // This fiber did not complete, because one of its children did not
+ // complete. Switch to unwinding the stack instead of completing it.
+ //
+ // The reason "unwind" and "complete" is interleaved is because when
+ // something suspends, we continue rendering the siblings even though
+ // they will be replaced by a fallback.
+ const skipSiblings = workInProgressRootDidSkipSuspendedSiblings;
+ unwindUnitOfWork(completedWork, skipSiblings);
+ return;
}
// The current, flushed, state of this fiber is the alternate. Ideally
@@ -2697,7 +2770,7 @@ function completeUnitOfWork(unitOfWork: Fiber): void {
}
}
-function unwindUnitOfWork(unitOfWork: Fiber): void {
+function unwindUnitOfWork(unitOfWork: Fiber, skipSiblings: boolean): void {
let incompleteWork: Fiber = unitOfWork;
do {
// The current, flushed, state of this fiber is the alternate. Ideally
@@ -2754,9 +2827,14 @@ function unwindUnitOfWork(unitOfWork: Fiber): void {
returnFiber.deletions = null;
}
- // NOTE: If we re-enable sibling prerendering in some cases, here we
- // would switch to the normal completion path: check if a sibling
- // exists, and if so, begin work on it.
+ if (!skipSiblings) {
+ const siblingFiber = incompleteWork.sibling;
+ if (siblingFiber !== null) {
+ // This branch will return us to the normal work loop.
+ workInProgress = siblingFiber;
+ return;
+ }
+ }
// Otherwise, return to the parent
// $FlowFixMe[incompatible-type] we bail out when we get a null
diff --git a/packages/react-reconciler/src/ReactInternalTypes.js b/packages/react-reconciler/src/ReactInternalTypes.js
index 871325f379385..d160aa0d228f5 100644
--- a/packages/react-reconciler/src/ReactInternalTypes.js
+++ b/packages/react-reconciler/src/ReactInternalTypes.js
@@ -256,6 +256,7 @@ type BaseFiberRootProperties = {
pendingLanes: Lanes,
suspendedLanes: Lanes,
pingedLanes: Lanes,
+ warmLanes: Lanes,
expiredLanes: Lanes,
errorRecoveryDisabledLanes: Lanes,
shellSuspendCounter: number,
diff --git a/packages/react-reconciler/src/__tests__/ActivitySuspense-test.js b/packages/react-reconciler/src/__tests__/ActivitySuspense-test.js
index d02ae70e523ca..c2a29c8f2139f 100644
--- a/packages/react-reconciler/src/__tests__/ActivitySuspense-test.js
+++ b/packages/react-reconciler/src/__tests__/ActivitySuspense-test.js
@@ -215,7 +215,15 @@ describe('Activity Suspense', () => {
);
});
});
- assertLog(['Open', 'Suspend! [Async]', 'Loading...']);
+ assertLog([
+ 'Open',
+ 'Suspend! [Async]',
+ 'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Open', 'Suspend! [Async]', 'Loading...']
+ : []),
+ ]);
// It should suspend with delay to prevent the already-visible Suspense
// boundary from switching to a fallback
expect(root).toMatchRenderedOutput(Closed);
@@ -276,7 +284,15 @@ describe('Activity Suspense', () => {
);
});
});
- assertLog(['Open', 'Suspend! [Async]', 'Loading...']);
+ assertLog([
+ 'Open',
+ 'Suspend! [Async]',
+ 'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Open', 'Suspend! [Async]', 'Loading...']
+ : []),
+ ]);
// It should suspend with delay to prevent the already-visible Suspense
// boundary from switching to a fallback
expect(root).toMatchRenderedOutput(
diff --git a/packages/react-reconciler/src/__tests__/ReactActWarnings-test.js b/packages/react-reconciler/src/__tests__/ReactActWarnings-test.js
index 03e04e8b1dc40..21fb6727527ab 100644
--- a/packages/react-reconciler/src/__tests__/ReactActWarnings-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactActWarnings-test.js
@@ -349,7 +349,14 @@ describe('act warnings', () => {
root.render();
});
});
- assertLog(['Suspend! [Async]', 'Loading...']);
+ assertLog([
+ 'Suspend! [Async]',
+ 'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Suspend! [Async]', 'Loading...']
+ : []),
+ ]);
expect(root).toMatchRenderedOutput('(empty)');
// This is a ping, not a retry, because no fallback is showing.
diff --git a/packages/react-reconciler/src/__tests__/ReactAsyncActions-test.js b/packages/react-reconciler/src/__tests__/ReactAsyncActions-test.js
index ad4c2d5e9737a..4cf830876b642 100644
--- a/packages/react-reconciler/src/__tests__/ReactAsyncActions-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactAsyncActions-test.js
@@ -297,7 +297,15 @@ describe('ReactAsyncActions', () => {
// This will schedule an update on C, and also the async action scope
// will end. This will allow React to attempt to render the updates.
await act(() => resolveText('Wait before updating C'));
- assertLog(['Async action ended', 'Pending: false', 'Suspend! [A1]']);
+ assertLog([
+ 'Async action ended',
+ 'Pending: false',
+ 'Suspend! [A1]',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Pending: false', 'Suspend! [A1]', 'Suspend! [B1]', 'Suspend! [C1]']
+ : []),
+ ]);
expect(root).toMatchRenderedOutput(
<>
Pending: true
@@ -309,7 +317,15 @@ describe('ReactAsyncActions', () => {
// together, only when the all of A, B, and C updates are unblocked is the
// render allowed to proceed.
await act(() => resolveText('A1'));
- assertLog(['Pending: false', 'A1', 'Suspend! [B1]']);
+ assertLog([
+ 'Pending: false',
+ 'A1',
+ 'Suspend! [B1]',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Pending: false', 'A1', 'Suspend! [B1]', 'Suspend! [C1]']
+ : []),
+ ]);
expect(root).toMatchRenderedOutput(
<>
Pending: true
@@ -317,7 +333,16 @@ describe('ReactAsyncActions', () => {
>,
);
await act(() => resolveText('B1'));
- assertLog(['Pending: false', 'A1', 'B1', 'Suspend! [C1]']);
+ assertLog([
+ 'Pending: false',
+ 'A1',
+ 'B1',
+ 'Suspend! [C1]',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Pending: false', 'A1', 'B1', 'Suspend! [C1]']
+ : []),
+ ]);
expect(root).toMatchRenderedOutput(
<>
Pending: true
@@ -690,6 +715,10 @@ describe('ReactAsyncActions', () => {
// automatically reverted.
'Pending: false',
'Suspend! [B]',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Pending: false', 'Suspend! [B]']
+ : []),
]);
// Resolve the transition
diff --git a/packages/react-reconciler/src/__tests__/ReactCPUSuspense-test.js b/packages/react-reconciler/src/__tests__/ReactCPUSuspense-test.js
index d640f87205e45..b36164fca285b 100644
--- a/packages/react-reconciler/src/__tests__/ReactCPUSuspense-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactCPUSuspense-test.js
@@ -231,7 +231,11 @@ describe('ReactSuspenseWithNoopRenderer', () => {
);
});
// Inner contents suspended, so we continue showing a fallback.
- assertLog(['Suspend! [Inner]']);
+ assertLog([
+ 'Suspend! [Inner]',
+
+ ...(gate('enableSiblingPrerendering') ? ['Suspend! [Inner]'] : []),
+ ]);
expect(root).toMatchRenderedOutput(
<>
Outer
diff --git a/packages/react-reconciler/src/__tests__/ReactConcurrentErrorRecovery-test.js b/packages/react-reconciler/src/__tests__/ReactConcurrentErrorRecovery-test.js
index 1af61323fc941..cec34dc722799 100644
--- a/packages/react-reconciler/src/__tests__/ReactConcurrentErrorRecovery-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactConcurrentErrorRecovery-test.js
@@ -209,7 +209,16 @@ describe('ReactConcurrentErrorRecovery', () => {
root.render();
});
});
- assertLog(['Suspend! [A2]', 'Loading...', 'Suspend! [B2]', 'Loading...']);
+ assertLog([
+ 'Suspend! [A2]',
+ 'Loading...',
+ 'Suspend! [B2]',
+ 'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Suspend! [A2]', 'Loading...', 'Suspend! [B2]', 'Loading...']
+ : []),
+ ]);
// Because this is a refresh, we don't switch to a fallback
expect(root).toMatchRenderedOutput('A1B1');
@@ -220,7 +229,16 @@ describe('ReactConcurrentErrorRecovery', () => {
// Because we're still suspended on A, we can't show an error boundary. We
// should wait for A to resolve.
- assertLog(['Suspend! [A2]', 'Loading...', 'Error! [B2]', 'Oops!']);
+ assertLog([
+ 'Suspend! [A2]',
+ 'Loading...',
+ 'Error! [B2]',
+ 'Oops!',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Suspend! [A2]', 'Loading...', 'Error! [B2]', 'Oops!']
+ : []),
+ ]);
// Remain on previous screen.
expect(root).toMatchRenderedOutput('A1B1');
@@ -281,7 +299,16 @@ describe('ReactConcurrentErrorRecovery', () => {
root.render();
});
});
- assertLog(['Suspend! [A2]', 'Loading...', 'Suspend! [B2]', 'Loading...']);
+ assertLog([
+ 'Suspend! [A2]',
+ 'Loading...',
+ 'Suspend! [B2]',
+ 'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Suspend! [A2]', 'Loading...', 'Suspend! [B2]', 'Loading...']
+ : []),
+ ]);
// Because this is a refresh, we don't switch to a fallback
expect(root).toMatchRenderedOutput('A1B1');
@@ -292,7 +319,16 @@ 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...']);
+ assertLog([
+ 'Error! [A2]',
+ 'Oops!',
+ 'Suspend! [B2]',
+ 'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Error! [A2]', 'Oops!', 'Suspend! [B2]', 'Loading...']
+ : []),
+ ]);
// Remain on previous screen.
expect(root).toMatchRenderedOutput('A1B1');
@@ -328,7 +364,11 @@ describe('ReactConcurrentErrorRecovery', () => {
root.render();
});
});
- assertLog(['Suspend! [Async]']);
+ assertLog([
+ 'Suspend! [Async]',
+
+ ...(gate('enableSiblingPrerendering') ? ['Suspend! [Async]'] : []),
+ ]);
expect(root).toMatchRenderedOutput(null);
// This also works if the suspended component is wrapped with an error
@@ -344,7 +384,11 @@ describe('ReactConcurrentErrorRecovery', () => {
);
});
});
- assertLog(['Suspend! [Async]']);
+ assertLog([
+ 'Suspend! [Async]',
+
+ ...(gate('enableSiblingPrerendering') ? ['Suspend! [Async]'] : []),
+ ]);
expect(root).toMatchRenderedOutput(null);
// Continues rendering once data resolves
@@ -397,8 +441,14 @@ describe('ReactConcurrentErrorRecovery', () => {
);
});
});
- assertLog(['Suspend! [Async]']);
- // The render suspended without committing or surfacing the error.
+ assertLog([
+ 'Suspend! [Async]',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Suspend! [Async]', 'Caught an error: Oops!']
+ : []),
+ ]);
+ // The render suspended without committing the error.
expect(root).toMatchRenderedOutput(null);
// Try the reverse order, too: throw then suspend
@@ -414,7 +464,13 @@ describe('ReactConcurrentErrorRecovery', () => {
);
});
});
- assertLog(['Suspend! [Async]']);
+ assertLog([
+ 'Suspend! [Async]',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Suspend! [Async]', 'Caught an error: Oops!']
+ : []),
+ ]);
expect(root).toMatchRenderedOutput(null);
await act(async () => {
diff --git a/packages/react-reconciler/src/__tests__/ReactDeferredValue-test.js b/packages/react-reconciler/src/__tests__/ReactDeferredValue-test.js
index b2c38696cc028..bf4ecd02eb3be 100644
--- a/packages/react-reconciler/src/__tests__/ReactDeferredValue-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactDeferredValue-test.js
@@ -420,6 +420,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(null);
@@ -459,6 +461,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(null);
@@ -531,6 +535,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(null);
@@ -540,6 +546,8 @@ describe('ReactDeferredValue', () => {
'Loading...',
// Still waiting for the final value.
'Suspend! [Final]',
+
+ ...(gate('enableSiblingPrerendering') ? ['Suspend! [Final]'] : []),
]);
expect(root).toMatchRenderedOutput('Loading...');
@@ -584,6 +592,8 @@ describe('ReactDeferredValue', () => {
// boundaries work, where we always prefer to show the innermost
// loading state.)
'Suspend! [Content]',
+
+ ...(gate('enableSiblingPrerendering') ? ['Suspend! [Content]'] : []),
]);
// Still showing the App preview state because the inner
// content suspended.
diff --git a/packages/react-reconciler/src/__tests__/ReactExpiration-test.js b/packages/react-reconciler/src/__tests__/ReactExpiration-test.js
index 1d659cae3c3bf..50fc16770916f 100644
--- a/packages/react-reconciler/src/__tests__/ReactExpiration-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactExpiration-test.js
@@ -652,7 +652,14 @@ describe('ReactExpiration', () => {
React.startTransition(() => {
root.render();
});
- await waitForAll(['Suspend! [A1]', 'Loading...']);
+ await waitForAll([
+ 'Suspend! [A1]',
+ 'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Suspend! [A1]', 'B', 'C', 'Loading...']
+ : []),
+ ]);
// Lots of time elapses before the promise resolves
Scheduler.unstable_advanceTime(10000);
diff --git a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js
index b0d1182278135..640a3d467e499 100644
--- a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js
@@ -652,14 +652,22 @@ describe('ReactHooksWithNoopRenderer', () => {
React.startTransition(() => {
root.render();
});
- await waitForAll(['Suspend!']);
+ await waitForAll([
+ 'Suspend!',
+
+ ...(gate('enableSiblingPrerendering') ? ['Suspend!'] : []),
+ ]);
expect(root).toMatchRenderedOutput();
// Rendering again should suspend again.
React.startTransition(() => {
root.render();
});
- await waitForAll(['Suspend!']);
+ await waitForAll([
+ 'Suspend!',
+
+ ...(gate('enableSiblingPrerendering') ? ['Suspend!'] : []),
+ ]);
});
it('discards render phase updates if something suspends, but not other updates in the same component', async () => {
@@ -709,14 +717,22 @@ describe('ReactHooksWithNoopRenderer', () => {
setLabel('B');
});
- await waitForAll(['Suspend!']);
+ await waitForAll([
+ 'Suspend!',
+
+ ...(gate('enableSiblingPrerendering') ? ['Suspend!'] : []),
+ ]);
expect(root).toMatchRenderedOutput();
// Rendering again should suspend again.
React.startTransition(() => {
root.render();
});
- await waitForAll(['Suspend!']);
+ await waitForAll([
+ 'Suspend!',
+
+ ...(gate('enableSiblingPrerendering') ? ['Suspend!'] : []),
+ ]);
// Flip the signal back to "cancel" the update. However, the update to
// label should still proceed. It shouldn't have been dropped.
@@ -3495,6 +3511,13 @@ describe('ReactHooksWithNoopRenderer', () => {
'Before... Pending: true',
'Suspend! [After... Pending: false]',
'Loading... Pending: false',
+
+ ...(gate('enableSiblingPrerendering')
+ ? [
+ 'Suspend! [After... Pending: false]',
+ 'Loading... Pending: false',
+ ]
+ : []),
]);
expect(ReactNoop).toMatchRenderedOutput(
,
@@ -3563,7 +3586,17 @@ describe('ReactHooksWithNoopRenderer', () => {
await act(async () => {
_setText('B');
- await waitForAll(['B', 'A', 'B', 'Suspend! [B]', 'Loading']);
+ await waitForAll([
+ 'B',
+ 'A',
+ 'B',
+ 'Suspend! [B]',
+ 'Loading',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['B', 'Suspend! [B]', 'Loading']
+ : []),
+ ]);
await waitForAll([]);
expect(ReactNoop).toMatchRenderedOutput(
<>
@@ -4201,7 +4234,13 @@ describe('ReactHooksWithNoopRenderer', () => {
await act(async () => {
await resolveText('A');
});
- assertLog(['Promise resolved [A]', 'A', 'Suspend! [B]']);
+ assertLog([
+ 'Promise resolved [A]',
+ 'A',
+ 'Suspend! [B]',
+
+ ...(gate('enableSiblingPrerendering') ? ['A', 'Suspend! [B]'] : []),
+ ]);
await act(() => {
root.render(null);
diff --git a/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js b/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
index 22ffbfc4f0cb4..b6c422749683a 100644
--- a/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
@@ -198,7 +198,11 @@ describe('ReactLazy', () => {
await resolveFakeImport(Foo);
- await waitForAll(['Foo']);
+ await waitForAll([
+ 'Foo',
+
+ ...(gate('enableSiblingPrerendering') ? ['Foo'] : []),
+ ]);
expect(root).not.toMatchRenderedOutput('FooBar');
await act(() => resolveFakeImport(Bar));
@@ -235,6 +239,13 @@ 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');
});
@@ -1072,7 +1083,7 @@ describe('ReactLazy', () => {
expect(ref.current).toBe(null);
await act(() => resolveFakeImport(Foo));
- assertLog(['Foo']);
+ assertLog(['Foo', ...(gate('enableSiblingPrerendering') ? ['Foo'] : [])]);
await act(() => resolveFakeImport(ForwardRefBar));
assertLog(['Foo', 'forwardRef', 'Bar']);
@@ -1388,7 +1399,12 @@ describe('ReactLazy', () => {
expect(root).not.toMatchRenderedOutput('AB');
await act(() => resolveFakeImport(ChildA));
- assertLog(['A', 'Init B']);
+ assertLog([
+ 'A',
+ 'Init B',
+
+ ...(gate('enableSiblingPrerendering') ? ['A'] : []),
+ ]);
await act(() => resolveFakeImport(ChildB));
assertLog(['A', 'B', 'Did mount: A', 'Did mount: B']);
@@ -1472,10 +1488,20 @@ describe('ReactLazy', () => {
React.startTransition(() => {
root.update();
});
- await waitForAll(['Init B2', 'Loading...']);
+ await waitForAll([
+ 'Init B2',
+ 'Loading...',
+
+ ...(gate('enableSiblingPrerendering') ? ['Loading...'] : []),
+ ]);
await act(() => resolveFakeImport(ChildB2));
// We need to flush to trigger the second one to load.
- assertLog(['Init A2', 'Loading...']);
+ assertLog([
+ 'Init A2',
+ 'Loading...',
+
+ ...(gate('enableSiblingPrerendering') ? ['Loading...'] : []),
+ ]);
await act(() => resolveFakeImport(ChildA2));
assertLog(['b', 'a', 'Did update: b', 'Did update: a']);
expect(root).toMatchRenderedOutput('ba');
diff --git a/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js b/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js
index 88c6e38160eea..164d3a9599eee 100644
--- a/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js
@@ -136,6 +136,10 @@ describe('ReactSuspense', () => {
// A suspends
'Suspend! [A]',
'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Foo', 'Bar', 'Suspend! [A]', 'B', 'Loading...']
+ : []),
]);
expect(container.textContent).toEqual('');
@@ -275,7 +279,15 @@ describe('ReactSuspense', () => {
expect(container.textContent).toEqual('Loading...');
await resolveText('A');
- await waitForAll(['A', 'Suspend! [B]', 'Loading more...']);
+ await waitForAll([
+ 'A',
+ 'Suspend! [B]',
+ 'Loading more...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['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 +339,14 @@ 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...']);
+ await waitForAll([
+ 'Suspend! [B]',
+ 'Loading more...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['A', 'Suspend! [B]', 'Loading more...']
+ : []),
+ ]);
expect(container.textContent).toEqual('Loading...');
// !! B could have finished before the throttle, but we show a fallback.
@@ -361,7 +380,15 @@ describe('ReactSuspense', () => {
expect(container.textContent).toEqual('Loading...');
await resolveText('A');
- await waitForAll(['A', 'Suspend! [B]', 'Loading more...']);
+ await waitForAll([
+ 'A',
+ 'Suspend! [B]',
+ 'Loading more...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['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
@@ -655,7 +682,14 @@ describe('ReactSuspense', () => {
assertLog(['Suspend! [Child 1]', 'Loading...']);
await resolveText('Child 1');
- await waitForAll(['Child 1', 'Suspend! [Child 2]']);
+ await waitForAll([
+ 'Child 1',
+ 'Suspend! [Child 2]',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Child 1', 'Suspend! [Child 2]']
+ : []),
+ ]);
jest.advanceTimersByTime(6000);
diff --git a/packages/react-reconciler/src/__tests__/ReactSuspenseEffectsSemantics-test.js b/packages/react-reconciler/src/__tests__/ReactSuspenseEffectsSemantics-test.js
index e0137575164b0..688ff38a74c0a 100644
--- a/packages/react-reconciler/src/__tests__/ReactSuspenseEffectsSemantics-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactSuspenseEffectsSemantics-test.js
@@ -1150,7 +1150,19 @@ describe('ReactSuspenseEffectsSemantics', () => {
await act(async () => {
await resolveText('InnerAsync_1');
});
- assertLog(['Text:Outer render', 'Suspend:OuterAsync_1']);
+ assertLog([
+ 'Text:Outer render',
+ 'Suspend:OuterAsync_1',
+
+ ...(gate('enableSiblingPrerendering')
+ ? [
+ 'Text:Outer render',
+ 'Suspend:OuterAsync_1',
+ 'Text:Inner render',
+ 'AsyncText:InnerAsync_1 render',
+ ]
+ : []),
+ ]);
expect(ReactNoop).toMatchRenderedOutput(
<>
@@ -1194,6 +1206,17 @@ describe('ReactSuspenseEffectsSemantics', () => {
'Text:Inner render',
'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',
@@ -2305,6 +2328,15 @@ describe('ReactSuspenseEffectsSemantics', () => {
'Text:Function render',
'AsyncText:Async_1 render',
'Suspend:Async_2',
+
+ ...(gate('enableSiblingPrerendering')
+ ? [
+ 'Text:Function render',
+ 'AsyncText:Async_1 render',
+ 'Suspend:Async_2',
+ 'ClassText:Class render',
+ ]
+ : []),
]);
expect(ReactNoop).toMatchRenderedOutput(
<>
@@ -2443,7 +2475,20 @@ describe('ReactSuspenseEffectsSemantics', () => {
await act(async () => {
await resolveText('A');
});
- assertLog(['Text:Function render', 'Suspender "B" render', 'Suspend:B']);
+ assertLog([
+ 'Text:Function render',
+ 'Suspender "B" render',
+ 'Suspend:B',
+
+ ...(gate('enableSiblingPrerendering')
+ ? [
+ 'Text:Function render',
+ 'Suspender "B" render',
+ 'Suspend:B',
+ 'ClassText:Class render',
+ ]
+ : []),
+ ]);
expect(ReactNoop).toMatchRenderedOutput(
<>
diff --git a/packages/react-reconciler/src/__tests__/ReactSuspenseList-test.js b/packages/react-reconciler/src/__tests__/ReactSuspenseList-test.js
index 8ccced15b342f..5d0d297bad8b7 100644
--- a/packages/react-reconciler/src/__tests__/ReactSuspenseList-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactSuspenseList-test.js
@@ -383,7 +383,13 @@ describe('ReactSuspenseList', () => {
);
await act(() => B.resolve());
- assertLog(['A', 'B', 'Suspend! [C]']);
+ assertLog([
+ 'A',
+ 'B',
+ 'Suspend! [C]',
+
+ ...(gate('enableSiblingPrerendering') ? ['A', 'B', 'Suspend! [C]'] : []),
+ ]);
expect(ReactNoop).toMatchRenderedOutput(
<>
@@ -459,7 +465,13 @@ describe('ReactSuspenseList', () => {
);
await act(() => B.resolve());
- assertLog(['A', 'B', 'Suspend! [C]']);
+ assertLog([
+ 'A',
+ 'B',
+ 'Suspend! [C]',
+
+ ...(gate('enableSiblingPrerendering') ? ['A', 'B', 'Suspend! [C]'] : []),
+ ]);
expect(ReactNoop).toMatchRenderedOutput(
<>
@@ -767,7 +779,12 @@ describe('ReactSuspenseList', () => {
);
await act(() => B.resolve());
- assertLog(['B', 'Suspend! [C]']);
+ assertLog([
+ 'B',
+ 'Suspend! [C]',
+
+ ...(gate('enableSiblingPrerendering') ? ['B', 'Suspend! [C]'] : []),
+ ]);
// Even though we could now show B, we're still waiting on C.
expect(ReactNoop).toMatchRenderedOutput(
@@ -854,7 +871,12 @@ describe('ReactSuspenseList', () => {
expect(ReactNoop).toMatchRenderedOutput(A);
await act(() => B.resolve());
- assertLog(['B', 'Suspend! [C]']);
+ assertLog([
+ 'B',
+ 'Suspend! [C]',
+
+ ...(gate('enableSiblingPrerendering') ? ['B', 'Suspend! [C]'] : []),
+ ]);
// Even though we could now show B, we're still waiting on C.
expect(ReactNoop).toMatchRenderedOutput(A);
@@ -908,7 +930,12 @@ describe('ReactSuspenseList', () => {
);
await act(() => A.resolve());
- assertLog(['A', 'Suspend! [B]']);
+ assertLog([
+ 'A',
+ 'Suspend! [B]',
+
+ ...(gate('enableSiblingPrerendering') ? ['A', 'Suspend! [B]'] : []),
+ ]);
expect(ReactNoop).toMatchRenderedOutput(
<>
@@ -967,7 +994,12 @@ describe('ReactSuspenseList', () => {
);
await act(() => C.resolve());
- assertLog(['C', 'Suspend! [B]']);
+ assertLog([
+ 'C',
+ 'Suspend! [B]',
+
+ ...(gate('enableSiblingPrerendering') ? ['C', 'Suspend! [B]'] : []),
+ ]);
expect(ReactNoop).toMatchRenderedOutput(
<>
@@ -1072,7 +1104,12 @@ describe('ReactSuspenseList', () => {
);
await act(() => A.resolve());
- assertLog(['A', 'Suspend! [C]']);
+ assertLog([
+ 'A',
+ 'Suspend! [C]',
+
+ ...(gate('enableSiblingPrerendering') ? ['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.
@@ -1088,7 +1125,13 @@ describe('ReactSuspenseList', () => {
);
await act(() => C.resolve());
- assertLog(['A', 'C', 'Suspend! [E]']);
+ assertLog([
+ 'A',
+ 'C',
+ 'Suspend! [E]',
+
+ ...(gate('enableSiblingPrerendering') ? ['A', 'C', 'Suspend! [E]'] : []),
+ ]);
// We can now resolve the full head.
expect(ReactNoop).toMatchRenderedOutput(
@@ -1103,7 +1146,12 @@ describe('ReactSuspenseList', () => {
);
await act(() => E.resolve());
- assertLog(['E', 'Suspend! [F]']);
+ assertLog([
+ 'E',
+ 'Suspend! [F]',
+
+ ...(gate('enableSiblingPrerendering') ? ['E', 'Suspend! [F]'] : []),
+ ]);
// In the tail we can resolve one-by-one.
expect(ReactNoop).toMatchRenderedOutput(
@@ -1267,7 +1315,12 @@ describe('ReactSuspenseList', () => {
await F.resolve();
- await waitForAll(['Suspend! [D]', 'F']);
+ await waitForAll([
+ 'Suspend! [D]',
+ 'F',
+
+ ...(gate('enableSiblingPrerendering') ? ['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.
@@ -1287,7 +1340,13 @@ describe('ReactSuspenseList', () => {
);
await act(() => D.resolve());
- assertLog(['D', 'F', 'Suspend! [B]']);
+ assertLog([
+ 'D',
+ 'F',
+ 'Suspend! [B]',
+
+ ...(gate('enableSiblingPrerendering') ? ['D', 'F', 'Suspend! [B]'] : []),
+ ]);
// We can now resolve the full head.
expect(ReactNoop).toMatchRenderedOutput(
@@ -1304,7 +1363,12 @@ describe('ReactSuspenseList', () => {
);
await act(() => B.resolve());
- assertLog(['B', 'Suspend! [A]']);
+ assertLog([
+ 'B',
+ 'Suspend! [A]',
+
+ ...(gate('enableSiblingPrerendering') ? ['B', 'Suspend! [A]'] : []),
+ ]);
// In the tail we can resolve one-by-one.
expect(ReactNoop).toMatchRenderedOutput(
@@ -1429,7 +1493,15 @@ describe('ReactSuspenseList', () => {
await A.resolve();
- await waitForAll(['A', 'Suspend! [B]', 'Loading B']);
+ await waitForAll([
+ 'A',
+ 'Suspend! [B]',
+ 'Loading B',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['A', 'Suspend! [B]', 'Loading B']
+ : []),
+ ]);
// Incremental loading is suspended.
jest.advanceTimersByTime(500);
@@ -1443,7 +1515,15 @@ describe('ReactSuspenseList', () => {
await B.resolve();
- await waitForAll(['B', 'Suspend! [C]', 'Loading C']);
+ await waitForAll([
+ 'B',
+ 'Suspend! [C]',
+ 'Loading C',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['B', 'Suspend! [C]', 'Loading C']
+ : []),
+ ]);
// Incremental loading is suspended.
jest.advanceTimersByTime(500);
@@ -1667,7 +1747,12 @@ describe('ReactSuspenseList', () => {
await B.resolve();
- await waitForAll(['B', 'Suspend! [C]']);
+ await waitForAll([
+ 'B',
+ 'Suspend! [C]',
+
+ ...(gate('enableSiblingPrerendering') ? ['B', 'Suspend! [C]'] : []),
+ ]);
// Incremental loading is suspended.
jest.advanceTimersByTime(500);
@@ -1687,7 +1772,17 @@ describe('ReactSuspenseList', () => {
await C.resolve();
await E.resolve();
- await waitForAll(['B', 'C', 'E', 'Suspend! [F]', 'Loading F']);
+ await waitForAll([
+ 'B',
+ 'C',
+ 'E',
+ 'Suspend! [F]',
+ 'Loading F',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['B', 'C', 'E', 'Suspend! [F]', 'Loading F']
+ : []),
+ ]);
jest.advanceTimersByTime(500);
@@ -1804,7 +1899,12 @@ describe('ReactSuspenseList', () => {
await D.resolve();
- await waitForAll(['D', 'Suspend! [E]']);
+ await waitForAll([
+ 'D',
+ 'Suspend! [E]',
+
+ ...(gate('enableSiblingPrerendering') ? ['D', 'Suspend! [E]'] : []),
+ ]);
// Incremental loading is suspended.
jest.advanceTimersByTime(500);
@@ -1829,7 +1929,17 @@ describe('ReactSuspenseList', () => {
await D.resolve();
await E.resolve();
- await waitForAll(['D', 'E', 'B', 'Suspend! [A]', 'Loading A']);
+ await waitForAll([
+ 'D',
+ 'E',
+ 'B',
+ 'Suspend! [A]',
+ 'Loading A',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['D', 'E', 'B', 'Suspend! [A]', 'Loading A']
+ : []),
+ ]);
jest.advanceTimersByTime(500);
@@ -1958,7 +2068,12 @@ describe('ReactSuspenseList', () => {
await B.resolve();
- await waitForAll(['B', 'Suspend! [C]']);
+ await waitForAll([
+ 'B',
+ 'Suspend! [C]',
+
+ ...(gate('enableSiblingPrerendering') ? ['B', 'Suspend! [C]'] : []),
+ ]);
// Incremental loading is suspended.
jest.advanceTimersByTime(500);
@@ -1981,7 +2096,17 @@ describe('ReactSuspenseList', () => {
await D.resolve();
await E.resolve();
- await waitForAll(['C', 'D', 'E', 'Suspend! [F]', 'Loading F']);
+ await waitForAll([
+ 'C',
+ 'D',
+ 'E',
+ 'Suspend! [F]',
+ 'Loading F',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['C', 'D', 'E', 'Suspend! [F]', 'Loading F']
+ : []),
+ ]);
jest.advanceTimersByTime(500);
@@ -2044,7 +2169,15 @@ describe('ReactSuspenseList', () => {
await A.resolve();
- await waitForAll(['A', 'Suspend! [B]', 'Loading B']);
+ await waitForAll([
+ 'A',
+ 'Suspend! [B]',
+ 'Loading B',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['A', 'Suspend! [B]', 'Loading B']
+ : []),
+ ]);
// Incremental loading is suspended.
jest.advanceTimersByTime(500);
@@ -2052,7 +2185,15 @@ describe('ReactSuspenseList', () => {
expect(ReactNoop).toMatchRenderedOutput(A);
await act(() => B.resolve());
- assertLog(['B', 'Suspend! [C]', 'Loading C']);
+ assertLog([
+ 'B',
+ 'Suspend! [C]',
+ 'Loading C',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['B', 'Suspend! [C]', 'Loading C']
+ : []),
+ ]);
// Incremental loading is suspended.
jest.advanceTimersByTime(500);
@@ -2787,7 +2928,12 @@ describe('ReactSuspenseList', () => {
expect(onRender.mock.calls[2][3]).toBe(1 + 4 + 3 + 3);
await act(() => C.resolve());
- assertLog(['C', 'Suspend! [D]']);
+ assertLog([
+ 'C',
+ 'Suspend! [D]',
+
+ ...(gate('enableSiblingPrerendering') ? ['C', 'Suspend! [D]'] : []),
+ ]);
expect(ReactNoop).toMatchRenderedOutput(
<>
A
@@ -2873,7 +3019,12 @@ describe('ReactSuspenseList', () => {
);
await act(() => A.resolve());
- assertLog(['A', 'Suspend! [B]']);
+ assertLog([
+ 'A',
+ 'Suspend! [B]',
+
+ ...(gate('enableSiblingPrerendering') ? ['A', 'Suspend! [B]'] : []),
+ ]);
expect(ReactNoop).toMatchRenderedOutput(
<>
A
diff --git a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js
index a0b0670726de2..f41de1c169d3c 100644
--- a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js
@@ -298,6 +298,19 @@ describe('ReactSuspenseWithNoopRenderer', () => {
// We immediately unwind and switch to a fallback without
// rendering siblings.
'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? [
+ 'Foo',
+ 'Bar',
+ // A suspends
+ 'Suspend! [A]',
+ 'B',
+ // We immediately unwind and switch to a fallback without
+ // rendering siblings.
+ 'Loading...',
+ ]
+ : []),
]);
expect(ReactNoop).toMatchRenderedOutput(null);
@@ -379,7 +392,15 @@ describe('ReactSuspenseWithNoopRenderer', () => {
});
// B suspends. Render a fallback
- await waitForAll(['A', 'Suspend! [B]', 'Loading...']);
+ await waitForAll([
+ 'A',
+ 'Suspend! [B]',
+ 'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['A', 'Suspend! [B]', 'C', 'D', 'Loading...']
+ : []),
+ ]);
// Did not commit yet.
expect(ReactNoop).toMatchRenderedOutput(null);
@@ -436,7 +457,14 @@ describe('ReactSuspenseWithNoopRenderer', () => {
React.startTransition(() => {
ReactNoop.render();
});
- await waitForAll(['Suspend! [Result]', 'Loading...']);
+ await waitForAll([
+ 'Suspend! [Result]',
+ 'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Suspend! [Result]', 'Loading...']
+ : []),
+ ]);
expect(ReactNoop).toMatchRenderedOutput(null);
await rejectText('Result', new Error('Failed to load: Result'));
@@ -496,6 +524,10 @@ 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 +579,14 @@ describe('ReactSuspenseWithNoopRenderer', () => {
// Update the low-pri text
await act(() => startTransition(() => setLowPri('2')));
// Suspends
- assertLog(['Suspend! [2]', 'Loading...']);
+ assertLog([
+ 'Suspend! [2]',
+ 'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Suspend! [2]', 'Loading...']
+ : []),
+ ]);
// While we're still waiting for the low-pri update to complete, update the
// high-pri text at high priority.
@@ -592,13 +631,27 @@ describe('ReactSuspenseWithNoopRenderer', () => {
React.startTransition(() => {
ReactNoop.render();
});
- await waitForAll(['Suspend! [A]', 'Loading...']);
+ await waitForAll([
+ 'Suspend! [A]',
+ 'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Suspend! [A]', 'Loading...']
+ : []),
+ ]);
expect(ReactNoop).toMatchRenderedOutput(null);
React.startTransition(() => {
ReactNoop.render();
});
- await waitForAll(['Suspend! [A]', 'Loading...']);
+ await waitForAll([
+ 'Suspend! [A]',
+ 'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Suspend! [A]', 'B', 'Loading...']
+ : []),
+ ]);
expect(ReactNoop).toMatchRenderedOutput(null);
await resolveText('A');
@@ -743,6 +796,10 @@ 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(
@@ -932,7 +989,14 @@ describe('ReactSuspenseWithNoopRenderer', () => {
,
);
});
- await waitForAll(['Suspend! [Async]', 'Loading...']);
+ await waitForAll([
+ 'Suspend! [Async]',
+ 'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Suspend! [Async]', 'Loading...']
+ : []),
+ ]);
expect(ReactNoop).toMatchRenderedOutput(null);
// Resolve the promise
@@ -964,7 +1028,14 @@ describe('ReactSuspenseWithNoopRenderer', () => {
// Schedule an update, and suspend for up to 5 seconds.
React.startTransition(() => ReactNoop.render());
// The update should suspend.
- await waitForAll(['Suspend! [A]', 'Loading...']);
+ await waitForAll([
+ 'Suspend! [A]',
+ 'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Suspend! [A]', 'Loading...']
+ : []),
+ ]);
expect(ReactNoop).toMatchRenderedOutput();
// Advance time until right before it expires.
@@ -976,7 +1047,14 @@ describe('ReactSuspenseWithNoopRenderer', () => {
// Schedule another low priority update.
React.startTransition(() => ReactNoop.render());
// This update should also suspend.
- await waitForAll(['Suspend! [B]', 'Loading...']);
+ await waitForAll([
+ 'Suspend! [B]',
+ 'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Suspend! [B]', 'Loading...']
+ : []),
+ ]);
expect(ReactNoop).toMatchRenderedOutput();
// Schedule a regular update. Its expiration time will fall between
@@ -1747,6 +1825,10 @@ 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...
@@ -2238,7 +2320,16 @@ describe('ReactSuspenseWithNoopRenderer', () => {
ReactNoop.render();
});
- await waitForAll(['Foo', 'A', 'Suspend! [B]', 'Loading B...']);
+ await waitForAll([
+ 'Foo',
+ 'A',
+ 'Suspend! [B]',
+ 'Loading B...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Foo', 'A', 'Suspend! [B]', 'Loading B...']
+ : []),
+ ]);
// Transitions never fall back.
expect(ReactNoop).toMatchRenderedOutput();
@@ -2314,7 +2405,14 @@ describe('ReactSuspenseWithNoopRenderer', () => {
// Start transition.
React.startTransition(() => ReactNoop.render());
- await waitForAll(['Suspend! [B]', 'Loading...']);
+ await waitForAll([
+ 'Suspend! [B]',
+ 'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Suspend! [B]', 'Loading...']
+ : []),
+ ]);
Scheduler.unstable_advanceTime(100000);
await advanceTimers(100000);
// Even after lots of time has passed, we have still not yet flushed the
@@ -2365,7 +2463,14 @@ describe('ReactSuspenseWithNoopRenderer', () => {
await act(async () => {
React.startTransition(() => transitionToPage('B'));
- await waitForAll(['Suspend! [B]', 'Loading...']);
+ await waitForAll([
+ 'Suspend! [B]',
+ 'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Suspend! [B]', 'Loading...']
+ : []),
+ ]);
Scheduler.unstable_advanceTime(100000);
await advanceTimers(100000);
// Even after lots of time has passed, we have still not yet flushed the
@@ -2420,7 +2525,14 @@ describe('ReactSuspenseWithNoopRenderer', () => {
await act(async () => {
React.startTransition(() => transitionToPage('B'));
- await waitForAll(['Suspend! [B]', 'Loading...']);
+ await waitForAll([
+ 'Suspend! [B]',
+ 'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Suspend! [B]', 'Loading...']
+ : []),
+ ]);
Scheduler.unstable_advanceTime(100000);
await advanceTimers(100000);
// Even after lots of time has passed, we have still not yet flushed the
@@ -2462,7 +2574,14 @@ describe('ReactSuspenseWithNoopRenderer', () => {
// Start transition.
React.startTransition(() => ReactNoop.render());
- await waitForAll(['Suspend! [B]', 'Loading...']);
+ await waitForAll([
+ 'Suspend! [B]',
+ 'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Suspend! [B]', 'Loading...']
+ : []),
+ ]);
Scheduler.unstable_advanceTime(2999);
await advanceTimers(2999);
// Since the timeout is infinite (or effectively infinite),
@@ -2476,7 +2595,14 @@ describe('ReactSuspenseWithNoopRenderer', () => {
// Start a long (infinite) transition.
React.startTransition(() => ReactNoop.render());
- await waitForAll(['Suspend! [C]', 'Loading...']);
+ await waitForAll([
+ 'Suspend! [C]',
+ 'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Suspend! [C]', 'Loading...']
+ : []),
+ ]);
// Even after lots of time has passed, we have still not yet flushed the
// loading state.
@@ -2524,7 +2650,14 @@ describe('ReactSuspenseWithNoopRenderer', () => {
await act(async () => {
React.startTransition(() => transitionToPage('B'));
- await waitForAll(['Suspend! [B]', 'Loading...']);
+ await waitForAll([
+ 'Suspend! [B]',
+ 'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Suspend! [B]', 'Loading...']
+ : []),
+ ]);
Scheduler.unstable_advanceTime(2999);
await advanceTimers(2999);
@@ -2542,7 +2675,14 @@ describe('ReactSuspenseWithNoopRenderer', () => {
await act(async () => {
React.startTransition(() => transitionToPage('C'));
- await waitForAll(['Suspend! [C]', 'Loading...']);
+ await waitForAll([
+ 'Suspend! [C]',
+ 'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Suspend! [C]', 'Loading...']
+ : []),
+ ]);
// Even after lots of time has passed, we have still not yet flushed the
// loading state.
@@ -2594,7 +2734,14 @@ describe('ReactSuspenseWithNoopRenderer', () => {
await act(async () => {
React.startTransition(() => transitionToPage('B'));
- await waitForAll(['Suspend! [B]', 'Loading...']);
+ await waitForAll([
+ 'Suspend! [B]',
+ 'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Suspend! [B]', 'Loading...']
+ : []),
+ ]);
Scheduler.unstable_advanceTime(2999);
await advanceTimers(2999);
// Since the timeout is infinite (or effectively infinite),
@@ -2611,7 +2758,14 @@ describe('ReactSuspenseWithNoopRenderer', () => {
await act(async () => {
React.startTransition(() => transitionToPage('C'));
- await waitForAll(['Suspend! [C]', 'Loading...']);
+ await waitForAll([
+ 'Suspend! [C]',
+ 'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Suspend! [C]', 'Loading...']
+ : []),
+ ]);
// Even after lots of time has passed, we have still not yet flushed the
// loading state.
@@ -2652,7 +2806,15 @@ describe('ReactSuspenseWithNoopRenderer', () => {
// Start transition.
React.startTransition(() => ReactNoop.render());
- await waitForAll(['Hi!', 'Suspend! [B]', 'Loading B...']);
+ await waitForAll([
+ 'Hi!',
+ 'Suspend! [B]',
+ 'Loading B...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Hi!', 'Suspend! [B]', 'Loading B...']
+ : []),
+ ]);
// Suspended
expect(ReactNoop).toMatchRenderedOutput(
@@ -2713,7 +2875,15 @@ describe('ReactSuspenseWithNoopRenderer', () => {
// Start transition.
React.startTransition(() => ReactNoop.render());
- await waitForAll(['Hi!', 'Suspend! [B]', 'Loading B...']);
+ await waitForAll([
+ 'Hi!',
+ 'Suspend! [B]',
+ 'Loading B...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Hi!', 'Suspend! [B]', 'Loading B...']
+ : []),
+ ]);
// Suspended
expect(ReactNoop).toMatchRenderedOutput(
@@ -2827,7 +2997,14 @@ describe('ReactSuspenseWithNoopRenderer', () => {
ReactNoop.render();
});
- await waitForAll(['Suspend! [A]', 'Loading...']);
+ await waitForAll([
+ 'Suspend! [A]',
+ 'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Suspend! [A]', 'Loading...']
+ : []),
+ ]);
await resolveText('A');
await waitFor(['A', 'Commit']);
expect(ReactNoop).toMatchRenderedOutput(
@@ -2879,7 +3056,14 @@ describe('ReactSuspenseWithNoopRenderer', () => {
ReactNoop.render();
});
- await waitForAll(['Suspend! [A]', 'Loading...']);
+ await waitForAll([
+ 'Suspend! [A]',
+ 'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Suspend! [A]', 'Loading...']
+ : []),
+ ]);
await resolveText('A');
await waitFor(['A', 'Commit']);
expect(ReactNoop).toMatchRenderedOutput(
@@ -3492,6 +3676,10 @@ 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;
@@ -3654,7 +3842,16 @@ describe('ReactSuspenseWithNoopRenderer', () => {
// Update to "a". That will suspend.
await act(async () => {
setTextWithShortTransition('a');
- await waitForAll(['Pending...', '', 'Suspend! [a]', 'Loading...']);
+ await waitForAll([
+ 'Pending...',
+ '',
+ 'Suspend! [a]',
+ 'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Suspend! [a]', 'Loading...']
+ : []),
+ ]);
});
assertLog([]);
expect(root).toMatchRenderedOutput(
@@ -3673,6 +3870,10 @@ describe('ReactSuspenseWithNoopRenderer', () => {
'',
'Suspend! [b]',
'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Suspend! [b]', 'Loading...']
+ : []),
]);
});
assertLog([]);
@@ -3687,7 +3888,14 @@ describe('ReactSuspenseWithNoopRenderer', () => {
await act(async () => {
await resolveText('a');
- await waitForAll(['Suspend! [b]', 'Loading...']);
+ await waitForAll([
+ 'Suspend! [b]',
+ 'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Suspend! [b]', 'Loading...']
+ : []),
+ ]);
expect(root).toMatchRenderedOutput(
<>
diff --git a/packages/react-reconciler/src/__tests__/ReactTransition-test.js b/packages/react-reconciler/src/__tests__/ReactTransition-test.js
index 9dcb36c148b9c..a0c4ea8db0aff 100644
--- a/packages/react-reconciler/src/__tests__/ReactTransition-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactTransition-test.js
@@ -199,6 +199,10 @@ describe('ReactTransition', () => {
'(empty)',
'Suspend! [Async]',
'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Suspend! [Async]', 'Loading...']
+ : []),
]);
expect(root).toMatchRenderedOutput('Pending...(empty)');
@@ -269,6 +273,10 @@ describe('ReactTransition', () => {
'B label',
'Suspend! [B content]',
'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['B label', 'Suspend! [B content]', 'Loading...']
+ : []),
]);
// This is a refresh transition so it shouldn't show a fallback
expect(root).toMatchRenderedOutput(
@@ -290,6 +298,10 @@ describe('ReactTransition', () => {
'C label',
'Suspend! [C content]',
'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['C label', 'Suspend! [C content]', 'Loading...']
+ : []),
]);
expect(root).toMatchRenderedOutput(
<>
@@ -307,6 +319,10 @@ describe('ReactTransition', () => {
'C label',
'Suspend! [C content]',
'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['C label', 'Suspend! [C content]', 'Loading...']
+ : []),
]);
expect(root).toMatchRenderedOutput(
<>
@@ -394,6 +410,10 @@ describe('ReactTransition', () => {
'B label',
'Suspend! [B content]',
'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['B label', 'Suspend! [B content]', 'Loading...']
+ : []),
]);
// This is a refresh transition so it shouldn't show a fallback
expect(root).toMatchRenderedOutput(
@@ -415,6 +435,10 @@ describe('ReactTransition', () => {
'C label',
'Suspend! [C content]',
'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['C label', 'Suspend! [C content]', 'Loading...']
+ : []),
]);
expect(root).toMatchRenderedOutput(
<>
@@ -432,6 +456,10 @@ describe('ReactTransition', () => {
'C label',
'Suspend! [C content]',
'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['C label', 'Suspend! [C content]', 'Loading...']
+ : []),
]);
expect(root).toMatchRenderedOutput(
<>
@@ -500,7 +528,14 @@ describe('ReactTransition', () => {
setShowA(true);
});
});
- assertLog(['Suspend! [A]', 'Loading...']);
+ assertLog([
+ 'Suspend! [A]',
+ 'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Suspend! [A]', 'Loading...']
+ : []),
+ ]);
expect(root).toMatchRenderedOutput(null);
// Before A loads, switch to B. This should entangle A with B.
@@ -510,7 +545,14 @@ describe('ReactTransition', () => {
setShowB(true);
});
});
- assertLog(['Suspend! [B]', 'Loading...']);
+ assertLog([
+ 'Suspend! [B]',
+ 'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Suspend! [B]', 'Loading...']
+ : []),
+ ]);
expect(root).toMatchRenderedOutput(null);
// Before A or B loads, switch to C. This should entangle C with B, and
@@ -521,7 +563,14 @@ describe('ReactTransition', () => {
setShowC(true);
});
});
- assertLog(['Suspend! [C]', 'Loading...']);
+ assertLog([
+ 'Suspend! [C]',
+ 'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Suspend! [C]', 'Loading...']
+ : []),
+ ]);
expect(root).toMatchRenderedOutput(null);
// Now the data starts resolving out of order.
@@ -533,7 +582,14 @@ describe('ReactTransition', () => {
resolveText('B');
});
});
- assertLog(['Suspend! [C]', 'Loading...']);
+ assertLog([
+ 'Suspend! [C]',
+ 'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Suspend! [C]', 'Loading...']
+ : []),
+ ]);
expect(root).toMatchRenderedOutput(null);
// Now resolve A. Again, this will attempt to render C, since everything
@@ -543,7 +599,14 @@ describe('ReactTransition', () => {
resolveText('A');
});
});
- assertLog(['Suspend! [C]', 'Loading...']);
+ assertLog([
+ 'Suspend! [C]',
+ 'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Suspend! [C]', 'Loading...']
+ : []),
+ ]);
expect(root).toMatchRenderedOutput(null);
// Finally, resolve C. This time we can finish.
@@ -861,6 +924,10 @@ describe('ReactTransition', () => {
// Suspend.
'Suspend! [Async]',
'Loading...',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Suspend! [Async]', 'Normal pri: 0', 'Loading...']
+ : []),
]);
expect(root).toMatchRenderedOutput('(empty), Normal pri: 0');
diff --git a/packages/react-reconciler/src/__tests__/ReactUse-test.js b/packages/react-reconciler/src/__tests__/ReactUse-test.js
index 0abb47d516a3d..c562d43dc3bb8 100644
--- a/packages/react-reconciler/src/__tests__/ReactUse-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactUse-test.js
@@ -1066,13 +1066,25 @@ describe('ReactUse', () => {
await act(() => {
resolveTextRequests('A');
});
- assertLog(['A', '(Loading B...)']);
+ assertLog([
+ 'A',
+ '(Loading B...)',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['A', '(Loading C...)', '(Loading B...)']
+ : []),
+ ]);
expect(root).toMatchRenderedOutput('A(Loading B...)');
await act(() => {
resolveTextRequests('B');
});
- assertLog(['B', '(Loading C...)']);
+ assertLog([
+ 'B',
+ '(Loading C...)',
+
+ ...(gate('enableSiblingPrerendering') ? ['B', '(Loading C...)'] : []),
+ ]);
expect(root).toMatchRenderedOutput('AB(Loading C...)');
await act(() => {
@@ -1868,16 +1880,26 @@ describe('ReactUse', () => {
await expect(async () => {
await act(() => resolveTextRequests('Hi'));
- }).toErrorDev(
+ }).toErrorDev([
// We get this warning because the generator's promise themselves are not cached.
'A component was suspended by an uncached promise. Creating ' +
'promises inside a Client Component or hook is not yet ' +
'supported, except via a Suspense-compatible library or framework.',
- );
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['A component was suspended by an uncached promise.']
+ : []),
+ ]);
assertLog(['Async text requested [World]']);
- await act(() => resolveTextRequests('World'));
+ if (gate('enableSiblingPrerendering')) {
+ await expect(async () => {
+ await act(() => resolveTextRequests('World'));
+ }).toErrorDev(['A component was suspended by an uncached promise.']);
+ } else {
+ await act(() => resolveTextRequests('World'));
+ }
assertLog(['Hi', 'World']);
expect(root).toMatchRenderedOutput('Hi World');
@@ -1913,16 +1935,26 @@ describe('ReactUse', () => {
await expect(async () => {
await act(() => resolveTextRequests('Hi'));
- }).toErrorDev(
+ }).toErrorDev([
// We get this warning because the generator's promise themselves are not cached.
'A component was suspended by an uncached promise. Creating ' +
'promises inside a Client Component or hook is not yet ' +
'supported, except via a Suspense-compatible library or framework.',
- );
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['A component was suspended by an uncached promise.']
+ : []),
+ ]);
assertLog(['Async text requested [World]']);
- await act(() => resolveTextRequests('World'));
+ if (gate('enableSiblingPrerendering')) {
+ await expect(async () => {
+ await act(() => resolveTextRequests('World'));
+ }).toErrorDev(['A component was suspended by an uncached promise.']);
+ } else {
+ await act(() => resolveTextRequests('World'));
+ }
assertLog(['Hi', 'World']);
expect(root).toMatchRenderedOutput(
Hi World
);
diff --git a/packages/react-reconciler/src/__tests__/useMemoCache-test.js b/packages/react-reconciler/src/__tests__/useMemoCache-test.js
index a3dbeeb38ce1c..6b7faceb748f8 100644
--- a/packages/react-reconciler/src/__tests__/useMemoCache-test.js
+++ b/packages/react-reconciler/src/__tests__/useMemoCache-test.js
@@ -559,13 +559,22 @@ describe('useMemoCache()', () => {
root.render();
});
});
- assertLog(['Suspend! [chunkA]']);
+ assertLog([
+ 'Suspend! [chunkA]',
+
+ ...(gate('enableSiblingPrerendering') ? ['Suspend! [chunkA]'] : []),
+ ]);
// The data starts to stream in. Loading the data in the first chunk
// triggers an expensive computation in the UI. Later, we'll test whether
// this computation is reused.
await act(() => updatedChunkA.resolve('A2'));
- assertLog(['Some expensive processing... [A2]', 'Suspend! [chunkB]']);
+ assertLog([
+ 'Some expensive processing... [A2]',
+ 'Suspend! [chunkB]',
+
+ ...(gate('enableSiblingPrerendering') ? ['Suspend! [chunkB]'] : []),
+ ]);
// The second chunk hasn't loaded yet, so we're still showing the
// initial UI.
@@ -586,11 +595,22 @@ describe('useMemoCache()', () => {
if (gate(flags => flags.enableNoCloningMemoCache)) {
// We did not have process the first chunk again. We reused the
// computation from the earlier attempt.
- assertLog(['Suspend! [chunkB]']);
+ assertLog([
+ 'Suspend! [chunkB]',
+
+ ...(gate('enableSiblingPrerendering') ? ['Suspend! [chunkB]'] : []),
+ ]);
} else {
// Because we clone/reset the memo cache after every aborted attempt, we
// must process the first chunk again.
- assertLog(['Some expensive processing... [A2]', 'Suspend! [chunkB]']);
+ assertLog([
+ 'Some expensive processing... [A2]',
+ 'Suspend! [chunkB]',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Some expensive processing... [A2]', 'Suspend! [chunkB]']
+ : []),
+ ]);
}
expect(root).toMatchRenderedOutput(
diff --git a/packages/react-reconciler/src/__tests__/useSyncExternalStore-test.js b/packages/react-reconciler/src/__tests__/useSyncExternalStore-test.js
index 52dbc45e242bb..17e5fe26c0e89 100644
--- a/packages/react-reconciler/src/__tests__/useSyncExternalStore-test.js
+++ b/packages/react-reconciler/src/__tests__/useSyncExternalStore-test.js
@@ -274,6 +274,10 @@ describe('useSyncExternalStore', () => {
// This should a synchronous re-render of A using the updated value. In
// this test, this causes A to suspend.
'Suspend A',
+
+ ...(gate('enableSiblingPrerendering')
+ ? ['Suspend A', 'B: Updated']
+ : []),
]);
// Nothing has committed, because A suspended and no fallback
// was provided.