mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
1330 lines
49 KiB
JavaScript
1330 lines
49 KiB
JavaScript
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
let React;
|
|
let ReactDOMClient;
|
|
let ReactDOMServer;
|
|
let act;
|
|
|
|
const util = require('util');
|
|
const realConsoleError = console.error;
|
|
|
|
describe('ReactDOMServerHydration', () => {
|
|
let container;
|
|
|
|
beforeEach(() => {
|
|
jest.resetModules();
|
|
React = require('react');
|
|
ReactDOMClient = require('react-dom/client');
|
|
ReactDOMServer = require('react-dom/server');
|
|
act = require('react-dom/test-utils').act;
|
|
|
|
console.error = jest.fn();
|
|
container = document.createElement('div');
|
|
document.body.appendChild(container);
|
|
});
|
|
|
|
afterEach(() => {
|
|
document.body.removeChild(container);
|
|
console.error = realConsoleError;
|
|
});
|
|
|
|
function normalizeCodeLocInfo(str) {
|
|
return (
|
|
typeof str === 'string' &&
|
|
str.replace(/\n +(?:at|in) ([\S]+)[^\n]*/g, function(m, name) {
|
|
return '\n in ' + name + ' (at **)';
|
|
})
|
|
);
|
|
}
|
|
|
|
function formatMessage(args) {
|
|
const [format, ...rest] = args;
|
|
if (format instanceof Error) {
|
|
return 'Caught [' + format.message + ']';
|
|
}
|
|
if (format.indexOf('Error: Uncaught [') === 0) {
|
|
// Ignore errors captured by jsdom and their stacks.
|
|
// We only want console errors in this suite.
|
|
return null;
|
|
}
|
|
rest[rest.length - 1] = normalizeCodeLocInfo(rest[rest.length - 1]);
|
|
return util.format(format, ...rest);
|
|
}
|
|
|
|
function formatConsoleErrors() {
|
|
return console.error.mock.calls.map(formatMessage).filter(Boolean);
|
|
}
|
|
|
|
function testMismatch(Mismatch) {
|
|
const htmlString = ReactDOMServer.renderToString(
|
|
<Mismatch isClient={false} />,
|
|
);
|
|
container.innerHTML = htmlString;
|
|
act(() => {
|
|
ReactDOMClient.hydrateRoot(container, <Mismatch isClient={true} />);
|
|
});
|
|
return formatConsoleErrors();
|
|
}
|
|
|
|
describe('text mismatch', () => {
|
|
// @gate __DEV__
|
|
it('warns when client and server render different text', () => {
|
|
function Mismatch({isClient}) {
|
|
return (
|
|
<div className="parent">
|
|
<main className="child">{isClient ? 'client' : 'server'}</main>
|
|
</div>
|
|
);
|
|
}
|
|
if (gate(flags => flags.enableClientRenderFallbackOnHydrationMismatch)) {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Text content did not match. Server: \\"server\\" Client: \\"client\\"
|
|
in main (at **)
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
"Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.",
|
|
"Caught [Text content does not match server-rendered HTML.]",
|
|
"Caught [There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.]",
|
|
]
|
|
`);
|
|
} else {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Text content did not match. Server: \\"server\\" Client: \\"client\\"
|
|
in main (at **)
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
]
|
|
`);
|
|
}
|
|
});
|
|
|
|
// @gate __DEV__
|
|
it('warns when client and server render different html', () => {
|
|
function Mismatch({isClient}) {
|
|
return (
|
|
<div className="parent">
|
|
<main
|
|
className="child"
|
|
dangerouslySetInnerHTML={{
|
|
__html: isClient
|
|
? '<span>client</span>'
|
|
: '<span>server</span>',
|
|
}}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Prop \`dangerouslySetInnerHTML\` did not match. Server: \\"<span>server</span>\\" Client: \\"<span>client</span>\\"
|
|
in main (at **)
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
]
|
|
`);
|
|
});
|
|
});
|
|
|
|
describe('attribute mismatch', () => {
|
|
// @gate __DEV__
|
|
it('warns when client and server render different attributes', () => {
|
|
function Mismatch({isClient}) {
|
|
return (
|
|
<div className="parent">
|
|
<main
|
|
className={isClient ? 'child client' : 'child server'}
|
|
dir={isClient ? 'ltr' : 'rtl'}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Prop \`className\` did not match. Server: \\"child server\\" Client: \\"child client\\"
|
|
in main (at **)
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
]
|
|
`);
|
|
});
|
|
|
|
// @gate __DEV__
|
|
it('warns when client renders extra attributes', () => {
|
|
function Mismatch({isClient}) {
|
|
return (
|
|
<div className="parent">
|
|
<main
|
|
className="child"
|
|
tabIndex={isClient ? 1 : null}
|
|
dir={isClient ? 'ltr' : null}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Prop \`tabIndex\` did not match. Server: \\"null\\" Client: \\"1\\"
|
|
in main (at **)
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
]
|
|
`);
|
|
});
|
|
|
|
// @gate __DEV__
|
|
it('warns when server renders extra attributes', () => {
|
|
function Mismatch({isClient}) {
|
|
return (
|
|
<div className="parent">
|
|
<main
|
|
className="child"
|
|
tabIndex={isClient ? null : 1}
|
|
dir={isClient ? null : 'rtl'}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Extra attributes from the server: tabindex,dir
|
|
in main (at **)
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
]
|
|
`);
|
|
});
|
|
|
|
// @gate __DEV__
|
|
it('warns when both client and server render extra attributes', () => {
|
|
function Mismatch({isClient}) {
|
|
return (
|
|
<div className="parent">
|
|
<main
|
|
className="child"
|
|
tabIndex={isClient ? 1 : null}
|
|
dir={isClient ? null : 'rtl'}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Prop \`tabIndex\` did not match. Server: \\"null\\" Client: \\"1\\"
|
|
in main (at **)
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
]
|
|
`);
|
|
});
|
|
|
|
// @gate __DEV__
|
|
it('warns when client and server render different styles', () => {
|
|
function Mismatch({isClient}) {
|
|
return (
|
|
<div className="parent">
|
|
<main
|
|
className="child"
|
|
style={{
|
|
opacity: isClient ? 1 : 0,
|
|
}}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Prop \`style\` did not match. Server: \\"opacity:0\\" Client: \\"opacity:1\\"
|
|
in main (at **)
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
]
|
|
`);
|
|
});
|
|
});
|
|
|
|
describe('extra nodes on the client', () => {
|
|
describe('extra elements on the client', () => {
|
|
// @gate __DEV__
|
|
it('warns when client renders an extra element as only child', () => {
|
|
function Mismatch({isClient}) {
|
|
return (
|
|
<div className="parent">
|
|
{isClient && <main className="only" />}
|
|
</div>
|
|
);
|
|
}
|
|
if (
|
|
gate(flags => flags.enableClientRenderFallbackOnHydrationMismatch)
|
|
) {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Expected server HTML to contain a matching <main> in <div>.
|
|
in main (at **)
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
"Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.",
|
|
"Caught [Hydration failed because the initial UI does not match what was rendered on the server.]",
|
|
"Caught [There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.]",
|
|
]
|
|
`);
|
|
} else {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Expected server HTML to contain a matching <main> in <div>.
|
|
in main (at **)
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
]
|
|
`);
|
|
}
|
|
});
|
|
|
|
// @gate __DEV__
|
|
it('warns when client renders an extra element in the beginning', () => {
|
|
function Mismatch({isClient}) {
|
|
return (
|
|
<div className="parent">
|
|
{isClient && <header className="1" />}
|
|
<main className="2" />
|
|
<footer className="3" />
|
|
</div>
|
|
);
|
|
}
|
|
if (
|
|
gate(flags => flags.enableClientRenderFallbackOnHydrationMismatch)
|
|
) {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Expected server HTML to contain a matching <header> in <div>.
|
|
in header (at **)
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
"Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.",
|
|
"Caught [Hydration failed because the initial UI does not match what was rendered on the server.]",
|
|
"Caught [There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.]",
|
|
]
|
|
`);
|
|
} else {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Expected server HTML to contain a matching <header> in <div>.
|
|
in header (at **)
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
]
|
|
`);
|
|
}
|
|
});
|
|
|
|
// @gate __DEV__
|
|
it('warns when client renders an extra element in the middle', () => {
|
|
function Mismatch({isClient}) {
|
|
return (
|
|
<div className="parent">
|
|
<header className="1" />
|
|
{isClient && <main className="2" />}
|
|
<footer className="3" />
|
|
</div>
|
|
);
|
|
}
|
|
if (
|
|
gate(flags => flags.enableClientRenderFallbackOnHydrationMismatch)
|
|
) {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Expected server HTML to contain a matching <main> in <div>.
|
|
in main (at **)
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
"Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.",
|
|
"Caught [Hydration failed because the initial UI does not match what was rendered on the server.]",
|
|
"Caught [There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.]",
|
|
]
|
|
`);
|
|
} else {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Expected server HTML to contain a matching <main> in <div>.
|
|
in main (at **)
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
]
|
|
`);
|
|
}
|
|
});
|
|
|
|
// @gate __DEV__
|
|
it('warns when client renders an extra element in the end', () => {
|
|
function Mismatch({isClient}) {
|
|
return (
|
|
<div className="parent">
|
|
<header className="1" />
|
|
<main className="2" />
|
|
{isClient && <footer className="3" />}
|
|
</div>
|
|
);
|
|
}
|
|
if (
|
|
gate(flags => flags.enableClientRenderFallbackOnHydrationMismatch)
|
|
) {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Expected server HTML to contain a matching <footer> in <div>.
|
|
in footer (at **)
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
"Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.",
|
|
"Caught [Hydration failed because the initial UI does not match what was rendered on the server.]",
|
|
"Caught [There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.]",
|
|
]
|
|
`);
|
|
} else {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Expected server HTML to contain a matching <footer> in <div>.
|
|
in footer (at **)
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
]
|
|
`);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('extra text nodes on the client', () => {
|
|
// @gate __DEV__
|
|
it('warns when client renders an extra text node as only child', () => {
|
|
function Mismatch({isClient}) {
|
|
return <div className="parent">{isClient && 'only'}</div>;
|
|
}
|
|
if (
|
|
gate(flags => flags.enableClientRenderFallbackOnHydrationMismatch)
|
|
) {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Text content did not match. Server: \\"\\" Client: \\"only\\"
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
"Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.",
|
|
"Caught [Text content does not match server-rendered HTML.]",
|
|
"Caught [There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.]",
|
|
]
|
|
`);
|
|
} else {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Text content did not match. Server: \\"\\" Client: \\"only\\"
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
]
|
|
`);
|
|
}
|
|
});
|
|
|
|
// @gate __DEV__
|
|
it('warns when client renders an extra text node in the beginning', () => {
|
|
function Mismatch({isClient}) {
|
|
return (
|
|
<div className="parent">
|
|
<header className="1" />
|
|
{isClient && 'second'}
|
|
<footer className="3" />
|
|
</div>
|
|
);
|
|
}
|
|
if (
|
|
gate(flags => flags.enableClientRenderFallbackOnHydrationMismatch)
|
|
) {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Expected server HTML to contain a matching text node for \\"second\\" in <div>.
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
"Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.",
|
|
"Caught [Hydration failed because the initial UI does not match what was rendered on the server.]",
|
|
"Caught [There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.]",
|
|
]
|
|
`);
|
|
} else {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Expected server HTML to contain a matching text node for \\"second\\" in <div>.
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
]
|
|
`);
|
|
}
|
|
});
|
|
|
|
// @gate __DEV__
|
|
it('warns when client renders an extra text node in the beginning', () => {
|
|
function Mismatch({isClient}) {
|
|
return (
|
|
<div className="parent">
|
|
{isClient && 'first'}
|
|
<main className="2" />
|
|
<footer className="3" />
|
|
</div>
|
|
);
|
|
}
|
|
if (
|
|
gate(flags => flags.enableClientRenderFallbackOnHydrationMismatch)
|
|
) {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Expected server HTML to contain a matching text node for \\"first\\" in <div>.
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
"Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.",
|
|
"Caught [Hydration failed because the initial UI does not match what was rendered on the server.]",
|
|
"Caught [There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.]",
|
|
]
|
|
`);
|
|
} else {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Expected server HTML to contain a matching text node for \\"first\\" in <div>.
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
]
|
|
`);
|
|
}
|
|
});
|
|
|
|
// @gate __DEV__
|
|
it('warns when client renders an extra text node in the end', () => {
|
|
function Mismatch({isClient}) {
|
|
return (
|
|
<div className="parent">
|
|
<header className="1" />
|
|
<main className="2" />
|
|
{isClient && 'third'}
|
|
</div>
|
|
);
|
|
}
|
|
if (
|
|
gate(flags => flags.enableClientRenderFallbackOnHydrationMismatch)
|
|
) {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Expected server HTML to contain a matching text node for \\"third\\" in <div>.
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
"Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.",
|
|
"Caught [Hydration failed because the initial UI does not match what was rendered on the server.]",
|
|
"Caught [There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.]",
|
|
]
|
|
`);
|
|
} else {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Expected server HTML to contain a matching text node for \\"third\\" in <div>.
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
]
|
|
`);
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('extra nodes on the server', () => {
|
|
describe('extra elements on the server', () => {
|
|
// @gate __DEV__
|
|
it('warns when server renders an extra element as only child', () => {
|
|
function Mismatch({isClient}) {
|
|
return (
|
|
<div className="parent">
|
|
{!isClient && <main className="only" />}
|
|
</div>
|
|
);
|
|
}
|
|
if (
|
|
gate(flags => flags.enableClientRenderFallbackOnHydrationMismatch)
|
|
) {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Did not expect server HTML to contain a <main> in <div>.
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
"Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.",
|
|
"Caught [Hydration failed because the initial UI does not match what was rendered on the server.]",
|
|
"Caught [There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.]",
|
|
]
|
|
`);
|
|
} else {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Did not expect server HTML to contain a <main> in <div>.
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
]
|
|
`);
|
|
}
|
|
});
|
|
|
|
// @gate __DEV__
|
|
it('warns when server renders an extra element in the beginning', () => {
|
|
function Mismatch({isClient}) {
|
|
return (
|
|
<div className="parent">
|
|
{!isClient && <header className="1" />}
|
|
<main className="2" />
|
|
<footer className="3" />
|
|
</div>
|
|
);
|
|
}
|
|
if (
|
|
gate(flags => flags.enableClientRenderFallbackOnHydrationMismatch)
|
|
) {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Expected server HTML to contain a matching <main> in <div>.
|
|
in main (at **)
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
"Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.",
|
|
"Caught [Hydration failed because the initial UI does not match what was rendered on the server.]",
|
|
"Caught [Hydration failed because the initial UI does not match what was rendered on the server.]",
|
|
"Caught [There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.]",
|
|
]
|
|
`);
|
|
} else {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Did not expect server HTML to contain a <header> in <div>.
|
|
in main (at **)
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
]
|
|
`);
|
|
}
|
|
});
|
|
|
|
// @gate __DEV__
|
|
it('warns when server renders an extra element in the middle', () => {
|
|
function Mismatch({isClient}) {
|
|
return (
|
|
<div className="parent">
|
|
<header className="1" />
|
|
{!isClient && <main className="2" />}
|
|
<footer className="3" />
|
|
</div>
|
|
);
|
|
}
|
|
if (
|
|
gate(flags => flags.enableClientRenderFallbackOnHydrationMismatch)
|
|
) {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Expected server HTML to contain a matching <footer> in <div>.
|
|
in footer (at **)
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
"Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.",
|
|
"Caught [Hydration failed because the initial UI does not match what was rendered on the server.]",
|
|
"Caught [There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.]",
|
|
]
|
|
`);
|
|
} else {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Did not expect server HTML to contain a <main> in <div>.
|
|
in footer (at **)
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
]
|
|
`);
|
|
}
|
|
});
|
|
|
|
// @gate __DEV__
|
|
it('warns when server renders an extra element in the end', () => {
|
|
function Mismatch({isClient}) {
|
|
return (
|
|
<div className="parent">
|
|
<header className="1" />
|
|
<main className="2" />
|
|
{!isClient && <footer className="3" />}
|
|
</div>
|
|
);
|
|
}
|
|
if (
|
|
gate(flags => flags.enableClientRenderFallbackOnHydrationMismatch)
|
|
) {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Did not expect server HTML to contain a <footer> in <div>.
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
"Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.",
|
|
"Caught [Hydration failed because the initial UI does not match what was rendered on the server.]",
|
|
"Caught [There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.]",
|
|
]
|
|
`);
|
|
} else {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Did not expect server HTML to contain a <footer> in <div>.
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
]
|
|
`);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('extra text nodes on the server', () => {
|
|
// @gate __DEV__
|
|
it('warns when server renders an extra text node as only child', () => {
|
|
function Mismatch({isClient}) {
|
|
return <div className="parent">{!isClient && 'only'}</div>;
|
|
}
|
|
if (
|
|
gate(flags => flags.enableClientRenderFallbackOnHydrationMismatch)
|
|
) {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Did not expect server HTML to contain the text node \\"only\\" in <div>.
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
"Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.",
|
|
"Caught [Hydration failed because the initial UI does not match what was rendered on the server.]",
|
|
"Caught [There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.]",
|
|
]
|
|
`);
|
|
} else {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Did not expect server HTML to contain the text node \\"only\\" in <div>.
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
]
|
|
`);
|
|
}
|
|
});
|
|
|
|
// @gate __DEV__
|
|
it('warns when server renders an extra text node in the beginning', () => {
|
|
function Mismatch({isClient}) {
|
|
return (
|
|
<div className="parent">
|
|
{!isClient && 'first'}
|
|
<main className="2" />
|
|
<footer className="3" />
|
|
</div>
|
|
);
|
|
}
|
|
if (
|
|
gate(flags => flags.enableClientRenderFallbackOnHydrationMismatch)
|
|
) {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Expected server HTML to contain a matching <main> in <div>.
|
|
in main (at **)
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
"Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.",
|
|
"Caught [Hydration failed because the initial UI does not match what was rendered on the server.]",
|
|
"Caught [Hydration failed because the initial UI does not match what was rendered on the server.]",
|
|
"Caught [There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.]",
|
|
]
|
|
`);
|
|
} else {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Did not expect server HTML to contain the text node \\"first\\" in <div>.
|
|
in main (at **)
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
]
|
|
`);
|
|
}
|
|
});
|
|
|
|
// @gate __DEV__
|
|
it('warns when server renders an extra text node in the middle', () => {
|
|
function Mismatch({isClient}) {
|
|
return (
|
|
<div className="parent">
|
|
<header className="1" />
|
|
{!isClient && 'second'}
|
|
<footer className="3" />
|
|
</div>
|
|
);
|
|
}
|
|
if (
|
|
gate(flags => flags.enableClientRenderFallbackOnHydrationMismatch)
|
|
) {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Expected server HTML to contain a matching <footer> in <div>.
|
|
in footer (at **)
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
"Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.",
|
|
"Caught [Hydration failed because the initial UI does not match what was rendered on the server.]",
|
|
"Caught [There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.]",
|
|
]
|
|
`);
|
|
} else {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Did not expect server HTML to contain the text node \\"second\\" in <div>.
|
|
in footer (at **)
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
]
|
|
`);
|
|
}
|
|
});
|
|
|
|
// @gate __DEV__
|
|
it('warns when server renders an extra text node in the end', () => {
|
|
function Mismatch({isClient}) {
|
|
return (
|
|
<div className="parent">
|
|
<header className="1" />
|
|
<main className="2" />
|
|
{!isClient && 'third'}
|
|
</div>
|
|
);
|
|
}
|
|
if (
|
|
gate(flags => flags.enableClientRenderFallbackOnHydrationMismatch)
|
|
) {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Did not expect server HTML to contain the text node \\"third\\" in <div>.
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
"Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.",
|
|
"Caught [Hydration failed because the initial UI does not match what was rendered on the server.]",
|
|
"Caught [There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.]",
|
|
]
|
|
`);
|
|
} else {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Did not expect server HTML to contain the text node \\"third\\" in <div>.
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
]
|
|
`);
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('special nodes', () => {
|
|
describe('Suspense', () => {
|
|
function Never() {
|
|
throw new Promise(resolve => {});
|
|
}
|
|
|
|
// @gate __DEV__
|
|
it('warns when client renders an extra Suspense node in content mode', () => {
|
|
function Mismatch({isClient}) {
|
|
return (
|
|
<div className="parent">
|
|
{isClient && (
|
|
<React.Suspense fallback={<p>Loading...</p>}>
|
|
<main className="only" />
|
|
</React.Suspense>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
if (
|
|
gate(flags => flags.enableClientRenderFallbackOnHydrationMismatch)
|
|
) {
|
|
// TODO: This message doesn't seem to have any useful details.
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.",
|
|
"Caught [Hydration failed because the initial UI does not match what was rendered on the server.]",
|
|
"Caught [There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.]",
|
|
]
|
|
`);
|
|
} else {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`Array []`);
|
|
}
|
|
});
|
|
|
|
// @gate __DEV__
|
|
it('warns when server renders an extra Suspense node in content mode', () => {
|
|
function Mismatch({isClient}) {
|
|
return (
|
|
<div className="parent">
|
|
{!isClient && (
|
|
<React.Suspense fallback={<p>Loading...</p>}>
|
|
<main className="only" />
|
|
</React.Suspense>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
if (
|
|
gate(flags => flags.enableClientRenderFallbackOnHydrationMismatch)
|
|
) {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Did not expect server HTML to contain a <main> in <div>.
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
"Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.",
|
|
"Caught [Hydration failed because the initial UI does not match what was rendered on the server.]",
|
|
"Caught [There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.]",
|
|
]
|
|
`);
|
|
} else {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Did not expect server HTML to contain a <main> in <div>.
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
]
|
|
`);
|
|
}
|
|
});
|
|
|
|
// @gate __DEV__
|
|
it('warns when client renders an extra Suspense node in fallback mode', () => {
|
|
function Mismatch({isClient}) {
|
|
return (
|
|
<div className="parent">
|
|
{isClient && (
|
|
<React.Suspense fallback={<p>Loading...</p>}>
|
|
<main className="only" />
|
|
<Never />
|
|
</React.Suspense>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
if (
|
|
gate(flags => flags.enableClientRenderFallbackOnHydrationMismatch)
|
|
) {
|
|
// TODO: This message doesn't seem to have any useful details.
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.",
|
|
"Caught [Hydration failed because the initial UI does not match what was rendered on the server.]",
|
|
"Caught [There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.]",
|
|
]
|
|
`);
|
|
} else {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`Array []`);
|
|
}
|
|
});
|
|
|
|
// @gate __DEV__
|
|
it('warns when server renders an extra Suspense node in fallback mode', () => {
|
|
function Mismatch({isClient}) {
|
|
return (
|
|
<div className="parent">
|
|
{!isClient && (
|
|
<React.Suspense fallback={<p>Loading...</p>}>
|
|
<main className="only" />
|
|
<Never />
|
|
</React.Suspense>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
if (
|
|
gate(flags => flags.enableClientRenderFallbackOnHydrationMismatch)
|
|
) {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Did not expect server HTML to contain a <p> in <div>.
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
"Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.",
|
|
"Caught [Hydration failed because the initial UI does not match what was rendered on the server.]",
|
|
"Caught [There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.]",
|
|
]
|
|
`);
|
|
} else {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Did not expect server HTML to contain a <p> in <div>.
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
]
|
|
`);
|
|
}
|
|
});
|
|
|
|
// @gate __DEV__
|
|
it('warns when client renders an extra node inside Suspense content', () => {
|
|
function Mismatch({isClient}) {
|
|
return (
|
|
<div className="parent">
|
|
<React.Suspense fallback={<p>Loading...</p>}>
|
|
<header className="1" />
|
|
{isClient && <main className="second" />}
|
|
<footer className="3" />
|
|
</React.Suspense>
|
|
</div>
|
|
);
|
|
}
|
|
if (
|
|
gate(flags => flags.enableClientRenderFallbackOnHydrationMismatch)
|
|
) {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Expected server HTML to contain a matching <main> in <div>.
|
|
in main (at **)
|
|
in Suspense (at **)
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
"Caught [Hydration failed because the initial UI does not match what was rendered on the server.]",
|
|
"Caught [There was an error while hydrating this Suspense boundary. Switched to client rendering.]",
|
|
]
|
|
`);
|
|
} else {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Expected server HTML to contain a matching <main> in <div>.
|
|
in main (at **)
|
|
in Suspense (at **)
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
]
|
|
`);
|
|
}
|
|
});
|
|
|
|
// @gate __DEV__
|
|
it('warns when server renders an extra node inside Suspense content', () => {
|
|
function Mismatch({isClient}) {
|
|
return (
|
|
<div className="parent">
|
|
<React.Suspense fallback={<p>Loading...</p>}>
|
|
<header className="1" />
|
|
{!isClient && <main className="second" />}
|
|
<footer className="3" />
|
|
</React.Suspense>
|
|
</div>
|
|
);
|
|
}
|
|
if (
|
|
gate(flags => flags.enableClientRenderFallbackOnHydrationMismatch)
|
|
) {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Expected server HTML to contain a matching <footer> in <div>.
|
|
in footer (at **)
|
|
in Suspense (at **)
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
"Caught [Hydration failed because the initial UI does not match what was rendered on the server.]",
|
|
"Caught [There was an error while hydrating this Suspense boundary. Switched to client rendering.]",
|
|
]
|
|
`);
|
|
} else {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Did not expect server HTML to contain a <main> in <div>.
|
|
in footer (at **)
|
|
in Suspense (at **)
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
]
|
|
`);
|
|
}
|
|
});
|
|
|
|
// @gate __DEV__
|
|
it('warns when client renders an extra node inside Suspense fallback', () => {
|
|
function Mismatch({isClient}) {
|
|
return (
|
|
<div className="parent">
|
|
<React.Suspense
|
|
fallback={
|
|
<>
|
|
<p>Loading...</p>
|
|
{isClient && <br />}
|
|
</>
|
|
}>
|
|
<main className="only" />
|
|
<Never />
|
|
</React.Suspense>
|
|
</div>
|
|
);
|
|
}
|
|
// TODO: Why does this not show a fallback mismatch?
|
|
// And why is this message different from the other ones?
|
|
if (
|
|
gate(flags => flags.enableClientRenderFallbackOnHydrationMismatch)
|
|
) {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Caught [The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.]",
|
|
]
|
|
`);
|
|
} else {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Caught [The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.]",
|
|
]
|
|
`);
|
|
}
|
|
});
|
|
|
|
// @gate __DEV__
|
|
it('warns when server renders an extra node inside Suspense fallback', () => {
|
|
function Mismatch({isClient}) {
|
|
return (
|
|
<div className="parent">
|
|
<React.Suspense
|
|
fallback={
|
|
<>
|
|
<p>Loading...</p>
|
|
{!isClient && <br />}
|
|
</>
|
|
}>
|
|
<main className="only" />
|
|
<Never />
|
|
</React.Suspense>
|
|
</div>
|
|
);
|
|
}
|
|
// TODO: Why does this not show a fallback mismatch?
|
|
// And why is this message different from the other ones?
|
|
if (
|
|
gate(flags => flags.enableClientRenderFallbackOnHydrationMismatch)
|
|
) {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Caught [The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.]",
|
|
]
|
|
`);
|
|
} else {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Caught [The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.]",
|
|
]
|
|
`);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('Fragment', () => {
|
|
// @gate __DEV__
|
|
it('warns when client renders an extra Fragment node', () => {
|
|
function Mismatch({isClient}) {
|
|
return (
|
|
<div className="parent">
|
|
{isClient && (
|
|
<>
|
|
<header className="1" />
|
|
<main className="2" />
|
|
<footer className="3" />
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
if (
|
|
gate(flags => flags.enableClientRenderFallbackOnHydrationMismatch)
|
|
) {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Expected server HTML to contain a matching <header> in <div>.
|
|
in header (at **)
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
"Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.",
|
|
"Caught [Hydration failed because the initial UI does not match what was rendered on the server.]",
|
|
"Caught [Hydration failed because the initial UI does not match what was rendered on the server.]",
|
|
"Caught [Hydration failed because the initial UI does not match what was rendered on the server.]",
|
|
"Caught [There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.]",
|
|
]
|
|
`);
|
|
} else {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Expected server HTML to contain a matching <header> in <div>.
|
|
in header (at **)
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
]
|
|
`);
|
|
}
|
|
});
|
|
|
|
// @gate __DEV__
|
|
it('warns when server renders an extra Fragment node', () => {
|
|
function Mismatch({isClient}) {
|
|
return (
|
|
<div className="parent">
|
|
{!isClient && (
|
|
<>
|
|
<header className="1" />
|
|
<main className="2" />
|
|
<footer className="3" />
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
if (
|
|
gate(flags => flags.enableClientRenderFallbackOnHydrationMismatch)
|
|
) {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Did not expect server HTML to contain a <header> in <div>.
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
"Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.",
|
|
"Caught [Hydration failed because the initial UI does not match what was rendered on the server.]",
|
|
"Caught [There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.]",
|
|
]
|
|
`);
|
|
} else {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Did not expect server HTML to contain a <header> in <div>.
|
|
in div (at **)
|
|
in Mismatch (at **)",
|
|
]
|
|
`);
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('misc cases', () => {
|
|
// @gate __DEV__
|
|
it('warns when client renders an extra node deeper in the tree', () => {
|
|
function Mismatch({isClient}) {
|
|
return isClient ? <ProfileSettings /> : <MediaSettings />;
|
|
}
|
|
|
|
function ProfileSettings() {
|
|
return (
|
|
<div className="parent">
|
|
<input />
|
|
<Panel type="profile" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function MediaSettings() {
|
|
return (
|
|
<div className="parent">
|
|
<input />
|
|
<Panel type="media" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function Panel({type}) {
|
|
return (
|
|
<>
|
|
<header className="1" />
|
|
<main className="2" />
|
|
{type === 'profile' && <footer className="3" />}
|
|
</>
|
|
);
|
|
}
|
|
|
|
if (gate(flags => flags.enableClientRenderFallbackOnHydrationMismatch)) {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Expected server HTML to contain a matching <footer> in <div>.
|
|
in footer (at **)
|
|
in Panel (at **)
|
|
in div (at **)
|
|
in ProfileSettings (at **)
|
|
in Mismatch (at **)",
|
|
"Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.",
|
|
"Caught [Hydration failed because the initial UI does not match what was rendered on the server.]",
|
|
"Caught [There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.]",
|
|
]
|
|
`);
|
|
} else {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Expected server HTML to contain a matching <footer> in <div>.
|
|
in footer (at **)
|
|
in Panel (at **)
|
|
in div (at **)
|
|
in ProfileSettings (at **)
|
|
in Mismatch (at **)",
|
|
]
|
|
`);
|
|
}
|
|
});
|
|
|
|
// @gate __DEV__
|
|
it('warns when server renders an extra node deeper in the tree', () => {
|
|
function Mismatch({isClient}) {
|
|
return isClient ? <ProfileSettings /> : <MediaSettings />;
|
|
}
|
|
|
|
function ProfileSettings() {
|
|
return (
|
|
<div className="parent">
|
|
<input />
|
|
<Panel type="profile" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function MediaSettings() {
|
|
return (
|
|
<div className="parent">
|
|
<input />
|
|
<Panel type="media" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function Panel({type}) {
|
|
return (
|
|
<>
|
|
<header className="1" />
|
|
<main className="2" />
|
|
{type !== 'profile' && <footer className="3" />}
|
|
</>
|
|
);
|
|
}
|
|
|
|
if (gate(flags => flags.enableClientRenderFallbackOnHydrationMismatch)) {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Did not expect server HTML to contain a <footer> in <div>.
|
|
in div (at **)
|
|
in ProfileSettings (at **)
|
|
in Mismatch (at **)",
|
|
"Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>.",
|
|
"Caught [Hydration failed because the initial UI does not match what was rendered on the server.]",
|
|
"Caught [There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.]",
|
|
]
|
|
`);
|
|
} else {
|
|
expect(testMismatch(Mismatch)).toMatchInlineSnapshot(`
|
|
Array [
|
|
"Warning: Did not expect server HTML to contain a <footer> in <div>.
|
|
in div (at **)
|
|
in ProfileSettings (at **)
|
|
in Mismatch (at **)",
|
|
]
|
|
`);
|
|
}
|
|
});
|
|
});
|
|
});
|