@samitouri / QOS-React / commits / 78761c387f

Add unit tests for props to attribute mapping in SSR. (#9106)

* Added a handful of SSR unit tests, ported from a previous pull request. * Fixing linting errors * Fixed a test helper function to properly report errors. * Un-nested the new rendering tests. Updated the fiber test passing/not passing lists. * Edited to comply with the react/jsx-space-before-closing eslint rule, which will soon be merged into master. * Response to code review from @spicyj. Moved tests to separate file, reworked wording of test names, corrected use of canUseDom, and simplified tests against tagName. Thanks for the help, @spicyj! * Converted the unit tests to use async-await style. * Moved async-await babel transform for tests from .babelrc to preprocessor.js. * Response to code review in PR #9089. Thanks, @spicyj! * Fixing some bugs in the SSR unit tests. * Missed deleting some repeated code in the last commit. * Adding unit tests for property to attribute mapping in SSR. * Removing some redundant unit tests. * Oops. I forgot to re-run record-tests after c593dbc; fixing that here. * Reformatting for prettier so that the build will pass.

Sasha Aickin committed Mar 19, 2017 at 16:08 UTC 78761c387f7ab24b0dbbb63e9085b26b808e5330
4 files changed +398 -15
scripts/fiber/tests-failing.txt
+41
@@ -6,6 +6,47 @@ src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
6 * gives source code refs for unknown prop warning for exact elements (ssr)
7 * gives source code refs for unknown prop warning for exact elements in composition (ssr)
8
9 +src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js
10 +* renders a blank div with client render on top of good server markup
11 +* renders a div with inline styles with client render on top of good server markup
12 +* renders a self-closing tag with client render on top of good server markup
13 +* renders a self-closing tag as a child with client render on top of good server markup
14 +* renders simple numbers with client render on top of good server markup
15 +* renders simple strings with client render on top of good server markup
16 +* renders string prop with true value with client render on top of good server markup
17 +* renders string prop with false value with client render on top of good server markup
18 +* renders boolean prop with true value with client render on top of good server markup
19 +* renders boolean prop with false value with client render on top of good server markup
20 +* renders boolean prop with self value with client render on top of good server markup
21 +* renders boolean prop with "" value with client render on top of good server markup
22 +* renders boolean prop with string value with client render on top of good server markup
23 +* renders boolean prop with array value with client render on top of good server markup
24 +* renders boolean prop with object value with client render on top of good server markup
25 +* renders boolean prop with non-zero number value with client render on top of good server markup
26 +* renders boolean prop with zero value with client render on top of good server markup
27 +* renders download prop with true value with client render on top of good server markup
28 +* renders download prop with false value with client render on top of good server markup
29 +* renders download prop with string value with client render on top of good server markup
30 +* renders download prop with string "true" value with client render on top of good server markup
31 +* renders className prop with string value with client render on top of good server markup
32 +* renders className prop with empty string value with client render on top of good server markup
33 +* renders className prop with true value with client render on top of good server markup
34 +* renders className prop with false value with client render on top of good server markup
35 +* renders htmlFor with string value with client render on top of good server markup
36 +* renders htmlFor with an empty string with client render on top of good server markup
37 +* renders className prop with true value with client render on top of good server markup
38 +* renders className prop with false value with client render on top of good server markup
39 +* renders no ref attribute with client render on top of good server markup
40 +* renders no children attribute with client render on top of good server markup
41 +* renders no key attribute with client render on top of good server markup
42 +* renders no dangerouslySetInnerHTML attribute with client render on top of good server markup
43 +* renders no unknown attributes with client render on top of good server markup
44 +* renders unknown data- attributes with client render on top of good server markup
45 +* renders no unknown attributes for non-standard elements with client render on top of good server markup
46 +* renders unknown attributes for custom elements with client render on top of good server markup
47 +* renders unknown attributes for custom elements using is with client render on top of good server markup
48 +* renders no HTML events with client render on top of good server markup
49 +
50 src/renderers/dom/shared/__tests__/ReactDOMTextComponent-test.js
51 * can reconcile text merged by Node.normalize() alongside other elements
52 * can reconcile text merged by Node.normalize()
scripts/fiber/tests-passing-except-dev.txt
+37
@@ -10,6 +10,43 @@ src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js
10 * renders a div with inline styles with client render on top of bad server markup
11 * renders a self-closing tag with client render on top of bad server markup
12 * renders a self-closing tag as a child with client render on top of bad server markup
13 +* renders simple numbers with client render on top of bad server markup
14 +* renders simple strings with client render on top of bad server markup
15 +* renders string prop with true value with client render on top of bad server markup
16 +* renders string prop with false value with client render on top of bad server markup
17 +* renders boolean prop with true value with client render on top of bad server markup
18 +* renders boolean prop with false value with client render on top of bad server markup
19 +* renders boolean prop with self value with client render on top of bad server markup
20 +* renders boolean prop with "" value with client render on top of bad server markup
21 +* renders boolean prop with string value with client render on top of bad server markup
22 +* renders boolean prop with array value with client render on top of bad server markup
23 +* renders boolean prop with object value with client render on top of bad server markup
24 +* renders boolean prop with non-zero number value with client render on top of bad server markup
25 +* renders boolean prop with zero value with client render on top of bad server markup
26 +* renders download prop with true value with client render on top of bad server markup
27 +* renders download prop with false value with client render on top of bad server markup
28 +* renders download prop with string value with client render on top of bad server markup
29 +* renders download prop with string "true" value with client render on top of bad server markup
30 +* renders className prop with string value with client render on top of bad server markup
31 +* renders className prop with empty string value with client render on top of bad server markup
32 +* renders className prop with true value with client render on top of bad server markup
33 +* renders className prop with false value with client render on top of bad server markup
34 +* renders htmlFor with string value with client render on top of bad server markup
35 +* renders htmlFor with an empty string with client render on top of bad server markup
36 +* renders className prop with true value with client render on top of bad server markup
37 +* renders className prop with false value with client render on top of bad server markup
38 +* renders no ref attribute with client render on top of bad server markup
39 +* renders no children attribute with client render on top of bad server markup
40 +* renders no key attribute with client render on top of bad server markup
41 +* renders no dangerouslySetInnerHTML attribute with client render on top of bad server markup
42 +* renders no unknown attributes with server string render
43 +* renders no unknown attributes with client render on top of bad server markup
44 +* renders unknown data- attributes with client render on top of bad server markup
45 +* renders no unknown attributes for non-standard elements with server string render
46 +* renders no unknown attributes for non-standard elements with client render on top of bad server markup
47 +* renders unknown attributes for custom elements with client render on top of bad server markup
48 +* renders unknown attributes for custom elements using is with client render on top of bad server markup
49 +* renders no HTML events with client render on top of bad server markup
50
51 src/renderers/dom/shared/__tests__/ReactMount-test.js
52 * should warn if mounting into dirty rendered markup
scripts/fiber/tests-passing.txt
+68 -4
@@ -1064,16 +1064,80 @@ src/renderers/dom/shared/__tests__/ReactDOMSVG-test.js
1064 src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js
1065 * renders a blank div with server string render
1066 * renders a blank div with clean client render
1067 -* renders a blank div with client render on top of good server markup
1067 * renders a div with inline styles with server string render
1068 * renders a div with inline styles with clean client render
1070 -* renders a div with inline styles with client render on top of good server markup
1069 * renders a self-closing tag with server string render
1070 * renders a self-closing tag with clean client render
1073 -* renders a self-closing tag with client render on top of good server markup
1071 * renders a self-closing tag as a child with server string render
1072 * renders a self-closing tag as a child with clean client render
1076 -* renders a self-closing tag as a child with client render on top of good server markup
1073 +* renders simple numbers with server string render
1074 +* renders simple numbers with clean client render
1075 +* renders simple strings with server string render
1076 +* renders simple strings with clean client render
1077 +* renders string prop with true value with server string render
1078 +* renders string prop with true value with clean client render
1079 +* renders string prop with false value with server string render
1080 +* renders string prop with false value with clean client render
1081 +* renders boolean prop with true value with server string render
1082 +* renders boolean prop with true value with clean client render
1083 +* renders boolean prop with false value with server string render
1084 +* renders boolean prop with false value with clean client render
1085 +* renders boolean prop with self value with server string render
1086 +* renders boolean prop with self value with clean client render
1087 +* renders boolean prop with "" value with server string render
1088 +* renders boolean prop with "" value with clean client render
1089 +* renders boolean prop with string value with server string render
1090 +* renders boolean prop with string value with clean client render
1091 +* renders boolean prop with array value with server string render
1092 +* renders boolean prop with array value with clean client render
1093 +* renders boolean prop with object value with server string render
1094 +* renders boolean prop with object value with clean client render
1095 +* renders boolean prop with non-zero number value with server string render
1096 +* renders boolean prop with non-zero number value with clean client render
1097 +* renders boolean prop with zero value with server string render
1098 +* renders boolean prop with zero value with clean client render
1099 +* renders download prop with true value with server string render
1100 +* renders download prop with true value with clean client render
1101 +* renders download prop with false value with server string render
1102 +* renders download prop with false value with clean client render
1103 +* renders download prop with string value with server string render
1104 +* renders download prop with string value with clean client render
1105 +* renders download prop with string "true" value with server string render
1106 +* renders download prop with string "true" value with clean client render
1107 +* renders className prop with string value with server string render
1108 +* renders className prop with string value with clean client render
1109 +* renders className prop with empty string value with server string render
1110 +* renders className prop with empty string value with clean client render
1111 +* renders className prop with true value with server string render
1112 +* renders className prop with true value with clean client render
1113 +* renders className prop with false value with server string render
1114 +* renders className prop with false value with clean client render
1115 +* renders htmlFor with string value with server string render
1116 +* renders htmlFor with string value with clean client render
1117 +* renders htmlFor with an empty string with server string render
1118 +* renders htmlFor with an empty string with clean client render
1119 +* renders className prop with true value with server string render
1120 +* renders className prop with true value with clean client render
1121 +* renders className prop with false value with server string render
1122 +* renders className prop with false value with clean client render
1123 +* renders no ref attribute with server string render
1124 +* renders no ref attribute with clean client render
1125 +* renders no children attribute with server string render
1126 +* renders no children attribute with clean client render
1127 +* renders no key attribute with server string render
1128 +* renders no key attribute with clean client render
1129 +* renders no dangerouslySetInnerHTML attribute with server string render
1130 +* renders no dangerouslySetInnerHTML attribute with clean client render
1131 +* renders no unknown attributes with clean client render
1132 +* renders unknown data- attributes with server string render
1133 +* renders unknown data- attributes with clean client render
1134 +* renders no unknown attributes for non-standard elements with clean client render
1135 +* renders unknown attributes for custom elements with server string render
1136 +* renders unknown attributes for custom elements with clean client render
1137 +* renders unknown attributes for custom elements using is with server string render
1138 +* renders unknown attributes for custom elements using is with clean client render
1139 +* renders no HTML events with server string render
1140 +* renders no HTML events with clean client render
1141
1142 src/renderers/dom/shared/__tests__/ReactDOMTextComponent-test.js
1143 * updates a mounted text component in place
src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js
+252 -11
@@ -65,14 +65,20 @@ function renderIntoDom(reactElement, domElement, errorCount = 0) {
65 );
66 }
67
68 +async function renderIntoString(reactElement, errorCount = 0) {
69 + return await expectErrors(
70 + () =>
71 + new Promise(resolve =>
72 + resolve(ReactDOMServer.renderToString(reactElement))),
73 + errorCount,
74 + );
75 +}
76 +
77 // Renders text using SSR and then stuffs it into a DOM node; returns the DOM
78 // element that corresponds with the reactElement.
79 // Does not render on client or perform client-side revival.
80 async function serverRender(reactElement, errorCount = 0) {
72 - const markup = await expectErrors(
73 - () => Promise.resolve(ReactDOMServer.renderToString(reactElement)),
74 - errorCount,
75 - );
81 + const markup = await renderIntoString(reactElement, errorCount);
82 var domElement = document.createElement('div');
83 domElement.innerHTML = markup;
84 return domElement.firstChild;
@@ -84,10 +90,17 @@ const clientCleanRender = (element, errorCount = 0) => {
90 };
91
92 const clientRenderOnServerString = async (element, errorCount = 0) => {
87 - const markup = await serverRender(element, errorCount);
93 + const markup = await renderIntoString(element, errorCount);
94 + resetModules();
95 var domElement = document.createElement('div');
96 domElement.innerHTML = markup;
90 - return renderIntoDom(element, domElement, errorCount);
97 + const serverElement = domElement.firstChild;
98 + const clientElement = await renderIntoDom(element, domElement, errorCount);
99 + // assert that the DOM element hasn't been replaced.
100 + // Note that we cannot use expect(serverElement).toBe(clientElement) because
101 + // of jest bug #1772
102 + expect(serverElement === clientElement).toBe(true);
103 + return clientElement;
104 };
105
106 const clientRenderOnBadMarkup = (element, errorCount = 0) => {
@@ -135,14 +148,22 @@ function itClientRenders(desc, testFn) {
148 testFn(clientRenderOnBadMarkup));
149 }
150
151 +// When there is a test that renders on server and then on client and expects a logged
152 +// error, you want to see the error show up both on server and client. Unfortunately,
153 +// React refuses to issue the same error twice to avoid clogging up the console.
154 +// To get around this, we must reload React modules in between server and client render.
155 +function resetModules() {
156 + jest.resetModuleRegistry();
157 + React = require('React');
158 + ReactDOM = require('ReactDOM');
159 + ReactDOMServer = require('ReactDOMServer');
160 + ExecutionEnvironment = require('ExecutionEnvironment');
161 +}
162 +
163 describe('ReactDOMServerIntegration', () => {
164 beforeEach(() => {
140 - jest.resetModuleRegistry();
141 - React = require('React');
142 - ReactDOM = require('ReactDOM');
143 - ReactDOMServer = require('ReactDOMServer');
165 + resetModules();
166
145 - ExecutionEnvironment = require('ExecutionEnvironment');
167 ExecutionEnvironment.canUseDOM = false;
168 });
169
@@ -169,4 +190,224 @@ describe('ReactDOMServerIntegration', () => {
190 expect(e.firstChild.tagName).toBe('BR');
191 });
192 });
193 +
194 + describe('property to attribute mapping', function() {
195 + describe('string properties', function() {
196 + itRenders('simple numbers', async render => {
197 + const e = await render(<div width={30} />);
198 + expect(e.getAttribute('width')).toBe('30');
199 + });
200 +
201 + itRenders('simple strings', async render => {
202 + const e = await render(<div width={'30'} />);
203 + expect(e.getAttribute('width')).toBe('30');
204 + });
205 +
206 + // this seems like it might mask programmer error, but it's existing behavior.
207 + itRenders('string prop with true value', async render => {
208 + const e = await render(<a href={true} />);
209 + expect(e.getAttribute('href')).toBe('true');
210 + });
211 +
212 + // this seems like it might mask programmer error, but it's existing behavior.
213 + itRenders('string prop with false value', async render => {
214 + const e = await render(<a href={false} />);
215 + expect(e.getAttribute('href')).toBe('false');
216 + });
217 + });
218 +
219 + describe('boolean properties', function() {
220 + itRenders('boolean prop with true value', async render => {
221 + const e = await render(<div hidden={true} />);
222 + expect(e.getAttribute('hidden')).toBe('');
223 + });
224 +
225 + itRenders('boolean prop with false value', async render => {
226 + const e = await render(<div hidden={false} />);
227 + expect(e.getAttribute('hidden')).toBe(null);
228 + });
229 +
230 + itRenders('boolean prop with self value', async render => {
231 + const e = await render(<div hidden="hidden" />);
232 + expect(e.getAttribute('hidden')).toBe('');
233 + });
234 +
235 + // this does not seem like correct behavior, since hidden="" in HTML indicates
236 + // that the boolean property is present. however, it is how the current code
237 + // behaves, so the test is included here.
238 + itRenders('boolean prop with "" value', async render => {
239 + const e = await render(<div hidden="" />);
240 + expect(e.getAttribute('hidden')).toBe(null);
241 + });
242 +
243 + // this seems like it might mask programmer error, but it's existing behavior.
244 + itRenders('boolean prop with string value', async render => {
245 + const e = await render(<div hidden="foo" />);
246 + expect(e.getAttribute('hidden')).toBe('');
247 + });
248 +
249 + // this seems like it might mask programmer error, but it's existing behavior.
250 + itRenders('boolean prop with array value', async render => {
251 + const e = await render(<div hidden={['foo', 'bar']} />);
252 + expect(e.getAttribute('hidden')).toBe('');
253 + });
254 +
255 + // this seems like it might mask programmer error, but it's existing behavior.
256 + itRenders('boolean prop with object value', async render => {
257 + const e = await render(<div hidden={{foo: 'bar'}} />);
258 + expect(e.getAttribute('hidden')).toBe('');
259 + });
260 +
261 + // this seems like it might mask programmer error, but it's existing behavior.
262 + itRenders('boolean prop with non-zero number value', async render => {
263 + const e = await render(<div hidden={10} />);
264 + expect(e.getAttribute('hidden')).toBe('');
265 + });
266 +
267 + // this seems like it might mask programmer error, but it's existing behavior.
268 + itRenders('boolean prop with zero value', async render => {
269 + const e = await render(<div hidden={0} />);
270 + expect(e.getAttribute('hidden')).toBe(null);
271 + });
272 + });
273 +
274 + describe('download property (combined boolean/string attribute)', function() {
275 + itRenders('download prop with true value', async render => {
276 + const e = await render(<a download={true} />);
277 + expect(e.getAttribute('download')).toBe('');
278 + });
279 +
280 + itRenders('download prop with false value', async render => {
281 + const e = await render(<a download={false} />);
282 + expect(e.getAttribute('download')).toBe(null);
283 + });
284 +
285 + itRenders('download prop with string value', async render => {
286 + const e = await render(<a download="myfile" />);
287 + expect(e.getAttribute('download')).toBe('myfile');
288 + });
289 +
290 + itRenders('download prop with string "true" value', async render => {
291 + const e = await render(<a download={'true'} />);
292 + expect(e.getAttribute('download')).toBe('true');
293 + });
294 + });
295 +
296 + describe('className property', function() {
297 + itRenders('className prop with string value', async render => {
298 + const e = await render(<div className="myClassName" />);
299 + expect(e.getAttribute('class')).toBe('myClassName');
300 + });
301 +
302 + itRenders('className prop with empty string value', async render => {
303 + const e = await render(<div className="" />);
304 + expect(e.getAttribute('class')).toBe('');
305 + });
306 +
307 + // this probably is just masking programmer error, but it is existing behavior.
308 + itRenders('className prop with true value', async render => {
309 + const e = await render(<div className={true} />);
310 + expect(e.getAttribute('class')).toBe('true');
311 + });
312 +
313 + // this probably is just masking programmer error, but it is existing behavior.
314 + itRenders('className prop with false value', async render => {
315 + const e = await render(<div className={false} />);
316 + expect(e.getAttribute('class')).toBe('false');
317 + });
318 + });
319 +
320 + describe('htmlFor property', function() {
321 + itRenders('htmlFor with string value', async render => {
322 + const e = await render(<div htmlFor="myFor" />);
323 + expect(e.getAttribute('for')).toBe('myFor');
324 + });
325 +
326 + itRenders('htmlFor with an empty string', async render => {
327 + const e = await render(<div htmlFor="" />);
328 + expect(e.getAttribute('for')).toBe('');
329 + });
330 +
331 + // this probably is just masking programmer error, but it is existing behavior.
332 + itRenders('className prop with true value', async render => {
333 + const e = await render(<div htmlFor={true} />);
334 + expect(e.getAttribute('for')).toBe('true');
335 + });
336 +
337 + // this probably is just masking programmer error, but it is existing behavior.
338 + itRenders('className prop with false value', async render => {
339 + const e = await render(<div htmlFor={false} />);
340 + expect(e.getAttribute('for')).toBe('false');
341 + });
342 + });
343 +
344 + describe('props with special meaning in React', function() {
345 + itRenders('no ref attribute', async render => {
346 + class RefComponent extends React.Component {
347 + render() {
348 + return <div ref="foo" />;
349 + }
350 + }
351 + const e = await render(<RefComponent />);
352 + expect(e.getAttribute('ref')).toBe(null);
353 + });
354 +
355 + itRenders('no children attribute', async render => {
356 + const e = await render(React.createElement('div', {}, 'foo'));
357 + expect(e.getAttribute('children')).toBe(null);
358 + });
359 +
360 + itRenders('no key attribute', async render => {
361 + const e = await render(<div key="foo" />);
362 + expect(e.getAttribute('key')).toBe(null);
363 + });
364 +
365 + itRenders('no dangerouslySetInnerHTML attribute', async render => {
366 + const e = await render(
367 + <div dangerouslySetInnerHTML={{__html: 'foo'}} />,
368 + );
369 + expect(e.getAttribute('dangerouslySetInnerHTML')).toBe(null);
370 + });
371 + });
372 +
373 + describe('unknown attributes', function() {
374 + itRenders('no unknown attributes', async render => {
375 + const e = await render(<div foo="bar" />, 1);
376 + expect(e.getAttribute('foo')).toBe(null);
377 + });
378 +
379 + itRenders('unknown data- attributes', async render => {
380 + const e = await render(<div data-foo="bar" />);
381 + expect(e.getAttribute('data-foo')).toBe('bar');
382 + });
383 +
384 + itRenders(
385 + 'no unknown attributes for non-standard elements',
386 + async render => {
387 + const e = await render(<nonstandard foo="bar" />, 1);
388 + expect(e.getAttribute('foo')).toBe(null);
389 + },
390 + );
391 +
392 + itRenders('unknown attributes for custom elements', async render => {
393 + const e = await render(<custom-element foo="bar" />);
394 + expect(e.getAttribute('foo')).toBe('bar');
395 + });
396 +
397 + itRenders(
398 + 'unknown attributes for custom elements using is',
399 + async render => {
400 + const e = await render(<div is="custom-element" foo="bar" />);
401 + expect(e.getAttribute('foo')).toBe('bar');
402 + },
403 + );
404 + });
405 +
406 + itRenders('no HTML events', async render => {
407 + const e = await render(<div onClick={() => {}} />);
408 + expect(e.getAttribute('onClick')).toBe(null);
409 + expect(e.getAttribute('onClick')).toBe(null);
410 + expect(e.getAttribute('click')).toBe(null);
411 + });
412 + });
413 });