Skip to content
10 changes: 8 additions & 2 deletions packages/react-dom/src/__tests__/ReactDOMFizzForm-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,8 @@ describe('ReactDOMFizzForm', () => {
ReactDOMClient.hydrateRoot(container, <App isClient={true} />);
});
}).toErrorDev(
'Prop `action` did not match. Server: "function" Client: "action"',
"A tree hydrated but some attributes of the server rendered HTML didn't match the client properties.",
{withoutStack: true},
);
});

Expand Down Expand Up @@ -344,7 +345,12 @@ describe('ReactDOMFizzForm', () => {
await act(async () => {
root = ReactDOMClient.hydrateRoot(container, <App />);
});
}).toErrorDev(['Prop `formTarget` did not match.']);
}).toErrorDev(
[
"A tree hydrated but some attributes of the server rendered HTML didn't match the client properties.",
],
{withoutStack: true},
);
await act(async () => {
root.render(<App isUpdate={true} />);
});
Expand Down
140 changes: 66 additions & 74 deletions packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,15 @@ let waitForPaint;
let clientAct;
let streamingContainer;

function normalizeError(msg) {
// Take the first sentence to make it easier to assert on.
const idx = msg.indexOf('.');
if (idx > -1) {
return msg.slice(0, idx + 1);
}
return msg;
}

