mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
These tests are useful and don't test addons specifically. I moved them to appropriate places in the codebase.
This commit is contained in:
@@ -466,6 +466,8 @@ src/renderers/__tests__/ReactComponent-test.js
|
||||
* fires the callback after a component is rendered
|
||||
* throws usefully when rendering badly-typed elements
|
||||
* includes owner name in the error about badly-typed elements
|
||||
* throws if a plain object is used as a child
|
||||
* throws if a plain object even if it is in an owner
|
||||
|
||||
src/renderers/__tests__/ReactComponentLifeCycle-test.js
|
||||
* should not reuse an instance when it has been unmounted
|
||||
@@ -526,6 +528,8 @@ src/renderers/__tests__/ReactCompositeComponent-test.js
|
||||
* should warn when mutated props are passed
|
||||
* should only call componentWillUnmount once
|
||||
* prepares new child before unmounting old
|
||||
* respects a shallow shouldComponentUpdate implementation
|
||||
* does not do a deep comparison for a shallow shouldComponentUpdate implementation
|
||||
|
||||
src/renderers/__tests__/ReactCompositeComponentDOMMinimalism-test.js
|
||||
* should not render extra nodes for non-interpolated text
|
||||
@@ -1151,6 +1155,15 @@ src/renderers/dom/shared/__tests__/quoteAttributeValueForBrowser-test.js
|
||||
* should escape number to string
|
||||
* should escape string
|
||||
|
||||
src/renderers/dom/shared/__tests__/renderSubtreeIntoContainer-test.js
|
||||
* should pass context when rendering subtree elsewhere
|
||||
* should throw if parentComponent is invalid
|
||||
* should update context if it changes due to setState
|
||||
* should update context if it changes due to re-render
|
||||
* should render portal with non-context-provider parent
|
||||
* should get context through non-context-provider parent
|
||||
* should get context through middle non-context-provider layer
|
||||
|
||||
src/renderers/dom/shared/__tests__/validateDOMNesting-test.js
|
||||
* allows any tag with no context
|
||||
* allows valid nestings
|
||||
|
||||
@@ -380,4 +380,38 @@ describe('ReactComponent', () => {
|
||||
// One warning for each element creation
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
});
|
||||
|
||||
it('throws if a plain object is used as a child', () => {
|
||||
var children = {
|
||||
x: <span />,
|
||||
y: <span />,
|
||||
z: <span />,
|
||||
};
|
||||
var element = <div>{[children]}</div>;
|
||||
var container = document.createElement('div');
|
||||
expect(() => ReactDOM.render(element, container)).toThrowError(
|
||||
'Objects are not valid as a React child (found: object with keys ' +
|
||||
'{x, y, z}). If you meant to render a collection of children, use an ' +
|
||||
'array instead.',
|
||||
);
|
||||
});
|
||||
|
||||
it('throws if a plain object even if it is in an owner', () => {
|
||||
class Foo extends React.Component {
|
||||
render() {
|
||||
var children = {
|
||||
a: <span />,
|
||||
b: <span />,
|
||||
c: <span />,
|
||||
};
|
||||
return <div>{[children]}</div>;
|
||||
}
|
||||
}
|
||||
var container = document.createElement('div');
|
||||
expect(() => ReactDOM.render(<Foo />, container)).toThrowError(
|
||||
'Objects are not valid as a React child (found: object with keys ' +
|
||||
'{a, b, c}). If you meant to render a collection of children, use an ' +
|
||||
'array instead.\n\nCheck the render method of `Foo`.',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,6 +20,8 @@ var ReactDOMServer;
|
||||
var ReactCurrentOwner;
|
||||
var ReactPropTypes;
|
||||
var ReactTestUtils;
|
||||
var shallowEqual;
|
||||
var shallowCompare;
|
||||
|
||||
describe('ReactCompositeComponent', () => {
|
||||
beforeEach(() => {
|
||||
@@ -31,6 +33,12 @@ describe('ReactCompositeComponent', () => {
|
||||
ReactCurrentOwner = require('react/lib/ReactCurrentOwner');
|
||||
ReactPropTypes = require('ReactPropTypes');
|
||||
ReactTestUtils = require('ReactTestUtils');
|
||||
shallowEqual = require('fbjs/lib/shallowEqual');
|
||||
|
||||
shallowCompare = function(instance, nextProps, nextState) {
|
||||
return !shallowEqual(instance.props, nextProps) ||
|
||||
!shallowEqual(instance.state, nextState);
|
||||
};
|
||||
|
||||
MorphingComponent = class extends React.Component {
|
||||
state = {activated: false};
|
||||
@@ -1396,4 +1404,119 @@ describe('ReactCompositeComponent', () => {
|
||||
'B componentDidMount',
|
||||
]);
|
||||
});
|
||||
|
||||
it('respects a shallow shouldComponentUpdate implementation', () => {
|
||||
var renderCalls = 0;
|
||||
class PlasticWrap extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.state = {
|
||||
color: 'green',
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return <Apple color={this.state.color} ref="apple" />;
|
||||
}
|
||||
}
|
||||
|
||||
class Apple extends React.Component {
|
||||
state = {
|
||||
cut: false,
|
||||
slices: 1,
|
||||
};
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
return shallowCompare(this, nextProps, nextState);
|
||||
}
|
||||
|
||||
cut() {
|
||||
this.setState({
|
||||
cut: true,
|
||||
slices: 10,
|
||||
});
|
||||
}
|
||||
|
||||
eatSlice() {
|
||||
this.setState({
|
||||
slices: this.state.slices - 1,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
renderCalls++;
|
||||
return <div />;
|
||||
}
|
||||
}
|
||||
|
||||
var container = document.createElement('div');
|
||||
var instance = ReactDOM.render(<PlasticWrap />, container);
|
||||
expect(renderCalls).toBe(1);
|
||||
|
||||
// Do not re-render based on props
|
||||
instance.setState({color: 'green'});
|
||||
expect(renderCalls).toBe(1);
|
||||
|
||||
// Re-render based on props
|
||||
instance.setState({color: 'red'});
|
||||
expect(renderCalls).toBe(2);
|
||||
|
||||
// Re-render base on state
|
||||
instance.refs.apple.cut();
|
||||
expect(renderCalls).toBe(3);
|
||||
|
||||
// No re-render based on state
|
||||
instance.refs.apple.cut();
|
||||
expect(renderCalls).toBe(3);
|
||||
|
||||
// Re-render based on state again
|
||||
instance.refs.apple.eatSlice();
|
||||
expect(renderCalls).toBe(4);
|
||||
});
|
||||
|
||||
it('does not do a deep comparison for a shallow shouldComponentUpdate implementation', () => {
|
||||
function getInitialState() {
|
||||
return {
|
||||
foo: [1, 2, 3],
|
||||
bar: {a: 4, b: 5, c: 6},
|
||||
};
|
||||
}
|
||||
|
||||
var renderCalls = 0;
|
||||
var initialSettings = getInitialState();
|
||||
|
||||
class Component extends React.Component {
|
||||
state = initialSettings;
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
return shallowCompare(this, nextProps, nextState);
|
||||
}
|
||||
|
||||
render() {
|
||||
renderCalls++;
|
||||
return <div />;
|
||||
}
|
||||
}
|
||||
|
||||
var container = document.createElement('div');
|
||||
var instance = ReactDOM.render(<Component />, container);
|
||||
expect(renderCalls).toBe(1);
|
||||
|
||||
// Do not re-render if state is equal
|
||||
var settings = {
|
||||
foo: initialSettings.foo,
|
||||
bar: initialSettings.bar,
|
||||
};
|
||||
instance.setState(settings);
|
||||
expect(renderCalls).toBe(1);
|
||||
|
||||
// Re-render because one field changed
|
||||
initialSettings.foo = [1, 2, 3];
|
||||
instance.setState(initialSettings);
|
||||
expect(renderCalls).toBe(2);
|
||||
|
||||
// Re-render because the object changed
|
||||
instance.setState(getInitialState());
|
||||
expect(renderCalls).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,300 @@
|
||||
/**
|
||||
* 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';
|
||||
|
||||
var React = require('react');
|
||||
var ReactDOM = require('react-dom');
|
||||
var ReactTestUtils = require('ReactTestUtils');
|
||||
var renderSubtreeIntoContainer = require('renderSubtreeIntoContainer');
|
||||
|
||||
describe('renderSubtreeIntoContainer', () => {
|
||||
it('should pass context when rendering subtree elsewhere', () => {
|
||||
var portal = document.createElement('div');
|
||||
|
||||
class Component extends React.Component {
|
||||
static contextTypes = {
|
||||
foo: React.PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
render() {
|
||||
return <div>{this.context.foo}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
class Parent extends React.Component {
|
||||
static childContextTypes = {
|
||||
foo: React.PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
getChildContext() {
|
||||
return {
|
||||
foo: 'bar',
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
expect(
|
||||
function() {
|
||||
renderSubtreeIntoContainer(this, <Component />, portal);
|
||||
}.bind(this),
|
||||
).not.toThrow();
|
||||
}
|
||||
}
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<Parent />);
|
||||
expect(portal.firstChild.innerHTML).toBe('bar');
|
||||
});
|
||||
|
||||
it('should throw if parentComponent is invalid', () => {
|
||||
var portal = document.createElement('div');
|
||||
|
||||
class Component extends React.Component {
|
||||
static contextTypes = {
|
||||
foo: React.PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
render() {
|
||||
return <div>{this.context.foo}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
// ESLint is confused here and thinks Parent is unused, presumably because
|
||||
// it is only used inside of the class body?
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
class Parent extends React.Component {
|
||||
static childContextTypes = {
|
||||
foo: React.PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
getChildContext() {
|
||||
return {
|
||||
foo: 'bar',
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
expect(function() {
|
||||
renderSubtreeIntoContainer(<Parent />, <Component />, portal);
|
||||
}).toThrowError('parentComponentmust be a valid React Component');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('should update context if it changes due to setState', () => {
|
||||
var container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
var portal = document.createElement('div');
|
||||
|
||||
class Component extends React.Component {
|
||||
static contextTypes = {
|
||||
foo: React.PropTypes.string.isRequired,
|
||||
getFoo: React.PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
render() {
|
||||
return <div>{this.context.foo + '-' + this.context.getFoo()}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
class Parent extends React.Component {
|
||||
static childContextTypes = {
|
||||
foo: React.PropTypes.string.isRequired,
|
||||
getFoo: React.PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
bar: 'initial',
|
||||
};
|
||||
|
||||
getChildContext() {
|
||||
return {
|
||||
foo: this.state.bar,
|
||||
getFoo: () => this.state.bar,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
renderSubtreeIntoContainer(this, <Component />, portal);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
renderSubtreeIntoContainer(this, <Component />, portal);
|
||||
}
|
||||
}
|
||||
|
||||
var instance = ReactDOM.render(<Parent />, container);
|
||||
expect(portal.firstChild.innerHTML).toBe('initial-initial');
|
||||
instance.setState({bar: 'changed'});
|
||||
expect(portal.firstChild.innerHTML).toBe('changed-changed');
|
||||
});
|
||||
|
||||
it('should update context if it changes due to re-render', () => {
|
||||
var container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
var portal = document.createElement('div');
|
||||
|
||||
class Component extends React.Component {
|
||||
static contextTypes = {
|
||||
foo: React.PropTypes.string.isRequired,
|
||||
getFoo: React.PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
render() {
|
||||
return <div>{this.context.foo + '-' + this.context.getFoo()}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
class Parent extends React.Component {
|
||||
static childContextTypes = {
|
||||
foo: React.PropTypes.string.isRequired,
|
||||
getFoo: React.PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
getChildContext() {
|
||||
return {
|
||||
foo: this.props.bar,
|
||||
getFoo: () => this.props.bar,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
renderSubtreeIntoContainer(this, <Component />, portal);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
renderSubtreeIntoContainer(this, <Component />, portal);
|
||||
}
|
||||
}
|
||||
|
||||
ReactDOM.render(<Parent bar="initial" />, container);
|
||||
expect(portal.firstChild.innerHTML).toBe('initial-initial');
|
||||
ReactDOM.render(<Parent bar="changed" />, container);
|
||||
expect(portal.firstChild.innerHTML).toBe('changed-changed');
|
||||
});
|
||||
|
||||
it('should render portal with non-context-provider parent', () => {
|
||||
var container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
var portal = document.createElement('div');
|
||||
|
||||
class Parent extends React.Component {
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
renderSubtreeIntoContainer(this, <div>hello</div>, portal);
|
||||
}
|
||||
}
|
||||
|
||||
ReactDOM.render(<Parent bar="initial" />, container);
|
||||
expect(portal.firstChild.innerHTML).toBe('hello');
|
||||
});
|
||||
|
||||
it('should get context through non-context-provider parent', () => {
|
||||
var container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
var portal = document.createElement('div');
|
||||
|
||||
class Parent extends React.Component {
|
||||
render() {
|
||||
return <Middle />;
|
||||
}
|
||||
getChildContext() {
|
||||
return {value: this.props.value};
|
||||
}
|
||||
static childContextTypes = {
|
||||
value: React.PropTypes.string.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
class Middle extends React.Component {
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
componentDidMount() {
|
||||
renderSubtreeIntoContainer(this, <Child />, portal);
|
||||
}
|
||||
}
|
||||
|
||||
class Child extends React.Component {
|
||||
static contextTypes = {
|
||||
value: React.PropTypes.string.isRequired,
|
||||
};
|
||||
render() {
|
||||
return <div>{this.context.value}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
ReactDOM.render(<Parent value="foo" />, container);
|
||||
expect(portal.textContent).toBe('foo');
|
||||
});
|
||||
|
||||
it('should get context through middle non-context-provider layer', () => {
|
||||
var container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
var portal1 = document.createElement('div');
|
||||
var portal2 = document.createElement('div');
|
||||
|
||||
class Parent extends React.Component {
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
getChildContext() {
|
||||
return {value: this.props.value};
|
||||
}
|
||||
componentDidMount() {
|
||||
renderSubtreeIntoContainer(this, <Middle />, portal1);
|
||||
}
|
||||
static childContextTypes = {
|
||||
value: React.PropTypes.string.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
class Middle extends React.Component {
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
componentDidMount() {
|
||||
renderSubtreeIntoContainer(this, <Child />, portal2);
|
||||
}
|
||||
}
|
||||
|
||||
class Child extends React.Component {
|
||||
static contextTypes = {
|
||||
value: React.PropTypes.string.isRequired,
|
||||
};
|
||||
render() {
|
||||
return <div>{this.context.value}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
ReactDOM.render(<Parent value="foo" />, container);
|
||||
expect(portal2.textContent).toBe('foo');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user