mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Move ReactClass, ReactElement and ReactPropTypes into "traditional"
This moves ReactClass, ReactElement and ReactPropTypes into a legacy folder but since it's not quite legacy yet, I call it "classic". These are "classic" because they are decoupled and can be replaced by ES6 classes, JSX and Flow respectively. This also extracts unit tests from ReactCompositeComponent, which was terribly overloaded, into the new corresponding test suites. There is one weird case for ReactContextValidator. This actually happens in core, and technically belongs to ReactCompositeComponent. I'm not sure we will be able to statically validate contexts so this might be a case for dynamic checks even in the future. Leaving the unit tests in classic until we can figure out what to do with them.
This commit is contained in:
@@ -0,0 +1,247 @@
|
||||
/**
|
||||
* Copyright 2013-2014, 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
|
||||
*/
|
||||
|
||||
// This test doesn't really have a good home yet. I'm leaving it here since this
|
||||
// behavior belongs to the old propTypes system yet is currently implemented
|
||||
// in the core ReactCompositeComponent. It should technically live in core's
|
||||
// test suite but I'll leave it here to indicate that this is an issue that
|
||||
// needs to be fixed.
|
||||
|
||||
"use strict";
|
||||
|
||||
var React;
|
||||
var ReactTestUtils;
|
||||
|
||||
var reactComponentExpect;
|
||||
var mocks;
|
||||
|
||||
describe('ReactContextValidator', function() {
|
||||
beforeEach(function() {
|
||||
require('mock-modules').dumpCache();
|
||||
|
||||
React = require('React');
|
||||
ReactTestUtils = require('ReactTestUtils');
|
||||
reactComponentExpect = require('reactComponentExpect');
|
||||
mocks = require('mocks');
|
||||
|
||||
console.warn = mocks.getMockFunction();
|
||||
});
|
||||
|
||||
// TODO: This behavior creates a runtime dependency on propTypes. We should
|
||||
// ensure that this is not required for ES6 classes with Flow.
|
||||
|
||||
it('should filter out context not in contextTypes', function() {
|
||||
var Component = React.createClass({
|
||||
contextTypes: {
|
||||
foo: React.PropTypes.string
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <div />;
|
||||
}
|
||||
});
|
||||
|
||||
var ComponentInFooBarContext = React.createClass({
|
||||
childContextTypes: {
|
||||
foo: React.PropTypes.string,
|
||||
bar: React.PropTypes.number
|
||||
},
|
||||
|
||||
getChildContext: function() {
|
||||
return {
|
||||
foo: 'abc',
|
||||
bar: 123
|
||||
};
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <Component />;
|
||||
}
|
||||
});
|
||||
|
||||
var instance = ReactTestUtils.renderIntoDocument(<ComponentInFooBarContext />);
|
||||
reactComponentExpect(instance).expectRenderedChild().scalarContextEqual({foo: 'abc'});
|
||||
});
|
||||
|
||||
it('should filter context properly in callbacks', function() {
|
||||
var actualComponentWillReceiveProps;
|
||||
var actualShouldComponentUpdate;
|
||||
var actualComponentWillUpdate;
|
||||
var actualComponentDidUpdate;
|
||||
|
||||
var Parent = React.createClass({
|
||||
childContextTypes: {
|
||||
foo: React.PropTypes.string.isRequired,
|
||||
bar: React.PropTypes.string.isRequired
|
||||
},
|
||||
|
||||
getChildContext: function() {
|
||||
return {
|
||||
foo: this.props.foo,
|
||||
bar: "bar"
|
||||
};
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <Component />;
|
||||
}
|
||||
});
|
||||
|
||||
var Component = React.createClass({
|
||||
contextTypes: {
|
||||
foo: React.PropTypes.string
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(nextProps, nextContext) {
|
||||
actualComponentWillReceiveProps = nextContext;
|
||||
return true;
|
||||
},
|
||||
|
||||
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" />;
|
||||
instance = ReactTestUtils.renderIntoDocument(instance);
|
||||
instance.replaceProps({foo: "def"});
|
||||
expect(actualComponentWillReceiveProps).toEqual({foo: 'def'});
|
||||
expect(actualShouldComponentUpdate).toEqual({foo: 'def'});
|
||||
expect(actualComponentWillUpdate).toEqual({foo: 'def'});
|
||||
expect(actualComponentDidUpdate).toEqual({foo: 'abc'});
|
||||
});
|
||||
|
||||
it('should check context types', function() {
|
||||
var Component = React.createClass({
|
||||
contextTypes: {
|
||||
foo: React.PropTypes.string.isRequired
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <div />;
|
||||
}
|
||||
});
|
||||
|
||||
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`.'
|
||||
);
|
||||
|
||||
var ComponentInFooStringContext = React.createClass({
|
||||
childContextTypes: {
|
||||
foo: React.PropTypes.string
|
||||
},
|
||||
|
||||
getChildContext: function() {
|
||||
return {
|
||||
foo: this.props.fooValue
|
||||
};
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <Component />;
|
||||
}
|
||||
});
|
||||
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<ComponentInFooStringContext fooValue={'bar'} />
|
||||
);
|
||||
|
||||
// Previous call should not error
|
||||
expect(console.warn.mock.calls.length).toBe(1);
|
||||
|
||||
var ComponentInFooNumberContext = React.createClass({
|
||||
childContextTypes: {
|
||||
foo: React.PropTypes.number
|
||||
},
|
||||
|
||||
getChildContext: function() {
|
||||
return {
|
||||
foo: this.props.fooValue
|
||||
};
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <Component />;
|
||||
}
|
||||
});
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<ComponentInFooNumberContext fooValue={123} />);
|
||||
|
||||
expect(console.warn.mock.calls.length).toBe(2);
|
||||
expect(console.warn.mock.calls[1][0]).toBe(
|
||||
'Warning: Invalid context `foo` of type `number` supplied ' +
|
||||
'to `Component`, expected `string`.' +
|
||||
' Check the render method of `ComponentInFooNumberContext`.'
|
||||
);
|
||||
});
|
||||
|
||||
it('should check child context types', function() {
|
||||
var Component = React.createClass({
|
||||
childContextTypes: {
|
||||
foo: React.PropTypes.string.isRequired,
|
||||
bar: React.PropTypes.number
|
||||
},
|
||||
|
||||
getChildContext: function() {
|
||||
return this.props.testContext;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <div />;
|
||||
}
|
||||
});
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<Component testContext={{bar: 123}} />);
|
||||
expect(console.warn.mock.calls.length).toBe(2);
|
||||
expect(console.warn.mock.calls[0][0]).toBe(
|
||||
'Warning: Required child context `foo` was not specified in `Component`.'
|
||||
);
|
||||
expect(console.warn.mock.calls[1][0]).toBe(
|
||||
'Warning: Required child context `foo` was not specified in `Component`.'
|
||||
);
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<Component testContext={{foo: 123}} />);
|
||||
|
||||
expect(console.warn.mock.calls.length).toBe(4);
|
||||
expect(console.warn.mock.calls[3][0]).toBe(
|
||||
'Warning: Invalid child context `foo` of type `number` ' +
|
||||
'supplied to `Component`, expected `string`.'
|
||||
);
|
||||
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<Component testContext={{foo: 'foo', bar: 123}} />
|
||||
);
|
||||
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<Component testContext={{foo: 'foo'}} />
|
||||
);
|
||||
|
||||
// Previous calls should not log errors
|
||||
expect(console.warn.mock.calls.length).toBe(4);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,338 @@
|
||||
/**
|
||||
* Copyright 2013-2014, 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 mocks = require('mocks');
|
||||
|
||||
var React;
|
||||
var ReactTestUtils;
|
||||
var reactComponentExpect;
|
||||
|
||||
describe('ReactClass-spec', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
React = require('React');
|
||||
ReactTestUtils = require('ReactTestUtils');
|
||||
reactComponentExpect = require('reactComponentExpect');
|
||||
});
|
||||
|
||||
it('should throw when `render` is not specified', function() {
|
||||
expect(function() {
|
||||
React.createClass({});
|
||||
}).toThrow(
|
||||
'Invariant Violation: createClass(...): Class specification must ' +
|
||||
'implement a `render` method.'
|
||||
);
|
||||
});
|
||||
|
||||
it('should copy `displayName` onto the Constructor', function() {
|
||||
var TestComponent = React.createClass({
|
||||
render: function() {
|
||||
return <div />;
|
||||
}
|
||||
});
|
||||
|
||||
expect(TestComponent.displayName)
|
||||
.toBe('TestComponent');
|
||||
});
|
||||
|
||||
it('should copy prop types onto the Constructor', function() {
|
||||
var propValidator = mocks.getMockFunction();
|
||||
var TestComponent = React.createClass({
|
||||
propTypes: {
|
||||
value: propValidator
|
||||
},
|
||||
render: function() {
|
||||
return <div />;
|
||||
}
|
||||
});
|
||||
|
||||
expect(TestComponent.propTypes).toBeDefined();
|
||||
expect(TestComponent.propTypes.value)
|
||||
.toBe(propValidator);
|
||||
});
|
||||
|
||||
it('should throw on invalid prop types', function() {
|
||||
expect(function() {
|
||||
React.createClass({
|
||||
displayName: 'Component',
|
||||
propTypes: {
|
||||
prop: null
|
||||
},
|
||||
render: function() {
|
||||
return <span>{this.props.prop}</span>;
|
||||
}
|
||||
});
|
||||
}).toThrow(
|
||||
'Invariant Violation: Component: prop type `prop` 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',
|
||||
contextTypes: {
|
||||
prop: null
|
||||
},
|
||||
render: function() {
|
||||
return <span>{this.props.prop}</span>;
|
||||
}
|
||||
});
|
||||
}).toThrow(
|
||||
'Invariant Violation: Component: context type `prop` 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',
|
||||
childContextTypes: {
|
||||
prop: null
|
||||
},
|
||||
render: function() {
|
||||
return <span>{this.props.prop}</span>;
|
||||
}
|
||||
});
|
||||
}).toThrow(
|
||||
'Invariant Violation: Component: child context type `prop` is invalid; ' +
|
||||
'it must be a function, usually from React.PropTypes.'
|
||||
);
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
||||
it('should throw if a reserved property is in statics', function() {
|
||||
expect(function() {
|
||||
React.createClass({
|
||||
statics: {
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
foo: 0
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <span />;
|
||||
}
|
||||
});
|
||||
}).toThrow(
|
||||
'Invariant Violation: ReactClass: You are attempting to ' +
|
||||
'define a reserved property, `getDefaultProps`, that shouldn\'t be on ' +
|
||||
'the "statics" key. Define it as an instance property instead; it ' +
|
||||
'will still be accessible on the constructor.'
|
||||
);
|
||||
});
|
||||
|
||||
// TODO: Consider actually moving these to statics or drop this unit test.
|
||||
|
||||
xit('should warn when using deprecated non-static spec keys', function() {
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
||||
it('should support statics', function() {
|
||||
var Component = React.createClass({
|
||||
statics: {
|
||||
abc: 'def',
|
||||
def: 0,
|
||||
ghi: null,
|
||||
jkl: 'mno',
|
||||
pqr: function() {
|
||||
return this;
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <span />;
|
||||
}
|
||||
});
|
||||
var instance = <Component />;
|
||||
instance = ReactTestUtils.renderIntoDocument(instance);
|
||||
expect(instance.constructor.abc).toBe('def');
|
||||
expect(Component.abc).toBe('def');
|
||||
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');
|
||||
expect(instance.constructor.pqr()).toBe(Component);
|
||||
expect(Component.pqr()).toBe(Component);
|
||||
});
|
||||
|
||||
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 />;
|
||||
instance = 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() {
|
||||
instance = ReactTestUtils.renderIntoDocument(instance);
|
||||
}).toThrow(
|
||||
'Invariant Violation: Component.getInitialState(): ' +
|
||||
'must return an object or null'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should work with a null getInitialState() return value', function() {
|
||||
var Component = React.createClass({
|
||||
getInitialState: function() {
|
||||
return null;
|
||||
},
|
||||
render: function() {
|
||||
return <span />;
|
||||
}
|
||||
});
|
||||
expect(
|
||||
() => ReactTestUtils.renderIntoDocument(<Component />)
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
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 />;
|
||||
instance = 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() {
|
||||
instance = ReactTestUtils.renderIntoDocument(instance);
|
||||
}).toThrow(
|
||||
'Invariant Violation: Component.getInitialState(): ' +
|
||||
'must return an object or null'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,459 @@
|
||||
/**
|
||||
* Copyright 2013-2014, 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 mocks = require('mocks');
|
||||
|
||||
var React;
|
||||
var ReactTestUtils;
|
||||
var reactComponentExpect;
|
||||
|
||||
var TestComponent;
|
||||
var TestComponentWithPropTypes;
|
||||
var TestComponentWithReverseSpec;
|
||||
var mixinPropValidator;
|
||||
var componentPropValidator;
|
||||
|
||||
describe('ReactClass-mixin', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
React = require('React');
|
||||
ReactTestUtils = require('ReactTestUtils');
|
||||
reactComponentExpect = require('reactComponentExpect');
|
||||
mixinPropValidator = mocks.getMockFunction();
|
||||
componentPropValidator = mocks.getMockFunction();
|
||||
|
||||
var MixinA = {
|
||||
propTypes: {
|
||||
propA: function() {}
|
||||
},
|
||||
componentDidMount: function() {
|
||||
this.props.listener('MixinA didMount');
|
||||
}
|
||||
};
|
||||
|
||||
var MixinB = {
|
||||
mixins: [MixinA],
|
||||
propTypes: {
|
||||
propB: function() {}
|
||||
},
|
||||
componentDidMount: function() {
|
||||
this.props.listener('MixinB didMount');
|
||||
}
|
||||
};
|
||||
|
||||
var MixinBWithReverseSpec = {
|
||||
componentDidMount: function() {
|
||||
this.props.listener('MixinBWithReverseSpec didMount');
|
||||
},
|
||||
mixins: [MixinA]
|
||||
};
|
||||
|
||||
var MixinC = {
|
||||
statics: {
|
||||
staticC: function() {}
|
||||
},
|
||||
componentDidMount: function() {
|
||||
this.props.listener('MixinC didMount');
|
||||
}
|
||||
};
|
||||
|
||||
var MixinD = {
|
||||
propTypes: {
|
||||
value: mixinPropValidator
|
||||
}
|
||||
};
|
||||
|
||||
TestComponent = React.createClass({
|
||||
mixins: [MixinB, MixinC, MixinD],
|
||||
statics: {
|
||||
staticComponent: function() {}
|
||||
},
|
||||
propTypes: {
|
||||
propComponent: function() {}
|
||||
},
|
||||
componentDidMount: function() {
|
||||
this.props.listener('Component didMount');
|
||||
},
|
||||
render: function() {
|
||||
return <div />;
|
||||
}
|
||||
});
|
||||
|
||||
TestComponentWithReverseSpec = React.createClass({
|
||||
render: function() {
|
||||
return <div />;
|
||||
},
|
||||
componentDidMount: function() {
|
||||
this.props.listener('Component didMount');
|
||||
},
|
||||
mixins: [MixinBWithReverseSpec, MixinC, MixinD]
|
||||
});
|
||||
|
||||
TestComponentWithPropTypes = React.createClass({
|
||||
mixins: [MixinD],
|
||||
propTypes: {
|
||||
value: componentPropValidator
|
||||
},
|
||||
render: function() {
|
||||
return <div />;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should support merging propTypes and statics', function() {
|
||||
var listener = mocks.getMockFunction();
|
||||
var instance = <TestComponent listener={listener} />;
|
||||
instance = ReactTestUtils.renderIntoDocument(instance);
|
||||
|
||||
var instancePropTypes = instance.constructor.propTypes;
|
||||
|
||||
expect('propA' in instancePropTypes).toBe(true);
|
||||
expect('propB' in instancePropTypes).toBe(true);
|
||||
expect('propComponent' in instancePropTypes).toBe(true);
|
||||
|
||||
expect('staticC' in TestComponent).toBe(true);
|
||||
expect('staticComponent' in TestComponent).toBe(true);
|
||||
});
|
||||
|
||||
it('should support chaining delegate functions', function() {
|
||||
var listener = mocks.getMockFunction();
|
||||
var instance = <TestComponent listener={listener} />;
|
||||
instance = ReactTestUtils.renderIntoDocument(instance);
|
||||
|
||||
expect(listener.mock.calls).toEqual([
|
||||
['MixinA didMount'],
|
||||
['MixinB didMount'],
|
||||
['MixinC didMount'],
|
||||
['Component didMount']
|
||||
]);
|
||||
});
|
||||
|
||||
it('should chain functions regardless of spec property order', function() {
|
||||
var listener = mocks.getMockFunction();
|
||||
var instance = <TestComponentWithReverseSpec listener={listener} />;
|
||||
instance = ReactTestUtils.renderIntoDocument(instance);
|
||||
|
||||
expect(listener.mock.calls).toEqual([
|
||||
['MixinA didMount'],
|
||||
['MixinBWithReverseSpec didMount'],
|
||||
['MixinC didMount'],
|
||||
['Component didMount']
|
||||
]);
|
||||
});
|
||||
|
||||
it('should validate prop types via mixins', function() {
|
||||
expect(TestComponent.propTypes).toBeDefined();
|
||||
expect(TestComponent.propTypes.value)
|
||||
.toBe(mixinPropValidator);
|
||||
});
|
||||
|
||||
it('should override mixin prop types with class prop types', function() {
|
||||
// Sanity check...
|
||||
expect(componentPropValidator).toNotBe(mixinPropValidator);
|
||||
// Actually check...
|
||||
expect(TestComponentWithPropTypes.propTypes)
|
||||
.toBeDefined();
|
||||
expect(TestComponentWithPropTypes.propTypes.value)
|
||||
.toNotBe(mixinPropValidator);
|
||||
expect(TestComponentWithPropTypes.propTypes.value)
|
||||
.toBe(componentPropValidator);
|
||||
});
|
||||
|
||||
|
||||
it('should support mixins with getInitialState()', function() {
|
||||
var Mixin = {
|
||||
getInitialState: function() {
|
||||
return {mixin: true};
|
||||
}
|
||||
};
|
||||
var Component = React.createClass({
|
||||
mixins: [Mixin],
|
||||
getInitialState: function() {
|
||||
return {component: true};
|
||||
},
|
||||
render: function() {
|
||||
return <span />;
|
||||
}
|
||||
});
|
||||
var instance = <Component />;
|
||||
instance = 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({
|
||||
mixins: [Mixin],
|
||||
getInitialState: function() {
|
||||
return {x: true};
|
||||
},
|
||||
render: function() {
|
||||
return <span />;
|
||||
}
|
||||
});
|
||||
var instance = <Component />;
|
||||
expect(function() {
|
||||
instance = ReactTestUtils.renderIntoDocument(instance);
|
||||
}).toThrow(
|
||||
'Invariant Violation: mergeIntoWithNoDuplicateKeys(): ' +
|
||||
'Tried to merge two objects with the same key: `x`. This conflict ' +
|
||||
'may be due to a mixin; in particular, this may be caused by two ' +
|
||||
'getInitialState() or getDefaultProps() methods returning objects ' +
|
||||
'with clashing keys.'
|
||||
);
|
||||
});
|
||||
|
||||
it('should not mutate objects returned by getInitialState()', function() {
|
||||
var Mixin = {
|
||||
getInitialState: function() {
|
||||
return Object.freeze({mixin: true});
|
||||
}
|
||||
};
|
||||
var Component = React.createClass({
|
||||
mixins: [Mixin],
|
||||
getInitialState: function() {
|
||||
return Object.freeze({component: true});
|
||||
},
|
||||
render: function() {
|
||||
return <span />;
|
||||
}
|
||||
});
|
||||
expect(() => {
|
||||
ReactTestUtils.renderIntoDocument(<Component />);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
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 />;
|
||||
instance = 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({
|
||||
mixins: [Mixin],
|
||||
|
||||
statics: {
|
||||
abc: 'bar'
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <span />;
|
||||
}
|
||||
});
|
||||
}).toThrow(
|
||||
'Invariant Violation: ReactClass: You are attempting to ' +
|
||||
'define `abc` on your component more than once. This conflict may be ' +
|
||||
'due to a mixin.'
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw if mixins override functions in statics", function() {
|
||||
expect(function() {
|
||||
var Mixin = {
|
||||
statics: {
|
||||
abc: function() { console.log('foo'); }
|
||||
}
|
||||
};
|
||||
React.createClass({
|
||||
mixins: [Mixin],
|
||||
|
||||
statics: {
|
||||
abc: function() { console.log('bar'); }
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <span />;
|
||||
}
|
||||
});
|
||||
}).toThrow(
|
||||
'Invariant Violation: ReactClass: You are attempting to ' +
|
||||
'define `abc` on your component more than once. This conflict may be ' +
|
||||
'due to a mixin.'
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw if the mixin is a React component", function() {
|
||||
expect(function() {
|
||||
React.createClass({
|
||||
mixins: [<div />],
|
||||
|
||||
render: function() {
|
||||
return <span />;
|
||||
}
|
||||
});
|
||||
}).toThrow(
|
||||
'Invariant Violation: ReactClass: 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: ReactClass: You\'re attempting to ' +
|
||||
'use a component class as a mixin. Instead, just use a regular object.'
|
||||
);
|
||||
});
|
||||
|
||||
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 />;
|
||||
instance = 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 />;
|
||||
instance = ReactTestUtils.renderIntoDocument(instance);
|
||||
});
|
||||
|
||||
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 />;
|
||||
instance = 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 />;
|
||||
instance = 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 />;
|
||||
instance = ReactTestUtils.renderIntoDocument(instance);
|
||||
expect(instance.state).toEqual({foo: 'bar', x: true});
|
||||
});
|
||||
|
||||
});
|
||||
+56
@@ -11,6 +11,8 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
var mocks;
|
||||
|
||||
var React;
|
||||
var ReactElement;
|
||||
var ReactTestUtils;
|
||||
@@ -21,6 +23,8 @@ describe('ReactElement', function() {
|
||||
beforeEach(function() {
|
||||
require('mock-modules').dumpCache();
|
||||
|
||||
mocks = require('mocks');
|
||||
|
||||
React = require('React');
|
||||
ReactElement = require('ReactElement');
|
||||
ReactTestUtils = require('ReactTestUtils');
|
||||
@@ -370,4 +374,56 @@ describe('ReactElement', function() {
|
||||
expect(element.constructor).toBe(object.constructor);
|
||||
});
|
||||
|
||||
it('should use default prop value when removing a prop', function() {
|
||||
var Component = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {fruit: 'persimmon'};
|
||||
},
|
||||
render: function() {
|
||||
return <span />;
|
||||
}
|
||||
});
|
||||
|
||||
var container = document.createElement('div');
|
||||
var instance = React.render(
|
||||
<Component fruit="mango" />,
|
||||
container
|
||||
);
|
||||
expect(instance.props.fruit).toBe('mango');
|
||||
|
||||
React.render(<Component />, container);
|
||||
expect(instance.props.fruit).toBe('persimmon');
|
||||
});
|
||||
|
||||
it('should normalize props with default values', function() {
|
||||
var warn = console.warn;
|
||||
console.warn = mocks.getMockFunction();
|
||||
|
||||
var Component = React.createClass({
|
||||
propTypes: {prop: React.PropTypes.string.isRequired},
|
||||
getDefaultProps: function() {
|
||||
return {prop: 'testKey'};
|
||||
},
|
||||
getInitialState: function() {
|
||||
return {prop: this.props.prop + 'State'};
|
||||
},
|
||||
render: function() {
|
||||
return <span>{this.props.prop}</span>;
|
||||
}
|
||||
});
|
||||
|
||||
var instance = ReactTestUtils.renderIntoDocument(<Component />);
|
||||
expect(instance.props.prop).toBe('testKey');
|
||||
expect(instance.state.prop).toBe('testKeyState');
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<Component prop={null} />);
|
||||
|
||||
expect(console.warn.mock.calls.length).toBe(1);
|
||||
expect(console.warn.mock.calls[0][0]).toBe(
|
||||
'Warning: Required prop `prop` was not specified in `Component`.'
|
||||
);
|
||||
|
||||
console.warn = warn;
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* Copyright 2013-2014, 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;
|
||||
var ReactTestUtils;
|
||||
|
||||
describe('ReactElementValidator', function() {
|
||||
var ComponentClass;
|
||||
|
||||
beforeEach(function() {
|
||||
require('mock-modules').dumpCache();
|
||||
|
||||
React = require('React');
|
||||
ReactTestUtils = require('ReactTestUtils');
|
||||
ComponentClass = React.createClass({
|
||||
render: function() { return <div />; }
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: These warnings currently come from the composite component, but
|
||||
// they should be moved into the ReactElementValidator.
|
||||
|
||||
it('should give context for PropType errors in nested components.', () => {
|
||||
// In this test, we're making sure that if a proptype error is found in a
|
||||
// component, we give a small hint as to which parent instantiated that
|
||||
// component as per warnings about key usage in ReactElementValidator.
|
||||
spyOn(console, 'warn');
|
||||
var MyComp = React.createClass({
|
||||
propTypes: {
|
||||
color: React.PropTypes.string
|
||||
},
|
||||
render: function() {
|
||||
return <div>My color is {this.color}</div>;
|
||||
}
|
||||
});
|
||||
var ParentComp = React.createClass({
|
||||
render: function() {
|
||||
return <MyComp color={123} />;
|
||||
}
|
||||
});
|
||||
ReactTestUtils.renderIntoDocument(<ParentComp />);
|
||||
expect(console.warn.calls[0].args[0]).toBe(
|
||||
'Warning: Invalid prop `color` of type `number` supplied to `MyComp`, ' +
|
||||
'expected `string`. Check the render method of `ParentComp`.'
|
||||
);
|
||||
});
|
||||
|
||||
it('should check default prop values', function() {
|
||||
spyOn(console, 'warn');
|
||||
|
||||
var Component = React.createClass({
|
||||
propTypes: {prop: React.PropTypes.string.isRequired},
|
||||
getDefaultProps: function() {
|
||||
return {prop: null};
|
||||
},
|
||||
render: function() {
|
||||
return <span>{this.props.prop}</span>;
|
||||
}
|
||||
});
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<Component />);
|
||||
|
||||
expect(console.warn.calls.length).toBe(1);
|
||||
expect(console.warn.calls[0].args[0]).toBe(
|
||||
'Warning: Required prop `prop` was not specified in `Component`.'
|
||||
);
|
||||
});
|
||||
|
||||
it('should check declared prop types', function() {
|
||||
spyOn(console, 'warn');
|
||||
|
||||
var Component = React.createClass({
|
||||
propTypes: {
|
||||
prop: React.PropTypes.string.isRequired
|
||||
},
|
||||
render: function() {
|
||||
return <span>{this.props.prop}</span>;
|
||||
}
|
||||
});
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<Component />);
|
||||
ReactTestUtils.renderIntoDocument(<Component prop={42} />);
|
||||
|
||||
expect(console.warn.calls.length).toBe(2);
|
||||
expect(console.warn.calls[0].args[0]).toBe(
|
||||
'Warning: Required prop `prop` was not specified in `Component`.'
|
||||
);
|
||||
|
||||
expect(console.warn.calls[1].args[0]).toBe(
|
||||
'Warning: Invalid prop `prop` of type `number` supplied to ' +
|
||||
'`Component`, expected `string`.'
|
||||
);
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<Component prop="string" />);
|
||||
|
||||
// Should not error for strings
|
||||
expect(console.warn.calls.length).toBe(2);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -85,31 +85,6 @@ describe('ReactCompositeComponent', function() {
|
||||
console.warn = warn;
|
||||
});
|
||||
|
||||
it('should give context for PropType errors in nested components.', () => {
|
||||
// In this test, we're making sure that if a proptype error is found in a
|
||||
// component, we give a small hint as to which parent instantiated that
|
||||
// component as per warnings about key usage in ReactElementValidator.
|
||||
spyOn(console, 'warn');
|
||||
var MyComp = React.createClass({
|
||||
propTypes: {
|
||||
color: ReactPropTypes.string
|
||||
},
|
||||
render: function() {
|
||||
return <div>My color is {this.color}</div>;
|
||||
}
|
||||
});
|
||||
var ParentComp = React.createClass({
|
||||
render: function() {
|
||||
return <MyComp color={123} />;
|
||||
}
|
||||
});
|
||||
ReactTestUtils.renderIntoDocument(<ParentComp />);
|
||||
expect(console.warn.calls[0].args[0]).toBe(
|
||||
'Warning: Invalid prop `color` of type `number` supplied to `MyComp`, ' +
|
||||
'expected `string`. Check the render method of `ParentComp`.'
|
||||
);
|
||||
});
|
||||
|
||||
it('should support rendering to different child types over time', function() {
|
||||
var instance = <MorphingComponent />;
|
||||
instance = ReactTestUtils.renderIntoDocument(instance);
|
||||
@@ -299,152 +274,6 @@ describe('ReactCompositeComponent', function() {
|
||||
expect(inputProps.prop).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should use default prop value when removing a prop', function() {
|
||||
var Component = React.createClass({
|
||||
getDefaultProps: function() {
|
||||
return {fruit: 'persimmon'};
|
||||
},
|
||||
render: function() {
|
||||
return <span />;
|
||||
}
|
||||
});
|
||||
|
||||
var container = document.createElement('div');
|
||||
var instance = React.render(
|
||||
<Component fruit="mango" />,
|
||||
container
|
||||
);
|
||||
expect(instance.props.fruit).toBe('mango');
|
||||
|
||||
React.render(<Component />, container);
|
||||
expect(instance.props.fruit).toBe('persimmon');
|
||||
});
|
||||
|
||||
it('should normalize props with default values', function() {
|
||||
var Component = React.createClass({
|
||||
propTypes: {prop: ReactPropTypes.string.isRequired},
|
||||
getDefaultProps: function() {
|
||||
return {prop: 'testKey'};
|
||||
},
|
||||
getInitialState: function() {
|
||||
return {prop: this.props.prop + 'State'};
|
||||
},
|
||||
render: function() {
|
||||
return <span>{this.props.prop}</span>;
|
||||
}
|
||||
});
|
||||
|
||||
var instance = ReactTestUtils.renderIntoDocument(<Component />);
|
||||
reactComponentExpect(instance).scalarPropsEqual({prop: 'testKey'});
|
||||
reactComponentExpect(instance).scalarStateEqual({prop: 'testKeyState'});
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<Component prop={null} />);
|
||||
|
||||
expect(console.warn.mock.calls.length).toBe(1);
|
||||
expect(console.warn.mock.calls[0][0]).toBe(
|
||||
'Warning: Required prop `prop` was not specified in `Component`.'
|
||||
);
|
||||
});
|
||||
|
||||
it('should check default prop values', function() {
|
||||
var Component = React.createClass({
|
||||
propTypes: {prop: ReactPropTypes.string.isRequired},
|
||||
getDefaultProps: function() {
|
||||
return {prop: null};
|
||||
},
|
||||
render: function() {
|
||||
return <span>{this.props.prop}</span>;
|
||||
}
|
||||
});
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<Component />);
|
||||
|
||||
expect(console.warn.mock.calls.length).toBe(1);
|
||||
expect(console.warn.mock.calls[0][0]).toBe(
|
||||
'Warning: Required prop `prop` was not specified in `Component`.'
|
||||
);
|
||||
});
|
||||
|
||||
it('should check declared prop types', function() {
|
||||
var Component = React.createClass({
|
||||
propTypes: {
|
||||
prop: ReactPropTypes.string.isRequired
|
||||
},
|
||||
render: function() {
|
||||
return <span>{this.props.prop}</span>;
|
||||
}
|
||||
});
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<Component />);
|
||||
ReactTestUtils.renderIntoDocument(<Component prop={42} />);
|
||||
|
||||
expect(console.warn.mock.calls.length).toBe(2);
|
||||
expect(console.warn.mock.calls[0][0]).toBe(
|
||||
'Warning: Required prop `prop` was not specified in `Component`.'
|
||||
);
|
||||
|
||||
expect(console.warn.mock.calls[1][0]).toBe(
|
||||
'Warning: Invalid prop `prop` of type `number` supplied to ' +
|
||||
'`Component`, expected `string`.'
|
||||
);
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<Component prop="string" />);
|
||||
|
||||
// Should not error for strings
|
||||
expect(console.warn.mock.calls.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should throw on invalid prop types', function() {
|
||||
expect(function() {
|
||||
React.createClass({
|
||||
displayName: 'Component',
|
||||
propTypes: {
|
||||
prop: null
|
||||
},
|
||||
render: function() {
|
||||
return <span>{this.props.prop}</span>;
|
||||
}
|
||||
});
|
||||
}).toThrow(
|
||||
'Invariant Violation: Component: prop type `prop` 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',
|
||||
contextTypes: {
|
||||
prop: null
|
||||
},
|
||||
render: function() {
|
||||
return <span>{this.props.prop}</span>;
|
||||
}
|
||||
});
|
||||
}).toThrow(
|
||||
'Invariant Violation: Component: context type `prop` 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',
|
||||
childContextTypes: {
|
||||
prop: null
|
||||
},
|
||||
render: function() {
|
||||
return <span>{this.props.prop}</span>;
|
||||
}
|
||||
});
|
||||
}).toThrow(
|
||||
'Invariant Violation: Component: child context type `prop` is invalid; ' +
|
||||
'it must be a function, usually from React.PropTypes.'
|
||||
);
|
||||
});
|
||||
|
||||
it('should not allow `forceUpdate` on unmounted components', function() {
|
||||
var container = document.createElement('div');
|
||||
document.documentElement.appendChild(container);
|
||||
@@ -574,228 +403,6 @@ describe('ReactCompositeComponent', function() {
|
||||
expect(ReactCurrentOwner.current).toBe(null);
|
||||
});
|
||||
|
||||
it('should support mixins with getInitialState()', function() {
|
||||
var Mixin = {
|
||||
getInitialState: function() {
|
||||
return {mixin: true};
|
||||
}
|
||||
};
|
||||
var Component = React.createClass({
|
||||
mixins: [Mixin],
|
||||
getInitialState: function() {
|
||||
return {component: true};
|
||||
},
|
||||
render: function() {
|
||||
return <span />;
|
||||
}
|
||||
});
|
||||
var instance = <Component />;
|
||||
instance = 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({
|
||||
mixins: [Mixin],
|
||||
getInitialState: function() {
|
||||
return {x: true};
|
||||
},
|
||||
render: function() {
|
||||
return <span />;
|
||||
}
|
||||
});
|
||||
var instance = <Component />;
|
||||
expect(function() {
|
||||
instance = ReactTestUtils.renderIntoDocument(instance);
|
||||
}).toThrow(
|
||||
'Invariant Violation: mergeIntoWithNoDuplicateKeys(): ' +
|
||||
'Tried to merge two objects with the same key: `x`. This conflict ' +
|
||||
'may be due to a mixin; in particular, this may be caused by two ' +
|
||||
'getInitialState() or getDefaultProps() methods returning objects ' +
|
||||
'with clashing keys.'
|
||||
);
|
||||
});
|
||||
|
||||
it('should not mutate objects returned by getInitialState()', function() {
|
||||
var Mixin = {
|
||||
getInitialState: function() {
|
||||
return Object.freeze({mixin: true});
|
||||
}
|
||||
};
|
||||
var Component = React.createClass({
|
||||
mixins: [Mixin],
|
||||
getInitialState: function() {
|
||||
return Object.freeze({component: true});
|
||||
},
|
||||
render: function() {
|
||||
return <span />;
|
||||
}
|
||||
});
|
||||
expect(() => {
|
||||
ReactTestUtils.renderIntoDocument(<Component />);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
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 />;
|
||||
instance = 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() {
|
||||
instance = ReactTestUtils.renderIntoDocument(instance);
|
||||
}).toThrow(
|
||||
'Invariant Violation: Component.getInitialState(): ' +
|
||||
'must return an object or null'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should work with a null getInitialState() return value', function() {
|
||||
var Component = React.createClass({
|
||||
getInitialState: function() {
|
||||
return null;
|
||||
},
|
||||
render: function() {
|
||||
return <span />;
|
||||
}
|
||||
});
|
||||
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 />;
|
||||
instance = 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 />;
|
||||
instance = 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 />;
|
||||
instance = ReactTestUtils.renderIntoDocument(instance);
|
||||
expect(instance.state).toEqual({foo: 'bar', x: true});
|
||||
});
|
||||
|
||||
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 />;
|
||||
instance = 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() {
|
||||
instance = ReactTestUtils.renderIntoDocument(instance);
|
||||
}).toThrow(
|
||||
'Invariant Violation: Component.getInitialState(): ' +
|
||||
'must return an object or null'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should call componentWillUnmount before unmounting', function() {
|
||||
var container = document.createElement('div');
|
||||
var innerUnmounted = false;
|
||||
@@ -866,88 +473,6 @@ describe('ReactCompositeComponent', function() {
|
||||
}
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
||||
xit('should warn when using deprecated non-static spec keys', function() {
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
||||
it('should pass context', function() {
|
||||
var childInstance = null;
|
||||
var grandchildInstance = null;
|
||||
@@ -1074,413 +599,6 @@ describe('ReactCompositeComponent', function() {
|
||||
|
||||
});
|
||||
|
||||
it('should check context types', function() {
|
||||
var Component = React.createClass({
|
||||
contextTypes: {
|
||||
foo: ReactPropTypes.string.isRequired
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <div />;
|
||||
}
|
||||
});
|
||||
|
||||
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`.'
|
||||
);
|
||||
|
||||
var ComponentInFooStringContext = React.createClass({
|
||||
childContextTypes: {
|
||||
foo: ReactPropTypes.string
|
||||
},
|
||||
|
||||
getChildContext: function() {
|
||||
return {
|
||||
foo: this.props.fooValue
|
||||
};
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <Component />;
|
||||
}
|
||||
});
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<ComponentInFooStringContext fooValue={'bar'} />);
|
||||
|
||||
// Previous call should not error
|
||||
expect(console.warn.mock.calls.length).toBe(1);
|
||||
|
||||
var ComponentInFooNumberContext = React.createClass({
|
||||
childContextTypes: {
|
||||
foo: ReactPropTypes.number
|
||||
},
|
||||
|
||||
getChildContext: function() {
|
||||
return {
|
||||
foo: this.props.fooValue
|
||||
};
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <Component />;
|
||||
}
|
||||
});
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<ComponentInFooNumberContext fooValue={123} />);
|
||||
|
||||
expect(console.warn.mock.calls.length).toBe(2);
|
||||
expect(console.warn.mock.calls[1][0]).toBe(
|
||||
'Warning: Invalid context `foo` of type `number` supplied ' +
|
||||
'to `Component`, expected `string`.' +
|
||||
' Check the render method of `ComponentInFooNumberContext`.'
|
||||
);
|
||||
});
|
||||
|
||||
it('should check child context types', function() {
|
||||
var Component = React.createClass({
|
||||
childContextTypes: {
|
||||
foo: ReactPropTypes.string.isRequired,
|
||||
bar: ReactPropTypes.number
|
||||
},
|
||||
|
||||
getChildContext: function() {
|
||||
return this.props.testContext;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <div />;
|
||||
}
|
||||
});
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<Component testContext={{bar: 123}} />);
|
||||
expect(console.warn.mock.calls.length).toBe(2);
|
||||
expect(console.warn.mock.calls[0][0]).toBe(
|
||||
'Warning: Required child context `foo` was not specified in `Component`.'
|
||||
);
|
||||
expect(console.warn.mock.calls[1][0]).toBe(
|
||||
'Warning: Required child context `foo` was not specified in `Component`.'
|
||||
);
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<Component testContext={{foo: 123}} />);
|
||||
|
||||
expect(console.warn.mock.calls.length).toBe(4);
|
||||
expect(console.warn.mock.calls[3][0]).toBe(
|
||||
'Warning: Invalid child context `foo` of type `number` ' +
|
||||
'supplied to `Component`, expected `string`.'
|
||||
);
|
||||
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<Component testContext={{foo: 'foo', bar: 123}} />
|
||||
);
|
||||
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<Component testContext={{foo: 'foo'}} />
|
||||
);
|
||||
|
||||
// Previous calls should not log errors
|
||||
expect(console.warn.mock.calls.length).toBe(4);
|
||||
});
|
||||
|
||||
it('should filter out context not in contextTypes', function() {
|
||||
var Component = React.createClass({
|
||||
contextTypes: {
|
||||
foo: ReactPropTypes.string
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <div />;
|
||||
}
|
||||
});
|
||||
|
||||
var ComponentInFooBarContext = React.createClass({
|
||||
childContextTypes: {
|
||||
foo: ReactPropTypes.string,
|
||||
bar: ReactPropTypes.number
|
||||
},
|
||||
|
||||
getChildContext: function() {
|
||||
return {
|
||||
foo: 'abc',
|
||||
bar: 123
|
||||
};
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <Component />;
|
||||
}
|
||||
});
|
||||
|
||||
var instance = ReactTestUtils.renderIntoDocument(<ComponentInFooBarContext />);
|
||||
reactComponentExpect(instance).expectRenderedChild().scalarContextEqual({foo: 'abc'});
|
||||
});
|
||||
|
||||
it('should filter context properly in callbacks', function() {
|
||||
var actualComponentWillReceiveProps;
|
||||
var actualShouldComponentUpdate;
|
||||
var actualComponentWillUpdate;
|
||||
var actualComponentDidUpdate;
|
||||
|
||||
var Parent = React.createClass({
|
||||
childContextTypes: {
|
||||
foo: ReactPropTypes.string.isRequired,
|
||||
bar: ReactPropTypes.string.isRequired
|
||||
},
|
||||
|
||||
getChildContext: function() {
|
||||
return {
|
||||
foo: this.props.foo,
|
||||
bar: "bar"
|
||||
};
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <Component />;
|
||||
}
|
||||
});
|
||||
|
||||
var Component = React.createClass({
|
||||
contextTypes: {
|
||||
foo: ReactPropTypes.string
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(nextProps, nextContext) {
|
||||
actualComponentWillReceiveProps = nextContext;
|
||||
return true;
|
||||
},
|
||||
|
||||
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" />;
|
||||
instance = ReactTestUtils.renderIntoDocument(instance);
|
||||
instance.replaceProps({foo: "def"});
|
||||
expect(actualComponentWillReceiveProps).toEqual({foo: 'def'});
|
||||
expect(actualShouldComponentUpdate).toEqual({foo: 'def'});
|
||||
expect(actualComponentWillUpdate).toEqual({foo: 'def'});
|
||||
expect(actualComponentDidUpdate).toEqual({foo: 'abc'});
|
||||
});
|
||||
|
||||
it('should support statics', function() {
|
||||
var Component = React.createClass({
|
||||
statics: {
|
||||
abc: 'def',
|
||||
def: 0,
|
||||
ghi: null,
|
||||
jkl: 'mno',
|
||||
pqr: function() {
|
||||
return this;
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <span />;
|
||||
}
|
||||
});
|
||||
var instance = <Component />;
|
||||
instance = ReactTestUtils.renderIntoDocument(instance);
|
||||
expect(instance.constructor.abc).toBe('def');
|
||||
expect(Component.abc).toBe('def');
|
||||
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');
|
||||
expect(instance.constructor.pqr()).toBe(Component);
|
||||
expect(Component.pqr()).toBe(Component);
|
||||
});
|
||||
|
||||
it('should throw if a reserved property is in statics', function() {
|
||||
expect(function() {
|
||||
React.createClass({
|
||||
statics: {
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
foo: 0
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <span />;
|
||||
}
|
||||
});
|
||||
}).toThrow(
|
||||
'Invariant Violation: ReactClass: You are attempting to ' +
|
||||
'define a reserved property, `getDefaultProps`, that shouldn\'t be on ' +
|
||||
'the "statics" key. Define it as an instance property instead; it ' +
|
||||
'will still be accessible on the constructor.'
|
||||
);
|
||||
});
|
||||
|
||||
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 />;
|
||||
instance = 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({
|
||||
mixins: [Mixin],
|
||||
|
||||
statics: {
|
||||
abc: 'bar'
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <span />;
|
||||
}
|
||||
});
|
||||
}).toThrow(
|
||||
'Invariant Violation: ReactClass: You are attempting to ' +
|
||||
'define `abc` on your component more than once. This conflict may be ' +
|
||||
'due to a mixin.'
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw if mixins override functions in statics", function() {
|
||||
expect(function() {
|
||||
var Mixin = {
|
||||
statics: {
|
||||
abc: function() { console.log('foo'); }
|
||||
}
|
||||
};
|
||||
React.createClass({
|
||||
mixins: [Mixin],
|
||||
|
||||
statics: {
|
||||
abc: function() { console.log('bar'); }
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <span />;
|
||||
}
|
||||
});
|
||||
}).toThrow(
|
||||
'Invariant Violation: ReactClass: You are attempting to ' +
|
||||
'define `abc` on your component more than once. This conflict may be ' +
|
||||
'due to a mixin.'
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw if the mixin is a React component", function() {
|
||||
expect(function() {
|
||||
React.createClass({
|
||||
mixins: [<div />],
|
||||
|
||||
render: function() {
|
||||
return <span />;
|
||||
}
|
||||
});
|
||||
}).toThrow(
|
||||
'Invariant Violation: ReactClass: 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: ReactClass: You\'re attempting to ' +
|
||||
'use a component class as a mixin. Instead, just use a regular object.'
|
||||
);
|
||||
});
|
||||
|
||||
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 />;
|
||||
instance = 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 />;
|
||||
instance = ReactTestUtils.renderIntoDocument(instance);
|
||||
});
|
||||
|
||||
it('should disallow nested render calls', function() {
|
||||
spyOn(console, 'warn');
|
||||
var Inner = React.createClass({
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-2014, 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 mocks = require('mocks');
|
||||
|
||||
var React;
|
||||
var ReactTestUtils;
|
||||
var reactComponentExpect;
|
||||
|
||||
var TestComponent;
|
||||
var TestComponentWithPropTypes;
|
||||
var TestComponentWithReverseSpec;
|
||||
var mixinPropValidator;
|
||||
var componentPropValidator;
|
||||
|
||||
describe('ReactCompositeComponent-mixin', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
React = require('React');
|
||||
ReactTestUtils = require('ReactTestUtils');
|
||||
reactComponentExpect = require('reactComponentExpect');
|
||||
mixinPropValidator = mocks.getMockFunction();
|
||||
componentPropValidator = mocks.getMockFunction();
|
||||
|
||||
var MixinA = {
|
||||
propTypes: {
|
||||
propA: function() {}
|
||||
},
|
||||
componentDidMount: function() {
|
||||
this.props.listener('MixinA didMount');
|
||||
}
|
||||
};
|
||||
|
||||
var MixinB = {
|
||||
mixins: [MixinA],
|
||||
propTypes: {
|
||||
propB: function() {}
|
||||
},
|
||||
componentDidMount: function() {
|
||||
this.props.listener('MixinB didMount');
|
||||
}
|
||||
};
|
||||
|
||||
var MixinBWithReverseSpec = {
|
||||
componentDidMount: function() {
|
||||
this.props.listener('MixinBWithReverseSpec didMount');
|
||||
},
|
||||
mixins: [MixinA]
|
||||
};
|
||||
|
||||
var MixinC = {
|
||||
statics: {
|
||||
staticC: function() {}
|
||||
},
|
||||
componentDidMount: function() {
|
||||
this.props.listener('MixinC didMount');
|
||||
}
|
||||
};
|
||||
|
||||
var MixinD = {
|
||||
propTypes: {
|
||||
value: mixinPropValidator
|
||||
}
|
||||
};
|
||||
|
||||
TestComponent = React.createClass({
|
||||
mixins: [MixinB, MixinC, MixinD],
|
||||
statics: {
|
||||
staticComponent: function() {}
|
||||
},
|
||||
propTypes: {
|
||||
propComponent: function() {}
|
||||
},
|
||||
componentDidMount: function() {
|
||||
this.props.listener('Component didMount');
|
||||
},
|
||||
render: function() {
|
||||
return <div />;
|
||||
}
|
||||
});
|
||||
|
||||
TestComponentWithReverseSpec = React.createClass({
|
||||
render: function() {
|
||||
return <div />;
|
||||
},
|
||||
componentDidMount: function() {
|
||||
this.props.listener('Component didMount');
|
||||
},
|
||||
mixins: [MixinBWithReverseSpec, MixinC, MixinD]
|
||||
});
|
||||
|
||||
TestComponentWithPropTypes = React.createClass({
|
||||
mixins: [MixinD],
|
||||
propTypes: {
|
||||
value: componentPropValidator
|
||||
},
|
||||
render: function() {
|
||||
return <div />;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should support merging propTypes and statics', function() {
|
||||
var listener = mocks.getMockFunction();
|
||||
var instance = <TestComponent listener={listener} />;
|
||||
instance = ReactTestUtils.renderIntoDocument(instance);
|
||||
|
||||
var instancePropTypes = instance.constructor.propTypes;
|
||||
|
||||
expect('propA' in instancePropTypes).toBe(true);
|
||||
expect('propB' in instancePropTypes).toBe(true);
|
||||
expect('propComponent' in instancePropTypes).toBe(true);
|
||||
|
||||
expect('staticC' in TestComponent).toBe(true);
|
||||
expect('staticComponent' in TestComponent).toBe(true);
|
||||
});
|
||||
|
||||
it('should support chaining delegate functions', function() {
|
||||
var listener = mocks.getMockFunction();
|
||||
var instance = <TestComponent listener={listener} />;
|
||||
instance = ReactTestUtils.renderIntoDocument(instance);
|
||||
|
||||
expect(listener.mock.calls).toEqual([
|
||||
['MixinA didMount'],
|
||||
['MixinB didMount'],
|
||||
['MixinC didMount'],
|
||||
['Component didMount']
|
||||
]);
|
||||
});
|
||||
|
||||
it('should chain functions regardless of spec property order', function() {
|
||||
var listener = mocks.getMockFunction();
|
||||
var instance = <TestComponentWithReverseSpec listener={listener} />;
|
||||
instance = ReactTestUtils.renderIntoDocument(instance);
|
||||
|
||||
expect(listener.mock.calls).toEqual([
|
||||
['MixinA didMount'],
|
||||
['MixinBWithReverseSpec didMount'],
|
||||
['MixinC didMount'],
|
||||
['Component didMount']
|
||||
]);
|
||||
});
|
||||
|
||||
it('should validate prop types via mixins', function() {
|
||||
expect(TestComponent.propTypes).toBeDefined();
|
||||
expect(TestComponent.propTypes.value)
|
||||
.toBe(mixinPropValidator);
|
||||
});
|
||||
|
||||
it('should override mixin prop types with class prop types', function() {
|
||||
// Sanity check...
|
||||
expect(componentPropValidator).toNotBe(mixinPropValidator);
|
||||
// Actually check...
|
||||
expect(TestComponentWithPropTypes.propTypes)
|
||||
.toBeDefined();
|
||||
expect(TestComponentWithPropTypes.propTypes.value)
|
||||
.toNotBe(mixinPropValidator);
|
||||
expect(TestComponentWithPropTypes.propTypes.value)
|
||||
.toBe(componentPropValidator);
|
||||
});
|
||||
});
|
||||
@@ -1,63 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-2014, 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 mocks = require('mocks');
|
||||
|
||||
var React;
|
||||
var ReactTestUtils;
|
||||
var reactComponentExpect;
|
||||
|
||||
describe('ReactCompositeComponent-spec', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
React = require('React');
|
||||
ReactTestUtils = require('ReactTestUtils');
|
||||
reactComponentExpect = require('reactComponentExpect');
|
||||
});
|
||||
|
||||
it('should throw when `render` is not specified', function() {
|
||||
expect(function() {
|
||||
React.createClass({});
|
||||
}).toThrow(
|
||||
'Invariant Violation: createClass(...): Class specification must ' +
|
||||
'implement a `render` method.'
|
||||
);
|
||||
});
|
||||
|
||||
it('should copy `displayName` onto the Constructor', function() {
|
||||
var TestComponent = React.createClass({
|
||||
render: function() {
|
||||
return <div />;
|
||||
}
|
||||
});
|
||||
|
||||
expect(TestComponent.displayName)
|
||||
.toBe('TestComponent');
|
||||
});
|
||||
|
||||
it('should copy prop types onto the Constructor', function() {
|
||||
var propValidator = mocks.getMockFunction();
|
||||
var TestComponent = React.createClass({
|
||||
propTypes: {
|
||||
value: propValidator
|
||||
},
|
||||
render: function() {
|
||||
return <div />;
|
||||
}
|
||||
});
|
||||
|
||||
expect(TestComponent.propTypes).toBeDefined();
|
||||
expect(TestComponent.propTypes.value)
|
||||
.toBe(propValidator);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user