Adding some server rendering unit tests. (#9055)
* 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!
Sasha Aickin committed
Mar 1, 2017 at 13:39 UTC
3788654d57933461961a0296afd924493129e51d
3 files changed
+186
scripts/fiber/tests-passing-except-dev.txt
+6
@@ -134,6 +134,12 @@ src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
134
* should warn about class (ssr)
135
* should suggest property name if available (ssr)
136
137
+src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js
138
+* renders a blank div with client render on top of bad server markup
139
+* renders a div with inline styles with client render on top of bad server markup
140
+* renders a self-closing tag with client render on top of bad server markup
141
+* renders a self-closing tag as a child with client render on top of bad server markup
142
+
143
src/renderers/dom/shared/__tests__/ReactMount-test.js
144
* should warn if mounting into dirty rendered markup
145
* should account for escaping on a checksum mismatch
scripts/fiber/tests-passing.txt
+14
@@ -1116,6 +1116,20 @@ src/renderers/dom/shared/__tests__/ReactDOMSVG-test.js
1116
* can render SVG into a non-React SVG tree
1117
* can render HTML into a foreignObject in non-React SVG tree
1118
1119
+src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js
1120
+* renders a blank div with server string render
1121
+* renders a blank div with clean client render
1122
+* renders a blank div with client render on top of good server markup
1123
+* renders a div with inline styles with server string render
1124
+* renders a div with inline styles with clean client render
1125
+* renders a div with inline styles with client render on top of good server markup
1126
+* renders a self-closing tag with server string render
1127
+* renders a self-closing tag with clean client render
1128
+* renders a self-closing tag with client render on top of good server markup
1129
+* renders a self-closing tag as a child with server string render
1130
+* renders a self-closing tag as a child with clean client render
1131
+* renders a self-closing tag as a child with client render on top of good server markup
1132
+
1133
src/renderers/dom/shared/__tests__/ReactDOMTextComponent-test.js
1134
* updates a mounted text component in place
1135
* can be toggled in and out of the markup
src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js
new
+166
@@ -0,0 +1,166 @@
1
+/**
2
+ * Copyright 2013-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @emails react-core
10
+ */
11
+
12
+'use strict';
13
+
14
+let ExecutionEnvironment;
15
+let React;
16
+let ReactDOM;
17
+let ReactDOMServer;
18
+
19
+// Helper functions for rendering tests
20
+// ====================================
21
+
22
+// performs fn asynchronously and expects count errors logged to console.error.
23
+// will fail the test if the count of errors logged is not equal to count.
24
+function expectErrors(fn, count) {
25
+ if (console.error.calls && console.error.calls.reset) {
26
+ console.error.calls.reset();
27
+ } else {
28
+ spyOn(console, 'error');
29
+ }
30
+
31
+ return fn().then((result) => {
32
+ if (console.error.calls.count() !== count) {
33
+ console.log(`We expected ${count} warning(s), but saw ${console.error.calls.count()} warning(s).`);
34
+ if (console.error.calls.count() > 0) {
35
+ console.log(`We saw these warnings:`);
36
+ for (var i = 0; i < console.error.calls.count(); i++) {
37
+ console.log(console.error.calls.argsFor(i)[0]);
38
+ }
39
+ }
40
+ }
41
+ expectDev(console.error.calls.count()).toBe(count);
42
+ return result;
43
+ });
44
+}
45
+
46
+// renders the reactElement into domElement, and expects a certain number of errors.
47
+// returns a Promise that resolves when the render is complete.
48
+function renderIntoDom(reactElement, domElement, errorCount = 0) {
49
+ return expectErrors(
50
+ () => new Promise((resolve) => {
51
+ ExecutionEnvironment.canUseDOM = true;
52
+ ReactDOM.render(reactElement, domElement, () => {
53
+ ExecutionEnvironment.canUseDOM = false;
54
+ resolve(domElement.firstChild);
55
+ });
56
+ }),
57
+ errorCount
58
+ );
59
+}
60
+
61
+// Renders text using SSR and then stuffs it into a DOM node; returns the DOM
62
+// element that corresponds with the reactElement.
63
+// Does not render on client or perform client-side revival.
64
+function serverRender(reactElement, errorCount = 0) {
65
+ return expectErrors(
66
+ () => Promise.resolve(ReactDOMServer.renderToString(reactElement)),
67
+ errorCount)
68
+ .then((markup) => {
69
+ var domElement = document.createElement('div');
70
+ domElement.innerHTML = markup;
71
+ return domElement.firstChild;
72
+ });
73
+}
74
+
75
+const clientCleanRender = (element, errorCount = 0) => {
76
+ const div = document.createElement('div');
77
+ return renderIntoDom(element, div, errorCount);
78
+};
79
+
80
+const clientRenderOnServerString = (element, errorCount = 0) => {
81
+ return serverRender(element, errorCount).then((markup) => {
82
+ var domElement = document.createElement('div');
83
+ domElement.innerHTML = markup;
84
+ return renderIntoDom(element, domElement, errorCount);
85
+ });
86
+};
87
+
88
+const clientRenderOnBadMarkup = (element, errorCount = 0) => {
89
+ var domElement = document.createElement('div');
90
+ domElement.innerHTML = '<div id="badIdWhichWillCauseMismatch" data-reactroot="" data-reactid="1"></div>';
91
+ return renderIntoDom(element, domElement, errorCount + 1);
92
+};
93
+
94
+// runs a DOM rendering test as four different tests, with four different rendering
95
+// scenarios:
96
+// -- render to string on server
97
+// -- render on client without any server markup "clean client render"
98
+// -- render on client on top of good server-generated string markup
99
+// -- render on client on top of bad server-generated markup
100
+//
101
+// testFn is a test that has one arg, which is a render function. the render
102
+// function takes in a ReactElement and an optional expected error count and
103
+// returns a promise of a DOM Element.
104
+//
105
+// You should only perform tests that examine the DOM of the results of
106
+// render; you should not depend on the interactivity of the returned DOM element,
107
+// as that will not work in the server string scenario.
108
+function itRenders(desc, testFn) {
109
+ it(`renders ${desc} with server string render`,
110
+ () => testFn(serverRender));
111
+ itClientRenders(desc, testFn);
112
+}
113
+
114
+// run testFn in three different rendering scenarios:
115
+// -- render on client without any server markup "clean client render"
116
+// -- render on client on top of good server-generated string markup
117
+// -- render on client on top of bad server-generated markup
118
+//
119
+// testFn is a test that has one arg, which is a render function. the render
120
+// function takes in a ReactElement and an optional expected error count and
121
+// returns a promise of a DOM Element.
122
+//
123
+// Since all of the renders in this function are on the client, you can test interactivity,
124
+// unlike with itRenders.
125
+function itClientRenders(desc, testFn) {
126
+ it(`renders ${desc} with clean client render`,
127
+ () => testFn(clientCleanRender));
128
+ it(`renders ${desc} with client render on top of good server markup`,
129
+ () => testFn(clientRenderOnServerString));
130
+ it(`renders ${desc} with client render on top of bad server markup`,
131
+ () => testFn(clientRenderOnBadMarkup));
132
+}
133
+
134
+describe('ReactDOMServerIntegration', () => {
135
+ beforeEach(() => {
136
+ jest.resetModuleRegistry();
137
+ React = require('React');
138
+ ReactDOM = require('ReactDOM');
139
+ ReactDOMServer = require('ReactDOMServer');
140
+
141
+ ExecutionEnvironment = require('ExecutionEnvironment');
142
+ ExecutionEnvironment.canUseDOM = false;
143
+ });
144
+
145
+ describe('basic rendering', function() {
146
+ itRenders('a blank div', render =>
147
+ render(<div />).then(e => expect(e.tagName).toBe('DIV')));
148
+
149
+ itRenders('a div with inline styles', render =>
150
+ render(<div style={{color:'red', width:'30px'}} />).then(e => {
151
+ expect(e.style.color).toBe('red');
152
+ expect(e.style.width).toBe('30px');
153
+ })
154
+ );
155
+
156
+ itRenders('a self-closing tag', render =>
157
+ render(<br />).then(e => expect(e.tagName).toBe('BR')));
158
+
159
+ itRenders('a self-closing tag as a child', render =>
160
+ render(<div><br /></div>).then(e => {
161
+ expect(e.childNodes.length).toBe(1);
162
+ expect(e.firstChild.tagName).toBe('BR');
163
+ })
164
+ );
165
+ });
166
+});