diff --git a/packages/react-dom/src/__tests__/validateDOMNesting-test.internal.js b/packages/react-dom/src/__tests__/validateDOMNesting-test.internal.js index f1a37c953d454..bb4d34794b12e 100644 --- a/packages/react-dom/src/__tests__/validateDOMNesting-test.internal.js +++ b/packages/react-dom/src/__tests__/validateDOMNesting-test.internal.js @@ -9,176 +9,54 @@ 'use strict'; -var validateDOMNesting; - -// https://html.spec.whatwg.org/multipage/syntax.html#special -var specialTags = [ - 'address', - 'applet', - 'area', - 'article', - 'aside', - 'base', - 'basefont', - 'bgsound', - 'blockquote', - 'body', - 'br', - 'button', - 'caption', - 'center', - 'col', - 'colgroup', - 'dd', - 'details', - 'dir', - 'div', - 'dl', - 'dt', - 'embed', - 'fieldset', - 'figcaption', - 'figure', - 'footer', - 'form', - 'frame', - 'frameset', - 'h1', - 'h2', - 'h3', - 'h4', - 'h5', - 'h6', - 'head', - 'header', - 'hgroup', - 'hr', - 'html', - 'iframe', - 'img', - 'input', - 'isindex', - 'li', - 'link', - 'listing', - 'main', - 'marquee', - 'menu', - 'menuitem', - 'meta', - 'nav', - 'noembed', - 'noframes', - 'noscript', - 'object', - 'ol', - 'p', - 'param', - 'plaintext', - 'pre', - 'script', - 'section', - 'select', - 'source', - 'style', - 'summary', - 'table', - 'tbody', - 'td', - 'template', - 'textarea', - 'tfoot', - 'th', - 'thead', - 'title', - 'tr', - 'track', - 'ul', - 'wbr', - 'xmp', -]; - -// https://html.spec.whatwg.org/multipage/syntax.html#formatting -var formattingTags = [ - 'a', - 'b', - 'big', - 'code', - 'em', - 'font', - 'i', - 'nobr', - 's', - 'small', - 'strike', - 'strong', - 'tt', - 'u', -]; - -function isTagStackValid(stack) { - var ancestorInfo = null; - for (var i = 0; i < stack.length; i++) { - if (!validateDOMNesting.isTagValidInContext(stack[i], ancestorInfo)) { - return false; - } - ancestorInfo = validateDOMNesting.updatedAncestorInfo( - ancestorInfo, - stack[i], - null, - ); +var React = require('react'); +var ReactDOM = require('react-dom'); + +function expectInvalidNestingWarning(shouldWarn, tags) { + let element = null; + const container = document.createElement(tags.splice(0, 1)); + while (tags.length) { + element = React.createElement(tags.pop(), null, element); + } + const errorCount = console.error.calls.count(); + ReactDOM.render(element, container); + if (shouldWarn) { + expect(console.error.calls.count() > errorCount).toEqual(true); + } else { + expect(console.error.calls.count()).toEqual(0); } - return true; } describe('validateDOMNesting', () => { - beforeEach(() => { - jest.resetModules(); - - // TODO: can we express this test with only public API? - validateDOMNesting = require('../client/validateDOMNesting').default; - }); - - it('allows any tag with no context', () => { - if (__DEV__) { - // With renderToString (for example), we don't know where we're mounting the - // tag so we must err on the side of leniency. - var allTags = [].concat(specialTags, formattingTags, ['mysterytag']); - allTags.forEach(function(tag) { - expect(validateDOMNesting.isTagValidInContext(tag, null)).toBe(true); - }); - } - }); - it('allows valid nestings', () => { if (__DEV__) { - expect(isTagStackValid(['table', 'tbody', 'tr', 'td', 'b'])).toBe(true); - expect(isTagStackValid(['body', 'datalist', 'option'])).toBe(true); - expect(isTagStackValid(['div', 'a', 'object', 'a'])).toBe(true); - expect(isTagStackValid(['div', 'p', 'button', 'p'])).toBe(true); - expect(isTagStackValid(['p', 'svg', 'foreignObject', 'p'])).toBe(true); - expect(isTagStackValid(['html', 'body', 'div'])).toBe(true); + spyOnDev(console, 'error'); + expectInvalidNestingWarning(false, ['table', 'tbody', 'tr', 'td', 'b']); + expectInvalidNestingWarning(false, ['div', 'a', 'object', 'a']); + expectInvalidNestingWarning(false, ['div', 'p', 'button', 'p']); + expectInvalidNestingWarning(false, ['p', 'svg', 'foreignObject', 'p']); + expectInvalidNestingWarning(false, ['html', 'body', 'div']); // Invalid, but not changed by browser parsing so we allow them - expect(isTagStackValid(['div', 'ul', 'ul', 'li'])).toBe(true); - expect(isTagStackValid(['div', 'label', 'div'])).toBe(true); - expect(isTagStackValid(['div', 'ul', 'li', 'section', 'li'])).toBe(true); - expect(isTagStackValid(['div', 'ul', 'li', 'dd', 'li'])).toBe(true); + expectInvalidNestingWarning(false, ['div', 'ul', 'ul', 'li']); + expectInvalidNestingWarning(false, ['div', 'label', 'div']); + expectInvalidNestingWarning(false, ['div', 'ul', 'li', 'section', 'li']); + expectInvalidNestingWarning(false, ['div', 'ul', 'li', 'dd', 'li']); } }); it('prevents problematic nestings', () => { if (__DEV__) { - expect(isTagStackValid(['a', 'a'])).toBe(false); - expect(isTagStackValid(['form', 'form'])).toBe(false); - expect(isTagStackValid(['p', 'p'])).toBe(false); - expect(isTagStackValid(['table', 'tr'])).toBe(false); - expect(isTagStackValid(['div', 'ul', 'li', 'div', 'li'])).toBe(false); - expect(isTagStackValid(['div', 'html'])).toBe(false); - expect(isTagStackValid(['body', 'body'])).toBe(false); - expect(isTagStackValid(['svg', 'foreignObject', 'body', 'p'])).toBe( - false, - ); + spyOnDev(console, 'error'); + expectInvalidNestingWarning(true, ['body', 'datalist', 'option']); + expectInvalidNestingWarning(true, ['table', 'tr']); + expectInvalidNestingWarning(true, ['p', 'p']); + expectInvalidNestingWarning(true, ['div', 'ul', 'li', 'div', 'li']); + expectInvalidNestingWarning(true, ['div', 'html']); + expectInvalidNestingWarning(true, ['body', 'body']); + expectInvalidNestingWarning(true, ['svg', 'foreignObject', 'body', 'p']); + expectInvalidNestingWarning(true, ['a', 'a']); + expectInvalidNestingWarning(true, ['form', 'form']); } }); }); diff --git a/packages/react-dom/src/client/validateDOMNesting.js b/packages/react-dom/src/client/validateDOMNesting.js index 8e6165d5e9912..9341bd1e3f591 100644 --- a/packages/react-dom/src/client/validateDOMNesting.js +++ b/packages/react-dom/src/client/validateDOMNesting.js @@ -482,17 +482,6 @@ if (__DEV__) { // TODO: turn this into a named export validateDOMNesting.updatedAncestorInfo = updatedAncestorInfo; - - // For testing - validateDOMNesting.isTagValidInContext = function(tag, ancestorInfo) { - ancestorInfo = ancestorInfo || emptyAncestorInfo; - const parentInfo = ancestorInfo.current; - const parentTag = parentInfo && parentInfo.tag; - return ( - isTagValidWithParent(tag, parentTag) && - !findInvalidAncestorForTag(tag, ancestorInfo) - ); - }; } export default validateDOMNesting;