mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
e0fe347967
Bassed off: https://github.com/facebook/react/pull/32425 Wait to land internally. [Commit to review.](https://github.com/facebook/react/pull/32426/commits/66aa6a4dbb78106b4f3d3eb367f5c27eb8f30c66) This has landed everywhere
228 lines
6.3 KiB
JavaScript
228 lines
6.3 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @emails react-core
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
const React = require('react');
|
|
const ReactDOM = require('react-dom');
|
|
const ReactDOMClient = require('react-dom/client');
|
|
const assertConsoleErrorDev =
|
|
require('internal-test-utils').assertConsoleErrorDev;
|
|
|
|
function expectWarnings(tags, warnings = [], withoutStack = 0) {
|
|
tags = [...tags];
|
|
warnings = [...warnings];
|
|
|
|
document.removeChild(document.documentElement);
|
|
document.appendChild(document.createElement('html'));
|
|
document.documentElement.innerHTML = '<head></head><body></body>';
|
|
|
|
let element = null;
|
|
const containerTag = tags.shift();
|
|
let container;
|
|
switch (containerTag) {
|
|
case '#document':
|
|
container = document;
|
|
break;
|
|
case 'html':
|
|
container = document.documentElement;
|
|
break;
|
|
case 'body':
|
|
container = document.body;
|
|
break;
|
|
case 'head':
|
|
container = document.head;
|
|
break;
|
|
case 'svg':
|
|
container = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
break;
|
|
default:
|
|
container = document.createElement(containerTag);
|
|
break;
|
|
}
|
|
|
|
while (tags.length) {
|
|
const Tag = tags.pop();
|
|
if (Tag === '#text') {
|
|
element = 'text';
|
|
} else {
|
|
element = <Tag>{element}</Tag>;
|
|
}
|
|
}
|
|
|
|
const root = ReactDOMClient.createRoot(container);
|
|
ReactDOM.flushSync(() => {
|
|
root.render(element);
|
|
});
|
|
if (warnings.length) {
|
|
assertConsoleErrorDev(
|
|
warnings,
|
|
withoutStack > 0 ? {withoutStack} : undefined,
|
|
);
|
|
}
|
|
root.unmount();
|
|
}
|
|
|
|
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']);
|
|
|
|
// Invalid, but not changed by browser parsing so we allow them
|
|
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'],
|
|
|
|
[
|
|
'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 **)',
|
|
],
|
|
);
|
|
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(
|
|
['head', 'body'],
|
|
[
|
|
'In HTML, <body> cannot be a child of <head>.\n' +
|
|
'This will cause a hydration error.\n' +
|
|
' in body (at **)',
|
|
],
|
|
);
|
|
expectWarnings(
|
|
['head', 'head'],
|
|
[
|
|
'In HTML, <head> cannot be a child of <head>.\n' +
|
|
'This will cause a hydration error.\n' +
|
|
' in head (at **)',
|
|
],
|
|
);
|
|
expectWarnings(
|
|
['html', 'html'],
|
|
[
|
|
'In HTML, <html> cannot be a child of <html>.\n' +
|
|
'This will cause a hydration error.\n' +
|
|
' in html (at **)',
|
|
],
|
|
);
|
|
expectWarnings(
|
|
['body', 'html'],
|
|
[
|
|
'In HTML, <html> cannot be a child of <body>.\n' +
|
|
'This will cause a hydration error.\n' +
|
|
' in html (at **)',
|
|
],
|
|
);
|
|
expectWarnings(
|
|
['head', 'html'],
|
|
[
|
|
'In HTML, <html> cannot be a child of <head>.\n' +
|
|
'This will cause a hydration error.\n' +
|
|
' in html (at **)',
|
|
],
|
|
);
|
|
expectWarnings(
|
|
['svg', 'foreignObject', 'body', 'p'],
|
|
[
|
|
// TODO, this should say "In SVG",
|
|
'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 **)',
|
|
],
|
|
);
|
|
});
|
|
|
|
it('relaxes the nesting rules at the root when the container is a singleton', () => {
|
|
expectWarnings(['#document', 'html']);
|
|
expectWarnings(['#document', 'body']);
|
|
expectWarnings(['#document', 'head']);
|
|
expectWarnings(['#document', 'div']);
|
|
expectWarnings(['#document', 'meta']);
|
|
expectWarnings(['#document', '#text']);
|
|
expectWarnings(['html', 'body']);
|
|
expectWarnings(['html', 'head']);
|
|
expectWarnings(['html', 'div']);
|
|
expectWarnings(['html', 'meta']);
|
|
expectWarnings(['html', '#text']);
|
|
expectWarnings(['body', 'head']);
|
|
expectWarnings(['body', 'div']);
|
|
expectWarnings(['body', 'meta']);
|
|
expectWarnings(['body', '#text']);
|
|
});
|
|
});
|