diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
index b7a4271e18bc2..3eb3085cb5c03 100644
--- a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
@@ -2962,4 +2962,83 @@ describe('ReactDOMFizzServer', () => {
expect(Scheduler).toFlushAndYield([]);
});
+
+ // @gate experimental && enableClientRenderFallbackOnTextMismatch && enableClientRenderFallbackOnHydrationMismatch
+ it('only warns once on hydration mismatch while within a suspense boundary', async () => {
+ const originalConsoleError = console.error;
+ const mockError = jest.fn();
+ console.error = (...args) => {
+ mockError(...args.map(normalizeCodeLocInfo));
+ };
+
+ const App = ({text}) => {
+ return (
+
+ Loading...}>
+ {text}
+ {text}
+ {text}
+
+
+ );
+ };
+
+ try {
+ await act(async () => {
+ const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
+ ,
+ );
+ pipe(writable);
+ });
+
+ expect(getVisibleChildren(container)).toEqual(
+
+
initial
+ initial
+ initial
+ ,
+ );
+
+ ReactDOMClient.hydrateRoot(container, , {
+ onRecoverableError(error) {
+ Scheduler.unstable_yieldValue(
+ 'Logged recoverable error: ' + error.message,
+ );
+ },
+ });
+ expect(Scheduler).toFlushAndYield([
+ 'Logged recoverable error: Text content does not match server-rendered HTML.',
+ 'Logged recoverable error: Text content does not match server-rendered HTML.',
+ 'Logged recoverable error: Text content does not match server-rendered HTML.',
+ 'Logged recoverable error: There was an error while hydrating this Suspense boundary. Switched to client rendering.',
+ ]);
+
+ expect(getVisibleChildren(container)).toEqual(
+
+
replaced
+ replaced
+ replaced
+ ,
+ );
+
+ expect(Scheduler).toFlushAndYield([]);
+ if (__DEV__) {
+ expect(mockError.mock.calls.length).toBe(1);
+ expect(mockError.mock.calls[0]).toEqual([
+ 'Warning: Text content did not match. Server: "%s" Client: "%s"%s',
+ 'initial',
+ 'replaced',
+ '\n' +
+ ' in h2 (at **)\n' +
+ ' in Suspense (at **)\n' +
+ ' in div (at **)\n' +
+ ' in App (at **)',
+ ]);
+ } else {
+ expect(mockError.mock.calls.length).toBe(0);
+ }
+ } finally {
+ console.error = originalConsoleError;
+ }
+ });
});
diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzSuppressHydrationWarning-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzSuppressHydrationWarning-test.js
index 69819314823e5..4f65ecf241c80 100644
--- a/packages/react-dom/src/__tests__/ReactDOMFizzSuppressHydrationWarning-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMFizzSuppressHydrationWarning-test.js
@@ -12,7 +12,6 @@
let JSDOM;
let Stream;
let Scheduler;
-let Suspense;
let React;
let ReactDOMClient;
let ReactDOMFizzServer;
@@ -29,7 +28,6 @@ describe('ReactDOMFizzServerHydrationWarning', () => {
JSDOM = require('jsdom').JSDOM;
Scheduler = require('scheduler');
React = require('react');
- Suspense = React.Suspense;
ReactDOMClient = require('react-dom/client');
if (__EXPERIMENTAL__) {
ReactDOMFizzServer = require('react-dom/server');
@@ -60,18 +58,6 @@ describe('ReactDOMFizzServerHydrationWarning', () => {
});
});
- function normalizeCodeLocInfo(strOrErr) {
- if (strOrErr && strOrErr.replace) {
- return strOrErr.replace(/\n +(?:at|in) ([\S]+)[^\n]*/g, function(
- m,
- name,
- ) {
- return '\n in ' + name + ' (at **)';
- });
- }
- return strOrErr;
- }
-
async function act(callback) {
await callback();
// Await one turn around the event loop.
@@ -716,173 +702,4 @@ describe('ReactDOMFizzServerHydrationWarning', () => {
,
);
});
-
- // @gate experimental && enableClientRenderFallbackOnTextMismatch && enableClientRenderFallbackOnHydrationMismatch
- it('#24384: Suspending should halt hydration warnings while still allowing siblings to warm up', async () => {
- const makeApp = () => {
- let resolve, resolved;
- const promise = new Promise(r => {
- resolve = () => {
- resolved = true;
- return r();
- };
- });
- function ComponentThatSuspends() {
- if (!resolved) {
- throw promise;
- }
- return A
;
- }
-
- const App = ({text}) => {
- return (
-
- Loading...}>
-
- {text}
-
-
- );
- };
-
- return [App, resolve];
- };
-
- const [ServerApp, serverResolve] = makeApp();
- await act(async () => {
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
- ,
- );
- pipe(writable);
- });
- await act(() => {
- serverResolve();
- });
-
- expect(getVisibleChildren(container)).toEqual(
- ,
- );
-
- // the client app is rendered with an intentionally incorrect text. The still Suspended component causes
- // hydration to fail silently (allowing for cache warming but otherwise skipping this boundary) until it
- // resolves.
- const [ClientApp, clientResolve] = makeApp();
- ReactDOMClient.hydrateRoot(container, , {
- onRecoverableError(error) {
- Scheduler.unstable_yieldValue(
- 'Logged recoverable error: ' + error.message,
- );
- },
- });
- Scheduler.unstable_flushAll();
-
- expect(getVisibleChildren(container)).toEqual(
- ,
- );
-
- // Now that the boundary resolves to it's children the hydration completes and discovers that there is a mismatch requiring
- // client-side rendering.
- await clientResolve();
- expect(() => {
- expect(Scheduler).toFlushAndYield([
- 'Logged recoverable error: Text content does not match server-rendered HTML.',
- 'Logged recoverable error: There was an error while hydrating this Suspense boundary. Switched to client rendering.',
- ]);
- }).toErrorDev(
- 'Warning: Prop `name` did not match. Server: "initial" Client: "replaced"',
- );
- expect(getVisibleChildren(container)).toEqual(
- ,
- );
-
- expect(Scheduler).toFlushAndYield([]);
- });
-
- // @gate experimental && enableClientRenderFallbackOnTextMismatch && enableClientRenderFallbackOnHydrationMismatch
- it('only warns once on hydration mismatch while within a suspense boundary', async () => {
- const originalConsoleError = console.error;
- const mockError = jest.fn();
- console.error = (...args) => {
- mockError(...args.map(normalizeCodeLocInfo));
- };
-
- const App = ({text}) => {
- return (
-
- Loading...}>
- {text}
- {text}
- {text}
-
-
- );
- };
-
- try {
- await act(async () => {
- const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
- ,
- );
- pipe(writable);
- });
-
- expect(getVisibleChildren(container)).toEqual(
-
-
initial
- initial
- initial
- ,
- );
-
- ReactDOMClient.hydrateRoot(container, , {
- onRecoverableError(error) {
- Scheduler.unstable_yieldValue(
- 'Logged recoverable error: ' + error.message,
- );
- },
- });
- expect(Scheduler).toFlushAndYield([
- 'Logged recoverable error: Text content does not match server-rendered HTML.',
- 'Logged recoverable error: Text content does not match server-rendered HTML.',
- 'Logged recoverable error: Text content does not match server-rendered HTML.',
- 'Logged recoverable error: There was an error while hydrating this Suspense boundary. Switched to client rendering.',
- ]);
-
- expect(getVisibleChildren(container)).toEqual(
-
-
replaced
- replaced
- replaced
- ,
- );
-
- expect(Scheduler).toFlushAndYield([]);
- if (__DEV__) {
- expect(mockError.mock.calls.length).toBe(1);
- expect(mockError.mock.calls[0]).toEqual([
- 'Warning: Text content did not match. Server: "%s" Client: "%s"%s',
- 'initial',
- 'replaced',
- '\n' +
- ' in h2 (at **)\n' +
- ' in Suspense (at **)\n' +
- ' in div (at **)\n' +
- ' in App (at **)',
- ]);
- } else {
- expect(mockError.mock.calls.length).toBe(0);
- }
- } finally {
- console.error = originalConsoleError;
- }
- });
});