From 825682390d825bb51be2e06835cf7e3458352e83 Mon Sep 17 00:00:00 2001 From: Anushree Subramani Date: Fri, 8 Dec 2017 00:15:42 +0530 Subject: [PATCH] ValidateDOMNesting tests(#11299) (#11742) * ValidateDOMNesting tests(#11299) * Rewrite tests using only public API. * Modified the tests to prevent duplication of code. * Code review changes implemented. * Removed the .internal from the test file name as its now written using public APIs. * Remove mutation * Remove unnecessary argument Now that we pass warnings, we don't need to pass a boolean. * Move things around a bit, and add component stack assertions --- .../validateDOMNesting-test.internal.js | 184 ------------------ .../src/__tests__/validateDOMNesting-test.js | 134 +++++++++++++ .../src/client/validateDOMNesting.js | 11 -- 3 files changed, 134 insertions(+), 195 deletions(-) delete mode 100644 packages/react-dom/src/__tests__/validateDOMNesting-test.internal.js create mode 100644 packages/react-dom/src/__tests__/validateDOMNesting-test.js diff --git a/packages/react-dom/src/__tests__/validateDOMNesting-test.internal.js b/packages/react-dom/src/__tests__/validateDOMNesting-test.internal.js deleted file mode 100644 index d68e6ea5a8d5b..0000000000000 --- a/packages/react-dom/src/__tests__/validateDOMNesting-test.internal.js +++ /dev/null @@ -1,184 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * - * 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'; - -let validateDOMNesting; - -// https://html.spec.whatwg.org/multipage/syntax.html#special -const 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 -const formattingTags = [ - 'a', - 'b', - 'big', - 'code', - 'em', - 'font', - 'i', - 'nobr', - 's', - 'small', - 'strike', - 'strong', - 'tt', - 'u', -]; - -function isTagStackValid(stack) { - let ancestorInfo = null; - for (let i = 0; i < stack.length; i++) { - if (!validateDOMNesting.isTagValidInContext(stack[i], ancestorInfo)) { - return false; - } - ancestorInfo = validateDOMNesting.updatedAncestorInfo( - ancestorInfo, - stack[i], - null, - ); - } - 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. - const 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); - - // 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); - } - }); - - 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, - ); - } - }); -}); diff --git a/packages/react-dom/src/__tests__/validateDOMNesting-test.js b/packages/react-dom/src/__tests__/validateDOMNesting-test.js new file mode 100644 index 0000000000000..abee5a13bf8cf --- /dev/null +++ b/packages/react-dom/src/__tests__/validateDOMNesting-test.js @@ -0,0 +1,134 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * 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'; + +var React = require('react'); +var ReactDOM = require('react-dom'); + +function normalizeCodeLocInfo(str) { + return str && str.replace(/at .+?:\d+/g, 'at **'); +} + +function expectWarnings(tags, warnings = []) { + tags = [...tags]; + warnings = [...warnings]; + + let element = null; + if (__DEV__) { + console.error.calls.reset(); + } + const container = document.createElement(tags.splice(0, 1)); + while (tags.length) { + const Tag = tags.pop(); + element = {element}; + } + ReactDOM.render(element, container); + + if (__DEV__) { + expect(console.error.calls.count()).toEqual(warnings.length); + while (warnings.length) { + expect( + normalizeCodeLocInfo( + console.error.calls.argsFor(warnings.length - 1)[0], + ), + ).toContain(warnings.pop()); + } + } +} + +describe('validateDOMNesting', () => { + it('allows valid nestings', () => { + spyOnDev(console, 'error'); + expectWarnings(['table', 'tbody', 'tr', 'td', 'b']); + expectWarnings( + ['body', 'datalist', 'option'], + [ + 'render(): Rendering components directly into document.body is discouraged', + ], + ); + 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', () => { + spyOnDev(console, 'error'); + expectWarnings( + ['a', 'a'], + [ + 'validateDOMNesting(...): cannot appear as a descendant of .\n' + + ' in a (at **)', + ], + ); + expectWarnings( + ['form', 'form'], + [ + 'validateDOMNesting(...):
cannot appear as a descendant of .\n' + + ' in form (at **)', + ], + ); + expectWarnings( + ['p', 'p'], + [ + 'validateDOMNesting(...):

cannot appear as a descendant of

.\n' + + ' in p (at **)', + ], + ); + expectWarnings( + ['table', 'tr'], + [ + 'validateDOMNesting(...): cannot appear as a child of . ' + + 'Add a to your code to match the DOM tree generated by the browser.\n' + + ' in tr (at **)', + ], + ); + expectWarnings( + ['div', 'ul', 'li', 'div', 'li'], + [ + 'validateDOMNesting(...):
  • cannot appear as a descendant of
  • .\n' + + ' in li (at **)\n' + + ' in div (at **)\n' + + ' in li (at **)\n' + + ' in ul (at **)', + ], + ); + expectWarnings( + ['div', 'html'], + [ + 'validateDOMNesting(...): cannot appear as a child of
    .\n' + + ' in html (at **)', + ], + ); + expectWarnings( + ['body', 'body'], + [ + 'render(): Rendering components directly into document.body is discouraged', + 'validateDOMNesting(...): cannot appear as a child of .\n' + + ' in body (at **)', + ], + ); + expectWarnings( + ['svg', 'foreignObject', 'body', 'p'], + [ + 'validateDOMNesting(...): cannot appear as a child of .\n' + + ' in body (at **)\n' + + ' in foreignObject (at **)', + ' is using uppercase HTML', + ], + ); + }); +}); 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;