Skip to content

Commit

Permalink
ValidateDOMNesting tests(#11299)
Browse files Browse the repository at this point in the history
 * Rewrite tests using only public API.
 * Modified the tests to prevent duplication of code.
  • Loading branch information
imanushree committed Dec 1, 2017
1 parent 0a2ed64 commit 5a76ca2
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 162 deletions.
193 changes: 42 additions & 151 deletions packages/react-dom/src/__tests__/validateDOMNesting-test.internal.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,176 +9,67 @@

'use strict';

var validateDOMNesting;
var React;
var ReactDOM;

// 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',
];
function expectInvalidNestingWarning(shouldWarn, tags) {
// TODO: I get the error <spyOn> : error has already been spied upon although
// i am resetting the jest modules before every assertion
jest.resetModules();
React = require('react');
ReactDOM = require('react-dom');

// 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,
);
let element = null;
const warningCount = shouldWarn ? 1 : 0;
tags = [...tags];
while (tags.length) {
element = React.createElement(tags.pop(), null, element);
}
return true;

spyOnDev(console, 'error');
const container = document.createElement('div');
ReactDOM.render(element, container);
expect(console.error.calls.count()).toEqual(warningCount);
}

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);
});
}
React = require('react');
ReactDOM = require('react-dom');
});

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);
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']);

// 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']);

// TODO: Previously used test scenarios which will fail now because
// root element is div. Remove them?

// expectInvalidNestingWarning(false, ['body', 'datalist', 'option']);
// expectInvalidNestingWarning(false, ['html', 'body', 'div']);
}
});

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,
);
expectInvalidNestingWarning(true, ['a', 'a']);
expectInvalidNestingWarning(true, ['form', 'form']);
expectInvalidNestingWarning(true, ['p', 'p']);
expectInvalidNestingWarning(true, ['table', 'tr']);
expectInvalidNestingWarning(true, ['div', 'ul', 'li', 'div', 'li']);
expectInvalidNestingWarning(true, ['div', 'html']);
expectInvalidNestingWarning(true, ['body', 'body']);
expectInvalidNestingWarning(true, ['svg', 'foreignObject', 'body', 'p']);
}
});
});
11 changes: 0 additions & 11 deletions packages/react-dom/src/client/validateDOMNesting.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

0 comments on commit 5a76ca2

Please sign in to comment.