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;