diff --git a/packages/react-cache/src/__tests__/ReactCacheOld-test.internal.js b/packages/react-cache/src/__tests__/ReactCacheOld-test.internal.js
index 117c0b488b17..579c835878eb 100644
--- a/packages/react-cache/src/__tests__/ReactCacheOld-test.internal.js
+++ b/packages/react-cache/src/__tests__/ReactCacheOld-test.internal.js
@@ -236,7 +236,7 @@ describe('ReactCache', () => {
jest.advanceTimersByTime(100);
assertLog(['Promise resolved [4]']);
- await waitForAll([1, 4, 'Suspend! [5]', 'Loading...']);
+ await waitForAll([1, 4, 'Suspend! [5]']);
jest.advanceTimersByTime(100);
assertLog(['Promise resolved [5]']);
@@ -264,7 +264,7 @@ describe('ReactCache', () => {
]);
jest.advanceTimersByTime(100);
assertLog(['Promise resolved [2]']);
- await waitForAll([1, 2, 'Suspend! [3]', 'Loading...']);
+ await waitForAll([1, 2, 'Suspend! [3]']);
jest.advanceTimersByTime(100);
assertLog(['Promise resolved [3]']);
diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js
index 401defb280fe..f0d093f72c92 100644
--- a/packages/react-reconciler/src/ReactFiberWorkLoop.js
+++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js
@@ -145,7 +145,6 @@ import {
includesExpiredLane,
getNextLanes,
getLanesToRetrySynchronouslyOnError,
- getMostRecentEventTime,
markRootUpdated,
markRootSuspended as markRootSuspended_dontCallThisOneDirectly,
markRootPinged,
@@ -284,8 +283,6 @@ import {
} from './ReactFiberRootScheduler';
import {getMaskedContext, getUnmaskedContext} from './ReactFiberContext';
-const ceil = Math.ceil;
-
const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
const {
@@ -1193,38 +1190,6 @@ function finishConcurrentRender(
break;
}
- if (!shouldForceFlushFallbacksInDEV()) {
- // This is not a transition, but we did trigger an avoided state.
- // Schedule a placeholder to display after a short delay, using the Just
- // Noticeable Difference.
- // TODO: Is the JND optimization worth the added complexity? If this is
- // the only reason we track the event time, then probably not.
- // Consider removing.
-
- const mostRecentEventTime = getMostRecentEventTime(root, lanes);
- const eventTimeMs = mostRecentEventTime;
- const timeElapsedMs = now() - eventTimeMs;
- const msUntilTimeout = jnd(timeElapsedMs) - timeElapsedMs;
-
- // Don't bother with a very short suspense time.
- if (msUntilTimeout > 10) {
- // Instead of committing the fallback immediately, wait for more data
- // to arrive.
- root.timeoutHandle = scheduleTimeout(
- commitRootWhenReady.bind(
- null,
- root,
- finishedWork,
- workInProgressRootRecoverableErrors,
- workInProgressTransitions,
- lanes,
- ),
- msUntilTimeout,
- );
- break;
- }
- }
-
// Commit the placeholder.
commitRootWhenReady(
root,
@@ -3580,31 +3545,6 @@ export function resolveRetryWakeable(boundaryFiber: Fiber, wakeable: Wakeable) {
retryTimedOutBoundary(boundaryFiber, retryLane);
}
-// Computes the next Just Noticeable Difference (JND) boundary.
-// The theory is that a person can't tell the difference between small differences in time.
-// Therefore, if we wait a bit longer than necessary that won't translate to a noticeable
-// difference in the experience. However, waiting for longer might mean that we can avoid
-// showing an intermediate loading state. The longer we have already waited, the harder it
-// is to tell small differences in time. Therefore, the longer we've already waited,
-// the longer we can wait additionally. At some point we have to give up though.
-// We pick a train model where the next boundary commits at a consistent schedule.
-// These particular numbers are vague estimates. We expect to adjust them based on research.
-function jnd(timeElapsed: number) {
- return timeElapsed < 120
- ? 120
- : timeElapsed < 480
- ? 480
- : timeElapsed < 1080
- ? 1080
- : timeElapsed < 1920
- ? 1920
- : timeElapsed < 3000
- ? 3000
- : timeElapsed < 4320
- ? 4320
- : ceil(timeElapsed / 1960) * 1960;
-}
-
export function throwIfInfiniteUpdateLoopDetected() {
if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
nestedUpdateCount = 0;
diff --git a/packages/react-reconciler/src/__tests__/ReactExpiration-test.js b/packages/react-reconciler/src/__tests__/ReactExpiration-test.js
index 2c38a05168a6..653bc304600b 100644
--- a/packages/react-reconciler/src/__tests__/ReactExpiration-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactExpiration-test.js
@@ -732,13 +732,9 @@ describe('ReactExpiration', () => {
expect(root).toMatchRenderedOutput('A0BC');
await act(async () => {
- if (gate(flags => flags.enableSyncDefaultUpdates)) {
- React.startTransition(() => {
- root.render();
- });
- } else {
+ React.startTransition(() => {
root.render();
- }
+ });
await waitForAll(['Suspend! [A1]', 'Loading...']);
// Lots of time elapses before the promise resolves
diff --git a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js
index 627dc4e85621..1c7b25a2c917 100644
--- a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js
@@ -692,24 +692,16 @@ describe('ReactHooksWithNoopRenderer', () => {
await waitForAll([0]);
expect(root).toMatchRenderedOutput();
- if (gate(flags => flags.enableSyncDefaultUpdates)) {
- React.startTransition(() => {
- root.render();
- });
- } else {
+ React.startTransition(() => {
root.render();
- }
+ });
await waitForAll(['Suspend!']);
expect(root).toMatchRenderedOutput();
// Rendering again should suspend again.
- if (gate(flags => flags.enableSyncDefaultUpdates)) {
- React.startTransition(() => {
- root.render();
- });
- } else {
+ React.startTransition(() => {
root.render();
- }
+ });
await waitForAll(['Suspend!']);
});
@@ -755,38 +747,25 @@ describe('ReactHooksWithNoopRenderer', () => {
expect(root).toMatchRenderedOutput();
await act(async () => {
- if (gate(flags => flags.enableSyncDefaultUpdates)) {
- React.startTransition(() => {
- root.render();
- setLabel('B');
- });
- } else {
+ React.startTransition(() => {
root.render();
setLabel('B');
- }
+ });
await waitForAll(['Suspend!']);
expect(root).toMatchRenderedOutput();
// Rendering again should suspend again.
- if (gate(flags => flags.enableSyncDefaultUpdates)) {
- React.startTransition(() => {
- root.render();
- });
- } else {
+ React.startTransition(() => {
root.render();
- }
+ });
await waitForAll(['Suspend!']);
// Flip the signal back to "cancel" the update. However, the update to
// label should still proceed. It shouldn't have been dropped.
- if (gate(flags => flags.enableSyncDefaultUpdates)) {
- React.startTransition(() => {
- root.render();
- });
- } else {
+ React.startTransition(() => {
root.render();
- }
+ });
await waitForAll(['B:0']);
expect(root).toMatchRenderedOutput();
});
diff --git a/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js b/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
index 0ebf8f2d5329..bd4c534a781b 100644
--- a/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
@@ -1414,10 +1414,12 @@ describe('ReactLazy', () => {
// Swap the position of A and B
root.update();
- await waitForAll(['Init B2', 'Loading...']);
- jest.runAllTimers();
-
- assertLog(['Did unmount: A', 'Did unmount: B']);
+ await waitForAll([
+ 'Init B2',
+ 'Loading...',
+ 'Did unmount: A',
+ 'Did unmount: B',
+ ]);
// The suspense boundary should've triggered now.
expect(root).toMatchRenderedOutput('Loading...');
@@ -1559,13 +1561,9 @@ describe('ReactLazy', () => {
expect(root).toMatchRenderedOutput('AB');
// Swap the position of A and B
- if (gate(flags => flags.enableSyncDefaultUpdates)) {
- React.startTransition(() => {
- root.update();
- });
- } else {
+ React.startTransition(() => {
root.update();
- }
+ });
await waitForAll(['Init B2', 'Loading...']);
await resolveFakeImport(ChildB2);
// We need to flush to trigger the second one to load.
diff --git a/packages/react-reconciler/src/__tests__/ReactOffscreenSuspense-test.js b/packages/react-reconciler/src/__tests__/ReactOffscreenSuspense-test.js
index 3a09abd9b10b..c73e3caf62c6 100644
--- a/packages/react-reconciler/src/__tests__/ReactOffscreenSuspense-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactOffscreenSuspense-test.js
@@ -9,6 +9,7 @@ let useState;
let useEffect;
let startTransition;
let textCache;
+let waitFor;
let waitForPaint;
let assertLog;
@@ -28,6 +29,7 @@ describe('ReactOffscreen', () => {
startTransition = React.startTransition;
const InternalTestUtils = require('internal-test-utils');
+ waitFor = InternalTestUtils.waitFor;
waitForPaint = InternalTestUtils.waitForPaint;
assertLog = InternalTestUtils.assertLog;
@@ -407,7 +409,6 @@ describe('ReactOffscreen', () => {
expect(root).toMatchRenderedOutput(B1);
});
- // Only works in new reconciler
// @gate enableOffscreen
test('detect updates to a hidden tree during a concurrent event', async () => {
// This is a pretty complex test case. It relates to how we detect if an
@@ -442,17 +443,17 @@ describe('ReactOffscreen', () => {
setOuter = _setOuter;
return (
<>
-
-
-
+
+
+
}>
-
+
>
@@ -466,50 +467,41 @@ describe('ReactOffscreen', () => {
root.render();
});
assertLog([
- 'Outer: 0',
'Inner: 0',
- 'Async: 0',
+ 'Outer: 0',
+ 'Sibling: 0',
'Inner and outer are consistent',
]);
expect(root).toMatchRenderedOutput(
<>
- Outer: 0
Inner: 0
- Async: 0
+ Outer: 0
+ Sibling: 0
>,
);
await act(async () => {
// Update a value both inside and outside the hidden tree. These values
// must always be consistent.
- setOuter(1);
- setInner(1);
- // In the same render, also hide the offscreen tree.
- root.render();
+ startTransition(() => {
+ setOuter(1);
+ setInner(1);
+ // In the same render, also hide the offscreen tree.
+ root.render();
+ });
- await waitForPaint([
+ await waitFor([
// The outer update will commit, but the inner update is deferred until
// a later render.
'Outer: 1',
-
- // Something suspended. This means we won't commit immediately; there
- // will be an async gap between render and commit. In this test, we will
- // use this property to schedule a concurrent update. The fact that
- // we're using Suspense to schedule a concurrent update is not directly
- // relevant to the test — we could also use time slicing, but I've
- // chosen to use Suspense the because implementation details of time
- // slicing are more volatile.
- 'Suspend! [Async: 1]',
-
- 'Loading...',
]);
// Assert that we haven't committed quite yet
expect(root).toMatchRenderedOutput(
<>
- Outer: 0
Inner: 0
- Async: 0
+ Outer: 0
+ Sibling: 0
>,
);
@@ -520,14 +512,13 @@ describe('ReactOffscreen', () => {
setInner(2);
});
- // Commit the previous render.
- jest.runAllTimers();
+ // Finish rendering and commit the in-progress render.
+ await waitForPaint(['Sibling: 1']);
expect(root).toMatchRenderedOutput(
<>
- Outer: 1
Inner: 0
- Async: 0
- Loading...
+ Outer: 1
+ Sibling: 1
>,
);
@@ -536,32 +527,27 @@ describe('ReactOffscreen', () => {
root.render();
});
assertLog([
- 'Outer: 1',
-
// There are two pending updates on Inner, but only the first one
// is processed, even though they share the same lane. If the second
// update were erroneously processed, then Inner would be inconsistent
// with Outer.
'Inner: 1',
-
- 'Suspend! [Async: 1]',
- 'Loading...',
+ 'Outer: 1',
+ 'Sibling: 1',
'Inner and outer are consistent',
]);
});
assertLog([
- 'Outer: 2',
'Inner: 2',
- 'Suspend! [Async: 2]',
- 'Loading...',
+ 'Outer: 2',
+ 'Sibling: 2',
'Inner and outer are consistent',
]);
expect(root).toMatchRenderedOutput(
<>
- Outer: 2
Inner: 2
- Async: 0
- Loading...
+ Outer: 2
+ Sibling: 2
>,
);
});
diff --git a/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js b/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js
index 5723e0039e2e..75827aa40936 100644
--- a/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js
@@ -125,13 +125,9 @@ describe('ReactSuspense', () => {
// Navigate the shell to now render the child content.
// This should suspend.
- if (gate(flags => flags.enableSyncDefaultUpdates)) {
- React.startTransition(() => {
- root.update();
- });
- } else {
+ React.startTransition(() => {
root.update();
- }
+ });
await waitForAll([
'Foo',
diff --git a/packages/react-reconciler/src/__tests__/ReactSuspenseEffectsSemantics-test.js b/packages/react-reconciler/src/__tests__/ReactSuspenseEffectsSemantics-test.js
index a684f6ab4d24..5200635b6fca 100644
--- a/packages/react-reconciler/src/__tests__/ReactSuspenseEffectsSemantics-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactSuspenseEffectsSemantics-test.js
@@ -576,7 +576,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
]);
});
- // @gate enableLegacyCache && enableSyncDefaultUpdates
+ // @gate enableLegacyCache
it('should be destroyed and recreated for function components', async () => {
function App({children = null}) {
Scheduler.log('App render');
@@ -642,19 +642,6 @@ describe('ReactSuspenseEffectsSemantics', () => {
'Suspend:Async',
'Text:Fallback render',
'Text:Outside render',
- ]);
- expect(ReactNoop).toMatchRenderedOutput(
- <>
-
-
-
- >,
- );
-
- await jest.runAllTimers();
-
- // Timing out should commit the fallback and destroy inner layout effects.
- assertLog([
'Text:Inside:Before destroy layout',
'Text:Inside:After destroy layout',
'Text:Fallback create layout',
@@ -711,7 +698,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
]);
});
- // @gate enableLegacyCache && enableSyncDefaultUpdates
+ // @gate enableLegacyCache
it('should be destroyed and recreated for class components', async () => {
class ClassText extends React.Component {
componentDidMount() {
@@ -796,19 +783,6 @@ describe('ReactSuspenseEffectsSemantics', () => {
'Suspend:Async',
'ClassText:Fallback render',
'ClassText:Outside render',
- ]);
- expect(ReactNoop).toMatchRenderedOutput(
- <>
-
-
-
- >,
- );
-
- await jest.runAllTimers();
-
- // Timing out should commit the fallback and destroy inner layout effects.
- assertLog([
'ClassText:Inside:Before componentWillUnmount',
'ClassText:Inside:After componentWillUnmount',
'ClassText:Fallback componentDidMount',
@@ -860,7 +834,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
]);
});
- // @gate enableLegacyCache && enableSyncDefaultUpdates
+ // @gate enableLegacyCache
it('should be destroyed and recreated when nested below host components', async () => {
function App({children = null}) {
Scheduler.log('App render');
@@ -914,17 +888,10 @@ describe('ReactSuspenseEffectsSemantics', () => {
,
);
- await waitFor(['App render', 'Suspend:Async', 'Text:Fallback render']);
- expect(ReactNoop).toMatchRenderedOutput(
-
-
- ,
- );
-
- await jest.runAllTimers();
-
- // Timing out should commit the fallback and destroy inner layout effects.
- assertLog([
+ await waitFor([
+ 'App render',
+ 'Suspend:Async',
+ 'Text:Fallback render',
'Text:Outer destroy layout',
'Text:Inner destroy layout',
'Text:Fallback create layout',
@@ -979,7 +946,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
]);
});
- // @gate enableLegacyCache && enableSyncDefaultUpdates
+ // @gate enableLegacyCache
it('should be destroyed and recreated even if there is a bailout because of memoization', async () => {
const MemoizedText = React.memo(Text, () => true);
@@ -1040,18 +1007,6 @@ describe('ReactSuspenseEffectsSemantics', () => {
'Suspend:Async',
// Text:MemoizedInner is memoized
'Text:Fallback render',
- ]);
- expect(ReactNoop).toMatchRenderedOutput(
-
-
- ,
- );
-
- await jest.runAllTimers();
-
- // Timing out should commit the fallback and destroy inner layout effects.
- // Even though the innermost layout effects are beneath a hidden HostComponent.
- assertLog([
'Text:Outer destroy layout',
'Text:MemoizedInner destroy layout',
'Text:Fallback create layout',
@@ -1448,7 +1403,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
);
});
- // @gate enableLegacyCache && enableSyncDefaultUpdates
+ // @gate enableLegacyCache
it('should be cleaned up inside of a fallback that suspends', async () => {
function App({fallbackChildren = null, outerChildren = null}) {
return (
@@ -1501,17 +1456,6 @@ describe('ReactSuspenseEffectsSemantics', () => {
'Text:Fallback:Inside render',
'Text:Fallback:Outside render',
'Text:Outside render',
- ]);
- expect(ReactNoop).toMatchRenderedOutput(
- <>
-
-
- >,
- );
-
- // Timing out should commit the fallback and destroy inner layout effects.
- await jest.runAllTimers();
- assertLog([
'Text:Inside destroy layout',
'Text:Fallback:Inside create layout',
'Text:Fallback:Outside create layout',
@@ -1546,19 +1490,6 @@ describe('ReactSuspenseEffectsSemantics', () => {
'Text:Fallback:Fallback render',
'Text:Fallback:Outside render',
'Text:Outside render',
- ]);
- expect(ReactNoop).toMatchRenderedOutput(
- <>
-
-
-
-
- >,
- );
-
- // Timing out should commit the inner fallback and destroy outer fallback layout effects.
- await jest.runAllTimers();
- assertLog([
'Text:Fallback:Inside destroy layout',
'Text:Fallback:Fallback create layout',
]);
@@ -1724,7 +1655,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
);
});
- // @gate enableLegacyCache && enableSyncDefaultUpdates
+ // @gate enableLegacyCache
it('should be cleaned up deeper inside of a subtree that suspends', async () => {
function ConditionalSuspense({shouldSuspend}) {
if (shouldSuspend) {
@@ -1771,17 +1702,6 @@ describe('ReactSuspenseEffectsSemantics', () => {
'Suspend:Suspend',
'Text:Fallback render',
'Text:Outside render',
- ]);
- expect(ReactNoop).toMatchRenderedOutput(
- <>
-
-
- >,
- );
-
- // Timing out should commit the inner fallback and destroy outer fallback layout effects.
- await jest.runAllTimers();
- assertLog([
'Text:Inside destroy layout',
'Text:Fallback create layout',
]);
@@ -2305,7 +2225,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
});
});
- // @gate enableLegacyCache && enableSyncDefaultUpdates
+ // @gate enableLegacyCache
it('should be only destroy layout effects once if a tree suspends in multiple places', async () => {
class ClassText extends React.Component {
componentDidMount() {
@@ -2366,18 +2286,6 @@ describe('ReactSuspenseEffectsSemantics', () => {
'Text:Function render',
'Suspend:Async_1',
'ClassText:Fallback render',
- ]);
- expect(ReactNoop).toMatchRenderedOutput(
- <>
-
-
- >,
- );
-
- await jest.runAllTimers();
-
- // Timing out should commit the fallback and destroy inner layout effects.
- assertLog([
'Text:Function destroy layout',
'ClassText:Class componentWillUnmount',
'ClassText:Fallback componentDidMount',
@@ -2448,7 +2356,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
]);
});
- // @gate enableLegacyCache && enableSyncDefaultUpdates
+ // @gate enableLegacyCache
it('should be only destroy layout effects once if a component suspends multiple times', async () => {
class ClassText extends React.Component {
componentDidMount() {
@@ -2518,19 +2426,6 @@ describe('ReactSuspenseEffectsSemantics', () => {
'Suspender "A" render',
'Suspend:A',
'ClassText:Fallback render',
- ]);
- expect(ReactNoop).toMatchRenderedOutput(
- <>
-
-
-
- >,
- );
-
- await jest.runAllTimers();
-
- // Timing out should commit the fallback and destroy inner layout effects.
- assertLog([
'Text:Function destroy layout',
'ClassText:Class componentWillUnmount',
'ClassText:Fallback componentDidMount',
diff --git a/packages/react-reconciler/src/__tests__/ReactSuspensePlaceholder-test.internal.js b/packages/react-reconciler/src/__tests__/ReactSuspensePlaceholder-test.internal.js
index 656bd5cacf38..b9bb57b58a0e 100644
--- a/packages/react-reconciler/src/__tests__/ReactSuspensePlaceholder-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactSuspensePlaceholder-test.internal.js
@@ -488,13 +488,12 @@ describe('ReactSuspensePlaceholder', () => {
'Suspend! [Loaded]',
'Fallback',
]);
- expect(ReactNoop).toMatchRenderedOutput('Text');
-
// Show the fallback UI.
- jest.advanceTimersByTime(900);
expect(ReactNoop).toMatchRenderedOutput('Loading...');
expect(onRender).toHaveBeenCalledTimes(2);
+ jest.advanceTimersByTime(900);
+
// The suspense update should only show the "Loading..." Fallback.
// The actual duration should include 10ms spent rendering Fallback,
// plus the 3ms render all of the partially rendered suspended subtree.
@@ -529,19 +528,19 @@ describe('ReactSuspensePlaceholder', () => {
'Suspend! [Sibling]',
]);
expect(ReactNoop).toMatchRenderedOutput('Loading...');
- expect(onRender).toHaveBeenCalledTimes(2);
+ expect(onRender).toHaveBeenCalledTimes(3);
// Resolve the pending promise.
jest.advanceTimersByTime(100);
assertLog(['Promise resolved [Loaded]', 'Promise resolved [Sibling]']);
- await waitForAll(['App', 'Suspending', 'Loaded', 'New', 'Sibling']);
- expect(onRender).toHaveBeenCalledTimes(3);
+ 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[2][2]).toBe(9);
- expect(onRender.mock.calls[2][3]).toBe(9);
+ expect(onRender.mock.calls[3][2]).toBe(9);
+ expect(onRender.mock.calls[3][3]).toBe(9);
});
});
});
diff --git a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js
index 9ef6f2630a61..1dcacd672131 100644
--- a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js
@@ -289,13 +289,9 @@ describe('ReactSuspenseWithNoopRenderer', () => {
await waitForAll(['Foo']);
// The update will suspend.
- if (gate(flags => flags.enableSyncDefaultUpdates)) {
- React.startTransition(() => {
- ReactNoop.render();
- });
- } else {
+ React.startTransition(() => {
ReactNoop.render();
- }
+ });
await waitForAll([
'Foo',
'Bar',
@@ -371,22 +367,11 @@ describe('ReactSuspenseWithNoopRenderer', () => {
});
// @gate enableLegacyCache
- it('continues rendering siblings after suspending', async () => {
+ it('when something suspends, unwinds immediately without rendering siblings', async () => {
// A shell is needed. The update cause it to suspend.
ReactNoop.render(} />);
await waitForAll([]);
- if (gate(flags => flags.enableSyncDefaultUpdates)) {
- React.startTransition(() => {
- ReactNoop.render(
- }>
-
-
-
-
- ,
- );
- });
- } else {
+ React.startTransition(() => {
ReactNoop.render(
}>
@@ -395,7 +380,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
,
);
- }
+ });
+
// B suspends. Render a fallback
await waitForAll(['A', 'Suspend! [B]', 'Loading...']);
// Did not commit yet.
@@ -453,13 +439,9 @@ describe('ReactSuspenseWithNoopRenderer', () => {
await waitForAll([]);
expect(ReactNoop).toMatchRenderedOutput(null);
- if (gate(flags => flags.enableSyncDefaultUpdates)) {
- React.startTransition(() => {
- ReactNoop.render();
- });
- } else {
+ React.startTransition(() => {
ReactNoop.render();
- }
+ });
await waitForAll(['Suspend! [Result]', 'Loading...']);
expect(ReactNoop).toMatchRenderedOutput(null);
@@ -588,9 +570,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
// @gate enableLegacyCache
it('keeps working on lower priority work after being pinged', async () => {
- // Advance the virtual time so that we're close to the edge of a bucket.
- ReactNoop.expire(149);
-
function App(props) {
return (
}>
@@ -604,26 +583,15 @@ describe('ReactSuspenseWithNoopRenderer', () => {
await waitForAll([]);
expect(ReactNoop).toMatchRenderedOutput(null);
- if (gate(flags => flags.enableSyncDefaultUpdates)) {
- React.startTransition(() => {
- ReactNoop.render();
- });
- } else {
+ React.startTransition(() => {
ReactNoop.render();
- }
+ });
await waitForAll(['Suspend! [A]', 'Loading...']);
expect(ReactNoop).toMatchRenderedOutput(null);
- // Advance React's virtual time by enough to fall into a new async bucket,
- // but not enough to expire the suspense timeout.
- ReactNoop.expire(120);
- if (gate(flags => flags.enableSyncDefaultUpdates)) {
- React.startTransition(() => {
- ReactNoop.render();
- });
- } else {
+ React.startTransition(() => {
ReactNoop.render();
- }
+ });
await waitForAll(['Suspend! [A]', 'Loading...']);
expect(ReactNoop).toMatchRenderedOutput(null);
@@ -744,61 +712,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
assertLog(['Sibling', 'Step 4']);
});
- // @gate enableLegacyCache
- it('forces an expiration after an update times out', async () => {
- ReactNoop.render(
-
- } />
- ,
- );
- await waitForAll([]);
-
- ReactNoop.render(
-
- }>
-
-
-
- ,
- );
-
- await waitForAll([
- // The async child suspends
- 'Suspend! [Async]',
- // Render the placeholder
- 'Loading...',
- // Continue on the sibling
- 'Sync',
- ]);
- // The update hasn't expired yet, so we commit nothing.
- expect(ReactNoop).toMatchRenderedOutput(null);
-
- // Advance both React's virtual time and Jest's timers by enough to expire
- // the update.
- ReactNoop.expire(10000);
- await advanceTimers(10000);
- // No additional rendering work is required, since we already prepared
- // the placeholder.
- assertLog([]);
- // Should have committed the placeholder.
- expect(ReactNoop).toMatchRenderedOutput(
- <>
-
-
- >,
- );
-
- // Once the promise resolves, we render the suspended view
- await resolveText('Async');
- await waitForAll(['Async']);
- expect(ReactNoop).toMatchRenderedOutput(
- <>
-
-
- >,
- );
- });
-
// @gate enableLegacyCache
it('switches to an inner fallback after suspending for a while', async () => {
// Advance the virtual time so that we're closer to the edge of a bucket.
@@ -934,109 +847,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
expect(ReactNoop).toMatchRenderedOutput();
});
- // @gate enableLegacyCache
- it('expires early by default', async () => {
- ReactNoop.render(
-
- } />
- ,
- );
- await waitForAll([]);
-
- ReactNoop.render(
-
- }>
-
-
-
- ,
- );
-
- await waitForAll([
- // The async child suspends
- 'Suspend! [Async]',
- 'Loading...',
- // Continue on the sibling
- 'Sync',
- ]);
- // The update hasn't expired yet, so we commit nothing.
- expect(ReactNoop).toMatchRenderedOutput(null);
-
- // Advance both React's virtual time and Jest's timers by enough to trigger
- // the timeout, but not by enough to flush the promise or reach the true
- // expiration time.
- ReactNoop.expire(2000);
- await advanceTimers(2000);
- await waitForAll([]);
- expect(ReactNoop).toMatchRenderedOutput(
- <>
-
-
- >,
- );
-
- // Once the promise resolves, we render the suspended view
- await resolveText('Async');
- await waitForAll(['Async']);
- expect(ReactNoop).toMatchRenderedOutput(
- <>
-
-
- >,
- );
- });
-
- // @gate enableLegacyCache
- it('does not expire for transitions', async () => {
- ReactNoop.render(
-
- } />
- ,
- );
- await waitForAll([]);
-
- React.startTransition(() => {
- ReactNoop.render(
-
- }>
-
-
-
- ,
- );
- });
-
- await waitForAll([
- // The async child suspends
- 'Suspend! [Async]',
- 'Loading...',
- // Continue on the sibling
- 'Sync',
- ]);
- // The update hasn't expired yet, so we commit nothing.
- expect(ReactNoop).toMatchRenderedOutput(null);
-
- // Advance both React's virtual time and Jest's timers,
- // but not by enough to flush the promise or reach the true expiration time.
- ReactNoop.expire(2000);
- await advanceTimers(2000);
- // Even flushing won't yield a fallback in a transition.
- expect(ReactNoop).toMatchRenderedOutput(null);
-
- await waitForAll([]);
- expect(ReactNoop).toMatchRenderedOutput(null);
-
- // Once the promise resolves, we render the suspended view
- await resolveText('Async');
- await waitForAll(['Async', 'Sync']);
- expect(ReactNoop).toMatchRenderedOutput(
- <>
-
-
- >,
- );
- });
-
// @gate enableLegacyCache
it('resolves successfully even if fallback render is pending', async () => {
const root = ReactNoop.createRoot();
@@ -1129,21 +939,13 @@ describe('ReactSuspenseWithNoopRenderer', () => {
ReactNoop.render(} />);
await waitForAll([]);
- if (gate(flags => flags.enableSyncDefaultUpdates)) {
- React.startTransition(() => {
- ReactNoop.render(
- }>
-
- ,
- );
- });
- } else {
+ React.startTransition(() => {
ReactNoop.render(
}>
,
);
- }
+ });
await waitForAll(['Suspend! [Async]', 'Loading...']);
expect(ReactNoop).toMatchRenderedOutput(null);
@@ -1910,74 +1712,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
]);
});
- // @gate enableLegacyCache
- it('suspends for longer if something took a long (CPU bound) time to render', async () => {
- function Foo({renderContent}) {
- Scheduler.log('Foo');
- return (
- }>
- {renderContent ? : null}
-
- );
- }
-
- ReactNoop.render();
- await waitForAll(['Foo']);
-
- if (gate(flags => flags.enableSyncDefaultUpdates)) {
- React.startTransition(() => {
- ReactNoop.render();
- });
- } else {
- ReactNoop.render();
- }
- Scheduler.unstable_advanceTime(100);
- await advanceTimers(100);
- // Start rendering
- await waitFor(['Foo']);
- // For some reason it took a long time to render Foo.
- Scheduler.unstable_advanceTime(1250);
- await advanceTimers(1250);
- await waitForAll([
- // A suspends
- 'Suspend! [A]',
- 'Loading...',
- ]);
- // We're now suspended and we haven't shown anything yet.
- expect(ReactNoop).toMatchRenderedOutput(null);
-
- // Flush some of the time
- Scheduler.unstable_advanceTime(450);
- await advanceTimers(450);
- // Because we've already been waiting for so long we can
- // wait a bit longer. Still nothing...
- await waitForAll([]);
- expect(ReactNoop).toMatchRenderedOutput(null);
-
- // Eventually we'll show the fallback.
- Scheduler.unstable_advanceTime(500);
- await advanceTimers(500);
- // No need to rerender.
- await waitForAll([]);
- if (gate(flags => flags.enableSyncDefaultUpdates)) {
- // Since this is a transition, we never fallback.
- expect(ReactNoop).toMatchRenderedOutput(null);
- } else {
- expect(ReactNoop).toMatchRenderedOutput();
- }
-
- // Flush the promise completely
- await resolveText('A');
- // Renders successfully
- if (gate(flags => flags.enableSyncDefaultUpdates)) {
- // TODO: Why does this render Foo
- await waitForAll(['Foo', 'A']);
- } else {
- await waitForAll(['A']);
- }
- expect(ReactNoop).toMatchRenderedOutput();
- });
-
// @gate enableLegacyCache
it('does not suspends if a fallback has been shown for a long time', async () => {
function Foo() {
@@ -2088,59 +1822,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
);
});
- // @gate enableLegacyCache
- it('does not suspend for very long after a higher priority update', async () => {
- function Foo({renderContent}) {
- Scheduler.log('Foo');
- return (
- }>
- {renderContent ? : null}
-
- );
- }
-
- ReactNoop.render();
- await waitForAll(['Foo']);
-
- if (gate(flags => flags.enableSyncDefaultUpdates)) {
- React.startTransition(() => {
- ReactNoop.render();
- });
- } else {
- ReactNoop.render();
- }
- await waitFor(['Foo']);
-
- // Advance some time.
- Scheduler.unstable_advanceTime(100);
- await advanceTimers(100);
-
- await waitForAll([
- // A suspends
- 'Suspend! [A]',
- 'Loading...',
- ]);
-
- // We're now suspended and we haven't shown anything yet.
- expect(ReactNoop).toMatchRenderedOutput(null);
-
- // Flush some of the time
- Scheduler.unstable_advanceTime(500);
- jest.advanceTimersByTime(500);
-
- // We should have already shown the fallback.
- // When we wrote this test, we inferred the start time of high priority
- // updates as way earlier in the past. This test ensures that we don't
- // use this assumption to add a very long JND.
- await waitForAll([]);
- if (gate(flags => flags.enableSyncDefaultUpdates)) {
- // Transitions never fallback.
- expect(ReactNoop).toMatchRenderedOutput(null);
- } else {
- expect(ReactNoop).toMatchRenderedOutput();
- }
- });
-
// TODO: flip to "warns" when this is implemented again.
// @gate enableLegacyCache
it('does not warn when a low priority update suspends inside a high priority update for functional components', async () => {
@@ -2499,12 +2180,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
}
await waitForAll(['Foo', 'A', 'Suspend! [B]', 'Loading B...']);
- // Still suspended.
- expect(ReactNoop).toMatchRenderedOutput();
-
- // Flush to skip suspended time.
- Scheduler.unstable_advanceTime(600);
- await advanceTimers(600);
if (gate(flags => flags.enableSyncDefaultUpdates)) {
// Transitions never fall back.
@@ -2570,54 +2245,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
}
});
- // @gate enableLegacyCache
- it('commits a suspended idle pri render within a reasonable time', async () => {
- function Foo({renderContent}) {
- return (
-
- }>
- {renderContent ? : null}
-
-
- );
- }
-
- ReactNoop.render();
- await waitForAll([]);
-
- ReactNoop.render();
-
- // Took a long time to render. This is to ensure we get a long suspense time.
- // Could also use something like startTransition to simulate this.
- Scheduler.unstable_advanceTime(1500);
- await advanceTimers(1500);
-
- await waitForAll(['Suspend! [A]', 'Loading A...']);
- // We're still suspended.
- expect(ReactNoop).toMatchRenderedOutput(null);
-
- // Schedule an update at idle pri.
- ReactNoop.idleUpdates(() => ReactNoop.render());
- // We won't even work on Idle priority.
- await waitForAll([]);
-
- // We're still suspended.
- expect(ReactNoop).toMatchRenderedOutput(null);
-
- // Advance time a little bit.
- Scheduler.unstable_advanceTime(150);
- await advanceTimers(150);
-
- // We should not have committed yet because we had a long suspense time.
- expect(ReactNoop).toMatchRenderedOutput(null);
-
- // Flush to skip suspended time.
- Scheduler.unstable_advanceTime(600);
- await advanceTimers(600);
-
- expect(ReactNoop).toMatchRenderedOutput();
- });
-
describe('startTransition', () => {
// @gate enableLegacyCache
it('top level render', async () => {
@@ -3074,62 +2701,6 @@ describe('ReactSuspenseWithNoopRenderer', () => {
);
});
- // TODO: This test is specifically about avoided commits that suspend for a
- // JND. We may remove this behavior.
- // @gate enableLegacyCache
- it("suspended commit remains suspended even if there's another update at same expiration", async () => {
- // Regression test
- function App({text}) {
- return (
-
-
-
- );
- }
-
- const root = ReactNoop.createRoot();
- await act(() => {
- root.render();
- });
- assertLog(['Suspend! [Initial]']);
-
- // Resolve initial render
- await act(async () => {
- await resolveText('Initial');
- });
- assertLog(['Initial']);
- expect(root).toMatchRenderedOutput();
-
- await act(async () => {
- // Update. Since showing a fallback would hide content that's already
- // visible, it should suspend for a JND without committing.
- if (gate(flags => flags.enableSyncDefaultUpdates)) {
- React.startTransition(() => {
- root.render();
- });
- } else {
- root.render();
- }
- await waitForAll(['Suspend! [First update]']);
-
- // Should not display a fallback
- expect(root).toMatchRenderedOutput();
-
- // Update again. This should also suspend for a JND.
- if (gate(flags => flags.enableSyncDefaultUpdates)) {
- React.startTransition(() => {
- root.render();
- });
- } else {
- root.render();
- }
- await waitForAll(['Suspend! [Second update]']);
-
- // Should not display a fallback
- expect(root).toMatchRenderedOutput();
- });
- });
-
it('regression test: resets current "debug phase" after suspending', async () => {
function App() {
return (
@@ -3391,14 +2962,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
setText('C');
});
- await waitForAll([
- // First we attempt the high pri update. It suspends.
- 'Suspend! [B]',
- 'Loading...',
- ]);
-
- // Commit the placeholder to unblock the Idle update.
- await advanceTimers(250);
+ // First we attempt the high pri update. It suspends.
+ await waitForPaint(['Suspend! [B]', 'Loading...']);
expect(root).toMatchRenderedOutput(
<>
@@ -3965,7 +3530,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
]);
expect(root).toMatchRenderedOutput(
<>
-
+
+
>,
);
@@ -4129,25 +3695,41 @@ describe('ReactSuspenseWithNoopRenderer', () => {
await act(async () => {
setText('B');
ReactNoop.idleUpdates(() => {
- setText('B');
+ setText('C');
});
- // Suspend the first update. The second update doesn't run because it has
- // Idle priority.
- await waitForAll(['Suspend! [B]', 'Loading...']);
- // Commit the fallback. Now we'll try working on Idle.
- jest.runAllTimers();
+ // Suspend the first update. This triggers an immediate fallback because
+ // it wasn't wrapped in startTransition.
+ await waitForPaint(['Suspend! [B]', 'Loading...']);
+ expect(root).toMatchRenderedOutput(
+ <>
+
+
+ >,
+ );
- // It also suspends.
- await waitForAll(['Suspend! [B]']);
+ // Once the fallback renders, proceed to the Idle update. This will
+ // also suspend.
+ await waitForAll(['Suspend! [C]']);
});
+ // Finish loading B.
await act(async () => {
setText('B');
await resolveText('B');
});
+ // We did not try to render the Idle update again because there have been no
+ // additional updates since the last time it was attempted.
assertLog(['B']);
expect(root).toMatchRenderedOutput();
+
+ // Finish loading C.
+ await act(async () => {
+ setText('C');
+ await resolveText('C');
+ });
+ assertLog(['C']);
+ expect(root).toMatchRenderedOutput();
});
// @gate enableLegacyCache