Skip to content

Commit 3788654

Browse files
aickinsophiebits
authored andcommitted
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!
1 parent d1ee199 commit 3788654

File tree

3 files changed

+186
-0
lines changed

3 files changed

+186
-0
lines changed

scripts/fiber/tests-passing-except-dev.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,12 @@ src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
134134
* should warn about class (ssr)
135135
* should suggest property name if available (ssr)
136136

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+
137143
src/renderers/dom/shared/__tests__/ReactMount-test.js
138144
* should warn if mounting into dirty rendered markup
139145
* should account for escaping on a checksum mismatch

scripts/fiber/tests-passing.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1116,6 +1116,20 @@ src/renderers/dom/shared/__tests__/ReactDOMSVG-test.js
11161116
* can render SVG into a non-React SVG tree
11171117
* can render HTML into a foreignObject in non-React SVG tree
11181118

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+
11191133
src/renderers/dom/shared/__tests__/ReactDOMTextComponent-test.js
11201134
* updates a mounted text component in place
11211135
* can be toggled in and out of the markup
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
});

0 commit comments

Comments
 (0)