diff --git a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js
index d27b422f46c58..646ebd28afe8c 100644
--- a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js
@@ -3758,4 +3758,119 @@ describe('ReactSuspenseWithNoopRenderer', () => {
>,
);
});
+
+ // Regression: https://github.com/facebook/react/issues/18486
+ it.experimental(
+ 'does not get stuck in pending state with render phase updates',
+ async () => {
+ let setTextWithTransition;
+
+ function App() {
+ const [startTransition, isPending] = React.useTransition({
+ timeoutMs: 30000,
+ });
+ const [text, setText] = React.useState('');
+ const [mirror, setMirror] = React.useState('');
+
+ if (text !== mirror) {
+ // Render phase update was needed to repro the bug.
+ setMirror(text);
+ }
+
+ setTextWithTransition = value => {
+ startTransition(() => {
+ setText(value);
+ });
+ };
+
+ return (
+ <>
+ {isPending ? : null}
+ {text !== '' ? : }
+ >
+ );
+ }
+
+ function Root() {
+ return (
+ }>
+
+
+ );
+ }
+
+ const root = ReactNoop.createRoot();
+ await ReactNoop.act(async () => {
+ root.render();
+ });
+ expect(Scheduler).toHaveYielded(['']);
+ expect(root).toMatchRenderedOutput();
+
+ // Update to "a". That will suspend.
+ await ReactNoop.act(async () => {
+ setTextWithTransition('a');
+ // Let it expire. This is important for the repro.
+ Scheduler.unstable_advanceTime(1000);
+ expect(Scheduler).toFlushAndYield([
+ 'Pending...',
+ '',
+ 'Suspend! [a]',
+ 'Loading...',
+ ]);
+ });
+ expect(Scheduler).toHaveYielded([]);
+ expect(root).toMatchRenderedOutput(
+ <>
+
+
+ >,
+ );
+
+ // Update to "b". That will suspend, too.
+ await ReactNoop.act(async () => {
+ setTextWithTransition('b');
+ expect(Scheduler).toFlushAndYield([
+ // Neither is resolved yet.
+ 'Pending...',
+ 'Suspend! [a]',
+ 'Loading...',
+ 'Suspend! [b]',
+ 'Loading...',
+ ]);
+ });
+ expect(Scheduler).toHaveYielded([]);
+ expect(root).toMatchRenderedOutput(
+ <>
+
+
+ >,
+ );
+
+ // Resolve "a". But "b" is still pending.
+ await ReactNoop.act(async () => {
+ await resolveText('a');
+ });
+ expect(Scheduler).toHaveYielded([
+ 'Promise resolved [a]',
+ 'Pending...',
+ 'a',
+ 'Suspend! [b]',
+ 'Loading...',
+ ]);
+ expect(root).toMatchRenderedOutput(
+ <>
+
+
+ >,
+ );
+
+ // Resolve "b". This should remove the pending state.
+ await ReactNoop.act(async () => {
+ await resolveText('b');
+ });
+ expect(Scheduler).toHaveYielded(['Promise resolved [b]', 'b']);
+ // The bug was that the pending state got stuck forever.
+ expect(root).toMatchRenderedOutput();
+ },
+ );
});