Run some tests in Node environment (#11948)

* Run some tests in Node environment

* Separate SSR tests that require DOM

This allow us to run others with Node environment.
This commit is contained in:
Dan Abramov
2018-01-02 18:42:18 +00:00
committed by GitHub
parent f2021d6c68
commit 4fbcd266ec
28 changed files with 306 additions and 255 deletions
@@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
* @jest-environment node
*/
'use strict';
+1 -254
View File
@@ -5,15 +5,14 @@
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
* @jest-environment node
*/
'use strict';
let React;
let ReactCallReturn;
let ReactDOM;
let ReactDOMServer;
let ReactTestUtils;
let PropTypes;
function normalizeCodeLocInfo(str) {
@@ -25,8 +24,6 @@ describe('ReactDOMServer', () => {
jest.resetModules();
React = require('react');
ReactCallReturn = require('react-call-return');
ReactDOM = require('react-dom');
ReactTestUtils = require('react-dom/test-utils');
PropTypes = require('prop-types');
ReactDOMServer = require('react-dom/server');
});
@@ -157,243 +154,6 @@ describe('ReactDOMServer', () => {
runTest();
});
it('should have the correct mounting behavior (old hydrate API)', () => {
spyOnDev(console, 'warn');
spyOnDev(console, 'error');
let mountCount = 0;
let numClicks = 0;
class TestComponent extends React.Component {
componentDidMount() {
mountCount++;
}
click = () => {
numClicks++;
};
render() {
return (
<span ref="span" onClick={this.click}>
Name: {this.props.name}
</span>
);
}
}
const element = document.createElement('div');
ReactDOM.render(<TestComponent />, element);
let lastMarkup = element.innerHTML;
// Exercise the update path. Markup should not change,
// but some lifecycle methods should be run again.
ReactDOM.render(<TestComponent name="x" />, element);
expect(mountCount).toEqual(1);
// Unmount and remount. We should get another mount event and
// we should get different markup, as the IDs are unique each time.
ReactDOM.unmountComponentAtNode(element);
expect(element.innerHTML).toEqual('');
ReactDOM.render(<TestComponent name="x" />, element);
expect(mountCount).toEqual(2);
expect(element.innerHTML).not.toEqual(lastMarkup);
// Now kill the node and render it on top of server-rendered markup, as if
// we used server rendering. We should mount again, but the markup should
// be unchanged. We will append a sentinel at the end of innerHTML to be
// sure that innerHTML was not changed.
ReactDOM.unmountComponentAtNode(element);
expect(element.innerHTML).toEqual('');
lastMarkup = ReactDOMServer.renderToString(<TestComponent name="x" />);
element.innerHTML = lastMarkup;
let instance = ReactDOM.render(<TestComponent name="x" />, element);
expect(mountCount).toEqual(3);
if (__DEV__) {
expect(console.warn.calls.count()).toBe(1);
expect(console.warn.calls.argsFor(0)[0]).toContain(
'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
'will stop working in React v17. Replace the ReactDOM.render() call ' +
'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
);
console.warn.calls.reset();
}
expect(element.innerHTML).toBe(lastMarkup);
// Ensure the events system works after mount into server markup
expect(numClicks).toEqual(0);
ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(instance.refs.span));
expect(numClicks).toEqual(1);
ReactDOM.unmountComponentAtNode(element);
expect(element.innerHTML).toEqual('');
// Now simulate a situation where the app is not idempotent. React should
// warn but do the right thing.
element.innerHTML = lastMarkup;
instance = ReactDOM.render(<TestComponent name="y" />, element);
expect(mountCount).toEqual(4);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toContain(
'Text content did not match. Server: "x" Client: "y"',
);
console.error.calls.reset();
}
expect(element.innerHTML.length > 0).toBe(true);
expect(element.innerHTML).not.toEqual(lastMarkup);
// Ensure the events system works after markup mismatch.
expect(numClicks).toEqual(1);
ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(instance.refs.span));
expect(numClicks).toEqual(2);
if (__DEV__) {
expect(console.warn.calls.count()).toBe(0);
expect(console.error.calls.count()).toBe(0);
}
});
it('should have the correct mounting behavior (new hydrate API)', () => {
spyOnDev(console, 'error');
let mountCount = 0;
let numClicks = 0;
class TestComponent extends React.Component {
componentDidMount() {
mountCount++;
}
click = () => {
numClicks++;
};
render() {
return (
<span ref="span" onClick={this.click}>
Name: {this.props.name}
</span>
);
}
}
const element = document.createElement('div');
ReactDOM.render(<TestComponent />, element);
let lastMarkup = element.innerHTML;
// Exercise the update path. Markup should not change,
// but some lifecycle methods should be run again.
ReactDOM.render(<TestComponent name="x" />, element);
expect(mountCount).toEqual(1);
// Unmount and remount. We should get another mount event and
// we should get different markup, as the IDs are unique each time.
ReactDOM.unmountComponentAtNode(element);
expect(element.innerHTML).toEqual('');
ReactDOM.render(<TestComponent name="x" />, element);
expect(mountCount).toEqual(2);
expect(element.innerHTML).not.toEqual(lastMarkup);
// Now kill the node and render it on top of server-rendered markup, as if
// we used server rendering. We should mount again, but the markup should
// be unchanged. We will append a sentinel at the end of innerHTML to be
// sure that innerHTML was not changed.
ReactDOM.unmountComponentAtNode(element);
expect(element.innerHTML).toEqual('');
lastMarkup = ReactDOMServer.renderToString(<TestComponent name="x" />);
element.innerHTML = lastMarkup;
let instance = ReactDOM.hydrate(<TestComponent name="x" />, element);
expect(mountCount).toEqual(3);
expect(element.innerHTML).toBe(lastMarkup);
// Ensure the events system works after mount into server markup
expect(numClicks).toEqual(0);
ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(instance.refs.span));
expect(numClicks).toEqual(1);
ReactDOM.unmountComponentAtNode(element);
expect(element.innerHTML).toEqual('');
// Now simulate a situation where the app is not idempotent. React should
// warn but do the right thing.
element.innerHTML = lastMarkup;
instance = ReactDOM.hydrate(<TestComponent name="y" />, element);
expect(mountCount).toEqual(4);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
}
expect(element.innerHTML.length > 0).toBe(true);
expect(element.innerHTML).not.toEqual(lastMarkup);
// Ensure the events system works after markup mismatch.
expect(numClicks).toEqual(1);
ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(instance.refs.span));
expect(numClicks).toEqual(2);
});
// We have a polyfill for autoFocus on the client, but we intentionally don't
// want it to call focus() when hydrating because this can mess up existing
// focus before the JS has loaded.
it('should emit autofocus on the server but not focus() when hydrating', () => {
const element = document.createElement('div');
element.innerHTML = ReactDOMServer.renderToString(
<input autoFocus={true} />,
);
expect(element.firstChild.autofocus).toBe(true);
// It should not be called on mount.
element.firstChild.focus = jest.fn();
ReactDOM.hydrate(<input autoFocus={true} />, element);
expect(element.firstChild.focus).not.toHaveBeenCalled();
// Or during an update.
ReactDOM.render(<input autoFocus={true} />, element);
expect(element.firstChild.focus).not.toHaveBeenCalled();
});
it('should not focus on either server or client with autofocus={false}', () => {
const element = document.createElement('div');
element.innerHTML = ReactDOMServer.renderToString(
<input autoFocus={false} />,
);
expect(element.firstChild.autofocus).toBe(false);
element.firstChild.focus = jest.fn();
ReactDOM.hydrate(<input autoFocus={false} />, element);
expect(element.firstChild.focus).not.toHaveBeenCalled();
ReactDOM.render(<input autoFocus={false} />, element);
expect(element.firstChild.focus).not.toHaveBeenCalled();
});
// Regression test for https://github.com/facebook/react/issues/11726
it('should not focus on either server or client with autofocus={false} even if there is a markup mismatch', () => {
spyOnDev(console, 'error');
const element = document.createElement('div');
element.innerHTML = ReactDOMServer.renderToString(
<button autoFocus={false}>server</button>,
);
expect(element.firstChild.autofocus).toBe(false);
element.firstChild.focus = jest.fn();
ReactDOM.hydrate(<button autoFocus={false}>client</button>, element);
expect(element.firstChild.focus).not.toHaveBeenCalled();
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toBe(
'Warning: Text content did not match. Server: "server" Client: "client"',
);
}
});
it('should throw with silly args', () => {
expect(
ReactDOMServer.renderToString.bind(ReactDOMServer, {x: 123}),
@@ -797,20 +557,7 @@ describe('ReactDOMServer', () => {
}
});
it('should throw rendering portals on the server', () => {
const div = document.createElement('div');
expect(() => {
ReactDOMServer.renderToString(
<div>{ReactDOM.createPortal(<div />, div)}</div>,
);
}).toThrow(
'Portals are not currently supported by the server renderer. ' +
'Render them conditionally so that they only appear on the client render.',
);
});
it('should throw rendering call/return on the server', () => {
const div = document.createElement('div');
expect(() => {
ReactDOMServer.renderToString(
<div>{ReactCallReturn.unstable_createReturn(42)}</div>,
@@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
* @jest-environment node
*/
'use strict';
@@ -0,0 +1,276 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
*/
'use strict';
let React;
let ReactDOM;
let ReactDOMServer;
let ReactTestUtils;
// These tests rely both on ReactDOMServer and ReactDOM.
// If a test only needs ReactDOMServer, put it in ReactServerRendering-test instead.
describe('ReactDOMServerHydration', () => {
beforeEach(() => {
jest.resetModules();
React = require('react');
ReactDOM = require('react-dom');
ReactTestUtils = require('react-dom/test-utils');
ReactDOMServer = require('react-dom/server');
});
it('should have the correct mounting behavior (old hydrate API)', () => {
spyOnDev(console, 'warn');
spyOnDev(console, 'error');
let mountCount = 0;
let numClicks = 0;
class TestComponent extends React.Component {
componentDidMount() {
mountCount++;
}
click = () => {
numClicks++;
};
render() {
return (
<span ref="span" onClick={this.click}>
Name: {this.props.name}
</span>
);
}
}
const element = document.createElement('div');
ReactDOM.render(<TestComponent />, element);
let lastMarkup = element.innerHTML;
// Exercise the update path. Markup should not change,
// but some lifecycle methods should be run again.
ReactDOM.render(<TestComponent name="x" />, element);
expect(mountCount).toEqual(1);
// Unmount and remount. We should get another mount event and
// we should get different markup, as the IDs are unique each time.
ReactDOM.unmountComponentAtNode(element);
expect(element.innerHTML).toEqual('');
ReactDOM.render(<TestComponent name="x" />, element);
expect(mountCount).toEqual(2);
expect(element.innerHTML).not.toEqual(lastMarkup);
// Now kill the node and render it on top of server-rendered markup, as if
// we used server rendering. We should mount again, but the markup should
// be unchanged. We will append a sentinel at the end of innerHTML to be
// sure that innerHTML was not changed.
ReactDOM.unmountComponentAtNode(element);
expect(element.innerHTML).toEqual('');
lastMarkup = ReactDOMServer.renderToString(<TestComponent name="x" />);
element.innerHTML = lastMarkup;
let instance = ReactDOM.render(<TestComponent name="x" />, element);
expect(mountCount).toEqual(3);
if (__DEV__) {
expect(console.warn.calls.count()).toBe(1);
expect(console.warn.calls.argsFor(0)[0]).toContain(
'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
'will stop working in React v17. Replace the ReactDOM.render() call ' +
'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
);
console.warn.calls.reset();
}
expect(element.innerHTML).toBe(lastMarkup);
// Ensure the events system works after mount into server markup
expect(numClicks).toEqual(0);
ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(instance.refs.span));
expect(numClicks).toEqual(1);
ReactDOM.unmountComponentAtNode(element);
expect(element.innerHTML).toEqual('');
// Now simulate a situation where the app is not idempotent. React should
// warn but do the right thing.
element.innerHTML = lastMarkup;
instance = ReactDOM.render(<TestComponent name="y" />, element);
expect(mountCount).toEqual(4);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toContain(
'Text content did not match. Server: "x" Client: "y"',
);
console.error.calls.reset();
}
expect(element.innerHTML.length > 0).toBe(true);
expect(element.innerHTML).not.toEqual(lastMarkup);
// Ensure the events system works after markup mismatch.
expect(numClicks).toEqual(1);
ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(instance.refs.span));
expect(numClicks).toEqual(2);
if (__DEV__) {
expect(console.warn.calls.count()).toBe(0);
expect(console.error.calls.count()).toBe(0);
}
});
it('should have the correct mounting behavior (new hydrate API)', () => {
spyOnDev(console, 'error');
let mountCount = 0;
let numClicks = 0;
class TestComponent extends React.Component {
componentDidMount() {
mountCount++;
}
click = () => {
numClicks++;
};
render() {
return (
<span ref="span" onClick={this.click}>
Name: {this.props.name}
</span>
);
}
}
const element = document.createElement('div');
ReactDOM.render(<TestComponent />, element);
let lastMarkup = element.innerHTML;
// Exercise the update path. Markup should not change,
// but some lifecycle methods should be run again.
ReactDOM.render(<TestComponent name="x" />, element);
expect(mountCount).toEqual(1);
// Unmount and remount. We should get another mount event and
// we should get different markup, as the IDs are unique each time.
ReactDOM.unmountComponentAtNode(element);
expect(element.innerHTML).toEqual('');
ReactDOM.render(<TestComponent name="x" />, element);
expect(mountCount).toEqual(2);
expect(element.innerHTML).not.toEqual(lastMarkup);
// Now kill the node and render it on top of server-rendered markup, as if
// we used server rendering. We should mount again, but the markup should
// be unchanged. We will append a sentinel at the end of innerHTML to be
// sure that innerHTML was not changed.
ReactDOM.unmountComponentAtNode(element);
expect(element.innerHTML).toEqual('');
lastMarkup = ReactDOMServer.renderToString(<TestComponent name="x" />);
element.innerHTML = lastMarkup;
let instance = ReactDOM.hydrate(<TestComponent name="x" />, element);
expect(mountCount).toEqual(3);
expect(element.innerHTML).toBe(lastMarkup);
// Ensure the events system works after mount into server markup
expect(numClicks).toEqual(0);
ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(instance.refs.span));
expect(numClicks).toEqual(1);
ReactDOM.unmountComponentAtNode(element);
expect(element.innerHTML).toEqual('');
// Now simulate a situation where the app is not idempotent. React should
// warn but do the right thing.
element.innerHTML = lastMarkup;
instance = ReactDOM.hydrate(<TestComponent name="y" />, element);
expect(mountCount).toEqual(4);
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
}
expect(element.innerHTML.length > 0).toBe(true);
expect(element.innerHTML).not.toEqual(lastMarkup);
// Ensure the events system works after markup mismatch.
expect(numClicks).toEqual(1);
ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(instance.refs.span));
expect(numClicks).toEqual(2);
});
// We have a polyfill for autoFocus on the client, but we intentionally don't
// want it to call focus() when hydrating because this can mess up existing
// focus before the JS has loaded.
it('should emit autofocus on the server but not focus() when hydrating', () => {
const element = document.createElement('div');
element.innerHTML = ReactDOMServer.renderToString(
<input autoFocus={true} />,
);
expect(element.firstChild.autofocus).toBe(true);
// It should not be called on mount.
element.firstChild.focus = jest.fn();
ReactDOM.hydrate(<input autoFocus={true} />, element);
expect(element.firstChild.focus).not.toHaveBeenCalled();
// Or during an update.
ReactDOM.render(<input autoFocus={true} />, element);
expect(element.firstChild.focus).not.toHaveBeenCalled();
});
it('should not focus on either server or client with autofocus={false}', () => {
const element = document.createElement('div');
element.innerHTML = ReactDOMServer.renderToString(
<input autoFocus={false} />,
);
expect(element.firstChild.autofocus).toBe(false);
element.firstChild.focus = jest.fn();
ReactDOM.hydrate(<input autoFocus={false} />, element);
expect(element.firstChild.focus).not.toHaveBeenCalled();
ReactDOM.render(<input autoFocus={false} />, element);
expect(element.firstChild.focus).not.toHaveBeenCalled();
});
// Regression test for https://github.com/facebook/react/issues/11726
it('should not focus on either server or client with autofocus={false} even if there is a markup mismatch', () => {
spyOnDev(console, 'error');
const element = document.createElement('div');
element.innerHTML = ReactDOMServer.renderToString(
<button autoFocus={false}>server</button>,
);
expect(element.firstChild.autofocus).toBe(false);
element.firstChild.focus = jest.fn();
ReactDOM.hydrate(<button autoFocus={false}>client</button>, element);
expect(element.firstChild.focus).not.toHaveBeenCalled();
if (__DEV__) {
expect(console.error.calls.count()).toBe(1);
expect(console.error.calls.argsFor(0)[0]).toBe(
'Warning: Text content did not match. Server: "server" Client: "client"',
);
}
});
it('should throw rendering portals on the server', () => {
const div = document.createElement('div');
expect(() => {
ReactDOMServer.renderToString(
<div>{ReactDOM.createPortal(<div />, div)}</div>,
);
}).toThrow(
'Portals are not currently supported by the server renderer. ' +
'Render them conditionally so that they only appear on the client render.',
);
});
});
@@ -4,6 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @jest-environment node
*/
'use strict';
@@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
* @jest-environment node
*/
'use strict';
@@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
* @jest-environment node
*/
'use strict';
@@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
* @jest-environment node
*/
'use strict';
@@ -3,6 +3,8 @@
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @jest-environment node
*/
'use strict';
@@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
* @jest-environment node
*/
'use strict';
@@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
* @jest-environment node
*/
'use strict';
@@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
* @jest-environment node
*/
'use strict';
@@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
* @jest-environment node
*/
'use strict';
@@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
* @jest-environment node
*/
'use strict';
@@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
* @jest-environment node
*/
'use strict';
@@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
* @jest-environment node
*/
'use strict';
@@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
* @jest-environment node
*/
'use strict';
@@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
* @jest-environment node
*/
'use strict';
@@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
* @jest-environment node
*/
'use strict';
@@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
* @jest-environment node
*/
'use strict';
@@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
* @jest-environment node
*/
'use strict';
@@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
* @jest-environment node
*/
'use strict';
@@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
* @jest-environment node
*/
'use strict';
@@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
* @jest-environment node
*/
'use strict';
@@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
* @jest-environment node
*/
'use strict';
@@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
* @jest-environment node
*/
'use strict';
@@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
* @jest-environment node
*/
'use strict';
+3 -1
View File
@@ -31,4 +31,6 @@ global.cancelIdleCallback = function(callbackID) {
// for the few that specifically test the logging by shadowing this
// property. In real apps, it would usually not be defined at all.
Error.prototype.suppressReactErrorLogging = true;
DOMException.prototype.suppressReactErrorLogging = true;
if (typeof DOMException === 'function') {
DOMException.prototype.suppressReactErrorLogging = true;
}