'use strict';
const React = require('react');
const ReactDOM = require('react-dom');
const ReactDOMClient = require('react-dom/client');
function expectWarnings(tags, warnings = [], withoutStack = 0) {
tags = [...tags];
warnings = [...warnings];
let element = null;
const containerTag = tags.shift();
const container =
containerTag === 'svg'
? document.createElementNS('http://www.w3.org/2000/svg', containerTag)
: document.createElement(containerTag);
while (tags.length) {
const Tag = tags.pop();
element = <Tag>{element}</Tag>;
}
const root = ReactDOMClient.createRoot(container);
if (warnings.length) {
expect(() => {
ReactDOM.flushSync(() => {
root.render(element);
});
}).toErrorDev(warnings, {
withoutStack,
});
}
}
describe('validateDOMNesting', () => {
it('allows valid nestings', () => {
expectWarnings(['table', 'tbody', 'tr', 'td', 'b']);
expectWarnings(['body', 'datalist', 'option']);
expectWarnings(['div', 'a', 'object', 'a']);
expectWarnings(['div', 'p', 'button', 'p']);
expectWarnings(['p', 'svg', 'foreignObject', 'p']);
expectWarnings(['html', 'body', 'div']);
expectWarnings(['div', 'ul', 'ul', 'li']);
expectWarnings(['div', 'label', 'div']);
expectWarnings(['div', 'ul', 'li', 'section', 'li']);
expectWarnings(['div', 'ul', 'li', 'dd', 'li']);
});
it('prevents problematic nestings', () => {
expectWarnings(
['a', 'a'],
[
'In HTML, <a> cannot be a descendant of <a>.\n' +
'This will cause a hydration error.\n' +
' in a (at **)',
],
);
expectWarnings(
['form', 'form'],
[
'In HTML, <form> cannot be a descendant of <form>.\n' +
'This will cause a hydration error.\n' +
' in form (at **)',
],
);
expectWarnings(
['p', 'p'],
[
'In HTML, <p> cannot be a descendant of <p>.\n' +
'This will cause a hydration error.\n' +
' in p (at **)',
],
);
expectWarnings(
['table', 'tr'],
[
'In HTML, <tr> cannot be a child of <table>. ' +
'Add a <tbody>, <thead> or <tfoot> to your code to match the DOM tree generated by the browser.\n' +
'This will cause a hydration error.\n' +
' in tr (at **)',
],
);
expectWarnings(
['div', 'ul', 'li', 'div', 'li'],
gate(flags => flags.enableOwnerStacks)
? [
'In HTML, <li> cannot be a descendant of <li>.\n' +
'This will cause a hydration error.\n' +
'\n' +
' <ul>\n' +
'> <li>\n' +
' <div>\n' +
'> <li>\n' +
'\n' +
' in li (at **)',
'<li> cannot contain a nested <li>.\nSee this log for the ancestor stack trace.\n' +
' in li (at **)',
]
: [
'In HTML, <li> cannot be a descendant of <li>.\n' +
'This will cause a hydration error.\n' +
'\n' +
' <ul>\n' +
'> <li>\n' +
' <div>\n' +
'> <li>\n' +
'\n' +
' in li (at **)\n' +
' in div (at **)\n' +
' in li (at **)\n' +
' in ul (at **)',
],
);
expectWarnings(
['div', 'html'],
[
'In HTML, <html> cannot be a child of <div>.\n' +
'This will cause a hydration error.\n' +
' in html (at **)',
],
);
expectWarnings(
['body', 'body'],
[
'In HTML, <body> cannot be a child of <body>.\n' +
'This will cause a hydration error.\n' +
' in body (at **)',
],
);
expectWarnings(
['svg', 'foreignObject', 'body', 'p'],
gate(flags => flags.enableOwnerStacks)
? [
'In HTML, <body> cannot be a child of <foreignObject>.\n' +
'This will cause a hydration error.\n' +
'\n' +
'> <foreignObject>\n' +
'> <body>\n' +
'\n' +
' in body (at **)',
'You are mounting a new body component when a previous one has not first unmounted. It is an error to render more than one body component at a time and attributes and children of these components will likely fail in unpredictable ways. Please only render a single instance of <body> and if you need to mount a new one, ensure any previous ones have unmounted first.\n' +
' in body (at **)',
]
: [
'In HTML, <body> cannot be a child of <foreignObject>.\n' +
'This will cause a hydration error.\n' +
'\n' +
'> <foreignObject>\n' +
'> <body>\n' +
'\n' +
' in body (at **)\n' +
' in foreignObject (at **)',
'You are mounting a new body component when a previous one has not first unmounted. It is an error to render more than one body component at a time and attributes and children of these components will likely fail in unpredictable ways. Please only render a single instance of <body> and if you need to mount a new one, ensure any previous ones have unmounted first.\n' +
' in body (at **)',
],
);
});
});