Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding some server rendering unit tests. #9055

Merged
merged 7 commits into from
Mar 1, 2017
6 changes: 6 additions & 0 deletions scripts/fiber/tests-passing-except-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,12 @@ src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
* should warn about class (ssr)
* should suggest property name if available (ssr)

src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js
* renders a blank div with client render on top of bad server markup
* renders a div with inline styles with client render on top of bad server markup
* renders a self-closing tag with client render on top of bad server markup
* renders a self-closing tag as a child with client render on top of bad server markup

src/renderers/dom/shared/__tests__/ReactMount-test.js
* should warn if mounting into dirty rendered markup
* should account for escaping on a checksum mismatch
Expand Down
14 changes: 14 additions & 0 deletions scripts/fiber/tests-passing.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1109,6 +1109,20 @@ src/renderers/dom/shared/__tests__/ReactDOMSVG-test.js
* can render SVG into a non-React SVG tree
* can render HTML into a foreignObject in non-React SVG tree

src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js
* renders a blank div with server string render
* renders a blank div with clean client render
* renders a blank div with client render on top of good server markup
* renders a div with inline styles with server string render
* renders a div with inline styles with clean client render
* renders a div with inline styles with client render on top of good server markup
* renders a self-closing tag with server string render
* renders a self-closing tag with clean client render
* renders a self-closing tag with client render on top of good server markup
* renders a self-closing tag as a child with server string render
* renders a self-closing tag as a child with clean client render
* renders a self-closing tag as a child with client render on top of good server markup

src/renderers/dom/shared/__tests__/ReactDOMTextComponent-test.js
* updates a mounted text component in place
* can be toggled in and out of the markup
Expand Down
166 changes: 166 additions & 0 deletions src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/**
* Copyright 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @emails react-core
*/

'use strict';

let ExecutionEnvironment;
let React;
let ReactDOM;
let ReactDOMServer;

// Helper functions for rendering tests
// ====================================

// performs fn asynchronously and expects count errors logged to console.error.
// will fail the test if the count of errors logged is not equal to count.
function expectErrors(fn, count) {
if (console.error.calls && console.error.calls.reset) {
console.error.calls.reset();
} else {
spyOn(console, 'error');
}

return fn().then((result) => {
if (console.error.calls.count() !== count) {
console.log(`We expected ${count} warning(s), but saw ${console.error.calls.count()} warning(s).`);
if (console.error.calls.count() > 0) {
console.log(`We saw these warnings:`);
for (var i = 0; i < console.error.calls.count(); i++) {
console.log(console.error.calls.argsFor(i)[0]);
}
}
}
expectDev(console.error.calls.count()).toBe(count);
return result;
});
}

// renders the reactElement into domElement, and expects a certain number of errors.
// returns a Promise that resolves when the render is complete.
function renderIntoDom(reactElement, domElement, errorCount = 0) {
return expectErrors(
() => new Promise((resolve) => {
ExecutionEnvironment.canUseDOM = true;
ReactDOM.render(reactElement, domElement, () => {
ExecutionEnvironment.canUseDOM = false;
resolve(domElement.firstChild);
});
}),
errorCount
);
}

// Renders text using SSR and then stuffs it into a DOM node; returns the DOM
// element that corresponds with the reactElement.
// Does not render on client or perform client-side revival.
function serverRender(reactElement, errorCount = 0) {
return expectErrors(
() => Promise.resolve(ReactDOMServer.renderToString(reactElement)),
errorCount)
.then((markup) => {
var domElement = document.createElement('div');
domElement.innerHTML = markup;
return domElement.firstChild;
});
}

const clientCleanRender = (element, errorCount = 0) => {
const div = document.createElement('div');
return renderIntoDom(element, div, errorCount);
};

const clientRenderOnServerString = (element, errorCount = 0) => {
return serverRender(element, errorCount).then((markup) => {
var domElement = document.createElement('div');
domElement.innerHTML = markup;
return renderIntoDom(element, domElement, errorCount);
});
};

const clientRenderOnBadMarkup = (element, errorCount = 0) => {
var domElement = document.createElement('div');
domElement.innerHTML = '<div id="badIdWhichWillCauseMismatch" data-reactroot="" data-reactid="1"></div>';
return renderIntoDom(element, domElement, errorCount + 1);
};

// runs a DOM rendering test as four different tests, with four different rendering
// scenarios:
// -- render to string on server
// -- render on client without any server markup "clean client render"
// -- render on client on top of good server-generated string markup
// -- render on client on top of bad server-generated markup
//
// testFn is a test that has one arg, which is a render function. the render
// function takes in a ReactElement and an optional expected error count and
// returns a promise of a DOM Element.
//
// You should only perform tests that examine the DOM of the results of
// render; you should not depend on the interactivity of the returned DOM element,
// as that will not work in the server string scenario.
function itRenders(desc, testFn) {
it(`renders ${desc} with server string render`,
() => testFn(serverRender));
itClientRenders(desc, testFn);
}

// run testFn in three different rendering scenarios:
// -- render on client without any server markup "clean client render"
// -- render on client on top of good server-generated string markup
// -- render on client on top of bad server-generated markup
//
// testFn is a test that has one arg, which is a render function. the render
// function takes in a ReactElement and an optional expected error count and
// returns a promise of a DOM Element.
//
// Since all of the renders in this function are on the client, you can test interactivity,
// unlike with itRenders.
function itClientRenders(desc, testFn) {
it(`renders ${desc} with clean client render`,
() => testFn(clientCleanRender));
it(`renders ${desc} with client render on top of good server markup`,
() => testFn(clientRenderOnServerString));
it(`renders ${desc} with client render on top of bad server markup`,
() => testFn(clientRenderOnBadMarkup));
}

describe('ReactDOMServerIntegration', () => {
beforeEach(() => {
jest.resetModuleRegistry();
React = require('React');
ReactDOM = require('ReactDOM');
ReactDOMServer = require('ReactDOMServer');

ExecutionEnvironment = require('ExecutionEnvironment');
ExecutionEnvironment.canUseDOM = false;
});

describe('basic rendering', function() {
itRenders('a blank div', render =>
render(<div />).then(e => expect(e.tagName).toBe('DIV')));

itRenders('a div with inline styles', render =>
render(<div style={{color:'red', width:'30px'}} />).then(e => {
expect(e.style.color).toBe('red');
expect(e.style.width).toBe('30px');
})
);

itRenders('a self-closing tag', render =>
render(<br />).then(e => expect(e.tagName).toBe('BR')));

itRenders('a self-closing tag as a child', render =>
render(<div><br /></div>).then(e => {
expect(e.childNodes.length).toBe(1);
expect(e.firstChild.tagName).toBe('BR');
})
);
});
});