Drop .textContent IE8 polyfill and rewrite escaping tests against public API (#11331)
* Rename escapeText util. Test quoteAttributeValueForBrowser through ReactDOMServer API * Fix lint errors * Prettier reformatting * Change syntax to prevent prettier escape doble quote * Name and description gardening. Add tests for escapeTextForBrowser. Add missing tests * Improve script tag as text content test * Update escapeTextForBrowser-test.js * Update quoteAttributeValueForBrowser-test.js * Simplify tests * Move utilities to server folder
Jeremias Menichelli committed
Nov 23, 2017 at 19:41 UTC
cafe352c1a6ab8591b6e42e1e788d48115b0a814
9 files changed
+129
-107
packages/react-dom/src/__tests__/ReactServerRendering-test.js
-7
@@ -42,13 +42,6 @@ describe('ReactDOMServer', () => {
42
expect(response).toMatch(new RegExp('<img data-reactroot=""' + '/>'));
43
});
44
45
- it('should generate simple markup for attribute with `>` symbol', () => {
46
- var response = ReactDOMServer.renderToString(<img data-attr=">" />);
47
- expect(response).toMatch(
48
- new RegExp('<img data-attr=">" data-reactroot=""' + '/>'),
49
- );
50
- });
51
-
45
it('should generate comment markup for component returns null', () => {
46
class NullComponent extends React.Component {
47
render() {
packages/react-dom/src/__tests__/escapeTextContentForBrowser-test.js
deleted
-48
@@ -1,48 +0,0 @@
1
-/**
2
- * Copyright (c) 2013-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
-describe('escapeTextContentForBrowser', () => {
13
- // TODO: can we express this test with only public API?
14
- var escapeTextContentForBrowser = require('../shared/escapeTextContentForBrowser')
15
- .default;
16
-
17
- it('should escape boolean to string', () => {
18
- expect(escapeTextContentForBrowser(true)).toBe('true');
19
- expect(escapeTextContentForBrowser(false)).toBe('false');
20
- });
21
-
22
- it('should escape object to string', () => {
23
- var escaped = escapeTextContentForBrowser({
24
- toString: function() {
25
- return 'ponys';
26
- },
27
- });
28
-
29
- expect(escaped).toBe('ponys');
30
- });
31
-
32
- it('should escape number to string', () => {
33
- expect(escapeTextContentForBrowser(42)).toBe('42');
34
- });
35
-
36
- it('should escape string', () => {
37
- var escaped = escapeTextContentForBrowser(
38
- '<script type=\'\' src=""></script>',
39
- );
40
- expect(escaped).not.toContain('<');
41
- expect(escaped).not.toContain('>');
42
- expect(escaped).not.toContain("'");
43
- expect(escaped).not.toContain('"');
44
-
45
- escaped = escapeTextContentForBrowser('&');
46
- expect(escaped).toBe('&');
47
- });
48
-});
packages/react-dom/src/__tests__/escapeTextForBrowser-test.js
new
+66
@@ -0,0 +1,66 @@
1
+/**
2
+ * Copyright (c) 2013-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;
13
+var ReactDOMServer;
14
+
15
+describe('escapeTextForBrowser', () => {
16
+ beforeEach(() => {
17
+ jest.resetModules();
18
+ React = require('react');
19
+ ReactDOMServer = require('react-dom/server');
20
+ });
21
+
22
+ it('ampersand is escaped when passed as text content', () => {
23
+ var response = ReactDOMServer.renderToString(<span>{'&'}</span>);
24
+ expect(response).toMatch('<span data-reactroot="">&</span>');
25
+ });
26
+
27
+ it('double quote is escaped when passed as text content', () => {
28
+ var response = ReactDOMServer.renderToString(<span>{'"'}</span>);
29
+ expect(response).toMatch('<span data-reactroot="">"</span>');
30
+ });
31
+
32
+ it('single quote is escaped when passed as text content', () => {
33
+ var response = ReactDOMServer.renderToString(<span>{"'"}</span>);
34
+ expect(response).toMatch('<span data-reactroot="">'</span>');
35
+ });
36
+
37
+ it('greater than entity is escaped when passed as text content', () => {
38
+ var response = ReactDOMServer.renderToString(<span>{'>'}</span>);
39
+ expect(response).toMatch('<span data-reactroot="">></span>');
40
+ });
41
+
42
+ it('lower than entity is escaped when passed as text content', () => {
43
+ var response = ReactDOMServer.renderToString(<span>{'<'}</span>);
44
+ expect(response).toMatch('<span data-reactroot=""><</span>');
45
+ });
46
+
47
+ it('number is correctly passed as text content', () => {
48
+ var response = ReactDOMServer.renderToString(<span>{42}</span>);
49
+ expect(response).toMatch('<span data-reactroot="">42</span>');
50
+ });
51
+
52
+ it('number is escaped to string when passed as text content', () => {
53
+ var response = ReactDOMServer.renderToString(<img data-attr={42} />);
54
+ expect(response).toMatch('<img data-attr="42" data-reactroot=""/>');
55
+ });
56
+
57
+ it('escape text content representing a script tag', () => {
58
+ var response = ReactDOMServer.renderToString(
59
+ <span>{'<script type=\'\' src=""></script>'}</span>,
60
+ );
61
+ expect(response).toMatch(
62
+ '<span data-reactroot=""><script type='' ' +
63
+ 'src=""></script></span>',
64
+ );
65
+ });
66
+});
packages/react-dom/src/__tests__/quoteAttributeValueForBrowser-test.js
+51
-24
@@ -9,40 +9,67 @@
9
10
'use strict';
11
12
+var React;
13
+var ReactDOMServer;
14
+
15
describe('quoteAttributeValueForBrowser', () => {
13
- // TODO: can we express this test with only public API?
14
- var quoteAttributeValueForBrowser = require('../shared/quoteAttributeValueForBrowser')
15
- .default;
16
+ beforeEach(() => {
17
+ jest.resetModules();
18
+ React = require('react');
19
+ ReactDOMServer = require('react-dom/server');
20
+ });
21
17
- it('should escape boolean to string', () => {
18
- expect(quoteAttributeValueForBrowser(true)).toBe('"true"');
19
- expect(quoteAttributeValueForBrowser(false)).toBe('"false"');
22
+ it('ampersand is escaped inside attributes', () => {
23
+ var response = ReactDOMServer.renderToString(<img data-attr="&" />);
24
+ expect(response).toMatch('<img data-attr="&" data-reactroot=""/>');
25
});
26
22
- it('should escape object to string', () => {
23
- var escaped = quoteAttributeValueForBrowser({
24
- toString: function() {
25
- return 'ponys';
26
- },
27
- });
27
+ it('double quote is escaped inside attributes', () => {
28
+ var response = ReactDOMServer.renderToString(<img data-attr={'"'} />);
29
+ expect(response).toMatch('<img data-attr=""" data-reactroot=""/>');
30
+ });
31
+
32
+ it('single quote is escaped inside attributes', () => {
33
+ var response = ReactDOMServer.renderToString(<img data-attr="'" />);
34
+ expect(response).toMatch('<img data-attr="'" data-reactroot=""/>');
35
+ });
36
29
- expect(escaped).toBe('"ponys"');
37
+ it('greater than entity is escaped inside attributes', () => {
38
+ var response = ReactDOMServer.renderToString(<img data-attr=">" />);
39
+ expect(response).toMatch('<img data-attr=">" data-reactroot=""/>');
40
});
41
32
- it('should escape number to string', () => {
33
- expect(quoteAttributeValueForBrowser(42)).toBe('"42"');
42
+ it('lower than entity is escaped inside attributes', () => {
43
+ var response = ReactDOMServer.renderToString(<img data-attr="<" />);
44
+ expect(response).toMatch('<img data-attr="<" data-reactroot=""/>');
45
});
46
36
- it('should escape string', () => {
37
- var escaped = quoteAttributeValueForBrowser(
38
- '<script type=\'\' src=""></script>',
47
+ it('number is escaped to string inside attributes', () => {
48
+ var response = ReactDOMServer.renderToString(<img data-attr={42} />);
49
+ expect(response).toMatch('<img data-attr="42" data-reactroot=""/>');
50
+ });
51
+
52
+ it('object is passed to a string inside attributes', () => {
53
+ var sampleObject = {
54
+ toString: function() {
55
+ return 'ponys';
56
+ },
57
+ };
58
+
59
+ var response = ReactDOMServer.renderToString(
60
+ <img data-attr={sampleObject} />,
61
);
40
- expect(escaped).not.toContain('<');
41
- expect(escaped).not.toContain('>');
42
- expect(escaped).not.toContain("'");
43
- expect(escaped.substr(1, -1)).not.toContain('"');
62
+ expect(response).toMatch('<img data-attr="ponys" data-reactroot=""/>');
63
+ });
64
45
- escaped = quoteAttributeValueForBrowser('&');
46
- expect(escaped).toBe('"&"');
65
+ it('script tag is escaped inside attributes', () => {
66
+ var response = ReactDOMServer.renderToString(
67
+ <img data-attr={'<script type=\'\' src=""></script>'} />,
68
+ );
69
+ expect(response).toMatch(
70
+ '<img data-attr="<script type='' ' +
71
+ 'src=""></script>" ' +
72
+ 'data-reactroot=""/>',
73
+ );
74
});
75
});
packages/react-dom/src/client/setTextContent.js
-16
@@ -5,10 +5,6 @@
5
* LICENSE file in the root directory of this source tree.
6
*/
7
8
-import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
9
-
10
-import setInnerHTML from './setInnerHTML';
11
-import escapeTextContentForBrowser from '../shared/escapeTextContentForBrowser';
8
import {TEXT_NODE} from '../shared/HTMLNodeType';
9
10
/**
@@ -37,16 +33,4 @@ let setTextContent = function(node, text) {
33
node.textContent = text;
34
};
35
40
-if (ExecutionEnvironment.canUseDOM) {
41
- if (!('textContent' in document.documentElement)) {
42
- setTextContent = function(node, text) {
43
- if (node.nodeType === TEXT_NODE) {
44
- node.nodeValue = text;
45
- return;
46
- }
47
- setInnerHTML(node, escapeTextContentForBrowser(text));
48
- };
49
- }
50
-}
51
-
36
export default setTextContent;
packages/react-dom/src/server/DOMMarkupOperations.js
+1
-1
@@ -14,7 +14,7 @@ import {
14
shouldAttributeAcceptBooleanValue,
15
shouldSetAttribute,
16
} from '../shared/DOMProperty';
17
-import quoteAttributeValueForBrowser from '../shared/quoteAttributeValueForBrowser';
17
+import quoteAttributeValueForBrowser from './quoteAttributeValueForBrowser';
18
import warning from 'fbjs/lib/warning';
19
20
// isAttributeNameSafe() is currently duplicated in DOMPropertyOperations.
packages/react-dom/src/server/ReactPartialRenderer.js
+5
-5
@@ -26,6 +26,7 @@ import {
26
createMarkupForProperty,
27
createMarkupForRoot,
28
} from './DOMMarkupOperations';
29
+import escapeTextForBrowser from './escapeTextForBrowser';
30
import {
31
Namespaces,
32
getIntrinsicNamespace,
@@ -34,7 +35,6 @@ import {
35
import ReactControlledValuePropTypes from '../shared/ReactControlledValuePropTypes';
36
import assertValidProps from '../shared/assertValidProps';
37
import dangerousStyleValue from '../shared/dangerousStyleValue';
37
-import escapeTextContentForBrowser from '../shared/escapeTextContentForBrowser';
38
import isCustomComponent from '../shared/isCustomComponent';
39
import omittedCloseTags from '../shared/omittedCloseTags';
40
import warnValidStyle from '../shared/warnValidStyle';
@@ -204,7 +204,7 @@ function getNonChildrenInnerMarkup(props) {
204
} else {
205
var content = props.children;
206
if (typeof content === 'string' || typeof content === 'number') {
207
- return escapeTextContentForBrowser(content);
207
+ return escapeTextForBrowser(content);
208
}
209
}
210
return null;
@@ -572,13 +572,13 @@ class ReactDOMServerRenderer {
572
return '';
573
}
574
if (this.makeStaticMarkup) {
575
- return escapeTextContentForBrowser(text);
575
+ return escapeTextForBrowser(text);
576
}
577
if (this.previousWasTextNode) {
578
- return '<!-- -->' + escapeTextContentForBrowser(text);
578
+ return '<!-- -->' + escapeTextForBrowser(text);
579
}
580
this.previousWasTextNode = true;
581
- return escapeTextContentForBrowser(text);
581
+ return escapeTextForBrowser(text);
582
} else {
583
var nextChild;
584
({child: nextChild, context} = resolve(child, context));
packages/react-dom/src/server/escapeTextForBrowser.js
renamed
+4
-4
@@ -39,9 +39,9 @@
39
var matchHtmlRegExp = /["'&<>]/;
40
41
/**
42
- * Escape special characters in the given string of html.
42
+ * Escapes special characters and HTML entities in a given html string.
43
*
44
- * @param {string} string The string to escape for inserting into HTML
44
+ * @param {string} string HTML string to escape for later insertion
45
* @return {string}
46
* @public
47
*/
@@ -98,7 +98,7 @@ function escapeHtml(string) {
98
* @param {*} text Text value to escape.
99
* @return {string} An escaped string.
100
*/
101
-function escapeTextContentForBrowser(text) {
101
+function escapeTextForBrowser(text) {
102
if (typeof text === 'boolean' || typeof text === 'number') {
103
// this shortcircuit helps perf for types that we know will never have
104
// special characters, especially given that this function is used often
@@ -108,4 +108,4 @@ function escapeTextContentForBrowser(text) {
108
return escapeHtml(text);
109
}
110
111
-export default escapeTextContentForBrowser;
111
+export default escapeTextForBrowser;
packages/react-dom/src/server/quoteAttributeValueForBrowser.js
renamed
+2
-2
@@ -5,7 +5,7 @@
5
* LICENSE file in the root directory of this source tree.
6
*/
7
8
-import escapeTextContentForBrowser from './escapeTextContentForBrowser';
8
+import escapeTextForBrowser from './escapeTextForBrowser';
9
10
/**
11
* Escapes attribute value to prevent scripting attacks.
@@ -14,7 +14,7 @@ import escapeTextContentForBrowser from './escapeTextContentForBrowser';
14
* @return {string} An escaped string.
15
*/
16
function quoteAttributeValueForBrowser(value) {
17
- return '"' + escapeTextContentForBrowser(value) + '"';
17
+ return '"' + escapeTextForBrowser(value) + '"';
18
}
19
20
export default quoteAttributeValueForBrowser;