describe('ReactDOMFizzServer', () => {
beforeEach(() => {
jest.resetModules();
Expand Down Expand Up @@ -2391,26 +2400,22 @@ describe('ReactDOMFizzServer', () => {

ReactDOMClient.hydrateRoot(container, <App />, {
onRecoverableError(error) {
Scheduler.log('Log recoverable error: ' + error.message);
Scheduler.log(
'Log recoverable error: ' + normalizeError(error.message),
);
},
});

await expect(async () => {
// The first paint switches to client rendering due to mismatch
await waitForPaint([
'client',
'Log recoverable error: Hydration failed because the initial ' +
'UI does not match what was rendered on the server.',
'Log recoverable error: There was an error while hydrating. ' +
'Because the error happened outside of a Suspense boundary, the ' +
'entire root will switch to client rendering.',
"Log recoverable error: Hydration failed because the server rendered HTML didn't match the client.",
'Log recoverable error: There was an error while hydrating.',
]);
}).toErrorDev(
[
'Warning: An error occurred during hydration. The server HTML was replaced with client content.',
'Warning: Expected server HTML to contain a matching <div> in the root.\n' +
' in div (at **)\n' +
' in App (at **)',
],
{withoutStack: 1},
);
Expand Down Expand Up @@ -2474,7 +2479,9 @@ describe('ReactDOMFizzServer', () => {

ReactDOMClient.hydrateRoot(container, <App />, {
onRecoverableError(error) {
Scheduler.log('Log recoverable error: ' + error.message);
Scheduler.log(
'Log recoverable error: ' + normalizeError(error.message),
);
},
});

Expand All @@ -2483,18 +2490,12 @@ describe('ReactDOMFizzServer', () => {
// The first paint switches to client rendering due to mismatch
await waitForPaint([
'client',
'Log recoverable error: Hydration failed because the initial ' +
'UI does not match what was rendered on the server.',
'Log recoverable error: There was an error while hydrating. ' +
'Because the error happened outside of a Suspense boundary, the ' +
'entire root will switch to client rendering.',
"Log recoverable error: Hydration failed because the server rendered HTML didn't match the client.",
'Log recoverable error: There was an error while hydrating.',
]);
}).toErrorDev(
[
'Warning: An error occurred during hydration. The server HTML was replaced with client content',
'Warning: Expected server HTML to contain a matching <div> in the root.\n' +
' in div (at **)\n' +
' in App (at **)',
],
{withoutStack: 1},
);
Expand Down Expand Up @@ -2557,7 +2558,7 @@ describe('ReactDOMFizzServer', () => {
isClient = true;
ReactDOMClient.hydrateRoot(container, <App />, {
onRecoverableError(error) {
Scheduler.log(error.message);
Scheduler.log(normalizeError(error.message));
},
});

Expand All @@ -2567,9 +2568,7 @@ describe('ReactDOMFizzServer', () => {
await waitForAll([
'Yay!',
'Hydration error',
'There was an error while hydrating. Because the error happened ' +
'outside of a Suspense boundary, the entire root will switch ' +
'to client rendering.',
'There was an error while hydrating.',
]);
}).toErrorDev(
'An error occurred during hydration. The server HTML was replaced',
Expand Down Expand Up @@ -2739,7 +2738,7 @@ describe('ReactDOMFizzServer', () => {
isClient = true;
ReactDOMClient.hydrateRoot(container, <App />, {
onRecoverableError(error) {
Scheduler.log(error.message);
Scheduler.log(normalizeError(error.message));
},
});

Expand All @@ -2748,7 +2747,7 @@ describe('ReactDOMFizzServer', () => {
await waitForAll([
'Yay!',
'Hydration error',
'There was an error while hydrating this Suspense boundary. Switched to client rendering.',
'There was an error while hydrating this Suspense boundary.',
]);
expect(getVisibleChildren(container)).toEqual(
<div>
Expand Down Expand Up @@ -3194,16 +3193,15 @@ describe('ReactDOMFizzServer', () => {
isClient = true;
ReactDOMClient.hydrateRoot(container, <App />, {
onRecoverableError(error) {
Scheduler.log(error.message);
Scheduler.log(normalizeError(error.message));
},
});

// An error logged but instead of surfacing it to the UI, we switched
// to client rendering.
await waitForAll([
'Hydration error',
'There was an error while hydrating this Suspense boundary. Switched ' +
'to client rendering.',
'There was an error while hydrating this Suspense boundary.',
]);
expect(getVisibleChildren(container)).toEqual(
<div>
Expand Down Expand Up @@ -3263,7 +3261,9 @@ describe('ReactDOMFizzServer', () => {

const root = ReactDOMClient.createRoot(container, {
onRecoverableError(error) {
Scheduler.log('Logged a recoverable error: ' + error.message);
Scheduler.log(
'Logged a recoverable error: ' + normalizeError(error.message),
);
},
});
React.startTransition(() => {
Expand Down Expand Up @@ -3339,7 +3339,9 @@ describe('ReactDOMFizzServer', () => {
isClient = true;
ReactDOMClient.hydrateRoot(container, <App />, {
onRecoverableError(error) {
Scheduler.log('Logged recoverable error: ' + error.message);
Scheduler.log(
'Logged recoverable error: ' + normalizeError(error.message),
);
},
});

Expand All @@ -3349,11 +3351,11 @@ describe('ReactDOMFizzServer', () => {

'Logged recoverable error: Hydration error',
'Logged recoverable error: There was an error while hydrating this ' +
'Suspense boundary. Switched to client rendering.',
'Suspense boundary.',

'Logged recoverable error: Hydration error',
'Logged recoverable error: There was an error while hydrating this ' +
'Suspense boundary. Switched to client rendering.',
'Suspense boundary.',
]);
});

Expand Down Expand Up @@ -4395,7 +4397,9 @@ describe('ReactDOMFizzServer', () => {
const [ClientApp, clientResolve] = makeApp();
ReactDOMClient.hydrateRoot(container, <ClientApp />, {
onRecoverableError(error) {
Scheduler.log('Logged recoverable error: ' + error.message);
Scheduler.log(
'Logged recoverable error: ' + normalizeError(error.message),
);
},
});
await waitForAll([]);
Expand Down Expand Up @@ -4471,7 +4475,9 @@ describe('ReactDOMFizzServer', () => {
const [ClientApp, clientResolve] = makeApp();
ReactDOMClient.hydrateRoot(container, <ClientApp text="replaced" />, {
onRecoverableError(error) {
Scheduler.log('Logged recoverable error: ' + error.message);
Scheduler.log(
'Logged recoverable error: ' + normalizeError(error.message),
);
},
});
await waitForAll([]);
Expand All @@ -4486,14 +4492,10 @@ describe('ReactDOMFizzServer', () => {
// 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();
await expect(async () => {
await waitForAll([
'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: Text content did not match. Server: "initial" Client: "replaced',
);
await waitForAll([
"Logged recoverable error: Hydration failed because the server rendered HTML didn't match the client.",
'Logged recoverable error: There was an error while hydrating this Suspense boundary.',
]);
expect(getVisibleChildren(container)).toEqual(
<div>
<p>A</p>
Expand Down Expand Up @@ -4539,12 +4541,14 @@ describe('ReactDOMFizzServer', () => {

ReactDOMClient.hydrateRoot(container, <App text="replaced" />, {
onRecoverableError(error) {
Scheduler.log('Logged recoverable error: ' + error.message);
Scheduler.log(
'Logged recoverable error: ' + normalizeError(error.message),
);
},
});
await waitForAll([
'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.',
"Logged recoverable error: Hydration failed because the server rendered HTML didn't match the client.",
'Logged recoverable error: There was an error while hydrating this Suspense boundary.',
]);

expect(getVisibleChildren(container)).toEqual(
Expand All @@ -4556,21 +4560,7 @@ describe('ReactDOMFizzServer', () => {
);

await waitForAll([]);
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);
}
expect(mockError.mock.calls.length).toBe(0);
} finally {
console.error = originalConsoleError;
}
Expand Down Expand Up @@ -4626,12 +4616,14 @@ describe('ReactDOMFizzServer', () => {

ReactDOMClient.hydrateRoot(container, <App />, {
onRecoverableError(error) {
Scheduler.log('Logged recoverable error: ' + error.message);
Scheduler.log(
'Logged recoverable error: ' + normalizeError(error.message),
);
},
});
await waitForAll([
'Logged recoverable error: uh oh',
'Logged recoverable error: There was an error while hydrating this Suspense boundary. Switched to client rendering.',
'Logged recoverable error: There was an error while hydrating this Suspense boundary.',
]);

expect(getVisibleChildren(container)).toEqual(
Expand Down Expand Up @@ -4713,7 +4705,9 @@ describe('ReactDOMFizzServer', () => {

ReactDOMClient.hydrateRoot(container, <App />, {
onRecoverableError(error) {
Scheduler.log('Logged recoverable error: ' + error.message);
Scheduler.log(
'Logged recoverable error: ' + normalizeError(error.message),
);
},
});
await waitForAll([
Expand All @@ -4722,7 +4716,7 @@ describe('ReactDOMFizzServer', () => {
// onRecoverableError because the UI recovered without surfacing the
// error to the user.
'Logged recoverable error: first error',
'Logged recoverable error: There was an error while hydrating this Suspense boundary. Switched to client rendering.',
'Logged recoverable error: There was an error while hydrating this Suspense boundary.',
]);
expect(mockError.mock.calls).toEqual([]);
mockError.mockClear();
Expand Down Expand Up @@ -4830,7 +4824,9 @@ describe('ReactDOMFizzServer', () => {

ReactDOMClient.hydrateRoot(container, <App />, {
onRecoverableError(error) {
Scheduler.log('Logged recoverable error: ' + error.message);
Scheduler.log(
'Logged recoverable error: ' + normalizeError(error.message),
);
},
});
await waitForAll(['suspending']);
Expand All @@ -4847,7 +4843,7 @@ describe('ReactDOMFizzServer', () => {
await waitForAll([
'throwing: first error',
'Logged recoverable error: first error',
'Logged recoverable error: There was an error while hydrating this Suspense boundary. Switched to client rendering.',
'Logged recoverable error: There was an error while hydrating this Suspense boundary.',
]);
expect(getVisibleChildren(container)).toEqual(
<div>
Expand Down Expand Up @@ -4954,14 +4950,16 @@ describe('ReactDOMFizzServer', () => {

ReactDOMClient.hydrateRoot(container, <App />, {
onRecoverableError(error) {
Scheduler.log('Logged recoverable error: ' + error.message);
Scheduler.log(
'Logged recoverable error: ' + normalizeError(error.message),
);
},
});
await waitForAll([
'throwing: first error',
'suspending',
'Logged recoverable error: first error',
'Logged recoverable error: There was an error while hydrating this Suspense boundary. Switched to client rendering.',
'Logged recoverable error: There was an error while hydrating this Suspense boundary.',
]);
expect(mockError.mock.calls).toEqual([]);
mockError.mockClear();
Expand Down Expand Up @@ -6341,13 +6339,7 @@ describe('ReactDOMFizzServer', () => {
});
await expect(async () => {
await waitForAll([]);
}).toErrorDev(
[
'Expected server HTML to contain a matching <span> in the root',
'An error occurred during hydration',
],
{withoutStack: 1},
);
}).toErrorDev(['An error occurred during hydration'], {withoutStack: 1});
expect(errors.length).toEqual(2);
expect(getVisibleChildren(container)).toEqual(<span />);
});
Expand Down
Loading