@samitouri / QOS-React / commits / 825682390d

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

Anushree Subramani committed Dec 8, 2017 at 00:15 UTC 825682390d825bb51be2e06835cf7e3458352e83
3 files changed +134 -195
packages/react-dom/src/__tests__/validateDOMNesting-test.internal.js deleted
-184
@@ -1,184 +0,0 @@
1 -/**
2 - * Copyright (c) 2015-present, Facebook, Inc.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - *
7 - * @emails react-core
8 - */
9 -
10 -'use strict';
11 -
12 -let validateDOMNesting;
13 -
14 -// https://html.spec.whatwg.org/multipage/syntax.html#special
15 -const specialTags = [
16 - 'address',
17 - 'applet',
18 - 'area',
19 - 'article',
20 - 'aside',
21 - 'base',
22 - 'basefont',
23 - 'bgsound',
24 - 'blockquote',
25 - 'body',
26 - 'br',
27 - 'button',
28 - 'caption',
29 - 'center',
30 - 'col',
31 - 'colgroup',
32 - 'dd',
33 - 'details',
34 - 'dir',
35 - 'div',
36 - 'dl',
37 - 'dt',
38 - 'embed',
39 - 'fieldset',
40 - 'figcaption',
41 - 'figure',
42 - 'footer',
43 - 'form',
44 - 'frame',
45 - 'frameset',
46 - 'h1',
47 - 'h2',
48 - 'h3',
49 - 'h4',
50 - 'h5',
51 - 'h6',
52 - 'head',
53 - 'header',
54 - 'hgroup',
55 - 'hr',
56 - 'html',
57 - 'iframe',
58 - 'img',
59 - 'input',
60 - 'isindex',
61 - 'li',
62 - 'link',
63 - 'listing',
64 - 'main',
65 - 'marquee',
66 - 'menu',
67 - 'menuitem',
68 - 'meta',
69 - 'nav',
70 - 'noembed',
71 - 'noframes',
72 - 'noscript',
73 - 'object',
74 - 'ol',
75 - 'p',
76 - 'param',
77 - 'plaintext',
78 - 'pre',
79 - 'script',
80 - 'section',
81 - 'select',
82 - 'source',
83 - 'style',
84 - 'summary',
85 - 'table',
86 - 'tbody',
87 - 'td',
88 - 'template',
89 - 'textarea',
90 - 'tfoot',
91 - 'th',
92 - 'thead',
93 - 'title',
94 - 'tr',
95 - 'track',
96 - 'ul',
97 - 'wbr',
98 - 'xmp',
99 -];
100 -
101 -// https://html.spec.whatwg.org/multipage/syntax.html#formatting
102 -const formattingTags = [
103 - 'a',
104 - 'b',
105 - 'big',
106 - 'code',
107 - 'em',
108 - 'font',
109 - 'i',
110 - 'nobr',
111 - 's',
112 - 'small',
113 - 'strike',
114 - 'strong',
115 - 'tt',
116 - 'u',
117 -];
118 -
119 -function isTagStackValid(stack) {
120 - let ancestorInfo = null;
121 - for (let i = 0; i < stack.length; i++) {
122 - if (!validateDOMNesting.isTagValidInContext(stack[i], ancestorInfo)) {
123 - return false;
124 - }
125 - ancestorInfo = validateDOMNesting.updatedAncestorInfo(
126 - ancestorInfo,
127 - stack[i],
128 - null,
129 - );
130 - }
131 - return true;
132 -}
133 -
134 -describe('validateDOMNesting', () => {
135 - beforeEach(() => {
136 - jest.resetModules();
137 -
138 - // TODO: can we express this test with only public API?
139 - validateDOMNesting = require('../client/validateDOMNesting').default;
140 - });
141 -
142 - it('allows any tag with no context', () => {
143 - if (__DEV__) {
144 - // With renderToString (for example), we don't know where we're mounting the
145 - // tag so we must err on the side of leniency.
146 - const allTags = [].concat(specialTags, formattingTags, ['mysterytag']);
147 - allTags.forEach(function(tag) {
148 - expect(validateDOMNesting.isTagValidInContext(tag, null)).toBe(true);
149 - });
150 - }
151 - });
152 -
153 - it('allows valid nestings', () => {
154 - if (__DEV__) {
155 - expect(isTagStackValid(['table', 'tbody', 'tr', 'td', 'b'])).toBe(true);
156 - expect(isTagStackValid(['body', 'datalist', 'option'])).toBe(true);
157 - expect(isTagStackValid(['div', 'a', 'object', 'a'])).toBe(true);
158 - expect(isTagStackValid(['div', 'p', 'button', 'p'])).toBe(true);
159 - expect(isTagStackValid(['p', 'svg', 'foreignObject', 'p'])).toBe(true);
160 - expect(isTagStackValid(['html', 'body', 'div'])).toBe(true);
161 -
162 - // Invalid, but not changed by browser parsing so we allow them
163 - expect(isTagStackValid(['div', 'ul', 'ul', 'li'])).toBe(true);
164 - expect(isTagStackValid(['div', 'label', 'div'])).toBe(true);
165 - expect(isTagStackValid(['div', 'ul', 'li', 'section', 'li'])).toBe(true);
166 - expect(isTagStackValid(['div', 'ul', 'li', 'dd', 'li'])).toBe(true);
167 - }
168 - });
169 -
170 - it('prevents problematic nestings', () => {
171 - if (__DEV__) {
172 - expect(isTagStackValid(['a', 'a'])).toBe(false);
173 - expect(isTagStackValid(['form', 'form'])).toBe(false);
174 - expect(isTagStackValid(['p', 'p'])).toBe(false);
175 - expect(isTagStackValid(['table', 'tr'])).toBe(false);
176 - expect(isTagStackValid(['div', 'ul', 'li', 'div', 'li'])).toBe(false);
177 - expect(isTagStackValid(['div', 'html'])).toBe(false);
178 - expect(isTagStackValid(['body', 'body'])).toBe(false);
179 - expect(isTagStackValid(['svg', 'foreignObject', 'body', 'p'])).toBe(
180 - false,
181 - );
182 - }
183 - });
184 -});
packages/react-dom/src/__tests__/validateDOMNesting-test.js new
+134
@@ -0,0 +1,134 @@
1 +/**
2 + * Copyright (c) 2015-present, Facebook, Inc.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @emails react-core
8 + */
9 +
10 +'use strict';
11 +
12 +var React = require('react');
13 +var ReactDOM = require('react-dom');
14 +
15 +function normalizeCodeLocInfo(str) {
16 + return str && str.replace(/at .+?:\d+/g, 'at **');
17 +}
18 +
19 +function expectWarnings(tags, warnings = []) {
20 + tags = [...tags];
21 + warnings = [...warnings];
22 +
23 + let element = null;
24 + if (__DEV__) {
25 + console.error.calls.reset();
26 + }
27 + const container = document.createElement(tags.splice(0, 1));
28 + while (tags.length) {
29 + const Tag = tags.pop();
30 + element = <Tag>{element}</Tag>;
31 + }
32 + ReactDOM.render(element, container);
33 +
34 + if (__DEV__) {
35 + expect(console.error.calls.count()).toEqual(warnings.length);
36 + while (warnings.length) {
37 + expect(
38 + normalizeCodeLocInfo(
39 + console.error.calls.argsFor(warnings.length - 1)[0],
40 + ),
41 + ).toContain(warnings.pop());
42 + }
43 + }
44 +}
45 +
46 +describe('validateDOMNesting', () => {
47 + it('allows valid nestings', () => {
48 + spyOnDev(console, 'error');
49 + expectWarnings(['table', 'tbody', 'tr', 'td', 'b']);
50 + expectWarnings(
51 + ['body', 'datalist', 'option'],
52 + [
53 + 'render(): Rendering components directly into document.body is discouraged',
54 + ],
55 + );
56 + expectWarnings(['div', 'a', 'object', 'a']);
57 + expectWarnings(['div', 'p', 'button', 'p']);
58 + expectWarnings(['p', 'svg', 'foreignObject', 'p']);
59 + expectWarnings(['html', 'body', 'div']);
60 +
61 + // Invalid, but not changed by browser parsing so we allow them
62 + expectWarnings(['div', 'ul', 'ul', 'li']);
63 + expectWarnings(['div', 'label', 'div']);
64 + expectWarnings(['div', 'ul', 'li', 'section', 'li']);
65 + expectWarnings(['div', 'ul', 'li', 'dd', 'li']);
66 + });
67 +
68 + it('prevents problematic nestings', () => {
69 + spyOnDev(console, 'error');
70 + expectWarnings(
71 + ['a', 'a'],
72 + [
73 + 'validateDOMNesting(...): <a> cannot appear as a descendant of <a>.\n' +
74 + ' in a (at **)',
75 + ],
76 + );
77 + expectWarnings(
78 + ['form', 'form'],
79 + [
80 + 'validateDOMNesting(...): <form> cannot appear as a descendant of <form>.\n' +
81 + ' in form (at **)',
82 + ],
83 + );
84 + expectWarnings(
85 + ['p', 'p'],
86 + [
87 + 'validateDOMNesting(...): <p> cannot appear as a descendant of <p>.\n' +
88 + ' in p (at **)',
89 + ],
90 + );
91 + expectWarnings(
92 + ['table', 'tr'],
93 + [
94 + 'validateDOMNesting(...): <tr> cannot appear as a child of <table>. ' +
95 + 'Add a <tbody> to your code to match the DOM tree generated by the browser.\n' +
96 + ' in tr (at **)',
97 + ],
98 + );
99 + expectWarnings(
100 + ['div', 'ul', 'li', 'div', 'li'],
101 + [
102 + 'validateDOMNesting(...): <li> cannot appear as a descendant of <li>.\n' +
103 + ' in li (at **)\n' +
104 + ' in div (at **)\n' +
105 + ' in li (at **)\n' +
106 + ' in ul (at **)',
107 + ],
108 + );
109 + expectWarnings(
110 + ['div', 'html'],
111 + [
112 + 'validateDOMNesting(...): <html> cannot appear as a child of <div>.\n' +
113 + ' in html (at **)',
114 + ],
115 + );
116 + expectWarnings(
117 + ['body', 'body'],
118 + [
119 + 'render(): Rendering components directly into document.body is discouraged',
120 + 'validateDOMNesting(...): <body> cannot appear as a child of <body>.\n' +
121 + ' in body (at **)',
122 + ],
123 + );
124 + expectWarnings(
125 + ['svg', 'foreignObject', 'body', 'p'],
126 + [
127 + 'validateDOMNesting(...): <body> cannot appear as a child of <foreignObject>.\n' +
128 + ' in body (at **)\n' +
129 + ' in foreignObject (at **)',
130 + '<foreignObject /> is using uppercase HTML',
131 + ],
132 + );
133 + });
134 +});
packages/react-dom/src/client/validateDOMNesting.js
-11
@@ -482,17 +482,6 @@ if (__DEV__) {
482
483 // TODO: turn this into a named export
484 validateDOMNesting.updatedAncestorInfo = updatedAncestorInfo;
485 -
486 - // For testing
487 - validateDOMNesting.isTagValidInContext = function(tag, ancestorInfo) {
488 - ancestorInfo = ancestorInfo || emptyAncestorInfo;
489 - const parentInfo = ancestorInfo.current;
490 - const parentTag = parentInfo && parentInfo.tag;
491 - return (
492 - isTagValidWithParent(tag, parentTag) &&
493 - !findInvalidAncestorForTag(tag, ancestorInfo)
494 - );
495 - };
485 }
486
487 export default validateDOMNesting;