From a20d8351e0b37b0956aef6a146e72a4602b174cb Mon Sep 17 00:00:00 2001 From: Anushree Subramani Date: Thu, 30 Nov 2017 16:41:44 +0530 Subject: [PATCH] ValidateDOMNesting tests(#11299) * Rewrite tests using only public API. * Modified the tests to prevent duplication of code. * Code review changes implemented. --- .../validateDOMNesting-test.internal.js | 243 +++++++----------- .../src/client/validateDOMNesting.js | 11 - 2 files changed, 90 insertions(+), 164 deletions(-) diff --git a/packages/react-dom/src/__tests__/validateDOMNesting-test.internal.js b/packages/react-dom/src/__tests__/validateDOMNesting-test.internal.js index d68e6ea5a8d5b8..a4710686aa8c34 100644 --- a/packages/react-dom/src/__tests__/validateDOMNesting-test.internal.js +++ b/packages/react-dom/src/__tests__/validateDOMNesting-test.internal.js @@ -9,175 +9,112 @@ 'use strict'; -let validateDOMNesting; +var React = require('react'); +var ReactDOM = require('react-dom'); -// 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 normalizeCodeLocInfo(str) { + return str && str.replace(/at .+?:\d+/g, 'at **'); +} -function isTagStackValid(stack) { - let ancestorInfo = null; - for (let i = 0; i < stack.length; i++) { - if (!validateDOMNesting.isTagValidInContext(stack[i], ancestorInfo)) { - return false; +function expectInvalidNestingWarning(shouldWarn, tagsList, warningsList = []) { + let element = null; + let tags = tagsList; + let warnings = warningsList; + console.error.calls.reset(); + const container = document.createElement(tags.splice(0, 1)); + while (tags.length) { + element = React.createElement(tags.pop(), null, element); + } + ReactDOM.render(element, container); + if (shouldWarn) { + expect(console.error.calls.count()).toEqual(warningsList.length); + while (warnings.length) { + expect( + normalizeCodeLocInfo( + console.error.calls.argsFor(warnings.length - 1)[0], + ), + ).toContain(warnings.pop()); } - ancestorInfo = validateDOMNesting.updatedAncestorInfo( - ancestorInfo, - stack[i], - null, - ); + } 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. - 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); + 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'], + [ + 'render(): Rendering components directly into document.body is discouraged', + ], + ); + expectInvalidNestingWarning( + true, + ['table', 'tr'], + ['validateDOMNesting(...): cannot appear as a child of '], + ); + expectInvalidNestingWarning( + true, + ['p', 'p'], + ['validateDOMNesting(...):

cannot appear as a descendant of

'], + ); + expectInvalidNestingWarning( + true, + ['div', 'ul', 'li', 'div', 'li'], + ['validateDOMNesting(...):

  • cannot appear as a descendant of
  • '], + ); + expectInvalidNestingWarning( + true, + ['div', 'html'], + ['validateDOMNesting(...): cannot appear as a child of
    '], + ); + expectInvalidNestingWarning( + true, + ['body', 'body'], + [ + 'render(): Rendering components directly into document.body is discouraged', + 'validateDOMNesting(...): cannot appear as a child of ', + ], + ); + expectInvalidNestingWarning( + true, + ['svg', 'foreignObject', 'body', 'p'], + [ + 'validateDOMNesting(...): cannot appear as a child of ', + ' is using uppercase HTML', + ], + ); + expectInvalidNestingWarning( + true, + ['a', 'a'], + ['validateDOMNesting(...): cannot appear as a descendant of '], + ); + expectInvalidNestingWarning( + true, + ['form', 'form'], + [ + 'validateDOMNesting(...):
    cannot appear as a descendant of ', + ], ); } }); diff --git a/packages/react-dom/src/client/validateDOMNesting.js b/packages/react-dom/src/client/validateDOMNesting.js index 8e6165d5e99120..9341bd1e3f5912 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;