2013-05-30 03:46:11 +08:00
|
|
|
/**
|
2014-02-19 09:06:43 +08:00
|
|
|
* Copyright 2013-2014 Facebook, Inc.
|
2013-05-30 03:46:11 +08:00
|
|
|
*
|
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
|
*
|
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
*
|
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
|
* limitations under the License.
|
|
|
|
|
*
|
|
|
|
|
* @jsx React.DOM
|
|
|
|
|
* @emails react-core
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
"use strict";
|
|
|
|
|
|
|
|
|
|
var MorphingComponent;
|
|
|
|
|
var ChildUpdates;
|
|
|
|
|
var React;
|
2014-01-03 07:36:58 +08:00
|
|
|
var ReactComponent;
|
2013-06-05 03:52:30 +08:00
|
|
|
var ReactCurrentOwner;
|
2013-07-11 05:45:16 +08:00
|
|
|
var ReactPropTypes;
|
2013-05-30 03:46:11 +08:00
|
|
|
var ReactTestUtils;
|
2013-07-25 08:19:36 +08:00
|
|
|
var ReactMount;
|
2013-06-25 07:09:09 +08:00
|
|
|
var ReactDoNotBindDeprecated;
|
2013-05-30 03:46:11 +08:00
|
|
|
|
|
|
|
|
var cx;
|
|
|
|
|
var reactComponentExpect;
|
2013-10-08 05:54:31 +08:00
|
|
|
var mocks;
|
2014-01-17 03:10:57 +08:00
|
|
|
var warn;
|
2013-05-30 03:46:11 +08:00
|
|
|
|
|
|
|
|
describe('ReactCompositeComponent', function() {
|
|
|
|
|
|
|
|
|
|
beforeEach(function() {
|
|
|
|
|
cx = require('cx');
|
2013-10-08 05:54:31 +08:00
|
|
|
mocks = require('mocks');
|
|
|
|
|
|
2013-05-30 03:46:11 +08:00
|
|
|
reactComponentExpect = require('reactComponentExpect');
|
|
|
|
|
React = require('React');
|
2014-01-03 07:36:58 +08:00
|
|
|
ReactComponent = require('ReactComponent');
|
2013-06-05 03:52:30 +08:00
|
|
|
ReactCurrentOwner = require('ReactCurrentOwner');
|
2013-06-25 07:09:09 +08:00
|
|
|
ReactDoNotBindDeprecated = require('ReactDoNotBindDeprecated');
|
2013-07-11 05:45:16 +08:00
|
|
|
ReactPropTypes = require('ReactPropTypes');
|
2013-05-30 03:46:11 +08:00
|
|
|
ReactTestUtils = require('ReactTestUtils');
|
2013-07-25 08:19:36 +08:00
|
|
|
ReactMount = require('ReactMount');
|
2013-05-30 03:46:11 +08:00
|
|
|
|
|
|
|
|
MorphingComponent = React.createClass({
|
|
|
|
|
getInitialState: function() {
|
|
|
|
|
return {activated: false};
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_toggleActivatedState: function() {
|
|
|
|
|
this.setState({activated: !this.state.activated});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
render: function() {
|
2013-06-25 07:09:09 +08:00
|
|
|
var toggleActivatedState = this._toggleActivatedState;
|
2013-05-30 03:46:11 +08:00
|
|
|
return !this.state.activated ?
|
|
|
|
|
<a ref="x" onClick={toggleActivatedState} /> :
|
|
|
|
|
<b ref="x" onClick={toggleActivatedState} />;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* We'll use this to ensure that an old version is not cached when it is
|
|
|
|
|
* reallocated again.
|
|
|
|
|
*/
|
|
|
|
|
ChildUpdates = React.createClass({
|
|
|
|
|
getAnchorID: function() {
|
|
|
|
|
return this.refs.anch._rootNodeID;
|
|
|
|
|
},
|
|
|
|
|
render: function() {
|
|
|
|
|
var className = cx({'anchorClass': this.props.anchorClassOn});
|
|
|
|
|
return this.props.renderAnchor ?
|
|
|
|
|
<a ref="anch" className={className}></a> :
|
|
|
|
|
<b></b>;
|
|
|
|
|
}
|
|
|
|
|
});
|
2014-01-17 03:10:57 +08:00
|
|
|
|
|
|
|
|
warn = console.warn;
|
|
|
|
|
console.warn = mocks.getMockFunction();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
afterEach(function() {
|
|
|
|
|
console.warn = warn;
|
2013-05-30 03:46:11 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should support rendering to different child types over time', function() {
|
|
|
|
|
var instance = <MorphingComponent />;
|
|
|
|
|
ReactTestUtils.renderIntoDocument(instance);
|
|
|
|
|
|
|
|
|
|
reactComponentExpect(instance)
|
|
|
|
|
.expectRenderedChild()
|
|
|
|
|
.toBeDOMComponentWithTag('a');
|
|
|
|
|
|
|
|
|
|
instance._toggleActivatedState();
|
|
|
|
|
reactComponentExpect(instance)
|
|
|
|
|
.expectRenderedChild()
|
|
|
|
|
.toBeDOMComponentWithTag('b');
|
|
|
|
|
|
|
|
|
|
instance._toggleActivatedState();
|
|
|
|
|
reactComponentExpect(instance)
|
|
|
|
|
.expectRenderedChild()
|
|
|
|
|
.toBeDOMComponentWithTag('a');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should react to state changes from callbacks', function() {
|
|
|
|
|
var instance = <MorphingComponent />;
|
|
|
|
|
ReactTestUtils.renderIntoDocument(instance);
|
|
|
|
|
|
|
|
|
|
var renderedChild = reactComponentExpect(instance)
|
|
|
|
|
.expectRenderedChild()
|
|
|
|
|
.instance();
|
|
|
|
|
|
|
|
|
|
ReactTestUtils.Simulate.click(renderedChild);
|
|
|
|
|
reactComponentExpect(instance)
|
|
|
|
|
.expectRenderedChild()
|
|
|
|
|
.toBeDOMComponentWithTag('b');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should rewire refs when rendering to different child types', function() {
|
|
|
|
|
var instance = <MorphingComponent />;
|
|
|
|
|
ReactTestUtils.renderIntoDocument(instance);
|
|
|
|
|
|
|
|
|
|
reactComponentExpect(instance.refs.x).toBeDOMComponentWithTag('a');
|
|
|
|
|
instance._toggleActivatedState();
|
|
|
|
|
reactComponentExpect(instance.refs.x).toBeDOMComponentWithTag('b');
|
|
|
|
|
instance._toggleActivatedState();
|
|
|
|
|
reactComponentExpect(instance.refs.x).toBeDOMComponentWithTag('a');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should not cache old DOM nodes when switching constructors', function() {
|
|
|
|
|
var instance = <ChildUpdates renderAnchor={true} anchorClassOn={false}/>;
|
|
|
|
|
ReactTestUtils.renderIntoDocument(instance);
|
|
|
|
|
instance.setProps({anchorClassOn: true}); // Warm any cache
|
|
|
|
|
instance.setProps({renderAnchor: false}); // Clear out the anchor
|
|
|
|
|
// rerender
|
|
|
|
|
instance.setProps({renderAnchor: true, anchorClassOn: false});
|
|
|
|
|
var anchorID = instance.getAnchorID();
|
2013-07-25 08:19:36 +08:00
|
|
|
var actualDOMAnchorNode = ReactMount.getNode(anchorID);
|
2013-05-30 03:46:11 +08:00
|
|
|
expect(actualDOMAnchorNode.className).toBe('');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should auto bind methods and values correctly', function() {
|
2013-07-18 10:56:47 +08:00
|
|
|
spyOn(console, 'warn');
|
|
|
|
|
|
2013-05-30 03:46:11 +08:00
|
|
|
var ComponentClass = React.createClass({
|
|
|
|
|
getInitialState: function() {
|
2013-06-25 07:09:09 +08:00
|
|
|
return {valueToReturn: 'hi'};
|
|
|
|
|
},
|
|
|
|
|
methodToBeExplicitlyBound: function() {
|
|
|
|
|
return this;
|
2013-05-30 03:46:11 +08:00
|
|
|
},
|
2013-06-25 07:09:09 +08:00
|
|
|
methodAutoBound: function() {
|
|
|
|
|
return this;
|
|
|
|
|
},
|
|
|
|
|
methodExplicitlyNotBound: ReactDoNotBindDeprecated.doNotBind(function() {
|
|
|
|
|
return this;
|
2013-05-30 03:46:11 +08:00
|
|
|
}),
|
|
|
|
|
render: function() {
|
2014-01-18 04:46:41 +08:00
|
|
|
return <div></div>;
|
2013-05-30 03:46:11 +08:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
var instance = <ComponentClass />;
|
|
|
|
|
|
2013-06-25 07:09:38 +08:00
|
|
|
// These are controversial assertions for now, they just exist
|
|
|
|
|
// because existing code depends on these assumptions.
|
2014-02-12 01:00:14 +08:00
|
|
|
// These are expected to log a warning. This use case will be deprecated.
|
2013-06-25 07:09:38 +08:00
|
|
|
expect(function() {
|
|
|
|
|
instance.methodToBeExplicitlyBound.bind(instance)();
|
|
|
|
|
}).not.toThrow();
|
|
|
|
|
expect(function() {
|
|
|
|
|
instance.methodAutoBound();
|
|
|
|
|
}).not.toThrow();
|
2013-06-25 07:09:09 +08:00
|
|
|
expect(function() {
|
|
|
|
|
instance.methodExplicitlyNotBound();
|
|
|
|
|
}).not.toThrow();
|
2013-05-30 03:46:11 +08:00
|
|
|
|
|
|
|
|
// Next, prove that once mounted, the scope is bound correctly to the actual
|
|
|
|
|
// component.
|
2014-03-11 06:26:31 +08:00
|
|
|
var mountedInstance = ReactTestUtils.renderIntoDocument(instance);
|
2014-02-12 01:00:14 +08:00
|
|
|
expect(console.warn.argsForCall.length).toBe(3);
|
2014-03-11 06:26:31 +08:00
|
|
|
// This will result in a warning that has already been issued before.
|
2013-06-25 07:09:09 +08:00
|
|
|
var explicitlyBound = instance.methodToBeExplicitlyBound.bind(instance);
|
2014-03-11 06:26:31 +08:00
|
|
|
expect(console.warn.argsForCall.length).toBe(3);
|
2013-06-25 07:09:09 +08:00
|
|
|
var autoBound = instance.methodAutoBound;
|
|
|
|
|
var explicitlyNotBound = instance.methodExplicitlyNotBound;
|
|
|
|
|
|
2013-06-27 02:20:36 +08:00
|
|
|
var context = {};
|
2014-03-11 06:26:31 +08:00
|
|
|
expect(explicitlyBound.call(context)).toBe(mountedInstance);
|
|
|
|
|
expect(autoBound.call(context)).toBe(mountedInstance);
|
2013-06-27 02:20:36 +08:00
|
|
|
expect(explicitlyNotBound.call(context)).toBe(context);
|
2013-06-25 07:09:09 +08:00
|
|
|
|
2014-03-11 06:26:31 +08:00
|
|
|
expect(explicitlyBound.call(instance)).toBe(mountedInstance);
|
|
|
|
|
expect(autoBound.call(instance)).toBe(mountedInstance);
|
|
|
|
|
// This one is the weird one
|
|
|
|
|
expect(explicitlyNotBound.call(instance)).toBe(mountedInstance);
|
2013-06-25 07:09:09 +08:00
|
|
|
|
2013-05-30 03:46:11 +08:00
|
|
|
});
|
|
|
|
|
|
2013-10-22 02:36:53 +08:00
|
|
|
it('should use default values for undefined props', function() {
|
|
|
|
|
var Component = React.createClass({
|
|
|
|
|
getDefaultProps: function() {
|
|
|
|
|
return {key: 'testKey'};
|
|
|
|
|
},
|
|
|
|
|
render: function() {
|
|
|
|
|
return <span />;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var instance1 = <Component />;
|
|
|
|
|
ReactTestUtils.renderIntoDocument(instance1);
|
|
|
|
|
reactComponentExpect(instance1).scalarPropsEqual({key: 'testKey'});
|
|
|
|
|
|
|
|
|
|
var instance2 = <Component key={undefined} />;
|
|
|
|
|
ReactTestUtils.renderIntoDocument(instance2);
|
|
|
|
|
reactComponentExpect(instance2).scalarPropsEqual({key: 'testKey'});
|
|
|
|
|
|
|
|
|
|
var instance3 = <Component key={null} />;
|
|
|
|
|
ReactTestUtils.renderIntoDocument(instance3);
|
|
|
|
|
reactComponentExpect(instance3).scalarPropsEqual({key: null});
|
|
|
|
|
});
|
2013-11-20 17:12:39 +08:00
|
|
|
|
2013-12-06 08:59:02 +08:00
|
|
|
it('should not mutate passed-in props object', function() {
|
|
|
|
|
var Component = React.createClass({
|
|
|
|
|
getDefaultProps: function() {
|
|
|
|
|
return {key: 'testKey'};
|
|
|
|
|
},
|
|
|
|
|
render: function() {
|
|
|
|
|
return <span />;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var inputProps = {};
|
|
|
|
|
var instance1 = Component(inputProps);
|
|
|
|
|
ReactTestUtils.renderIntoDocument(instance1);
|
|
|
|
|
expect(instance1.props.key).toBe('testKey');
|
|
|
|
|
|
|
|
|
|
// We don't mutate the input, just in case the caller wants to do something
|
|
|
|
|
// with it after using it to instantiate a component
|
|
|
|
|
expect(inputProps.key).not.toBeDefined();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should use default prop value when removing a key', function() {
|
|
|
|
|
var Component = React.createClass({
|
|
|
|
|
getDefaultProps: function() {
|
|
|
|
|
return {fruit: 'persimmon'};
|
|
|
|
|
},
|
|
|
|
|
render: function() {
|
|
|
|
|
return <span />;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var container = document.createElement('div');
|
|
|
|
|
var instance = React.renderComponent(
|
|
|
|
|
<Component fruit="mango" />,
|
|
|
|
|
container
|
|
|
|
|
);
|
|
|
|
|
expect(instance.props.fruit).toBe('mango');
|
|
|
|
|
|
|
|
|
|
React.renderComponent(<Component />, container);
|
|
|
|
|
expect(instance.props.fruit).toBe('persimmon');
|
|
|
|
|
});
|
|
|
|
|
|
2013-06-05 03:48:27 +08:00
|
|
|
it('should normalize props with default values', function() {
|
|
|
|
|
var Component = React.createClass({
|
2013-12-05 09:47:58 +08:00
|
|
|
propTypes: {key: ReactPropTypes.string.isRequired},
|
2013-06-05 03:48:27 +08:00
|
|
|
getDefaultProps: function() {
|
|
|
|
|
return {key: 'testKey'};
|
|
|
|
|
},
|
|
|
|
|
getInitialState: function() {
|
|
|
|
|
return {key: this.props.key + 'State'};
|
|
|
|
|
},
|
|
|
|
|
render: function() {
|
|
|
|
|
return <span>{this.props.key}</span>;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2014-03-11 06:26:31 +08:00
|
|
|
var instance = ReactTestUtils.renderIntoDocument(<Component />);
|
2013-06-05 03:48:27 +08:00
|
|
|
reactComponentExpect(instance).scalarPropsEqual({key: 'testKey'});
|
|
|
|
|
reactComponentExpect(instance).scalarStateEqual({key: 'testKeyState'});
|
|
|
|
|
|
2014-01-17 03:10:57 +08:00
|
|
|
ReactTestUtils.renderIntoDocument(<Component key={null} />);
|
|
|
|
|
|
|
|
|
|
expect(console.warn.mock.calls.length).toBe(1);
|
|
|
|
|
expect(console.warn.mock.calls[0][0]).toBe(
|
|
|
|
|
'Warning: Required prop `key` was not specified in `Component`.'
|
2013-06-05 03:48:27 +08:00
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should check default prop values', function() {
|
|
|
|
|
var Component = React.createClass({
|
2013-12-05 09:47:58 +08:00
|
|
|
propTypes: {key: ReactPropTypes.string.isRequired},
|
2013-06-05 03:48:27 +08:00
|
|
|
getDefaultProps: function() {
|
|
|
|
|
return {key: null};
|
|
|
|
|
},
|
|
|
|
|
render: function() {
|
|
|
|
|
return <span>{this.props.key}</span>;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2014-01-17 03:10:57 +08:00
|
|
|
ReactTestUtils.renderIntoDocument(<Component />);
|
|
|
|
|
|
|
|
|
|
expect(console.warn.mock.calls.length).toBe(1);
|
|
|
|
|
expect(console.warn.mock.calls[0][0]).toBe(
|
|
|
|
|
'Warning: Required prop `key` was not specified in `Component`.'
|
2013-06-05 03:48:27 +08:00
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2013-05-30 03:46:11 +08:00
|
|
|
it('should check declared prop types', function() {
|
|
|
|
|
var Component = React.createClass({
|
2013-12-05 09:47:58 +08:00
|
|
|
propTypes: {
|
|
|
|
|
key: ReactPropTypes.string.isRequired
|
2013-05-30 03:46:11 +08:00
|
|
|
},
|
|
|
|
|
render: function() {
|
|
|
|
|
return <span>{this.props.key}</span>;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2014-01-17 03:10:57 +08:00
|
|
|
ReactTestUtils.renderIntoDocument(<Component />);
|
|
|
|
|
ReactTestUtils.renderIntoDocument(<Component key={42} />);
|
|
|
|
|
|
|
|
|
|
expect(console.warn.mock.calls.length).toBe(2);
|
|
|
|
|
expect(console.warn.mock.calls[0][0]).toBe(
|
|
|
|
|
'Warning: Required prop `key` was not specified in `Component`.'
|
2013-05-30 03:46:11 +08:00
|
|
|
);
|
|
|
|
|
|
2014-01-17 03:10:57 +08:00
|
|
|
expect(console.warn.mock.calls[1][0]).toBe(
|
|
|
|
|
'Warning: Invalid prop `key` of type `number` supplied to ' +
|
2013-05-30 03:46:11 +08:00
|
|
|
'`Component`, expected `string`.'
|
|
|
|
|
);
|
|
|
|
|
|
2014-01-17 03:10:57 +08:00
|
|
|
ReactTestUtils.renderIntoDocument(<Component key="string" />);
|
|
|
|
|
|
|
|
|
|
// Should not error for strings
|
|
|
|
|
expect(console.warn.mock.calls.length).toBe(2);
|
2013-05-30 03:46:11 +08:00
|
|
|
});
|
|
|
|
|
|
2013-11-28 04:00:30 +08:00
|
|
|
it('should throw on invalid prop types', function() {
|
|
|
|
|
expect(function() {
|
|
|
|
|
React.createClass({
|
|
|
|
|
displayName: 'Component',
|
2013-12-05 09:47:58 +08:00
|
|
|
propTypes: {
|
|
|
|
|
key: null
|
2013-11-28 04:00:30 +08:00
|
|
|
},
|
|
|
|
|
render: function() {
|
|
|
|
|
return <span>{this.props.key}</span>;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}).toThrow(
|
|
|
|
|
'Invariant Violation: Component: prop type `key` is invalid; ' +
|
|
|
|
|
'it must be a function, usually from React.PropTypes.'
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should throw on invalid context types', function() {
|
|
|
|
|
expect(function() {
|
|
|
|
|
React.createClass({
|
|
|
|
|
displayName: 'Component',
|
2013-12-05 09:47:58 +08:00
|
|
|
contextTypes: {
|
|
|
|
|
key: null
|
2013-11-28 04:00:30 +08:00
|
|
|
},
|
|
|
|
|
render: function() {
|
|
|
|
|
return <span>{this.props.key}</span>;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}).toThrow(
|
|
|
|
|
'Invariant Violation: Component: context type `key` is invalid; ' +
|
|
|
|
|
'it must be a function, usually from React.PropTypes.'
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should throw on invalid child context types', function() {
|
|
|
|
|
expect(function() {
|
|
|
|
|
React.createClass({
|
|
|
|
|
displayName: 'Component',
|
2013-12-05 09:47:58 +08:00
|
|
|
childContextTypes: {
|
|
|
|
|
key: null
|
2013-11-28 04:00:30 +08:00
|
|
|
},
|
|
|
|
|
render: function() {
|
|
|
|
|
return <span>{this.props.key}</span>;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}).toThrow(
|
|
|
|
|
'Invariant Violation: Component: child context type `key` is invalid; ' +
|
|
|
|
|
'it must be a function, usually from React.PropTypes.'
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2013-06-05 03:52:27 +08:00
|
|
|
it('should not allow `forceUpdate` on unmounted components', function() {
|
|
|
|
|
var container = document.createElement('div');
|
|
|
|
|
document.documentElement.appendChild(container);
|
|
|
|
|
|
|
|
|
|
var Component = React.createClass({
|
|
|
|
|
render: function() {
|
|
|
|
|
return <div />;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var instance = <Component />;
|
|
|
|
|
expect(function() {
|
|
|
|
|
instance.forceUpdate();
|
|
|
|
|
}).toThrow(
|
|
|
|
|
'Invariant Violation: forceUpdate(...): Can only force an update on ' +
|
2013-06-25 07:09:46 +08:00
|
|
|
'mounted or mounting components.'
|
2013-06-05 03:52:27 +08:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
React.renderComponent(instance, container);
|
|
|
|
|
expect(function() {
|
|
|
|
|
instance.forceUpdate();
|
|
|
|
|
}).not.toThrow();
|
|
|
|
|
|
2013-09-20 05:46:49 +08:00
|
|
|
React.unmountComponentAtNode(container);
|
2013-06-05 03:52:27 +08:00
|
|
|
expect(function() {
|
|
|
|
|
instance.forceUpdate();
|
|
|
|
|
}).toThrow(
|
|
|
|
|
'Invariant Violation: forceUpdate(...): Can only force an update on ' +
|
2013-06-25 07:09:46 +08:00
|
|
|
'mounted or mounting components.'
|
2013-06-05 03:52:27 +08:00
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2013-06-05 03:52:30 +08:00
|
|
|
it('should cleanup even if render() fatals', function() {
|
|
|
|
|
var BadComponent = React.createClass({
|
|
|
|
|
render: function() {
|
|
|
|
|
throw new Error();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
var instance = <BadComponent />;
|
|
|
|
|
|
|
|
|
|
expect(ReactCurrentOwner.current).toBe(null);
|
|
|
|
|
|
|
|
|
|
expect(function() {
|
|
|
|
|
ReactTestUtils.renderIntoDocument(instance);
|
|
|
|
|
}).toThrow();
|
|
|
|
|
|
|
|
|
|
expect(ReactCurrentOwner.current).toBe(null);
|
|
|
|
|
});
|
|
|
|
|
|
2013-08-28 05:16:57 +08:00
|
|
|
it('should support mixins with getInitialState()', function() {
|
|
|
|
|
var Mixin = {
|
|
|
|
|
getInitialState: function() {
|
|
|
|
|
return {mixin: true};
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
var Component = React.createClass({
|
2013-12-05 09:47:58 +08:00
|
|
|
mixins: [Mixin],
|
2013-08-28 05:16:57 +08:00
|
|
|
getInitialState: function() {
|
|
|
|
|
return {component: true};
|
|
|
|
|
},
|
|
|
|
|
render: function() {
|
|
|
|
|
return <span />;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
var instance = <Component />;
|
|
|
|
|
ReactTestUtils.renderIntoDocument(instance);
|
|
|
|
|
expect(instance.state.component).toBe(true);
|
|
|
|
|
expect(instance.state.mixin).toBe(true);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should throw with conflicting getInitialState() methods', function() {
|
|
|
|
|
var Mixin = {
|
|
|
|
|
getInitialState: function() {
|
|
|
|
|
return {x: true};
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
var Component = React.createClass({
|
2013-12-05 09:47:58 +08:00
|
|
|
mixins: [Mixin],
|
2013-08-28 05:16:57 +08:00
|
|
|
getInitialState: function() {
|
|
|
|
|
return {x: true};
|
|
|
|
|
},
|
|
|
|
|
render: function() {
|
|
|
|
|
return <span />;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
var instance = <Component />;
|
|
|
|
|
expect(function() {
|
|
|
|
|
ReactTestUtils.renderIntoDocument(instance);
|
|
|
|
|
}).toThrow(
|
|
|
|
|
'Invariant Violation: mergeObjectsWithNoDuplicateKeys(): ' +
|
|
|
|
|
'Tried to merge two objects with the same key: x'
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2014-01-03 11:42:19 +08:00
|
|
|
it('should work with object getInitialState() return values', function() {
|
|
|
|
|
var Component = React.createClass({
|
|
|
|
|
getInitialState: function() {
|
|
|
|
|
return {
|
|
|
|
|
occupation: 'clown'
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
render: function() {
|
|
|
|
|
return <span />;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
var instance = <Component />;
|
|
|
|
|
ReactTestUtils.renderIntoDocument(instance);
|
|
|
|
|
expect(instance.state.occupation).toEqual('clown');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should throw with non-object getInitialState() return values', function() {
|
|
|
|
|
[['an array'], 'a string', 1234].forEach(function(state) {
|
|
|
|
|
var Component = React.createClass({
|
|
|
|
|
getInitialState: function() {
|
|
|
|
|
return state;
|
|
|
|
|
},
|
|
|
|
|
render: function() {
|
|
|
|
|
return <span />;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
var instance = <Component />;
|
|
|
|
|
expect(function() {
|
|
|
|
|
ReactTestUtils.renderIntoDocument(instance);
|
|
|
|
|
}).toThrow(
|
|
|
|
|
'Invariant Violation: Component.getInitialState(): ' +
|
|
|
|
|
'must return an object or null'
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2013-12-27 21:17:17 +08:00
|
|
|
it('should work with a null getInitialState() return value', function() {
|
|
|
|
|
var Component = React.createClass({
|
|
|
|
|
getInitialState: function() {
|
|
|
|
|
return null;
|
|
|
|
|
},
|
|
|
|
|
render: function() {
|
|
|
|
|
return <span />;
|
|
|
|
|
}
|
|
|
|
|
});
|
2014-01-15 02:45:35 +08:00
|
|
|
expect(
|
|
|
|
|
() => ReactTestUtils.renderIntoDocument(<Component />)
|
|
|
|
|
).not.toThrow();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should work with a null getInitialState return value and a mixin', () => {
|
|
|
|
|
var Component;
|
|
|
|
|
var instance;
|
|
|
|
|
|
|
|
|
|
var Mixin = {
|
|
|
|
|
getInitialState: function() {
|
|
|
|
|
return {foo: 'bar'};
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
Component = React.createClass({
|
|
|
|
|
mixins: [Mixin],
|
|
|
|
|
getInitialState: function() {
|
|
|
|
|
return null;
|
|
|
|
|
},
|
|
|
|
|
render: function() {
|
|
|
|
|
return <span />;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
expect(
|
|
|
|
|
() => ReactTestUtils.renderIntoDocument(<Component />)
|
|
|
|
|
).not.toThrow();
|
|
|
|
|
|
|
|
|
|
instance = <Component />;
|
|
|
|
|
ReactTestUtils.renderIntoDocument(instance);
|
|
|
|
|
expect(instance.state).toEqual({foo: 'bar'});
|
|
|
|
|
|
|
|
|
|
// Also the other way round should work
|
|
|
|
|
var Mixin2 = {
|
|
|
|
|
getInitialState: function() {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
Component = React.createClass({
|
|
|
|
|
mixins: [Mixin2],
|
|
|
|
|
getInitialState: function() {
|
|
|
|
|
return {foo: 'bar'};
|
|
|
|
|
},
|
|
|
|
|
render: function() {
|
|
|
|
|
return <span />;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
expect(
|
|
|
|
|
() => ReactTestUtils.renderIntoDocument(<Component />)
|
|
|
|
|
).not.toThrow();
|
|
|
|
|
|
|
|
|
|
instance = <Component />;
|
|
|
|
|
ReactTestUtils.renderIntoDocument(instance);
|
|
|
|
|
expect(instance.state).toEqual({foo: 'bar'});
|
|
|
|
|
|
|
|
|
|
// Multiple mixins should be fine too
|
|
|
|
|
Component = React.createClass({
|
|
|
|
|
mixins: [Mixin, Mixin2],
|
|
|
|
|
getInitialState: function() {
|
|
|
|
|
return {x: true};
|
|
|
|
|
},
|
|
|
|
|
render: function() {
|
|
|
|
|
return <span />;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
expect(
|
|
|
|
|
() => ReactTestUtils.renderIntoDocument(<Component />)
|
|
|
|
|
).not.toThrow();
|
|
|
|
|
|
|
|
|
|
instance = <Component />;
|
|
|
|
|
ReactTestUtils.renderIntoDocument(instance);
|
|
|
|
|
expect(instance.state).toEqual({foo: 'bar', x: true});
|
2013-12-27 21:17:17 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should work with object getInitialState() return values', function() {
|
|
|
|
|
var Component = React.createClass({
|
|
|
|
|
getInitialState: function() {
|
|
|
|
|
return {
|
|
|
|
|
occupation: 'clown'
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
render: function() {
|
|
|
|
|
return <span />;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
var instance = <Component />;
|
|
|
|
|
ReactTestUtils.renderIntoDocument(instance);
|
|
|
|
|
expect(instance.state.occupation).toEqual('clown');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should throw with non-object getInitialState() return values', function() {
|
|
|
|
|
[['an array'], 'a string', 1234].forEach(function(state) {
|
|
|
|
|
var Component = React.createClass({
|
|
|
|
|
getInitialState: function() {
|
|
|
|
|
return state;
|
|
|
|
|
},
|
|
|
|
|
render: function() {
|
|
|
|
|
return <span />;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
var instance = <Component />;
|
|
|
|
|
expect(function() {
|
|
|
|
|
ReactTestUtils.renderIntoDocument(instance);
|
|
|
|
|
}).toThrow(
|
|
|
|
|
'Invariant Violation: Component.getInitialState(): ' +
|
|
|
|
|
'must return an object or null'
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2014-01-03 07:36:58 +08:00
|
|
|
it('should call componentWillUnmount before unmounting', function() {
|
|
|
|
|
var container = document.createElement('div');
|
|
|
|
|
var innerUnmounted = false;
|
|
|
|
|
|
2014-01-09 13:48:48 +08:00
|
|
|
spyOn(ReactMount, 'purgeID').andCallThrough();
|
2014-01-03 07:36:58 +08:00
|
|
|
|
|
|
|
|
var Component = React.createClass({
|
|
|
|
|
render: function() {
|
|
|
|
|
return <div>
|
|
|
|
|
<Inner />
|
|
|
|
|
</div>;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
var Inner = React.createClass({
|
|
|
|
|
componentWillUnmount: function() {
|
2014-01-09 13:48:48 +08:00
|
|
|
// It's important that ReactMount.purgeID be called after any component
|
|
|
|
|
// lifecycle methods, because a componentWillMount implementation is
|
|
|
|
|
// likely call this.getDOMNode(), which will repopulate the node cache
|
|
|
|
|
// after it's been cleared, causing a memory leak.
|
|
|
|
|
expect(ReactMount.purgeID.callCount).toBe(0);
|
2014-01-03 07:36:58 +08:00
|
|
|
innerUnmounted = true;
|
|
|
|
|
},
|
|
|
|
|
render: function() {
|
|
|
|
|
return <div />;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
React.renderComponent(<Component />, container);
|
|
|
|
|
React.unmountComponentAtNode(container);
|
|
|
|
|
expect(innerUnmounted).toBe(true);
|
|
|
|
|
|
|
|
|
|
// <Component />, <Inner />, and both <div /> elements each call
|
2014-01-09 13:48:48 +08:00
|
|
|
// unmountIDFromEnvironment which calls purgeID, for a total of 4.
|
|
|
|
|
expect(ReactMount.purgeID.callCount).toBe(4);
|
2014-01-03 07:36:58 +08:00
|
|
|
});
|
|
|
|
|
|
2013-08-30 08:32:53 +08:00
|
|
|
it('should detect valid CompositeComponent classes', function() {
|
|
|
|
|
var Component = React.createClass({
|
|
|
|
|
render: function() {
|
|
|
|
|
return <div/>;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(React.isValidClass(Component)).toBe(true);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should detect invalid CompositeComponent classes', function() {
|
|
|
|
|
var FnComponent = function() {
|
|
|
|
|
return false;
|
2013-10-08 05:54:31 +08:00
|
|
|
};
|
2013-08-30 08:32:53 +08:00
|
|
|
|
|
|
|
|
var NullComponent = null;
|
|
|
|
|
|
|
|
|
|
var TrickFnComponent = function() {
|
|
|
|
|
return true;
|
2013-10-08 05:54:31 +08:00
|
|
|
};
|
2013-08-30 08:32:53 +08:00
|
|
|
TrickFnComponent.componentConstructor = true;
|
|
|
|
|
|
|
|
|
|
expect(React.isValidClass(FnComponent)).toBe(false);
|
|
|
|
|
expect(React.isValidClass(NullComponent)).toBe(false);
|
|
|
|
|
expect(React.isValidClass(TrickFnComponent)).toBe(false);
|
|
|
|
|
});
|
2013-10-08 05:54:31 +08:00
|
|
|
|
|
|
|
|
it('should warn when mispelling shouldComponentUpdate', function() {
|
|
|
|
|
var warn = console.warn;
|
|
|
|
|
console.warn = mocks.getMockFunction();
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
React.createClass({
|
|
|
|
|
componentShouldUpdate: function() {
|
|
|
|
|
return false;
|
|
|
|
|
},
|
|
|
|
|
render: function() {
|
|
|
|
|
return <div />;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
expect(console.warn.mock.calls.length).toBe(1);
|
|
|
|
|
expect(console.warn.mock.calls[0][0]).toBe(
|
|
|
|
|
'A component has a method called componentShouldUpdate(). Did you ' +
|
|
|
|
|
'mean shouldComponentUpdate()? The name is phrased as a question ' +
|
|
|
|
|
'because the function is expected to return a value.'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var NamedComponent = React.createClass({
|
|
|
|
|
componentShouldUpdate: function() {
|
|
|
|
|
return false;
|
|
|
|
|
},
|
|
|
|
|
render: function() {
|
|
|
|
|
return <div />;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
expect(console.warn.mock.calls.length).toBe(2);
|
|
|
|
|
expect(console.warn.mock.calls[1][0]).toBe(
|
|
|
|
|
'NamedComponent has a method called componentShouldUpdate(). Did you ' +
|
|
|
|
|
'mean shouldComponentUpdate()? The name is phrased as a question ' +
|
|
|
|
|
'because the function is expected to return a value.'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
NamedComponent(); // Shut up lint
|
|
|
|
|
} finally {
|
|
|
|
|
console.warn = warn;
|
|
|
|
|
}
|
|
|
|
|
});
|
Contexts
Summary:
adds `this.context` which you can think of as implicit props, which are passed automatically down the //ownership// hierarchy.
Contexts should be used sparingly, since they essentially allow components to communicate with descendants (in the ownership sense, not parenthood sense), which is not usually a good idea. You probably would only use contexts in places where you'd normally use a global, but contexts allow you to override them for certain view subtrees which you can't do with globals.
The context starts out `null`:
var RootComponent = React.createClass({
render: function() {
// this.context === null
}
});
You should **never** mutate the context directly, just like props and state.
You can change the context of your children (the ones you own, not `this.props.children` or via other props) using the new `withContext` method on `React`:
var RootComponent = React.createClass({
render: function() {
// this.context === null
var children = React.withContext({foo: 'a', bar: 'b'}, () => (
// In ChildComponent#render, this.context === {foo: 'a', bar: 'b'}
<ChildComponent />
));
// this.context === null
}
});
Contexts are merged, so a component can override its owner's context **for its children**:
var ChildComponent = React.createClass({
render: function() {
// this.context === {foo: 'a', bar: 'b'} (for the caller above)
var children = React.withContext({foo: 'c'},() => (
// In GrandchildComponent#render,
// this.context === {foo: 'c', bar: 'b'}
<GrandchildComponent />
));
// this.context === {foo: 'a', bar: 'b'}
}
});
2013-11-19 02:41:42 +08:00
|
|
|
|
2013-12-05 09:45:59 +08:00
|
|
|
xit('should warn when using deprecated non-static spec keys', function() {
|
2013-12-03 09:20:55 +08:00
|
|
|
var warn = console.warn;
|
|
|
|
|
console.warn = mocks.getMockFunction();
|
|
|
|
|
try {
|
|
|
|
|
React.createClass({
|
|
|
|
|
mixins: [{}],
|
|
|
|
|
propTypes: {
|
|
|
|
|
foo: ReactPropTypes.string
|
|
|
|
|
},
|
|
|
|
|
contextTypes: {
|
|
|
|
|
foo: ReactPropTypes.string
|
|
|
|
|
},
|
|
|
|
|
childContextTypes: {
|
|
|
|
|
foo: ReactPropTypes.string
|
|
|
|
|
},
|
|
|
|
|
render: function() {
|
|
|
|
|
return <div />;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
expect(console.warn.mock.calls.length).toBe(4);
|
|
|
|
|
expect(console.warn.mock.calls[0][0]).toBe(
|
|
|
|
|
'createClass(...): `mixins` is now a static property and should ' +
|
|
|
|
|
'be defined inside "statics".'
|
|
|
|
|
);
|
|
|
|
|
expect(console.warn.mock.calls[1][0]).toBe(
|
|
|
|
|
'createClass(...): `propTypes` is now a static property and should ' +
|
|
|
|
|
'be defined inside "statics".'
|
|
|
|
|
);
|
|
|
|
|
expect(console.warn.mock.calls[2][0]).toBe(
|
|
|
|
|
'createClass(...): `contextTypes` is now a static property and ' +
|
|
|
|
|
'should be defined inside "statics".'
|
|
|
|
|
);
|
|
|
|
|
expect(console.warn.mock.calls[3][0]).toBe(
|
|
|
|
|
'createClass(...): `childContextTypes` is now a static property and ' +
|
|
|
|
|
'should be defined inside "statics".'
|
|
|
|
|
);
|
|
|
|
|
} finally {
|
|
|
|
|
console.warn = warn;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
Contexts
Summary:
adds `this.context` which you can think of as implicit props, which are passed automatically down the //ownership// hierarchy.
Contexts should be used sparingly, since they essentially allow components to communicate with descendants (in the ownership sense, not parenthood sense), which is not usually a good idea. You probably would only use contexts in places where you'd normally use a global, but contexts allow you to override them for certain view subtrees which you can't do with globals.
The context starts out `null`:
var RootComponent = React.createClass({
render: function() {
// this.context === null
}
});
You should **never** mutate the context directly, just like props and state.
You can change the context of your children (the ones you own, not `this.props.children` or via other props) using the new `withContext` method on `React`:
var RootComponent = React.createClass({
render: function() {
// this.context === null
var children = React.withContext({foo: 'a', bar: 'b'}, () => (
// In ChildComponent#render, this.context === {foo: 'a', bar: 'b'}
<ChildComponent />
));
// this.context === null
}
});
Contexts are merged, so a component can override its owner's context **for its children**:
var ChildComponent = React.createClass({
render: function() {
// this.context === {foo: 'a', bar: 'b'} (for the caller above)
var children = React.withContext({foo: 'c'},() => (
// In GrandchildComponent#render,
// this.context === {foo: 'c', bar: 'b'}
<GrandchildComponent />
));
// this.context === {foo: 'a', bar: 'b'}
}
});
2013-11-19 02:41:42 +08:00
|
|
|
it('should pass context', function() {
|
|
|
|
|
var childInstance = null;
|
|
|
|
|
var grandchildInstance = null;
|
|
|
|
|
|
|
|
|
|
var Parent = React.createClass({
|
2013-12-05 09:47:58 +08:00
|
|
|
childContextTypes: {
|
|
|
|
|
foo: ReactPropTypes.string,
|
|
|
|
|
depth: ReactPropTypes.number
|
Contexts
Summary:
adds `this.context` which you can think of as implicit props, which are passed automatically down the //ownership// hierarchy.
Contexts should be used sparingly, since they essentially allow components to communicate with descendants (in the ownership sense, not parenthood sense), which is not usually a good idea. You probably would only use contexts in places where you'd normally use a global, but contexts allow you to override them for certain view subtrees which you can't do with globals.
The context starts out `null`:
var RootComponent = React.createClass({
render: function() {
// this.context === null
}
});
You should **never** mutate the context directly, just like props and state.
You can change the context of your children (the ones you own, not `this.props.children` or via other props) using the new `withContext` method on `React`:
var RootComponent = React.createClass({
render: function() {
// this.context === null
var children = React.withContext({foo: 'a', bar: 'b'}, () => (
// In ChildComponent#render, this.context === {foo: 'a', bar: 'b'}
<ChildComponent />
));
// this.context === null
}
});
Contexts are merged, so a component can override its owner's context **for its children**:
var ChildComponent = React.createClass({
render: function() {
// this.context === {foo: 'a', bar: 'b'} (for the caller above)
var children = React.withContext({foo: 'c'},() => (
// In GrandchildComponent#render,
// this.context === {foo: 'c', bar: 'b'}
<GrandchildComponent />
));
// this.context === {foo: 'a', bar: 'b'}
}
});
2013-11-19 02:41:42 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
getChildContext: function() {
|
|
|
|
|
return {
|
|
|
|
|
foo: 'bar',
|
|
|
|
|
depth: 0
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
render: function() {
|
|
|
|
|
childInstance = <Child />;
|
|
|
|
|
return childInstance;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var Child = React.createClass({
|
2013-12-05 09:47:58 +08:00
|
|
|
contextTypes: {
|
|
|
|
|
foo: ReactPropTypes.string,
|
|
|
|
|
depth: ReactPropTypes.number
|
|
|
|
|
},
|
Contexts
Summary:
adds `this.context` which you can think of as implicit props, which are passed automatically down the //ownership// hierarchy.
Contexts should be used sparingly, since they essentially allow components to communicate with descendants (in the ownership sense, not parenthood sense), which is not usually a good idea. You probably would only use contexts in places where you'd normally use a global, but contexts allow you to override them for certain view subtrees which you can't do with globals.
The context starts out `null`:
var RootComponent = React.createClass({
render: function() {
// this.context === null
}
});
You should **never** mutate the context directly, just like props and state.
You can change the context of your children (the ones you own, not `this.props.children` or via other props) using the new `withContext` method on `React`:
var RootComponent = React.createClass({
render: function() {
// this.context === null
var children = React.withContext({foo: 'a', bar: 'b'}, () => (
// In ChildComponent#render, this.context === {foo: 'a', bar: 'b'}
<ChildComponent />
));
// this.context === null
}
});
Contexts are merged, so a component can override its owner's context **for its children**:
var ChildComponent = React.createClass({
render: function() {
// this.context === {foo: 'a', bar: 'b'} (for the caller above)
var children = React.withContext({foo: 'c'},() => (
// In GrandchildComponent#render,
// this.context === {foo: 'c', bar: 'b'}
<GrandchildComponent />
));
// this.context === {foo: 'a', bar: 'b'}
}
});
2013-11-19 02:41:42 +08:00
|
|
|
|
2013-12-05 09:47:58 +08:00
|
|
|
childContextTypes: {
|
|
|
|
|
depth: ReactPropTypes.number
|
Contexts
Summary:
adds `this.context` which you can think of as implicit props, which are passed automatically down the //ownership// hierarchy.
Contexts should be used sparingly, since they essentially allow components to communicate with descendants (in the ownership sense, not parenthood sense), which is not usually a good idea. You probably would only use contexts in places where you'd normally use a global, but contexts allow you to override them for certain view subtrees which you can't do with globals.
The context starts out `null`:
var RootComponent = React.createClass({
render: function() {
// this.context === null
}
});
You should **never** mutate the context directly, just like props and state.
You can change the context of your children (the ones you own, not `this.props.children` or via other props) using the new `withContext` method on `React`:
var RootComponent = React.createClass({
render: function() {
// this.context === null
var children = React.withContext({foo: 'a', bar: 'b'}, () => (
// In ChildComponent#render, this.context === {foo: 'a', bar: 'b'}
<ChildComponent />
));
// this.context === null
}
});
Contexts are merged, so a component can override its owner's context **for its children**:
var ChildComponent = React.createClass({
render: function() {
// this.context === {foo: 'a', bar: 'b'} (for the caller above)
var children = React.withContext({foo: 'c'},() => (
// In GrandchildComponent#render,
// this.context === {foo: 'c', bar: 'b'}
<GrandchildComponent />
));
// this.context === {foo: 'a', bar: 'b'}
}
});
2013-11-19 02:41:42 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
getChildContext: function() {
|
|
|
|
|
return {
|
|
|
|
|
depth: this.context.depth + 1
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
render: function() {
|
|
|
|
|
grandchildInstance = <Grandchild />;
|
|
|
|
|
return grandchildInstance;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var Grandchild = React.createClass({
|
2013-12-05 09:47:58 +08:00
|
|
|
contextTypes: {
|
|
|
|
|
foo: ReactPropTypes.string,
|
|
|
|
|
depth: ReactPropTypes.number
|
Contexts
Summary:
adds `this.context` which you can think of as implicit props, which are passed automatically down the //ownership// hierarchy.
Contexts should be used sparingly, since they essentially allow components to communicate with descendants (in the ownership sense, not parenthood sense), which is not usually a good idea. You probably would only use contexts in places where you'd normally use a global, but contexts allow you to override them for certain view subtrees which you can't do with globals.
The context starts out `null`:
var RootComponent = React.createClass({
render: function() {
// this.context === null
}
});
You should **never** mutate the context directly, just like props and state.
You can change the context of your children (the ones you own, not `this.props.children` or via other props) using the new `withContext` method on `React`:
var RootComponent = React.createClass({
render: function() {
// this.context === null
var children = React.withContext({foo: 'a', bar: 'b'}, () => (
// In ChildComponent#render, this.context === {foo: 'a', bar: 'b'}
<ChildComponent />
));
// this.context === null
}
});
Contexts are merged, so a component can override its owner's context **for its children**:
var ChildComponent = React.createClass({
render: function() {
// this.context === {foo: 'a', bar: 'b'} (for the caller above)
var children = React.withContext({foo: 'c'},() => (
// In GrandchildComponent#render,
// this.context === {foo: 'c', bar: 'b'}
<GrandchildComponent />
));
// this.context === {foo: 'a', bar: 'b'}
}
});
2013-11-19 02:41:42 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
render: function() {
|
|
|
|
|
return <div />;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var instance = <Parent />;
|
|
|
|
|
ReactTestUtils.renderIntoDocument(instance);
|
|
|
|
|
reactComponentExpect(childInstance).scalarContextEqual({foo: 'bar', depth: 0});
|
|
|
|
|
reactComponentExpect(grandchildInstance).scalarContextEqual({foo: 'bar', depth: 1});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should check context types', function() {
|
|
|
|
|
var Component = React.createClass({
|
2013-12-05 09:47:58 +08:00
|
|
|
contextTypes: {
|
|
|
|
|
foo: ReactPropTypes.string.isRequired
|
Contexts
Summary:
adds `this.context` which you can think of as implicit props, which are passed automatically down the //ownership// hierarchy.
Contexts should be used sparingly, since they essentially allow components to communicate with descendants (in the ownership sense, not parenthood sense), which is not usually a good idea. You probably would only use contexts in places where you'd normally use a global, but contexts allow you to override them for certain view subtrees which you can't do with globals.
The context starts out `null`:
var RootComponent = React.createClass({
render: function() {
// this.context === null
}
});
You should **never** mutate the context directly, just like props and state.
You can change the context of your children (the ones you own, not `this.props.children` or via other props) using the new `withContext` method on `React`:
var RootComponent = React.createClass({
render: function() {
// this.context === null
var children = React.withContext({foo: 'a', bar: 'b'}, () => (
// In ChildComponent#render, this.context === {foo: 'a', bar: 'b'}
<ChildComponent />
));
// this.context === null
}
});
Contexts are merged, so a component can override its owner's context **for its children**:
var ChildComponent = React.createClass({
render: function() {
// this.context === {foo: 'a', bar: 'b'} (for the caller above)
var children = React.withContext({foo: 'c'},() => (
// In GrandchildComponent#render,
// this.context === {foo: 'c', bar: 'b'}
<GrandchildComponent />
));
// this.context === {foo: 'a', bar: 'b'}
}
});
2013-11-19 02:41:42 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
render: function() {
|
|
|
|
|
return <div />;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2014-01-17 03:10:57 +08:00
|
|
|
ReactTestUtils.renderIntoDocument(<Component />);
|
|
|
|
|
|
|
|
|
|
expect(console.warn.mock.calls.length).toBe(1);
|
|
|
|
|
expect(console.warn.mock.calls[0][0]).toBe(
|
|
|
|
|
'Warning: Required context `foo` was not specified in `Component`.'
|
Contexts
Summary:
adds `this.context` which you can think of as implicit props, which are passed automatically down the //ownership// hierarchy.
Contexts should be used sparingly, since they essentially allow components to communicate with descendants (in the ownership sense, not parenthood sense), which is not usually a good idea. You probably would only use contexts in places where you'd normally use a global, but contexts allow you to override them for certain view subtrees which you can't do with globals.
The context starts out `null`:
var RootComponent = React.createClass({
render: function() {
// this.context === null
}
});
You should **never** mutate the context directly, just like props and state.
You can change the context of your children (the ones you own, not `this.props.children` or via other props) using the new `withContext` method on `React`:
var RootComponent = React.createClass({
render: function() {
// this.context === null
var children = React.withContext({foo: 'a', bar: 'b'}, () => (
// In ChildComponent#render, this.context === {foo: 'a', bar: 'b'}
<ChildComponent />
));
// this.context === null
}
});
Contexts are merged, so a component can override its owner's context **for its children**:
var ChildComponent = React.createClass({
render: function() {
// this.context === {foo: 'a', bar: 'b'} (for the caller above)
var children = React.withContext({foo: 'c'},() => (
// In GrandchildComponent#render,
// this.context === {foo: 'c', bar: 'b'}
<GrandchildComponent />
));
// this.context === {foo: 'a', bar: 'b'}
}
});
2013-11-19 02:41:42 +08:00
|
|
|
);
|
|
|
|
|
|
2014-01-17 03:10:57 +08:00
|
|
|
React.withContext({foo: 'bar'}, function() {
|
|
|
|
|
ReactTestUtils.renderIntoDocument(<Component />);
|
|
|
|
|
});
|
Contexts
Summary:
adds `this.context` which you can think of as implicit props, which are passed automatically down the //ownership// hierarchy.
Contexts should be used sparingly, since they essentially allow components to communicate with descendants (in the ownership sense, not parenthood sense), which is not usually a good idea. You probably would only use contexts in places where you'd normally use a global, but contexts allow you to override them for certain view subtrees which you can't do with globals.
The context starts out `null`:
var RootComponent = React.createClass({
render: function() {
// this.context === null
}
});
You should **never** mutate the context directly, just like props and state.
You can change the context of your children (the ones you own, not `this.props.children` or via other props) using the new `withContext` method on `React`:
var RootComponent = React.createClass({
render: function() {
// this.context === null
var children = React.withContext({foo: 'a', bar: 'b'}, () => (
// In ChildComponent#render, this.context === {foo: 'a', bar: 'b'}
<ChildComponent />
));
// this.context === null
}
});
Contexts are merged, so a component can override its owner's context **for its children**:
var ChildComponent = React.createClass({
render: function() {
// this.context === {foo: 'a', bar: 'b'} (for the caller above)
var children = React.withContext({foo: 'c'},() => (
// In GrandchildComponent#render,
// this.context === {foo: 'c', bar: 'b'}
<GrandchildComponent />
));
// this.context === {foo: 'a', bar: 'b'}
}
});
2013-11-19 02:41:42 +08:00
|
|
|
|
2014-01-17 03:10:57 +08:00
|
|
|
// Previous call should not error
|
|
|
|
|
expect(console.warn.mock.calls.length).toBe(1);
|
|
|
|
|
|
|
|
|
|
React.withContext({foo: 123}, function() {
|
|
|
|
|
ReactTestUtils.renderIntoDocument(<Component />);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(console.warn.mock.calls.length).toBe(2);
|
|
|
|
|
expect(console.warn.mock.calls[1][0]).toBe(
|
|
|
|
|
'Warning: Invalid context `foo` of type `number` supplied ' +
|
Contexts
Summary:
adds `this.context` which you can think of as implicit props, which are passed automatically down the //ownership// hierarchy.
Contexts should be used sparingly, since they essentially allow components to communicate with descendants (in the ownership sense, not parenthood sense), which is not usually a good idea. You probably would only use contexts in places where you'd normally use a global, but contexts allow you to override them for certain view subtrees which you can't do with globals.
The context starts out `null`:
var RootComponent = React.createClass({
render: function() {
// this.context === null
}
});
You should **never** mutate the context directly, just like props and state.
You can change the context of your children (the ones you own, not `this.props.children` or via other props) using the new `withContext` method on `React`:
var RootComponent = React.createClass({
render: function() {
// this.context === null
var children = React.withContext({foo: 'a', bar: 'b'}, () => (
// In ChildComponent#render, this.context === {foo: 'a', bar: 'b'}
<ChildComponent />
));
// this.context === null
}
});
Contexts are merged, so a component can override its owner's context **for its children**:
var ChildComponent = React.createClass({
render: function() {
// this.context === {foo: 'a', bar: 'b'} (for the caller above)
var children = React.withContext({foo: 'c'},() => (
// In GrandchildComponent#render,
// this.context === {foo: 'c', bar: 'b'}
<GrandchildComponent />
));
// this.context === {foo: 'a', bar: 'b'}
}
});
2013-11-19 02:41:42 +08:00
|
|
|
'to `Component`, expected `string`.'
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should check child context types', function() {
|
|
|
|
|
var Component = React.createClass({
|
2013-12-05 09:47:58 +08:00
|
|
|
childContextTypes: {
|
|
|
|
|
foo: ReactPropTypes.string.isRequired,
|
|
|
|
|
bar: ReactPropTypes.number
|
Contexts
Summary:
adds `this.context` which you can think of as implicit props, which are passed automatically down the //ownership// hierarchy.
Contexts should be used sparingly, since they essentially allow components to communicate with descendants (in the ownership sense, not parenthood sense), which is not usually a good idea. You probably would only use contexts in places where you'd normally use a global, but contexts allow you to override them for certain view subtrees which you can't do with globals.
The context starts out `null`:
var RootComponent = React.createClass({
render: function() {
// this.context === null
}
});
You should **never** mutate the context directly, just like props and state.
You can change the context of your children (the ones you own, not `this.props.children` or via other props) using the new `withContext` method on `React`:
var RootComponent = React.createClass({
render: function() {
// this.context === null
var children = React.withContext({foo: 'a', bar: 'b'}, () => (
// In ChildComponent#render, this.context === {foo: 'a', bar: 'b'}
<ChildComponent />
));
// this.context === null
}
});
Contexts are merged, so a component can override its owner's context **for its children**:
var ChildComponent = React.createClass({
render: function() {
// this.context === {foo: 'a', bar: 'b'} (for the caller above)
var children = React.withContext({foo: 'c'},() => (
// In GrandchildComponent#render,
// this.context === {foo: 'c', bar: 'b'}
<GrandchildComponent />
));
// this.context === {foo: 'a', bar: 'b'}
}
});
2013-11-19 02:41:42 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
getChildContext: function() {
|
|
|
|
|
return this.props.testContext;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
render: function() {
|
|
|
|
|
return <div />;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2014-01-17 03:10:57 +08:00
|
|
|
ReactTestUtils.renderIntoDocument(<Component testContext={{bar: 123}} />);
|
|
|
|
|
|
|
|
|
|
expect(console.warn.mock.calls.length).toBe(1);
|
|
|
|
|
expect(console.warn.mock.calls[0][0]).toBe(
|
|
|
|
|
'Warning: Required child context `foo` was not specified in `Component`.'
|
Contexts
Summary:
adds `this.context` which you can think of as implicit props, which are passed automatically down the //ownership// hierarchy.
Contexts should be used sparingly, since they essentially allow components to communicate with descendants (in the ownership sense, not parenthood sense), which is not usually a good idea. You probably would only use contexts in places where you'd normally use a global, but contexts allow you to override them for certain view subtrees which you can't do with globals.
The context starts out `null`:
var RootComponent = React.createClass({
render: function() {
// this.context === null
}
});
You should **never** mutate the context directly, just like props and state.
You can change the context of your children (the ones you own, not `this.props.children` or via other props) using the new `withContext` method on `React`:
var RootComponent = React.createClass({
render: function() {
// this.context === null
var children = React.withContext({foo: 'a', bar: 'b'}, () => (
// In ChildComponent#render, this.context === {foo: 'a', bar: 'b'}
<ChildComponent />
));
// this.context === null
}
});
Contexts are merged, so a component can override its owner's context **for its children**:
var ChildComponent = React.createClass({
render: function() {
// this.context === {foo: 'a', bar: 'b'} (for the caller above)
var children = React.withContext({foo: 'c'},() => (
// In GrandchildComponent#render,
// this.context === {foo: 'c', bar: 'b'}
<GrandchildComponent />
));
// this.context === {foo: 'a', bar: 'b'}
}
});
2013-11-19 02:41:42 +08:00
|
|
|
);
|
|
|
|
|
|
2014-01-17 03:10:57 +08:00
|
|
|
ReactTestUtils.renderIntoDocument(<Component testContext={{foo: 123}} />);
|
|
|
|
|
|
|
|
|
|
expect(console.warn.mock.calls.length).toBe(2);
|
|
|
|
|
expect(console.warn.mock.calls[1][0]).toBe(
|
|
|
|
|
'Warning: Invalid child context `foo` of type `number` ' +
|
Contexts
Summary:
adds `this.context` which you can think of as implicit props, which are passed automatically down the //ownership// hierarchy.
Contexts should be used sparingly, since they essentially allow components to communicate with descendants (in the ownership sense, not parenthood sense), which is not usually a good idea. You probably would only use contexts in places where you'd normally use a global, but contexts allow you to override them for certain view subtrees which you can't do with globals.
The context starts out `null`:
var RootComponent = React.createClass({
render: function() {
// this.context === null
}
});
You should **never** mutate the context directly, just like props and state.
You can change the context of your children (the ones you own, not `this.props.children` or via other props) using the new `withContext` method on `React`:
var RootComponent = React.createClass({
render: function() {
// this.context === null
var children = React.withContext({foo: 'a', bar: 'b'}, () => (
// In ChildComponent#render, this.context === {foo: 'a', bar: 'b'}
<ChildComponent />
));
// this.context === null
}
});
Contexts are merged, so a component can override its owner's context **for its children**:
var ChildComponent = React.createClass({
render: function() {
// this.context === {foo: 'a', bar: 'b'} (for the caller above)
var children = React.withContext({foo: 'c'},() => (
// In GrandchildComponent#render,
// this.context === {foo: 'c', bar: 'b'}
<GrandchildComponent />
));
// this.context === {foo: 'a', bar: 'b'}
}
});
2013-11-19 02:41:42 +08:00
|
|
|
'supplied to `Component`, expected `string`.'
|
|
|
|
|
);
|
|
|
|
|
|
2014-01-17 03:10:57 +08:00
|
|
|
ReactTestUtils.renderIntoDocument(
|
|
|
|
|
<Component testContext={{foo: 'foo', bar: 123}} />
|
|
|
|
|
);
|
Contexts
Summary:
adds `this.context` which you can think of as implicit props, which are passed automatically down the //ownership// hierarchy.
Contexts should be used sparingly, since they essentially allow components to communicate with descendants (in the ownership sense, not parenthood sense), which is not usually a good idea. You probably would only use contexts in places where you'd normally use a global, but contexts allow you to override them for certain view subtrees which you can't do with globals.
The context starts out `null`:
var RootComponent = React.createClass({
render: function() {
// this.context === null
}
});
You should **never** mutate the context directly, just like props and state.
You can change the context of your children (the ones you own, not `this.props.children` or via other props) using the new `withContext` method on `React`:
var RootComponent = React.createClass({
render: function() {
// this.context === null
var children = React.withContext({foo: 'a', bar: 'b'}, () => (
// In ChildComponent#render, this.context === {foo: 'a', bar: 'b'}
<ChildComponent />
));
// this.context === null
}
});
Contexts are merged, so a component can override its owner's context **for its children**:
var ChildComponent = React.createClass({
render: function() {
// this.context === {foo: 'a', bar: 'b'} (for the caller above)
var children = React.withContext({foo: 'c'},() => (
// In GrandchildComponent#render,
// this.context === {foo: 'c', bar: 'b'}
<GrandchildComponent />
));
// this.context === {foo: 'a', bar: 'b'}
}
});
2013-11-19 02:41:42 +08:00
|
|
|
|
2014-01-17 03:10:57 +08:00
|
|
|
ReactTestUtils.renderIntoDocument(
|
|
|
|
|
<Component testContext={{foo: 'foo'}} />
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Previous calls should not log errors
|
|
|
|
|
expect(console.warn.mock.calls.length).toBe(2);
|
Contexts
Summary:
adds `this.context` which you can think of as implicit props, which are passed automatically down the //ownership// hierarchy.
Contexts should be used sparingly, since they essentially allow components to communicate with descendants (in the ownership sense, not parenthood sense), which is not usually a good idea. You probably would only use contexts in places where you'd normally use a global, but contexts allow you to override them for certain view subtrees which you can't do with globals.
The context starts out `null`:
var RootComponent = React.createClass({
render: function() {
// this.context === null
}
});
You should **never** mutate the context directly, just like props and state.
You can change the context of your children (the ones you own, not `this.props.children` or via other props) using the new `withContext` method on `React`:
var RootComponent = React.createClass({
render: function() {
// this.context === null
var children = React.withContext({foo: 'a', bar: 'b'}, () => (
// In ChildComponent#render, this.context === {foo: 'a', bar: 'b'}
<ChildComponent />
));
// this.context === null
}
});
Contexts are merged, so a component can override its owner's context **for its children**:
var ChildComponent = React.createClass({
render: function() {
// this.context === {foo: 'a', bar: 'b'} (for the caller above)
var children = React.withContext({foo: 'c'},() => (
// In GrandchildComponent#render,
// this.context === {foo: 'c', bar: 'b'}
<GrandchildComponent />
));
// this.context === {foo: 'a', bar: 'b'}
}
});
2013-11-19 02:41:42 +08:00
|
|
|
});
|
2013-11-21 06:03:10 +08:00
|
|
|
|
|
|
|
|
it('should filter out context not in contextTypes', function() {
|
|
|
|
|
var Component = React.createClass({
|
2013-12-05 09:47:58 +08:00
|
|
|
contextTypes: {
|
|
|
|
|
foo: ReactPropTypes.string
|
2013-11-21 06:03:10 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
render: function() {
|
|
|
|
|
return <div />;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var instance = React.withContext({foo: 'abc', bar: 123}, function() {
|
|
|
|
|
return <Component />;
|
|
|
|
|
});
|
|
|
|
|
ReactTestUtils.renderIntoDocument(instance);
|
|
|
|
|
reactComponentExpect(instance).scalarContextEqual({foo: 'abc'});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should filter context properly in callbacks', function() {
|
2013-11-21 06:03:52 +08:00
|
|
|
var actualComponentWillReceiveProps;
|
2013-11-21 06:03:10 +08:00
|
|
|
var actualShouldComponentUpdate;
|
|
|
|
|
var actualComponentWillUpdate;
|
|
|
|
|
var actualComponentDidUpdate;
|
|
|
|
|
|
|
|
|
|
var Parent = React.createClass({
|
2013-12-05 09:47:58 +08:00
|
|
|
childContextTypes: {
|
|
|
|
|
foo: ReactPropTypes.string.isRequired,
|
|
|
|
|
bar: ReactPropTypes.string.isRequired
|
2013-11-21 06:03:10 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
getChildContext: function() {
|
|
|
|
|
return {
|
|
|
|
|
foo: this.props.foo,
|
|
|
|
|
bar: "bar"
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
render: function() {
|
|
|
|
|
return <Component />;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var Component = React.createClass({
|
2013-12-05 09:47:58 +08:00
|
|
|
contextTypes: {
|
|
|
|
|
foo: ReactPropTypes.string
|
2013-11-21 06:03:10 +08:00
|
|
|
},
|
|
|
|
|
|
2013-11-21 06:03:52 +08:00
|
|
|
componentWillReceiveProps: function(nextProps, nextContext) {
|
|
|
|
|
actualComponentWillReceiveProps = nextContext;
|
|
|
|
|
return true;
|
|
|
|
|
},
|
|
|
|
|
|
2013-11-21 06:03:10 +08:00
|
|
|
shouldComponentUpdate: function(nextProps, nextState, nextContext) {
|
|
|
|
|
actualShouldComponentUpdate = nextContext;
|
|
|
|
|
return true;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
componentWillUpdate: function(nextProps, nextState, nextContext) {
|
|
|
|
|
actualComponentWillUpdate = nextContext;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
componentDidUpdate: function(prevProps, prevState, prevContext) {
|
|
|
|
|
actualComponentDidUpdate = prevContext;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
render: function() {
|
|
|
|
|
return <div />;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var instance = <Parent foo="abc" />;
|
|
|
|
|
ReactTestUtils.renderIntoDocument(instance);
|
|
|
|
|
instance.replaceProps({foo: "def"});
|
2013-11-21 06:03:52 +08:00
|
|
|
expect(actualComponentWillReceiveProps).toEqual({foo: 'def'});
|
2013-11-21 06:03:10 +08:00
|
|
|
expect(actualShouldComponentUpdate).toEqual({foo: 'def'});
|
|
|
|
|
expect(actualComponentWillUpdate).toEqual({foo: 'def'});
|
|
|
|
|
expect(actualComponentDidUpdate).toEqual({foo: 'abc'});
|
|
|
|
|
});
|
2013-12-03 09:20:55 +08:00
|
|
|
|
|
|
|
|
it('should support statics', function() {
|
|
|
|
|
var Component = React.createClass({
|
|
|
|
|
statics: {
|
2014-03-05 10:12:13 +08:00
|
|
|
abc: 'def',
|
|
|
|
|
def: 0,
|
|
|
|
|
ghi: null,
|
|
|
|
|
jkl: 'mno'
|
2013-12-03 09:20:55 +08:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
render: function() {
|
|
|
|
|
return <span />;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
var instance = <Component />;
|
|
|
|
|
ReactTestUtils.renderIntoDocument(instance);
|
|
|
|
|
expect(instance.constructor.abc).toBe('def');
|
|
|
|
|
expect(Component.abc).toBe('def');
|
2014-03-05 10:12:13 +08:00
|
|
|
expect(instance.constructor.def).toBe(0);
|
|
|
|
|
expect(Component.def).toBe(0);
|
|
|
|
|
expect(instance.constructor.ghi).toBe(null);
|
|
|
|
|
expect(Component.ghi).toBe(null);
|
|
|
|
|
expect(instance.constructor.jkl).toBe('mno');
|
|
|
|
|
expect(Component.jkl).toBe('mno');
|
2013-12-03 09:20:55 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should support statics in mixins', function() {
|
|
|
|
|
var Mixin = {
|
|
|
|
|
statics: {
|
|
|
|
|
foo: 'bar'
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
var Component = React.createClass({
|
|
|
|
|
mixins: [Mixin],
|
|
|
|
|
|
|
|
|
|
statics: {
|
|
|
|
|
abc: 'def'
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
render: function() {
|
|
|
|
|
return <span />;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
var instance = <Component />;
|
|
|
|
|
ReactTestUtils.renderIntoDocument(instance);
|
|
|
|
|
expect(instance.constructor.foo).toBe('bar');
|
|
|
|
|
expect(Component.foo).toBe('bar');
|
|
|
|
|
expect(instance.constructor.abc).toBe('def');
|
|
|
|
|
expect(Component.abc).toBe('def');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should throw if mixins override each others' statics", function() {
|
|
|
|
|
expect(function() {
|
|
|
|
|
var Mixin = {
|
|
|
|
|
statics: {
|
|
|
|
|
abc: 'foo'
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
React.createClass({
|
2013-12-05 09:47:58 +08:00
|
|
|
mixins: [Mixin],
|
|
|
|
|
|
2013-12-03 09:20:55 +08:00
|
|
|
statics: {
|
|
|
|
|
abc: 'bar'
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
render: function() {
|
|
|
|
|
return <span />;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}).toThrow(
|
2013-12-05 09:47:58 +08:00
|
|
|
'Invariant Violation: ReactCompositeComponent: You are attempting to ' +
|
|
|
|
|
'define `abc` on your component more than once, but that is only ' +
|
|
|
|
|
'supported for functions, which are chained together. This conflict ' +
|
|
|
|
|
'may be due to a mixin.'
|
2013-12-03 09:20:55 +08:00
|
|
|
);
|
|
|
|
|
});
|
2014-01-15 13:11:25 +08:00
|
|
|
|
|
|
|
|
it("should throw if the mixin is a React component", function() {
|
|
|
|
|
expect(function() {
|
|
|
|
|
React.createClass({
|
|
|
|
|
mixins: [<div />],
|
|
|
|
|
|
|
|
|
|
render: function() {
|
|
|
|
|
return <span />;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}).toThrow(
|
|
|
|
|
'Invariant Violation: ReactCompositeComponent: You\'re attempting to ' +
|
|
|
|
|
'use a component as a mixin. Instead, just use a regular object.'
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("should throw if the mixin is a React component class", function() {
|
|
|
|
|
expect(function() {
|
|
|
|
|
var Component = React.createClass({
|
|
|
|
|
render: function() {
|
|
|
|
|
return <span />;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
React.createClass({
|
|
|
|
|
mixins: [Component],
|
|
|
|
|
|
|
|
|
|
render: function() {
|
|
|
|
|
return <span />;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}).toThrow(
|
|
|
|
|
'Invariant Violation: ReactCompositeComponent: You\'re attempting to ' +
|
|
|
|
|
'use a component class as a mixin. Instead, just use a regular object.'
|
|
|
|
|
);
|
|
|
|
|
});
|
2014-02-12 01:00:14 +08:00
|
|
|
|
2014-02-15 07:36:04 +08:00
|
|
|
it('should have bound the mixin methods to the component', function() {
|
|
|
|
|
var mixin = {
|
|
|
|
|
mixinFunc: function() {return this;}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var Component = React.createClass({
|
|
|
|
|
mixins: [mixin],
|
|
|
|
|
componentDidMount: function() {
|
|
|
|
|
expect(this.mixinFunc()).toBe(this);
|
|
|
|
|
},
|
|
|
|
|
render: function() {
|
|
|
|
|
return <span />;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
var instance = <Component />;
|
|
|
|
|
ReactTestUtils.renderIntoDocument(instance);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should include the mixin keys in even if their values are falsy',
|
|
|
|
|
function() {
|
|
|
|
|
var mixin = {
|
|
|
|
|
keyWithNullValue: null,
|
|
|
|
|
randomCounter: 0
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var Component = React.createClass({
|
|
|
|
|
mixins: [mixin],
|
|
|
|
|
componentDidMount: function() {
|
|
|
|
|
expect(this.randomCounter).toBe(0);
|
|
|
|
|
expect(this.keyWithNullValue).toBeNull();
|
|
|
|
|
},
|
|
|
|
|
render: function() {
|
|
|
|
|
return <span />;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
var instance = <Component />;
|
|
|
|
|
ReactTestUtils.renderIntoDocument(instance);
|
|
|
|
|
});
|
|
|
|
|
|
2014-02-12 01:00:14 +08:00
|
|
|
it('should warn if an umounted component is touched', function() {
|
|
|
|
|
spyOn(console, 'warn');
|
|
|
|
|
|
|
|
|
|
var ComponentClass = React.createClass({
|
|
|
|
|
getInitialState: function() {
|
|
|
|
|
return {valueToReturn: 'hi'};
|
|
|
|
|
},
|
|
|
|
|
someMethod: function() {
|
|
|
|
|
return this;
|
|
|
|
|
},
|
|
|
|
|
someOtherMethod: function() {
|
|
|
|
|
return this;
|
|
|
|
|
},
|
|
|
|
|
render: function() {
|
|
|
|
|
return <div></div>;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var descriptor = <ComponentClass />;
|
|
|
|
|
var instance = ReactTestUtils.renderIntoDocument(descriptor);
|
|
|
|
|
instance.someMethod();
|
|
|
|
|
expect(console.warn.argsForCall.length).toBe(0);
|
|
|
|
|
|
|
|
|
|
var unmountedInstance = <ComponentClass />;
|
2014-03-11 06:26:31 +08:00
|
|
|
unmountedInstance.someMethod();
|
2014-02-12 01:00:14 +08:00
|
|
|
expect(console.warn.argsForCall.length).toBe(1);
|
|
|
|
|
|
|
|
|
|
var unmountedInstance2 = <ComponentClass />;
|
|
|
|
|
unmountedInstance2.someOtherMethod = 'override';
|
|
|
|
|
expect(console.warn.argsForCall.length).toBe(2);
|
|
|
|
|
expect(unmountedInstance2.someOtherMethod).toBe('override');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should allow static methods called using type property', function() {
|
|
|
|
|
spyOn(console, 'warn');
|
|
|
|
|
|
|
|
|
|
var ComponentClass = React.createClass({
|
|
|
|
|
statics: {
|
|
|
|
|
someStaticMethod: function() {
|
|
|
|
|
return 'someReturnValue';
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
getInitialState: function() {
|
|
|
|
|
return {valueToReturn: 'hi'};
|
|
|
|
|
},
|
|
|
|
|
render: function() {
|
|
|
|
|
return <div></div>;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var descriptor = <ComponentClass />;
|
|
|
|
|
expect(descriptor.type.someStaticMethod()).toBe('someReturnValue');
|
|
|
|
|
expect(console.warn.argsForCall.length).toBe(0);
|
|
|
|
|
});
|
|
|
|
|
|
2013-05-30 03:46:11 +08:00
|
|
|
});
|