diff --git a/packages/react-dom/src/__tests__/ReactUpdates-test.js b/packages/react-dom/src/__tests__/ReactUpdates-test.js
index 3c900a1987b2d..67abeb6dd0ad6 100644
--- a/packages/react-dom/src/__tests__/ReactUpdates-test.js
+++ b/packages/react-dom/src/__tests__/ReactUpdates-test.js
@@ -1627,7 +1627,6 @@ describe('ReactUpdates', () => {
const [step, setStep] = React.useState(0);
React.useEffect(() => {
setStep(x => x + 1);
- Scheduler.log(step);
});
return step;
}
@@ -1642,23 +1641,19 @@ describe('ReactUpdates', () => {
console.error = (e, s) => {
error = e;
stack = s;
+ Scheduler.log('stop');
};
try {
const container = document.createElement('div');
- expect(() => {
- const root = ReactDOMClient.createRoot(container);
- root.render();
- while (error === null) {
- Scheduler.unstable_flushNumberOfYields(1);
- Scheduler.unstable_clearLog();
- }
- expect(stack).toContain(' NonTerminating');
- // rethrow error to prevent going into an infinite loop when act() exits
- throw error;
- }).toThrow('Maximum update depth exceeded.');
+ const root = ReactDOMClient.createRoot(container);
+ root.render();
+ await waitFor(['stop']);
} finally {
console.error = originalConsoleError;
}
+
+ expect(error).toContain('Maximum update depth exceeded');
+ expect(stack).toContain('at NonTerminating');
});
it('can have nested updates if they do not cross the limit', async () => {
diff --git a/packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.js b/packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.js
index 5fd12fee1f8bb..a85cf89c68ca4 100644
--- a/packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.js
@@ -36,13 +36,9 @@ describe('ReactIncrementalUpdates', () => {
assertLog = InternalTestUtils.assertLog;
});
- function flushNextRenderIfExpired() {
- // This will start rendering the next level of work. If the work hasn't
- // expired yet, React will exit without doing anything. If it has expired,
- // it will schedule a sync task.
- Scheduler.unstable_flushExpired();
- // Flush the sync task.
- ReactNoop.flushSync();
+ function Text({text}) {
+ Scheduler.log(text);
+ return text;
}
it('applies updates in order of priority', async () => {
@@ -528,35 +524,38 @@ describe('ReactIncrementalUpdates', () => {
setCount = _setCount;
Scheduler.log('Render: ' + count);
useLayoutEffect(() => {
- setCount(prevCount => prevCount + 1);
+ setCount(1);
Scheduler.log('Commit: ' + count);
}, []);
- return null;
+ return ;
}
await act(async () => {
React.startTransition(() => {
ReactNoop.render();
});
- flushNextRenderIfExpired();
assertLog([]);
- await waitForAll(['Render: 0', 'Commit: 0', 'Render: 1']);
+ await waitForAll([
+ 'Render: 0',
+ 'Child',
+ 'Commit: 0',
+ 'Render: 1',
+ 'Child',
+ ]);
Scheduler.unstable_advanceTime(10000);
React.startTransition(() => {
setCount(2);
});
- flushNextRenderIfExpired();
- assertLog([]);
+ // The transition should not have expired, so we should be able to
+ // partially render it.
+ await waitFor(['Render: 2']);
+ // Now do the rest
+ await waitForAll(['Child']);
});
});
- it('regression: does not expire soon due to previous flushSync', () => {
- function Text({text}) {
- Scheduler.log(text);
- return text;
- }
-
+ it('regression: does not expire soon due to previous flushSync', async () => {
ReactNoop.flushSync(() => {
ReactNoop.render();
});
@@ -565,32 +564,70 @@ describe('ReactIncrementalUpdates', () => {
Scheduler.unstable_advanceTime(10000);
React.startTransition(() => {
- ReactNoop.render();
+ ReactNoop.render(
+ <>
+
+
+
+
+ >,
+ );
+ });
+ // The transition should not have expired, so we should be able to
+ // partially render it.
+ await waitFor(['A']);
+
+ // FIXME: We should be able to partially render B, too, but currently it
+ // expires. This is an existing bug that I discovered, which will be fixed
+ // in a PR that I'm currently working on.
+ //
+ // Correct behavior:
+ // await waitFor(['B']);
+ // await waitForAll(['C', 'D']);
+ //
+ // Current behavior:
+ await waitFor(['B'], {
+ additionalLogsAfterAttemptingToYield: ['C', 'D'],
});
- flushNextRenderIfExpired();
- assertLog([]);
});
- it('regression: does not expire soon due to previous expired work', () => {
- function Text({text}) {
- Scheduler.log(text);
- return text;
- }
-
+ it('regression: does not expire soon due to previous expired work', async () => {
React.startTransition(() => {
- ReactNoop.render();
+ ReactNoop.render(
+ <>
+
+
+
+
+ >,
+ );
});
+ await waitFor(['A']);
+
+ // This will expire the rest of the update
Scheduler.unstable_advanceTime(10000);
- flushNextRenderIfExpired();
- assertLog(['A']);
+ await waitFor(['B'], {
+ additionalLogsAfterAttemptingToYield: ['C', 'D'],
+ });
Scheduler.unstable_advanceTime(10000);
+ // Now do another transition. This one should not expire.
React.startTransition(() => {
- ReactNoop.render();
+ ReactNoop.render(
+ <>
+
+
+
+
+ >,
+ );
});
- flushNextRenderIfExpired();
- assertLog([]);
+ // The transition should not have expired, so we should be able to
+ // partially render it.
+ await waitFor(['A']);
+ await waitFor(['B']);
+ await waitForAll(['C', 'D']);
});
it('when rebasing, does not exclude updates that were already committed, regardless of priority', async () => {