Compare commits
1 Commits
pr34666
...
rh/fix-tes
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9f6a6a2a6 |
@@ -203,17 +203,35 @@ describe('ReactCache', () => {
|
||||
<AsyncText ms={100} text={3} />
|
||||
</Suspense>,
|
||||
);
|
||||
await waitForAll(['Suspend! [1]', 'Loading...']);
|
||||
jest.advanceTimersByTime(100);
|
||||
assertLog(['Promise resolved [1]']);
|
||||
await waitForAll([1, 'Suspend! [2]']);
|
||||
if (gate(flags => flags.disableLegacySuspenseThrowSemantics)) {
|
||||
await waitForAll(['Suspend! [1]', 'Loading...']);
|
||||
jest.advanceTimersByTime(100);
|
||||
assertLog(['Promise resolved [1]']);
|
||||
await waitForAll([1, 'Suspend! [2]']);
|
||||
|
||||
jest.advanceTimersByTime(100);
|
||||
assertLog(['Promise resolved [2]']);
|
||||
await waitForAll([1, 2, 'Suspend! [3]']);
|
||||
jest.advanceTimersByTime(100);
|
||||
assertLog(['Promise resolved [2]']);
|
||||
await waitForAll([1, 2, 'Suspend! [3]']);
|
||||
|
||||
await act(() => jest.advanceTimersByTime(100));
|
||||
assertLog(['Promise resolved [3]', 1, 2, 3]);
|
||||
await act(() => jest.advanceTimersByTime(100));
|
||||
assertLog(['Promise resolved [3]', 1, 2, 3]);
|
||||
} else {
|
||||
await waitForAll([
|
||||
'Suspend! [1]',
|
||||
'Suspend! [2]',
|
||||
'Suspend! [3]',
|
||||
'Loading...',
|
||||
]);
|
||||
await act(() => jest.advanceTimersByTime(100));
|
||||
assertLog([
|
||||
'Promise resolved [1]',
|
||||
'Promise resolved [2]',
|
||||
'Promise resolved [3]',
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
]);
|
||||
}
|
||||
|
||||
expect(root).toMatchRenderedOutput('123');
|
||||
|
||||
@@ -226,19 +244,26 @@ describe('ReactCache', () => {
|
||||
</Suspense>,
|
||||
);
|
||||
|
||||
await waitForAll([1, 'Suspend! [4]', 'Loading...']);
|
||||
if (gate(flags => flags.disableLegacySuspenseThrowSemantics)) {
|
||||
await waitForAll([1, 'Suspend! [4]', 'Loading...']);
|
||||
|
||||
await act(() => jest.advanceTimersByTime(100));
|
||||
assertLog([
|
||||
'Promise resolved [4]',
|
||||
1,
|
||||
4,
|
||||
'Suspend! [5]',
|
||||
'Promise resolved [5]',
|
||||
1,
|
||||
4,
|
||||
5,
|
||||
]);
|
||||
await act(() => jest.advanceTimersByTime(100));
|
||||
assertLog([
|
||||
'Promise resolved [4]',
|
||||
1,
|
||||
4,
|
||||
'Suspend! [5]',
|
||||
'Promise resolved [5]',
|
||||
1,
|
||||
4,
|
||||
5,
|
||||
]);
|
||||
} else {
|
||||
await waitForAll([1, 'Suspend! [4]', 'Suspend! [5]', 'Loading...']);
|
||||
|
||||
await act(() => jest.advanceTimersByTime(100));
|
||||
assertLog(['Promise resolved [4]', 'Promise resolved [5]', 1, 4, 5]);
|
||||
}
|
||||
|
||||
expect(root).toMatchRenderedOutput('145');
|
||||
|
||||
@@ -253,25 +278,39 @@ describe('ReactCache', () => {
|
||||
</Suspense>,
|
||||
);
|
||||
|
||||
await waitForAll([
|
||||
// 1 is still cached
|
||||
1,
|
||||
// 2 and 3 suspend because they were evicted from the cache
|
||||
'Suspend! [2]',
|
||||
'Loading...',
|
||||
]);
|
||||
if (gate(flags => flags.disableLegacySuspenseThrowSemantics)) {
|
||||
await waitForAll([
|
||||
// 1 is still cached
|
||||
1,
|
||||
// 2 and 3 suspend because they were evicted from the cache
|
||||
'Suspend! [2]',
|
||||
'Loading...',
|
||||
]);
|
||||
|
||||
await act(() => jest.advanceTimersByTime(100));
|
||||
assertLog([
|
||||
'Promise resolved [2]',
|
||||
1,
|
||||
2,
|
||||
'Suspend! [3]',
|
||||
'Promise resolved [3]',
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
]);
|
||||
await act(() => jest.advanceTimersByTime(100));
|
||||
assertLog([
|
||||
'Promise resolved [2]',
|
||||
1,
|
||||
2,
|
||||
'Suspend! [3]',
|
||||
'Promise resolved [3]',
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
]);
|
||||
} else {
|
||||
await waitForAll([
|
||||
// 1 is still cached
|
||||
1,
|
||||
// 2 and 3 suspend because they were evicted from the cache
|
||||
'Suspend! [2]',
|
||||
'Suspend! [3]',
|
||||
'Loading...',
|
||||
]);
|
||||
|
||||
await act(() => jest.advanceTimersByTime(100));
|
||||
assertLog(['Promise resolved [2]', 'Promise resolved [3]', 1, 2, 3]);
|
||||
}
|
||||
expect(root).toMatchRenderedOutput('123');
|
||||
});
|
||||
|
||||
|
||||
@@ -5222,7 +5222,7 @@ describe('ReactDOMFizzServer', () => {
|
||||
);
|
||||
|
||||
resolve({default: () => <p>lazy</p>});
|
||||
await waitForAll([]);
|
||||
await waitFor([]);
|
||||
expect(errors).toEqual([]);
|
||||
expect(getVisibleChildren(container)).toEqual(
|
||||
<div>
|
||||
|
||||
@@ -160,7 +160,11 @@ describe('ReactDOMSuspensePlaceholder', () => {
|
||||
});
|
||||
|
||||
expect(container.textContent).toEqual('Loading...');
|
||||
assertLog(['A', 'Suspend! [B]', 'Loading...']);
|
||||
assertLog(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? ['A', 'Suspend! [B]', 'Loading...']
|
||||
: ['A', 'Suspend! [B]', 'C', 'Loading...'],
|
||||
);
|
||||
await act(() => {
|
||||
resolveText('B');
|
||||
});
|
||||
|
||||
@@ -43,6 +43,7 @@ import {
|
||||
disableLegacyMode,
|
||||
disableDefaultPropsExceptForClasses,
|
||||
disableStringRefs,
|
||||
disableLegacySuspenseThrowSemantics,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
import ReactSharedInternals from 'shared/ReactSharedInternals';
|
||||
import is from 'shared/objectIs';
|
||||
@@ -333,6 +334,8 @@ let workInProgressThrownValue: mixed = null;
|
||||
// listeners to a promise we've already seen (per root and lane).
|
||||
let workInProgressRootDidAttachPingListener: boolean = false;
|
||||
|
||||
let workInProgressRootSuspendedReason: SuspendedReason = NotSuspended;
|
||||
|
||||
// A contextual version of workInProgressRootRenderLanes. It is a superset of
|
||||
// the lanes that we started working on at the root. When we enter a subtree
|
||||
// that is currently hidden, we add the lanes that would have committed if
|
||||
@@ -1639,6 +1642,9 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
|
||||
workInProgressSuspendedReason = NotSuspended;
|
||||
workInProgressThrownValue = null;
|
||||
workInProgressRootDidAttachPingListener = false;
|
||||
if (!disableLegacySuspenseThrowSemantics) {
|
||||
workInProgressRootSuspendedReason = NotSuspended;
|
||||
}
|
||||
workInProgressRootExitStatus = RootInProgress;
|
||||
workInProgressRootSkippedLanes = NoLanes;
|
||||
workInProgressRootInterleavedUpdatedLanes = NoLanes;
|
||||
@@ -1738,13 +1744,24 @@ function handleThrow(root: FiberRoot, thrownValue: any): void {
|
||||
typeof thrownValue === 'object' &&
|
||||
typeof thrownValue.then === 'function';
|
||||
|
||||
workInProgressSuspendedReason = isWakeable
|
||||
? // A wakeable object was thrown by a legacy Suspense implementation.
|
||||
// This has slightly different behavior than suspending with `use`.
|
||||
SuspendedOnDeprecatedThrowPromise
|
||||
: // This is a regular error. If something earlier in the component already
|
||||
// suspended, we must clear the thenable state to unblock the work loop.
|
||||
SuspendedOnError;
|
||||
if (!disableLegacySuspenseThrowSemantics) {
|
||||
workInProgressSuspendedReason = workInProgressRootSuspendedReason =
|
||||
isWakeable
|
||||
? // A wakeable object was thrown by a legacy Suspense implementation.
|
||||
// This has slightly different behavior than suspending with `use`.
|
||||
SuspendedOnDeprecatedThrowPromise
|
||||
: // This is a regular error. If something earlier in the component already
|
||||
// suspended, we must clear the thenable state to unblock the work loop.
|
||||
SuspendedOnError;
|
||||
} else {
|
||||
workInProgressSuspendedReason = isWakeable
|
||||
? // A wakeable object was thrown by a legacy Suspense implementation.
|
||||
// This has slightly different behavior than suspending with `use`.
|
||||
SuspendedOnDeprecatedThrowPromise
|
||||
: // This is a regular error. If something earlier in the component already
|
||||
// suspended, we must clear the thenable state to unblock the work loop.
|
||||
SuspendedOnError;
|
||||
}
|
||||
}
|
||||
|
||||
workInProgressThrownValue = thrownValue;
|
||||
@@ -2032,6 +2049,17 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) {
|
||||
workInProgressRootExitStatus = RootDidNotComplete;
|
||||
break outer;
|
||||
}
|
||||
case SuspendedOnDeprecatedThrowPromise: {
|
||||
// Unwind then continue with the normal work loop.
|
||||
workInProgressSuspendedReason = NotSuspended;
|
||||
if (!disableLegacySuspenseThrowSemantics) {
|
||||
workInProgressRootSuspendedReason =
|
||||
SuspendedOnDeprecatedThrowPromise;
|
||||
}
|
||||
workInProgressThrownValue = null;
|
||||
throwAndUnwindWorkLoop(root, unitOfWork, thrownValue);
|
||||
break;
|
||||
}
|
||||
case SuspendedOnImmediate:
|
||||
case SuspendedOnData: {
|
||||
if (!didSuspendInShell && getSuspenseHandler() === null) {
|
||||
@@ -2293,6 +2321,10 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
|
||||
// like infinite ping loops. So we maintain the old behavior and
|
||||
// always unwind.
|
||||
workInProgressSuspendedReason = NotSuspended;
|
||||
if (!disableLegacySuspenseThrowSemantics) {
|
||||
workInProgressRootSuspendedReason =
|
||||
SuspendedOnDeprecatedThrowPromise;
|
||||
}
|
||||
workInProgressThrownValue = null;
|
||||
throwAndUnwindWorkLoop(root, unitOfWork, thrownValue);
|
||||
break;
|
||||
@@ -2623,7 +2655,27 @@ 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 (!disableLegacySuspenseThrowSemantics) {
|
||||
if (
|
||||
(completedWork.flags & Incomplete) !== NoFlags &&
|
||||
// TODO: Where is the best place to store this?
|
||||
// The root isn't the right place, because if there are `use` calls
|
||||
// then those boundaries show switch to not pre-rendering.
|
||||
workInProgressRootSuspendedReason ===
|
||||
SuspendedOnDeprecatedThrowPromise &&
|
||||
!getIsHydrating()
|
||||
) {
|
||||
// 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.
|
||||
// TODO: Disable sibling prerendering, then remove this branch.
|
||||
unwindUnitOfWork(completedWork);
|
||||
return;
|
||||
}
|
||||
} else 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.
|
||||
@@ -2755,6 +2807,21 @@ function unwindUnitOfWork(unitOfWork: Fiber): void {
|
||||
// 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 (
|
||||
!disableLegacySuspenseThrowSemantics &&
|
||||
// TODO: Where is the best place to store this?
|
||||
// The root isn't the right place, because if there are `use` calls
|
||||
// then those boundaries show switch to not pre-rendering.
|
||||
workInProgressRootSuspendedReason === SuspendedOnDeprecatedThrowPromise &&
|
||||
!getIsHydrating()
|
||||
) {
|
||||
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
|
||||
|
||||
@@ -297,7 +297,17 @@ 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(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? ['Async action ended', 'Pending: false', 'Suspend! [A1]']
|
||||
: [
|
||||
'Async action ended',
|
||||
'Pending: false',
|
||||
'Suspend! [A1]',
|
||||
'Suspend! [B1]',
|
||||
'Suspend! [C1]',
|
||||
],
|
||||
);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<span>Pending: true</span>
|
||||
@@ -309,7 +319,11 @@ 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(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? ['Pending: false', 'A1', 'Suspend! [B1]']
|
||||
: ['Pending: false', 'A1', 'Suspend! [B1]', 'Suspend! [C1]'],
|
||||
);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<span>Pending: true</span>
|
||||
|
||||
@@ -109,7 +109,11 @@ describe('ReactBlockingMode', () => {
|
||||
</Suspense>,
|
||||
);
|
||||
|
||||
await waitForAll(['A', 'Suspend! [B]', 'Loading...']);
|
||||
await waitForAll(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? ['A', 'Suspend! [B]', 'Loading...']
|
||||
: ['A', 'Suspend! [B]', 'C', 'Loading...'],
|
||||
);
|
||||
// In Legacy Mode, A and B would mount in a hidden primary tree. In
|
||||
// Concurrent Mode, nothing in the primary tree should mount. But the
|
||||
// fallback should mount immediately.
|
||||
|
||||
@@ -397,7 +397,17 @@ describe('ReactConcurrentErrorRecovery', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
assertLog(['Suspend! [Async]']);
|
||||
assertLog(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? ['Suspend! [Async]']
|
||||
: [
|
||||
'Suspend! [Async]',
|
||||
// TODO: Ideally we would skip this second render pass to render the
|
||||
// error UI, since it's not going to commit anyway. The same goes for
|
||||
// Suspense fallbacks during a refresh transition.
|
||||
'Caught an error: Oops!',
|
||||
],
|
||||
);
|
||||
// The render suspended without committing or surfacing the error.
|
||||
expect(root).toMatchRenderedOutput(null);
|
||||
|
||||
@@ -414,7 +424,11 @@ describe('ReactConcurrentErrorRecovery', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
assertLog(['Suspend! [Async]']);
|
||||
assertLog(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? ['Suspend! [Async]']
|
||||
: ['Suspend! [Async]', 'Caught an error: Oops!'],
|
||||
);
|
||||
expect(root).toMatchRenderedOutput(null);
|
||||
|
||||
await act(async () => {
|
||||
|
||||
@@ -699,7 +699,11 @@ describe('ReactExpiration', () => {
|
||||
React.startTransition(() => {
|
||||
root.render(<App step={1} />);
|
||||
});
|
||||
await waitForAll(['Suspend! [A1]', 'Loading...']);
|
||||
await waitForAll(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? ['Suspend! [A1]', 'Loading...']
|
||||
: ['Suspend! [A1]', 'B', 'C', 'Loading...'],
|
||||
);
|
||||
|
||||
// Lots of time elapses before the promise resolves
|
||||
Scheduler.unstable_advanceTime(10000);
|
||||
|
||||
@@ -305,7 +305,11 @@ describe('ReactLazy', () => {
|
||||
unstable_isConcurrent: true,
|
||||
});
|
||||
|
||||
await waitForAll(['Suspend! [LazyChildA]', 'Loading...']);
|
||||
await waitForAll(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? ['Suspend! [LazyChildA]', 'Loading...']
|
||||
: ['Suspend! [LazyChildA]', 'Suspend! [LazyChildB]', 'Loading...'],
|
||||
);
|
||||
expect(root).not.toMatchRenderedOutput('AB');
|
||||
|
||||
await act(async () => {
|
||||
@@ -314,9 +318,17 @@ describe('ReactLazy', () => {
|
||||
// B suspends even though it happens to share the same import as A.
|
||||
// TODO: React.lazy should implement the `status` and `value` fields, so
|
||||
// we can unwrap the result synchronously if it already loaded. Like `use`.
|
||||
await waitFor(['A', 'Suspend! [LazyChildB]']);
|
||||
await waitFor(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? ['A', 'Suspend! [LazyChildB]']
|
||||
: ['A', 'B'],
|
||||
);
|
||||
});
|
||||
assertLog(['A', 'B', 'Did mount: A', 'Did mount: B']);
|
||||
assertLog(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? ['A', 'B', 'Did mount: A', 'Did mount: B']
|
||||
: ['Did mount: A', 'Did mount: B'],
|
||||
);
|
||||
expect(root).toMatchRenderedOutput('AB');
|
||||
|
||||
// Swap the position of A and B
|
||||
@@ -1354,12 +1366,19 @@ describe('ReactLazy', () => {
|
||||
unstable_isConcurrent: true,
|
||||
});
|
||||
|
||||
await waitForAll(['Init A', 'Loading...']);
|
||||
await waitForAll(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? ['Init A', 'Loading...']
|
||||
: ['Init A', 'Init B', 'Loading...'],
|
||||
);
|
||||
expect(root).not.toMatchRenderedOutput('AB');
|
||||
|
||||
await act(() => resolveFakeImport(ChildA));
|
||||
assertLog(['A', 'Init B']);
|
||||
|
||||
assertLog(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? ['A', 'Init B']
|
||||
: ['A'],
|
||||
);
|
||||
await act(() => resolveFakeImport(ChildB));
|
||||
assertLog(['A', 'B', 'Did mount: A', 'Did mount: B']);
|
||||
expect(root).toMatchRenderedOutput('AB');
|
||||
|
||||
@@ -130,13 +130,24 @@ describe('ReactSuspense', () => {
|
||||
root.render(<Foo renderBar={true} />);
|
||||
});
|
||||
|
||||
await waitForAll([
|
||||
'Foo',
|
||||
'Bar',
|
||||
// A suspends
|
||||
'Suspend! [A]',
|
||||
'Loading...',
|
||||
]);
|
||||
await waitForAll(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? [
|
||||
'Foo',
|
||||
'Bar',
|
||||
// A suspends
|
||||
'Suspend! [A]',
|
||||
'Loading...',
|
||||
]
|
||||
: [
|
||||
'Foo',
|
||||
'Bar',
|
||||
// A suspends
|
||||
'Suspend! [A]',
|
||||
'B',
|
||||
'Loading...',
|
||||
],
|
||||
);
|
||||
expect(container.textContent).toEqual('');
|
||||
|
||||
await waitForAll([]);
|
||||
@@ -271,7 +282,17 @@ describe('ReactSuspense', () => {
|
||||
root.render(<Foo />);
|
||||
});
|
||||
|
||||
assertLog(['Foo', 'Suspend! [A]', 'Loading...']);
|
||||
assertLog(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? ['Foo', 'Suspend! [A]', 'Loading...']
|
||||
: [
|
||||
'Foo',
|
||||
'Suspend! [A]',
|
||||
'Suspend! [B]',
|
||||
'Loading more...',
|
||||
'Loading...',
|
||||
],
|
||||
);
|
||||
expect(container.textContent).toEqual('Loading...');
|
||||
|
||||
await resolveText('A');
|
||||
@@ -316,7 +337,17 @@ describe('ReactSuspense', () => {
|
||||
// Render an empty shell
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
root.render(<Foo />);
|
||||
await waitForAll(['Foo', 'Suspend! [A]', 'Loading...']);
|
||||
await waitForAll(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? ['Foo', 'Suspend! [A]', 'Loading...']
|
||||
: [
|
||||
'Foo',
|
||||
'Suspend! [A]',
|
||||
'Suspend! [B]',
|
||||
'Loading more...',
|
||||
'Loading...',
|
||||
],
|
||||
);
|
||||
expect(container.textContent).toEqual('Loading...');
|
||||
|
||||
// Now resolve A
|
||||
@@ -357,7 +388,17 @@ describe('ReactSuspense', () => {
|
||||
await act(() => {
|
||||
root.render(<Foo />);
|
||||
});
|
||||
assertLog(['Foo', 'Suspend! [A]', 'Loading...']);
|
||||
assertLog(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? ['Foo', 'Suspend! [A]', 'Loading...']
|
||||
: [
|
||||
'Foo',
|
||||
'Suspend! [A]',
|
||||
'Suspend! [B]',
|
||||
'Loading more...',
|
||||
'Loading...',
|
||||
],
|
||||
);
|
||||
expect(container.textContent).toEqual('Loading...');
|
||||
|
||||
await resolveText('A');
|
||||
@@ -721,7 +762,11 @@ describe('ReactSuspense', () => {
|
||||
root.render(<App />);
|
||||
});
|
||||
|
||||
assertLog(['Suspend! [Child 1]', 'Loading...']);
|
||||
assertLog(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? ['Suspend! [Child 1]', 'Loading...']
|
||||
: ['Suspend! [Child 1]', 'Suspend! [Child 2]', 'Loading...'],
|
||||
);
|
||||
await resolveText('Child 1');
|
||||
await waitForAll(['Child 1', 'Suspend! [Child 2]']);
|
||||
|
||||
|
||||
@@ -128,7 +128,13 @@ describe('ReactSuspense', () => {
|
||||
ReactNoop.render(element);
|
||||
await waitForAll([]);
|
||||
expect(ReactNoop).toMatchRenderedOutput('Waiting Tier 1');
|
||||
expect(ops).toEqual([new Set([promise1])]);
|
||||
expect(ops).toEqual([
|
||||
new Set(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? [promise1]
|
||||
: [promise1, promise2],
|
||||
),
|
||||
]);
|
||||
ops = [];
|
||||
|
||||
await act(() => resolve1());
|
||||
|
||||
@@ -262,19 +262,36 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
</App>,
|
||||
);
|
||||
});
|
||||
assertLog([
|
||||
'App render',
|
||||
'Text:Inside:Before render',
|
||||
'Suspend:Async',
|
||||
'Text:Fallback render',
|
||||
'Text:Outside render',
|
||||
'Text:Fallback create layout',
|
||||
'Text:Outside create layout',
|
||||
'App create layout',
|
||||
'Text:Fallback create passive',
|
||||
'Text:Outside create passive',
|
||||
'App create passive',
|
||||
]);
|
||||
assertLog(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? [
|
||||
'App render',
|
||||
'Text:Inside:Before render',
|
||||
'Suspend:Async',
|
||||
'Text:Fallback render',
|
||||
'Text:Outside render',
|
||||
'Text:Fallback create layout',
|
||||
'Text:Outside create layout',
|
||||
'App create layout',
|
||||
'Text:Fallback create passive',
|
||||
'Text:Outside create passive',
|
||||
'App create passive',
|
||||
]
|
||||
: [
|
||||
'App render',
|
||||
'Text:Inside:Before render',
|
||||
'Suspend:Async',
|
||||
'ClassText:Inside:After render',
|
||||
'Text:Fallback render',
|
||||
'Text:Outside render',
|
||||
'Text:Fallback create layout',
|
||||
'Text:Outside create layout',
|
||||
'App create layout',
|
||||
'Text:Fallback create passive',
|
||||
'Text:Outside create passive',
|
||||
'App create passive',
|
||||
],
|
||||
);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Fallback" />
|
||||
@@ -636,16 +653,30 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
<AsyncText text="Async" />
|
||||
</App>,
|
||||
);
|
||||
await waitFor([
|
||||
'App render',
|
||||
'Text:Inside:Before render',
|
||||
'Suspend:Async',
|
||||
'Text:Fallback render',
|
||||
'Text:Outside render',
|
||||
'Text:Inside:Before destroy layout',
|
||||
'Text:Inside:After destroy layout',
|
||||
'Text:Fallback create layout',
|
||||
]);
|
||||
await waitFor(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? [
|
||||
'App render',
|
||||
'Text:Inside:Before render',
|
||||
'Suspend:Async',
|
||||
'Text:Fallback render',
|
||||
'Text:Outside render',
|
||||
'Text:Inside:Before destroy layout',
|
||||
'Text:Inside:After destroy layout',
|
||||
'Text:Fallback create layout',
|
||||
]
|
||||
: [
|
||||
'App render',
|
||||
'Text:Inside:Before render',
|
||||
'Suspend:Async',
|
||||
'Text:Inside:After render',
|
||||
'Text:Fallback render',
|
||||
'Text:Outside render',
|
||||
'Text:Inside:Before destroy layout',
|
||||
'Text:Inside:After destroy layout',
|
||||
'Text:Fallback create layout',
|
||||
],
|
||||
);
|
||||
await waitForAll(['Text:Fallback create passive']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
@@ -777,17 +808,32 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
</App>,
|
||||
);
|
||||
|
||||
await waitFor([
|
||||
'App render',
|
||||
'ClassText:Inside:Before render',
|
||||
'Suspend:Async',
|
||||
'ClassText:Fallback render',
|
||||
'ClassText:Outside render',
|
||||
'ClassText:Inside:Before componentWillUnmount',
|
||||
'ClassText:Inside:After componentWillUnmount',
|
||||
'ClassText:Fallback componentDidMount',
|
||||
'ClassText:Outside componentDidUpdate',
|
||||
]);
|
||||
await waitFor(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? [
|
||||
'App render',
|
||||
'ClassText:Inside:Before render',
|
||||
'Suspend:Async',
|
||||
'ClassText:Fallback render',
|
||||
'ClassText:Outside render',
|
||||
'ClassText:Inside:Before componentWillUnmount',
|
||||
'ClassText:Inside:After componentWillUnmount',
|
||||
'ClassText:Fallback componentDidMount',
|
||||
'ClassText:Outside componentDidUpdate',
|
||||
]
|
||||
: [
|
||||
'App render',
|
||||
'ClassText:Inside:Before render',
|
||||
'Suspend:Async',
|
||||
'ClassText:Inside:After render',
|
||||
'ClassText:Fallback render',
|
||||
'ClassText:Outside render',
|
||||
'ClassText:Inside:Before componentWillUnmount',
|
||||
'ClassText:Inside:After componentWillUnmount',
|
||||
'ClassText:Fallback componentDidMount',
|
||||
'ClassText:Outside componentDidUpdate',
|
||||
],
|
||||
);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Inside:Before" hidden={true} />
|
||||
@@ -888,14 +934,27 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
<AsyncText text="Async" />
|
||||
</App>,
|
||||
);
|
||||
await waitFor([
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'Text:Fallback render',
|
||||
'Text:Outer destroy layout',
|
||||
'Text:Inner destroy layout',
|
||||
'Text:Fallback create layout',
|
||||
]);
|
||||
await waitFor(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? [
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'Text:Fallback render',
|
||||
'Text:Outer destroy layout',
|
||||
'Text:Inner destroy layout',
|
||||
'Text:Fallback create layout',
|
||||
]
|
||||
: [
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'Text:Outer render',
|
||||
'Text:Inner render',
|
||||
'Text:Fallback render',
|
||||
'Text:Outer destroy layout',
|
||||
'Text:Inner destroy layout',
|
||||
'Text:Fallback create layout',
|
||||
],
|
||||
);
|
||||
await waitForAll(['Text:Fallback create passive']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
@@ -1002,15 +1061,28 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
<AsyncText text="Async" />
|
||||
</App>,
|
||||
);
|
||||
await waitFor([
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
// Text:MemoizedInner is memoized
|
||||
'Text:Fallback render',
|
||||
'Text:Outer destroy layout',
|
||||
'Text:MemoizedInner destroy layout',
|
||||
'Text:Fallback create layout',
|
||||
]);
|
||||
await waitFor(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? [
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
// Text:MemoizedInner is memoized
|
||||
'Text:Fallback render',
|
||||
'Text:Outer destroy layout',
|
||||
'Text:MemoizedInner destroy layout',
|
||||
'Text:Fallback create layout',
|
||||
]
|
||||
: [
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'Text:Outer render',
|
||||
// Text:MemoizedInner is memoized
|
||||
'Text:Fallback render',
|
||||
'Text:Outer destroy layout',
|
||||
'Text:MemoizedInner destroy layout',
|
||||
'Text:Fallback create layout',
|
||||
],
|
||||
);
|
||||
await waitForAll(['Text:Fallback create passive']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
@@ -1128,15 +1200,30 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
);
|
||||
});
|
||||
await advanceTimers(1000);
|
||||
assertLog([
|
||||
'Text:Outer render',
|
||||
'Suspend:OuterAsync_1',
|
||||
'Text:OuterFallback render',
|
||||
'Text:Outer destroy layout',
|
||||
'Text:InnerFallback destroy layout',
|
||||
'Text:OuterFallback create layout',
|
||||
'Text:OuterFallback create passive',
|
||||
]);
|
||||
assertLog(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? [
|
||||
'Text:Outer render',
|
||||
'Suspend:OuterAsync_1',
|
||||
'Text:OuterFallback render',
|
||||
'Text:Outer destroy layout',
|
||||
'Text:InnerFallback destroy layout',
|
||||
'Text:OuterFallback create layout',
|
||||
'Text:OuterFallback create passive',
|
||||
]
|
||||
: [
|
||||
'Text:Outer render',
|
||||
'Suspend:OuterAsync_1',
|
||||
'Text:Inner render',
|
||||
'Suspend:InnerAsync_1',
|
||||
'Text:InnerFallback render',
|
||||
'Text:OuterFallback render',
|
||||
'Text:Outer destroy layout',
|
||||
'Text:InnerFallback destroy layout',
|
||||
'Text:OuterFallback create layout',
|
||||
'Text:OuterFallback create passive',
|
||||
],
|
||||
);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Outer" hidden={true} />
|
||||
@@ -1150,7 +1237,16 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
await resolveText('InnerAsync_1');
|
||||
});
|
||||
assertLog(['Text:Outer render', 'Suspend:OuterAsync_1']);
|
||||
assertLog(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? ['Text:Outer render', 'Suspend:OuterAsync_1']
|
||||
: [
|
||||
'Text:Outer render',
|
||||
'Suspend:OuterAsync_1',
|
||||
'Text:Inner render',
|
||||
'AsyncText:InnerAsync_1 render',
|
||||
],
|
||||
);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Outer" hidden={true} />
|
||||
@@ -1170,11 +1266,22 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
);
|
||||
});
|
||||
await advanceTimers(1000);
|
||||
assertLog([
|
||||
'Text:Outer render',
|
||||
'Suspend:OuterAsync_1',
|
||||
'Text:OuterFallback render',
|
||||
]);
|
||||
assertLog(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? [
|
||||
'Text:Outer render',
|
||||
'Suspend:OuterAsync_1',
|
||||
'Text:OuterFallback render',
|
||||
]
|
||||
: [
|
||||
'Text:Outer render',
|
||||
'Suspend:OuterAsync_1',
|
||||
'Text:Inner render',
|
||||
'Suspend:InnerAsync_2',
|
||||
'Text:InnerFallback render',
|
||||
'Text:OuterFallback render',
|
||||
],
|
||||
);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Outer" hidden={true} />
|
||||
@@ -1241,17 +1348,33 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
/>,
|
||||
);
|
||||
});
|
||||
assertLog([
|
||||
'Text:Outer render',
|
||||
'Suspend:OuterAsync_2',
|
||||
'Text:OuterFallback render',
|
||||
'Text:Outer destroy layout',
|
||||
'AsyncText:OuterAsync_1 destroy layout',
|
||||
'Text:Inner destroy layout',
|
||||
'AsyncText:InnerAsync_2 destroy layout',
|
||||
'Text:OuterFallback create layout',
|
||||
'Text:OuterFallback create passive',
|
||||
]);
|
||||
assertLog(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? [
|
||||
'Text:Outer render',
|
||||
'Suspend:OuterAsync_2',
|
||||
'Text:OuterFallback render',
|
||||
'Text:Outer destroy layout',
|
||||
'AsyncText:OuterAsync_1 destroy layout',
|
||||
'Text:Inner destroy layout',
|
||||
'AsyncText:InnerAsync_2 destroy layout',
|
||||
'Text:OuterFallback create layout',
|
||||
'Text:OuterFallback create passive',
|
||||
]
|
||||
: [
|
||||
'Text:Outer render',
|
||||
'Suspend:OuterAsync_2',
|
||||
'Text:Inner render',
|
||||
'AsyncText:InnerAsync_2 render',
|
||||
'Text:OuterFallback render',
|
||||
'Text:Outer destroy layout',
|
||||
'AsyncText:OuterAsync_1 destroy layout',
|
||||
'Text:Inner destroy layout',
|
||||
'AsyncText:InnerAsync_2 destroy layout',
|
||||
'Text:OuterFallback create layout',
|
||||
'Text:OuterFallback create passive',
|
||||
],
|
||||
);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Outer" hidden={true} />
|
||||
@@ -1355,15 +1478,30 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
/>,
|
||||
);
|
||||
});
|
||||
assertLog([
|
||||
'Text:Outer render',
|
||||
'Suspend:OuterAsync_1',
|
||||
'Text:OuterFallback render',
|
||||
'Text:Outer destroy layout',
|
||||
'Text:InnerFallback destroy layout',
|
||||
'Text:OuterFallback create layout',
|
||||
'Text:OuterFallback create passive',
|
||||
]);
|
||||
assertLog(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? [
|
||||
'Text:Outer render',
|
||||
'Suspend:OuterAsync_1',
|
||||
'Text:OuterFallback render',
|
||||
'Text:Outer destroy layout',
|
||||
'Text:InnerFallback destroy layout',
|
||||
'Text:OuterFallback create layout',
|
||||
'Text:OuterFallback create passive',
|
||||
]
|
||||
: [
|
||||
'Text:Outer render',
|
||||
'Suspend:OuterAsync_1',
|
||||
'Text:Inner render',
|
||||
'Suspend:InnerAsync_1',
|
||||
'Text:InnerFallback render',
|
||||
'Text:OuterFallback render',
|
||||
'Text:Outer destroy layout',
|
||||
'Text:InnerFallback destroy layout',
|
||||
'Text:OuterFallback create layout',
|
||||
'Text:OuterFallback create passive',
|
||||
],
|
||||
);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Outer" hidden={true} />
|
||||
@@ -1812,17 +1950,33 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
</ErrorBoundary>,
|
||||
);
|
||||
});
|
||||
assertLog([
|
||||
'ErrorBoundary render: try',
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'Text:Fallback render',
|
||||
'Text:Outside render',
|
||||
'ThrowsInDidMount componentWillUnmount',
|
||||
'Text:Inside destroy layout',
|
||||
'Text:Fallback create layout',
|
||||
'Text:Fallback create passive',
|
||||
]);
|
||||
assertLog(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? [
|
||||
'ErrorBoundary render: try',
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'Text:Fallback render',
|
||||
'Text:Outside render',
|
||||
'ThrowsInDidMount componentWillUnmount',
|
||||
'Text:Inside destroy layout',
|
||||
'Text:Fallback create layout',
|
||||
'Text:Fallback create passive',
|
||||
]
|
||||
: [
|
||||
'ErrorBoundary render: try',
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'ThrowsInDidMount render',
|
||||
'Text:Inside render',
|
||||
'Text:Fallback render',
|
||||
'Text:Outside render',
|
||||
'ThrowsInDidMount componentWillUnmount',
|
||||
'Text:Inside destroy layout',
|
||||
'Text:Fallback create layout',
|
||||
'Text:Fallback create passive',
|
||||
],
|
||||
);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="ThrowsInDidMount" hidden={true} />
|
||||
@@ -1946,36 +2100,71 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
</ErrorBoundary>,
|
||||
);
|
||||
});
|
||||
assertLog([
|
||||
'ErrorBoundary render: try',
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'Text:Fallback render',
|
||||
'Text:Outside render',
|
||||
assertLog(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? [
|
||||
'ErrorBoundary render: try',
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'Text:Fallback render',
|
||||
'Text:Outside render',
|
||||
|
||||
// Even though an error was thrown in componentWillUnmount,
|
||||
// subsequent layout effects should still be destroyed.
|
||||
'ThrowsInWillUnmount componentWillUnmount',
|
||||
'Text:Inside destroy layout',
|
||||
// Even though an error was thrown in componentWillUnmount,
|
||||
// subsequent layout effects should still be destroyed.
|
||||
'ThrowsInWillUnmount componentWillUnmount',
|
||||
'Text:Inside destroy layout',
|
||||
|
||||
// Finish the in-progress commit
|
||||
'Text:Fallback create layout',
|
||||
'Text:Fallback create passive',
|
||||
// Finish the in-progress commit
|
||||
'Text:Fallback create layout',
|
||||
'Text:Fallback create passive',
|
||||
|
||||
// Destroy layout and passive effects in the errored tree.
|
||||
'App destroy layout',
|
||||
'Text:Fallback destroy layout',
|
||||
'Text:Outside destroy layout',
|
||||
'Text:Inside destroy passive',
|
||||
'Text:Fallback destroy passive',
|
||||
'Text:Outside destroy passive',
|
||||
// Destroy layout and passive effects in the errored tree.
|
||||
'App destroy layout',
|
||||
'Text:Fallback destroy layout',
|
||||
'Text:Outside destroy layout',
|
||||
'Text:Inside destroy passive',
|
||||
'Text:Fallback destroy passive',
|
||||
'Text:Outside destroy passive',
|
||||
|
||||
// Render fallback
|
||||
'ErrorBoundary render: catch',
|
||||
'Text:Error render',
|
||||
'Text:Error create layout',
|
||||
'Text:Error create passive',
|
||||
]);
|
||||
// Render fallback
|
||||
'ErrorBoundary render: catch',
|
||||
'Text:Error render',
|
||||
'Text:Error create layout',
|
||||
'Text:Error create passive',
|
||||
]
|
||||
: [
|
||||
'ErrorBoundary render: try',
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'ThrowsInWillUnmount render',
|
||||
'Text:Inside render',
|
||||
'Text:Fallback render',
|
||||
'Text:Outside render',
|
||||
|
||||
// Even though an error was thrown in componentWillUnmount,
|
||||
// subsequent layout effects should still be destroyed.
|
||||
'ThrowsInWillUnmount componentWillUnmount',
|
||||
'Text:Inside destroy layout',
|
||||
|
||||
// Finish the in-progress commit
|
||||
'Text:Fallback create layout',
|
||||
'Text:Fallback create passive',
|
||||
|
||||
// Destroy layout and passive effects in the errored tree.
|
||||
'App destroy layout',
|
||||
'Text:Fallback destroy layout',
|
||||
'Text:Outside destroy layout',
|
||||
'Text:Inside destroy passive',
|
||||
'Text:Fallback destroy passive',
|
||||
'Text:Outside destroy passive',
|
||||
|
||||
// Render fallback
|
||||
'ErrorBoundary render: catch',
|
||||
'Text:Error render',
|
||||
'Text:Error create layout',
|
||||
'Text:Error create passive',
|
||||
],
|
||||
);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Error" />);
|
||||
});
|
||||
|
||||
@@ -2055,17 +2244,33 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
</ErrorBoundary>,
|
||||
);
|
||||
});
|
||||
assertLog([
|
||||
'ErrorBoundary render: try',
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'Text:Fallback render',
|
||||
'Text:Outside render',
|
||||
'ThrowsInLayoutEffect useLayoutEffect destroy',
|
||||
'Text:Inside destroy layout',
|
||||
'Text:Fallback create layout',
|
||||
'Text:Fallback create passive',
|
||||
]);
|
||||
assertLog(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? [
|
||||
'ErrorBoundary render: try',
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'Text:Fallback render',
|
||||
'Text:Outside render',
|
||||
'ThrowsInLayoutEffect useLayoutEffect destroy',
|
||||
'Text:Inside destroy layout',
|
||||
'Text:Fallback create layout',
|
||||
'Text:Fallback create passive',
|
||||
]
|
||||
: [
|
||||
'ErrorBoundary render: try',
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'ThrowsInLayoutEffect render',
|
||||
'Text:Inside render',
|
||||
'Text:Fallback render',
|
||||
'Text:Outside render',
|
||||
'ThrowsInLayoutEffect useLayoutEffect destroy',
|
||||
'Text:Inside destroy layout',
|
||||
'Text:Fallback create layout',
|
||||
'Text:Fallback create passive',
|
||||
],
|
||||
);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="ThrowsInLayoutEffect" hidden={true} />
|
||||
@@ -2189,36 +2394,71 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
</ErrorBoundary>,
|
||||
);
|
||||
});
|
||||
assertLog([
|
||||
'ErrorBoundary render: try',
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'Text:Fallback render',
|
||||
'Text:Outside render',
|
||||
assertLog(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? [
|
||||
'ErrorBoundary render: try',
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'Text:Fallback render',
|
||||
'Text:Outside render',
|
||||
|
||||
// Even though an error was thrown in useLayoutEffect destroy,
|
||||
// subsequent layout effects should still be destroyed.
|
||||
'ThrowsInLayoutEffectDestroy useLayoutEffect destroy',
|
||||
'Text:Inside destroy layout',
|
||||
// Even though an error was thrown in useLayoutEffect destroy,
|
||||
// subsequent layout effects should still be destroyed.
|
||||
'ThrowsInLayoutEffectDestroy useLayoutEffect destroy',
|
||||
'Text:Inside destroy layout',
|
||||
|
||||
// Finish the in-progress commit
|
||||
'Text:Fallback create layout',
|
||||
'Text:Fallback create passive',
|
||||
// Finish the in-progress commit
|
||||
'Text:Fallback create layout',
|
||||
'Text:Fallback create passive',
|
||||
|
||||
// Destroy layout and passive effects in the errored tree.
|
||||
'App destroy layout',
|
||||
'Text:Fallback destroy layout',
|
||||
'Text:Outside destroy layout',
|
||||
'Text:Inside destroy passive',
|
||||
'Text:Fallback destroy passive',
|
||||
'Text:Outside destroy passive',
|
||||
// Destroy layout and passive effects in the errored tree.
|
||||
'App destroy layout',
|
||||
'Text:Fallback destroy layout',
|
||||
'Text:Outside destroy layout',
|
||||
'Text:Inside destroy passive',
|
||||
'Text:Fallback destroy passive',
|
||||
'Text:Outside destroy passive',
|
||||
|
||||
// Render fallback
|
||||
'ErrorBoundary render: catch',
|
||||
'Text:Error render',
|
||||
'Text:Error create layout',
|
||||
'Text:Error create passive',
|
||||
]);
|
||||
// Render fallback
|
||||
'ErrorBoundary render: catch',
|
||||
'Text:Error render',
|
||||
'Text:Error create layout',
|
||||
'Text:Error create passive',
|
||||
]
|
||||
: [
|
||||
'ErrorBoundary render: try',
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'ThrowsInLayoutEffectDestroy render',
|
||||
'Text:Inside render',
|
||||
'Text:Fallback render',
|
||||
'Text:Outside render',
|
||||
|
||||
// Even though an error was thrown in useLayoutEffect destroy,
|
||||
// subsequent layout effects should still be destroyed.
|
||||
'ThrowsInLayoutEffectDestroy useLayoutEffect destroy',
|
||||
'Text:Inside destroy layout',
|
||||
|
||||
// Finish the in-progress commit
|
||||
'Text:Fallback create layout',
|
||||
'Text:Fallback create passive',
|
||||
|
||||
// Destroy layout and passive effects in the errored tree.
|
||||
'App destroy layout',
|
||||
'Text:Fallback destroy layout',
|
||||
'Text:Outside destroy layout',
|
||||
'Text:Inside destroy passive',
|
||||
'Text:Fallback destroy passive',
|
||||
'Text:Outside destroy passive',
|
||||
|
||||
// Render fallback
|
||||
'ErrorBoundary render: catch',
|
||||
'Text:Error render',
|
||||
'Text:Error create layout',
|
||||
'Text:Error create passive',
|
||||
],
|
||||
);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Error" />);
|
||||
});
|
||||
});
|
||||
@@ -2280,14 +2520,27 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
<AsyncText text="Async_2" />
|
||||
</App>,
|
||||
);
|
||||
await waitFor([
|
||||
'Text:Function render',
|
||||
'Suspend:Async_1',
|
||||
'ClassText:Fallback render',
|
||||
'Text:Function destroy layout',
|
||||
'ClassText:Class componentWillUnmount',
|
||||
'ClassText:Fallback componentDidMount',
|
||||
]);
|
||||
await waitFor(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? [
|
||||
'Text:Function render',
|
||||
'Suspend:Async_1',
|
||||
'ClassText:Fallback render',
|
||||
'Text:Function destroy layout',
|
||||
'ClassText:Class componentWillUnmount',
|
||||
'ClassText:Fallback componentDidMount',
|
||||
]
|
||||
: [
|
||||
'Text:Function render',
|
||||
'Suspend:Async_1',
|
||||
'Suspend:Async_2',
|
||||
'ClassText:Class render',
|
||||
'ClassText:Fallback render',
|
||||
'Text:Function destroy layout',
|
||||
'ClassText:Class componentWillUnmount',
|
||||
'ClassText:Fallback componentDidMount',
|
||||
],
|
||||
);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Function" hidden={true} />
|
||||
@@ -2301,11 +2554,20 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
await resolveText('Async_1');
|
||||
});
|
||||
assertLog([
|
||||
'Text:Function render',
|
||||
'AsyncText:Async_1 render',
|
||||
'Suspend:Async_2',
|
||||
]);
|
||||
assertLog(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? [
|
||||
'Text:Function render',
|
||||
'AsyncText:Async_1 render',
|
||||
'Suspend:Async_2',
|
||||
]
|
||||
: [
|
||||
'Text:Function render',
|
||||
'AsyncText:Async_1 render',
|
||||
'Suspend:Async_2',
|
||||
'ClassText:Class render',
|
||||
],
|
||||
);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Function" hidden={true} />
|
||||
@@ -2419,15 +2681,28 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
textToRead = 'A';
|
||||
await act(async () => {
|
||||
ReactNoop.render(<App />);
|
||||
await waitFor([
|
||||
'Text:Function render',
|
||||
'Suspender "A" render',
|
||||
'Suspend:A',
|
||||
'ClassText:Fallback render',
|
||||
'Text:Function destroy layout',
|
||||
'ClassText:Class componentWillUnmount',
|
||||
'ClassText:Fallback componentDidMount',
|
||||
]);
|
||||
await waitFor(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? [
|
||||
'Text:Function render',
|
||||
'Suspender "A" render',
|
||||
'Suspend:A',
|
||||
'ClassText:Fallback render',
|
||||
'Text:Function destroy layout',
|
||||
'ClassText:Class componentWillUnmount',
|
||||
'ClassText:Fallback componentDidMount',
|
||||
]
|
||||
: [
|
||||
'Text:Function render',
|
||||
'Suspender "A" render',
|
||||
'Suspend:A',
|
||||
'ClassText:Class render',
|
||||
'ClassText:Fallback render',
|
||||
'Text:Function destroy layout',
|
||||
'ClassText:Class componentWillUnmount',
|
||||
'ClassText:Fallback componentDidMount',
|
||||
],
|
||||
);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Function" hidden={true} />
|
||||
@@ -2443,7 +2718,16 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
await resolveText('A');
|
||||
});
|
||||
assertLog(['Text:Function render', 'Suspender "B" render', 'Suspend:B']);
|
||||
assertLog(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? ['Text:Function render', 'Suspender "B" render', 'Suspend:B']
|
||||
: [
|
||||
'Text:Function render',
|
||||
'Suspender "B" render',
|
||||
'Suspend:B',
|
||||
'ClassText:Class render',
|
||||
],
|
||||
);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Function" hidden={true} />
|
||||
@@ -2664,17 +2948,34 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
ReactNoop.render(<App children={<AsyncText text="Async" />} />);
|
||||
});
|
||||
await advanceTimers(1000);
|
||||
assertLog([
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'Text:Fallback render',
|
||||
'RefCheckerOuter destroy layout refObject? true refCallback? true',
|
||||
'RefCheckerInner:refObject destroy layout ref? false',
|
||||
'RefCheckerOuter refCallback value? false',
|
||||
'RefCheckerInner:refCallback destroy layout ref? false',
|
||||
'Text:Fallback create layout',
|
||||
'Text:Fallback create passive',
|
||||
]);
|
||||
assertLog(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? [
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'Text:Fallback render',
|
||||
'RefCheckerOuter destroy layout refObject? true refCallback? true',
|
||||
'RefCheckerInner:refObject destroy layout ref? false',
|
||||
'RefCheckerOuter refCallback value? false',
|
||||
'RefCheckerInner:refCallback destroy layout ref? false',
|
||||
'Text:Fallback create layout',
|
||||
'Text:Fallback create passive',
|
||||
]
|
||||
: [
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'RefCheckerOuter render',
|
||||
'RefCheckerInner:refObject render',
|
||||
'RefCheckerInner:refCallback render',
|
||||
'Text:Fallback render',
|
||||
'RefCheckerOuter destroy layout refObject? true refCallback? true',
|
||||
'RefCheckerInner:refObject destroy layout ref? false',
|
||||
'RefCheckerOuter refCallback value? false',
|
||||
'RefCheckerInner:refCallback destroy layout ref? false',
|
||||
'Text:Fallback create layout',
|
||||
'Text:Fallback create passive',
|
||||
],
|
||||
);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="refObject" hidden={true} />
|
||||
@@ -2765,17 +3066,36 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
ReactNoop.render(<App children={<AsyncText text="Async" />} />);
|
||||
});
|
||||
await advanceTimers(1000);
|
||||
assertLog([
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'Text:Fallback render',
|
||||
'RefCheckerOuter destroy layout refObject? true refCallback? true',
|
||||
'RefCheckerInner:refObject destroy layout ref? false',
|
||||
'RefCheckerOuter refCallback value? false',
|
||||
'RefCheckerInner:refCallback destroy layout ref? false',
|
||||
'Text:Fallback create layout',
|
||||
'Text:Fallback create passive',
|
||||
]);
|
||||
assertLog(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? [
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'Text:Fallback render',
|
||||
'RefCheckerOuter destroy layout refObject? true refCallback? true',
|
||||
'RefCheckerInner:refObject destroy layout ref? false',
|
||||
'RefCheckerOuter refCallback value? false',
|
||||
'RefCheckerInner:refCallback destroy layout ref? false',
|
||||
'Text:Fallback create layout',
|
||||
'Text:Fallback create passive',
|
||||
]
|
||||
: [
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'RefCheckerOuter render',
|
||||
'ClassComponent:refObject render',
|
||||
'RefCheckerInner:refObject render',
|
||||
'ClassComponent:refCallback render',
|
||||
'RefCheckerInner:refCallback render',
|
||||
'Text:Fallback render',
|
||||
'RefCheckerOuter destroy layout refObject? true refCallback? true',
|
||||
'RefCheckerInner:refObject destroy layout ref? false',
|
||||
'RefCheckerOuter refCallback value? false',
|
||||
'RefCheckerInner:refCallback destroy layout ref? false',
|
||||
'Text:Fallback create layout',
|
||||
'Text:Fallback create passive',
|
||||
],
|
||||
);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Fallback" />);
|
||||
|
||||
// Resolving the suspended resource should re-create inner layout effects.
|
||||
@@ -2862,17 +3182,36 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
ReactNoop.render(<App children={<AsyncText text="Async" />} />);
|
||||
});
|
||||
await advanceTimers(1000);
|
||||
assertLog([
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'Text:Fallback render',
|
||||
'RefCheckerOuter destroy layout refObject? true refCallback? true',
|
||||
'RefCheckerInner:refObject destroy layout ref? false',
|
||||
'RefCheckerOuter refCallback value? false',
|
||||
'RefCheckerInner:refCallback destroy layout ref? false',
|
||||
'Text:Fallback create layout',
|
||||
'Text:Fallback create passive',
|
||||
]);
|
||||
assertLog(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? [
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'Text:Fallback render',
|
||||
'RefCheckerOuter destroy layout refObject? true refCallback? true',
|
||||
'RefCheckerInner:refObject destroy layout ref? false',
|
||||
'RefCheckerOuter refCallback value? false',
|
||||
'RefCheckerInner:refCallback destroy layout ref? false',
|
||||
'Text:Fallback create layout',
|
||||
'Text:Fallback create passive',
|
||||
]
|
||||
: [
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'RefCheckerOuter render',
|
||||
'FunctionComponent render',
|
||||
'RefCheckerInner:refObject render',
|
||||
'FunctionComponent render',
|
||||
'RefCheckerInner:refCallback render',
|
||||
'Text:Fallback render',
|
||||
'RefCheckerOuter destroy layout refObject? true refCallback? true',
|
||||
'RefCheckerInner:refObject destroy layout ref? false',
|
||||
'RefCheckerOuter refCallback value? false',
|
||||
'RefCheckerInner:refCallback destroy layout ref? false',
|
||||
'Text:Fallback create layout',
|
||||
'Text:Fallback create passive',
|
||||
],
|
||||
);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Fallback" />);
|
||||
|
||||
// Resolving the suspended resource should re-create inner layout effects.
|
||||
@@ -2964,14 +3303,26 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
ReactNoop.render(<App children={<AsyncText text="Async" />} />);
|
||||
});
|
||||
await advanceTimers(1000);
|
||||
assertLog([
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'Text:Fallback render',
|
||||
'RefChecker destroy layout ref? true',
|
||||
'Text:Fallback create layout',
|
||||
'Text:Fallback create passive',
|
||||
]);
|
||||
assertLog(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? [
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'Text:Fallback render',
|
||||
'RefChecker destroy layout ref? true',
|
||||
'Text:Fallback create layout',
|
||||
'Text:Fallback create passive',
|
||||
]
|
||||
: [
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'RefChecker render',
|
||||
'Text:Fallback render',
|
||||
'RefChecker destroy layout ref? true',
|
||||
'Text:Fallback create layout',
|
||||
'Text:Fallback create passive',
|
||||
],
|
||||
);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Fallback" />);
|
||||
|
||||
// Resolving the suspended resource should re-create inner layout effects.
|
||||
@@ -3075,17 +3426,33 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
</ErrorBoundary>,
|
||||
);
|
||||
});
|
||||
assertLog([
|
||||
'ErrorBoundary render: try',
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'Text:Fallback render',
|
||||
'Text:Outside render',
|
||||
'ThrowsInRefCallback refCallback ref? false',
|
||||
'Text:Inside destroy layout',
|
||||
'Text:Fallback create layout',
|
||||
'Text:Fallback create passive',
|
||||
]);
|
||||
assertLog(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? [
|
||||
'ErrorBoundary render: try',
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'Text:Fallback render',
|
||||
'Text:Outside render',
|
||||
'ThrowsInRefCallback refCallback ref? false',
|
||||
'Text:Inside destroy layout',
|
||||
'Text:Fallback create layout',
|
||||
'Text:Fallback create passive',
|
||||
]
|
||||
: [
|
||||
'ErrorBoundary render: try',
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'ThrowsInRefCallback render',
|
||||
'Text:Inside render',
|
||||
'Text:Fallback render',
|
||||
'Text:Outside render',
|
||||
'ThrowsInRefCallback refCallback ref? false',
|
||||
'Text:Inside destroy layout',
|
||||
'Text:Fallback create layout',
|
||||
'Text:Fallback create passive',
|
||||
],
|
||||
);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="ThrowsInRefCallback" hidden={true} />
|
||||
|
||||
@@ -135,7 +135,11 @@ describe('ReactSuspensePlaceholder', () => {
|
||||
// Initial mount
|
||||
ReactNoop.render(<App middleText="B" />);
|
||||
|
||||
await waitForAll(['A', 'Suspend! [B]', 'Loading...']);
|
||||
await waitForAll(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? ['A', 'Suspend! [B]', 'Loading...']
|
||||
: ['A', 'Suspend! [B]', 'C', 'Loading...'],
|
||||
);
|
||||
expect(ReactNoop).toMatchRenderedOutput('Loading...');
|
||||
|
||||
await act(() => jest.advanceTimersByTime(1000));
|
||||
@@ -151,7 +155,11 @@ describe('ReactSuspensePlaceholder', () => {
|
||||
|
||||
// Update
|
||||
ReactNoop.render(<App middleText="B2" />);
|
||||
await waitForAll(['Suspend! [B2]', 'Loading...']);
|
||||
await waitForAll(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? ['Suspend! [B2]', 'Loading...']
|
||||
: ['Suspend! [B2]', 'C', 'Loading...'],
|
||||
);
|
||||
|
||||
// Time out the update
|
||||
jest.advanceTimersByTime(750);
|
||||
@@ -194,7 +202,11 @@ describe('ReactSuspensePlaceholder', () => {
|
||||
// Initial mount
|
||||
ReactNoop.render(<App middleText="B" />);
|
||||
|
||||
await waitForAll(['A', 'Suspend! [B]', 'Loading...']);
|
||||
await waitForAll(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? ['A', 'Suspend! [B]', 'Loading...']
|
||||
: ['A', 'Suspend! [B]', 'C', 'Loading...'],
|
||||
);
|
||||
|
||||
expect(ReactNoop).not.toMatchRenderedOutput('ABC');
|
||||
|
||||
@@ -204,7 +216,11 @@ describe('ReactSuspensePlaceholder', () => {
|
||||
|
||||
// Update
|
||||
ReactNoop.render(<App middleText="B2" />);
|
||||
await waitForAll(['A', 'Suspend! [B2]', 'Loading...']);
|
||||
await waitForAll(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? ['A', 'Suspend! [B2]', 'Loading...']
|
||||
: ['A', 'Suspend! [B2]', 'C', 'Loading...'],
|
||||
);
|
||||
// Time out the update
|
||||
jest.advanceTimersByTime(750);
|
||||
await waitForAll([]);
|
||||
@@ -237,7 +253,11 @@ describe('ReactSuspensePlaceholder', () => {
|
||||
// Initial mount
|
||||
ReactNoop.render(<App middleText="b" />);
|
||||
|
||||
await waitForAll(['a', 'Suspend! [b]', 'Loading...']);
|
||||
await waitForAll(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? ['a', 'Suspend! [b]', 'Loading...']
|
||||
: ['a', 'Suspend! [b]', 'c', 'Loading...'],
|
||||
);
|
||||
|
||||
expect(ReactNoop).toMatchRenderedOutput(<uppercase>LOADING...</uppercase>);
|
||||
|
||||
@@ -247,7 +267,11 @@ describe('ReactSuspensePlaceholder', () => {
|
||||
|
||||
// Update
|
||||
ReactNoop.render(<App middleText="b2" />);
|
||||
await waitForAll(['a', 'Suspend! [b2]', 'Loading...']);
|
||||
await waitForAll(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? ['a', 'Suspend! [b2]', 'Loading...']
|
||||
: ['a', 'Suspend! [b2]', 'c', 'Loading...'],
|
||||
);
|
||||
// Time out the update
|
||||
jest.advanceTimersByTime(750);
|
||||
await waitForAll([]);
|
||||
@@ -335,12 +359,11 @@ describe('ReactSuspensePlaceholder', () => {
|
||||
it('properly accounts for base durations when a suspended times out in a concurrent tree', async () => {
|
||||
ReactNoop.render(<App shouldSuspend={true} />);
|
||||
|
||||
await waitForAll([
|
||||
'App',
|
||||
'Suspending',
|
||||
'Suspend! [Loaded]',
|
||||
'Fallback',
|
||||
]);
|
||||
await waitForAll(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? ['App', 'Suspending', 'Suspend! [Loaded]', 'Fallback']
|
||||
: ['App', 'Suspending', 'Suspend! [Loaded]', 'Text', 'Fallback'],
|
||||
);
|
||||
// Since this is initial render we immediately commit the fallback. Another test below
|
||||
// deals with the update case where this suspends.
|
||||
expect(ReactNoop).toMatchRenderedOutput('Loading...');
|
||||
@@ -349,7 +372,9 @@ describe('ReactSuspensePlaceholder', () => {
|
||||
// Initial mount only shows the "Loading..." Fallback.
|
||||
// The treeBaseDuration then should be 10ms spent rendering Fallback,
|
||||
// but the actualDuration should also include the 3ms spent rendering the hidden tree.
|
||||
expect(onRender.mock.calls[0][2]).toBe(13);
|
||||
expect(onRender.mock.calls[0][2]).toBe(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics) ? 13 : 18,
|
||||
);
|
||||
expect(onRender.mock.calls[0][3]).toBe(10);
|
||||
|
||||
// Resolve the pending promise.
|
||||
@@ -482,12 +507,11 @@ describe('ReactSuspensePlaceholder', () => {
|
||||
<Suspense fallback={null} />
|
||||
</>,
|
||||
);
|
||||
await waitForAll([
|
||||
'App',
|
||||
'Suspending',
|
||||
'Suspend! [Loaded]',
|
||||
'Fallback',
|
||||
]);
|
||||
await waitForAll(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? ['App', 'Suspending', 'Suspend! [Loaded]', 'Fallback']
|
||||
: ['App', 'Suspending', 'Suspend! [Loaded]', 'Text', 'Fallback'],
|
||||
);
|
||||
// Show the fallback UI.
|
||||
expect(ReactNoop).toMatchRenderedOutput('Loading...');
|
||||
expect(onRender).toHaveBeenCalledTimes(2);
|
||||
@@ -498,7 +522,9 @@ describe('ReactSuspensePlaceholder', () => {
|
||||
// The actual duration should include 10ms spent rendering Fallback,
|
||||
// plus the 3ms render all of the partially rendered suspended subtree.
|
||||
// But the tree base duration should only include 10ms spent rendering Fallback.
|
||||
expect(onRender.mock.calls[1][2]).toBe(13);
|
||||
expect(onRender.mock.calls[1][2]).toBe(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics) ? 13 : 18,
|
||||
);
|
||||
expect(onRender.mock.calls[1][3]).toBe(10);
|
||||
|
||||
// Update again while timed out.
|
||||
@@ -520,13 +546,24 @@ describe('ReactSuspensePlaceholder', () => {
|
||||
// from timers.
|
||||
Scheduler.unstable_advanceTime(200);
|
||||
|
||||
await waitForAll([
|
||||
'App',
|
||||
'Suspending',
|
||||
'Suspend! [Loaded]',
|
||||
'Fallback',
|
||||
'Suspend! [Sibling]',
|
||||
]);
|
||||
await waitForAll(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? [
|
||||
'App',
|
||||
'Suspending',
|
||||
'Suspend! [Loaded]',
|
||||
'Fallback',
|
||||
'Suspend! [Sibling]',
|
||||
]
|
||||
: [
|
||||
'App',
|
||||
'Suspending',
|
||||
'Suspend! [Loaded]',
|
||||
'New',
|
||||
'Fallback',
|
||||
'Suspend! [Sibling]',
|
||||
],
|
||||
);
|
||||
expect(ReactNoop).toMatchRenderedOutput('Loading...');
|
||||
expect(onRender).toHaveBeenCalledTimes(3);
|
||||
|
||||
|
||||
@@ -221,17 +221,31 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
React.startTransition(() => {
|
||||
ReactNoop.render(<Foo />);
|
||||
});
|
||||
await waitFor([
|
||||
'Foo',
|
||||
'Bar',
|
||||
// A suspends
|
||||
'Suspend! [A]',
|
||||
// We immediately unwind and switch to a fallback without
|
||||
// rendering siblings.
|
||||
'Loading...',
|
||||
'C',
|
||||
// Yield before rendering D
|
||||
]);
|
||||
await waitFor(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? [
|
||||
'Foo',
|
||||
'Bar',
|
||||
// A suspends
|
||||
'Suspend! [A]',
|
||||
// We immediately unwind and switch to a fallback without
|
||||
// rendering siblings.
|
||||
'Loading...',
|
||||
'C',
|
||||
// Yield before rendering D
|
||||
]
|
||||
: [
|
||||
'Foo',
|
||||
'Bar',
|
||||
// A suspends
|
||||
'Suspend! [A]',
|
||||
// But we keep rendering the siblings
|
||||
'B',
|
||||
'Loading...',
|
||||
'C',
|
||||
// We leave D incomplete.
|
||||
],
|
||||
);
|
||||
expect(ReactNoop).toMatchRenderedOutput(null);
|
||||
|
||||
// Flush the promise completely
|
||||
@@ -290,15 +304,27 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
React.startTransition(() => {
|
||||
ReactNoop.render(<Foo renderBar={true} />);
|
||||
});
|
||||
await waitForAll([
|
||||
'Foo',
|
||||
'Bar',
|
||||
// A suspends
|
||||
'Suspend! [A]',
|
||||
// We immediately unwind and switch to a fallback without
|
||||
// rendering siblings.
|
||||
'Loading...',
|
||||
]);
|
||||
await waitForAll(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? [
|
||||
'Foo',
|
||||
'Bar',
|
||||
// A suspends
|
||||
'Suspend! [A]',
|
||||
// We immediately unwind and switch to a fallback without
|
||||
// rendering siblings.
|
||||
'Loading...',
|
||||
]
|
||||
: [
|
||||
'Foo',
|
||||
'Bar',
|
||||
// A suspends
|
||||
'Suspend! [A]',
|
||||
// But we keep rendering the siblings
|
||||
'B',
|
||||
'Loading...',
|
||||
],
|
||||
);
|
||||
expect(ReactNoop).toMatchRenderedOutput(null);
|
||||
|
||||
// Resolve the data
|
||||
@@ -379,7 +405,25 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
});
|
||||
|
||||
// B suspends. Render a fallback
|
||||
await waitForAll(['A', 'Suspend! [B]', 'Loading...']);
|
||||
await waitForAll(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? [
|
||||
'A',
|
||||
// B suspends.
|
||||
'Suspend! [B]',
|
||||
'Loading...',
|
||||
]
|
||||
: [
|
||||
'A',
|
||||
// B suspends.
|
||||
'Suspend! [B]',
|
||||
// Continue rendering the remaining siblings.
|
||||
'C',
|
||||
'D',
|
||||
'Loading...',
|
||||
],
|
||||
);
|
||||
|
||||
// Did not commit yet.
|
||||
expect(ReactNoop).toMatchRenderedOutput(null);
|
||||
|
||||
@@ -598,7 +642,11 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
React.startTransition(() => {
|
||||
ReactNoop.render(<App showA={true} showB={true} />);
|
||||
});
|
||||
await waitForAll(['Suspend! [A]', 'Loading...']);
|
||||
await waitForAll(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? ['Suspend! [A]', 'Loading...']
|
||||
: ['Suspend! [A]', 'B', 'Loading...'],
|
||||
);
|
||||
expect(ReactNoop).toMatchRenderedOutput(null);
|
||||
|
||||
await resolveText('A');
|
||||
@@ -723,12 +771,24 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
</Fragment>,
|
||||
);
|
||||
|
||||
await waitForAll([
|
||||
'Sync',
|
||||
// The async content suspends
|
||||
'Suspend! [Outer content]',
|
||||
'Loading outer...',
|
||||
]);
|
||||
await waitForAll(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? [
|
||||
'Sync',
|
||||
// The async content suspends
|
||||
'Suspend! [Outer content]',
|
||||
'Loading outer...',
|
||||
]
|
||||
: [
|
||||
'Sync',
|
||||
// The async content suspends
|
||||
'Suspend! [Outer content]',
|
||||
// Continue rendering the remaining siblings.
|
||||
'Suspend! [Inner content]',
|
||||
'Loading inner...',
|
||||
'Loading outer...',
|
||||
],
|
||||
);
|
||||
// The outer loading state finishes immediately.
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
@@ -832,11 +892,20 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
</Fragment>,
|
||||
),
|
||||
);
|
||||
assertLog([
|
||||
'Suspend! [Async]',
|
||||
'Suspend! [Loading (inner)...]',
|
||||
'Loading (outer)...',
|
||||
]);
|
||||
assertLog(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? [
|
||||
'Suspend! [Async]',
|
||||
'Suspend! [Loading (inner)...]',
|
||||
'Loading (outer)...',
|
||||
]
|
||||
: [
|
||||
'Suspend! [Async]',
|
||||
'Suspend! [Loading (inner)...]',
|
||||
'Sync',
|
||||
'Loading (outer)...',
|
||||
],
|
||||
);
|
||||
// The tree commits synchronously
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Loading (outer)..." />);
|
||||
});
|
||||
@@ -904,7 +973,11 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
<AsyncText text="B" />
|
||||
</Suspense>,
|
||||
);
|
||||
await waitForAll(['Suspend! [A]', 'Loading...']);
|
||||
await waitForAll(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? ['Suspend! [A]', 'Loading...']
|
||||
: ['Suspend! [A]', 'Suspend! [B]', 'Loading...'],
|
||||
);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Loading..." />);
|
||||
|
||||
await act(() => {
|
||||
@@ -1014,7 +1087,11 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
}
|
||||
|
||||
ReactNoop.render(<App />);
|
||||
await waitForAll(['Suspend! [A]']);
|
||||
await waitForAll(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? ['Suspend! [A]']
|
||||
: ['Suspend! [A]', 'Suspend! [B]', 'Suspend! [C]'],
|
||||
);
|
||||
expect(ReactNoop).toMatchRenderedOutput('Loading...');
|
||||
|
||||
await resolveText('A');
|
||||
@@ -1672,12 +1749,23 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
|
||||
ReactNoop.render(<Foo />);
|
||||
// Start rendering
|
||||
await waitForAll([
|
||||
'Foo',
|
||||
// A suspends
|
||||
'Suspend! [A]',
|
||||
'Loading...',
|
||||
]);
|
||||
await waitForAll(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? [
|
||||
'Foo',
|
||||
// A suspends
|
||||
'Suspend! [A]',
|
||||
'Loading...',
|
||||
]
|
||||
: [
|
||||
'Foo',
|
||||
// A suspends
|
||||
'Suspend! [A]',
|
||||
'Suspend! [B]',
|
||||
'Loading more...',
|
||||
'Loading...',
|
||||
],
|
||||
);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Loading..." />);
|
||||
|
||||
// Wait a long time.
|
||||
@@ -1730,12 +1818,23 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
|
||||
ReactNoop.render(<Foo />);
|
||||
// Start rendering
|
||||
await waitForAll([
|
||||
'Foo',
|
||||
// A suspends
|
||||
'Suspend! [A]',
|
||||
'Loading...',
|
||||
]);
|
||||
await waitForAll(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? [
|
||||
'Foo',
|
||||
// A suspends
|
||||
'Suspend! [A]',
|
||||
'Loading...',
|
||||
]
|
||||
: [
|
||||
'Foo',
|
||||
// A suspends
|
||||
'Suspend! [A]',
|
||||
'Suspend! [B]',
|
||||
'Loading more...',
|
||||
'Loading...',
|
||||
],
|
||||
);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Loading..." />);
|
||||
|
||||
await act(async () => {
|
||||
@@ -1964,7 +2063,11 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
ReactNoop.flushSync(() => showB());
|
||||
});
|
||||
|
||||
assertLog(['Suspend! [A]']);
|
||||
assertLog(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? ['Suspend! [A]']
|
||||
: ['Suspend! [A]', 'Suspend! [B]'],
|
||||
);
|
||||
});
|
||||
|
||||
// TODO: flip to "warns" when this is implemented again.
|
||||
@@ -2070,7 +2173,11 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
}
|
||||
|
||||
ReactNoop.render(<Foo />);
|
||||
await waitForAll(['Foo', 'Suspend! [A]', 'Initial load...']);
|
||||
await waitForAll(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? ['Foo', 'Suspend! [A]', 'Initial load...']
|
||||
: ['Foo', 'Suspend! [A]', 'B', 'Initial load...'],
|
||||
);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Initial load..." />);
|
||||
|
||||
// Eventually we resolve and show the data.
|
||||
@@ -3354,12 +3461,22 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
await act(() => {
|
||||
setText('B');
|
||||
});
|
||||
assertLog([
|
||||
'Outer text: B',
|
||||
'Outer step: 0',
|
||||
'Suspend! [Inner text: B]',
|
||||
'Loading...',
|
||||
]);
|
||||
assertLog(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? [
|
||||
'Outer text: B',
|
||||
'Outer step: 0',
|
||||
'Suspend! [Inner text: B]',
|
||||
'Loading...',
|
||||
]
|
||||
: [
|
||||
'Outer text: B',
|
||||
'Outer step: 0',
|
||||
'Suspend! [Inner text: B]',
|
||||
'Inner step: 0',
|
||||
'Loading...',
|
||||
],
|
||||
);
|
||||
// Commit the placeholder
|
||||
await advanceTimers(250);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
@@ -3382,12 +3499,22 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
// Only the outer part can update. The inner part should still show a
|
||||
// fallback because we haven't finished loading B yet. Otherwise, the
|
||||
// inner text would be inconsistent with the outer text.
|
||||
assertLog([
|
||||
'Outer text: B',
|
||||
'Outer step: 1',
|
||||
'Suspend! [Inner text: B]',
|
||||
'Loading...',
|
||||
]);
|
||||
assertLog(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? [
|
||||
'Outer text: B',
|
||||
'Outer step: 1',
|
||||
'Suspend! [Inner text: B]',
|
||||
'Loading...',
|
||||
]
|
||||
: [
|
||||
'Outer text: B',
|
||||
'Outer step: 1',
|
||||
'Suspend! [Inner text: B]',
|
||||
'Inner step: 1',
|
||||
'Loading...',
|
||||
],
|
||||
);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Outer text: B" />
|
||||
|
||||
@@ -857,11 +857,20 @@ describe('ReactTransition', () => {
|
||||
updateTransitionPri();
|
||||
});
|
||||
|
||||
assertLog([
|
||||
// Suspend.
|
||||
'Suspend! [Async]',
|
||||
'Loading...',
|
||||
]);
|
||||
assertLog(
|
||||
gate(flags => flags.disableLegacySuspenseThrowSemantics)
|
||||
? [
|
||||
// Suspend.
|
||||
'Suspend! [Async]',
|
||||
'Loading...',
|
||||
]
|
||||
: [
|
||||
// Suspend.
|
||||
'Suspend! [Async]',
|
||||
'Normal pri: 0',
|
||||
'Loading...',
|
||||
],
|
||||
);
|
||||
expect(root).toMatchRenderedOutput('(empty), Normal pri: 0');
|
||||
|
||||
await act(async () => {
|
||||
|
||||
@@ -272,3 +272,5 @@ export const enableGetInspectorDataForInstanceInProduction = false;
|
||||
export const consoleManagedByDevToolsDuringStrictMode = true;
|
||||
|
||||
export const enableDO_NOT_USE_disableStrictPassiveEffect = false;
|
||||
|
||||
export const disableLegacySuspenseThrowSemantics = false;
|
||||
|
||||
@@ -100,6 +100,7 @@ export const disableStringRefs = true;
|
||||
export const enableReactTestRendererWarning = false;
|
||||
export const disableLegacyMode = false;
|
||||
export const enableOwnerStacks = false;
|
||||
export const disableLegacySuspenseThrowSemantics = true;
|
||||
|
||||
// Flow magic to verify the exports of this file match the original version.
|
||||
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
|
||||
|
||||
@@ -104,6 +104,7 @@ export const enableAsyncIterableChildren = false;
|
||||
export const enableAddPropertiesFastPath = false;
|
||||
export const enableShallowPropDiffing = false;
|
||||
export const renameElementSymbol = true;
|
||||
export const disableLegacySuspenseThrowSemantics = true;
|
||||
|
||||
export const enableOwnerStacks = __EXPERIMENTAL__;
|
||||
|
||||
|
||||
@@ -96,6 +96,7 @@ export const disableLegacyContext = true;
|
||||
export const enableRenderableContext = true;
|
||||
export const enableReactTestRendererWarning = true;
|
||||
export const disableDefaultPropsExceptForClasses = true;
|
||||
export const disableLegacySuspenseThrowSemantics = true;
|
||||
|
||||
export const enableOwnerStacks = false;
|
||||
|
||||
|
||||
@@ -93,6 +93,7 @@ export const renameElementSymbol = false;
|
||||
|
||||
export const enableOwnerStacks = false;
|
||||
export const enableShallowPropDiffing = false;
|
||||
export const disableLegacySuspenseThrowSemantics = true;
|
||||
|
||||
// Flow magic to verify the exports of this file match the original version.
|
||||
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
|
||||
|
||||
@@ -93,6 +93,7 @@ export const renameElementSymbol = false;
|
||||
|
||||
export const enableOwnerStacks = false;
|
||||
export const enableShallowPropDiffing = false;
|
||||
export const disableLegacySuspenseThrowSemantics = true;
|
||||
|
||||
// Flow magic to verify the exports of this file match the original version.
|
||||
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
|
||||
|
||||
@@ -114,6 +114,7 @@ export const enableServerComponentLogs = true;
|
||||
|
||||
export const enableReactTestRendererWarning = false;
|
||||
export const useModernStrictMode = true;
|
||||
export const disableLegacySuspenseThrowSemantics = true;
|
||||
|
||||
// TODO: Roll out with GK. Don't keep as dynamic flag for too long, though,
|
||||
// because JSX is an extremely hot path.
|
||||
|
||||
@@ -14,6 +14,7 @@ jest.mock('shared/ReactFeatureFlags', () => {
|
||||
// Flags that aren't currently used, but we still want to force variants to keep the
|
||||
// code live.
|
||||
actual.disableInputAttributeSyncing = __VARIANT__;
|
||||
actual.disableLegacySuspenseThrowSemantics = __VARIANT__;
|
||||
|
||||
// These are hardcoded to true for the next release,
|
||||
// but still run the tests against both variants until
|
||||
|
||||
Reference in New Issue
Block a user