@samitouri / QOS-React / commits / cafe352c1a

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="&gt;" 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('&amp;');
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="">&amp;</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="">&quot;</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="">&#x27;</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="">&gt;</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="">&lt;</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="">&lt;script type=&#x27;&#x27; ' +
63 + 'src=&quot;&quot;&gt;&lt;/script&gt;</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="&amp;" 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="&quot;" 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="&#x27;" 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="&gt;" 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="&lt;" 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('"&amp;"');
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="&lt;script type=&#x27;&#x27; ' +
71 + 'src=&quot;&quot;&gt;&lt;/script&gt;" ' +
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;