mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Delete createClass
Remove createClass from isomorphic package
This commit is contained in:
1 parent
c2309bf4b5
commit
5cfaa7cf68
6 files changed
-2073
No files matched your search
@@ -13,7 +13,6 @@
|
||||
|
||||
var ReactBaseClasses = require('ReactBaseClasses');
|
||||
var ReactChildren = require('ReactChildren');
|
||||
var ReactClass = require('ReactClass');
|
||||
var ReactDOMFactories = require('ReactDOMFactories');
|
||||
var ReactElement = require('ReactElement');
|
||||
var ReactPropTypes = require('ReactPropTypes');
|
||||
@@ -75,7 +74,6 @@ var React = {
|
||||
// Classic
|
||||
|
||||
PropTypes: ReactPropTypes,
|
||||
createClass: ReactClass.createClass,
|
||||
createFactory: createFactory,
|
||||
createMixin: createMixin,
|
||||
|
||||
|
||||
@@ -1,833 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactClass
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var ReactBaseClasses = require('ReactBaseClasses');
|
||||
var ReactElement = require('ReactElement');
|
||||
var ReactNoopUpdateQueue = require('ReactNoopUpdateQueue');
|
||||
|
||||
var emptyObject = require('fbjs/lib/emptyObject');
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
var warning = require('fbjs/lib/warning');
|
||||
|
||||
var ReactComponent = ReactBaseClasses.Component;
|
||||
|
||||
var MIXINS_KEY = 'mixins';
|
||||
|
||||
// Helper function to allow the creation of anonymous functions which do not
|
||||
// have .name set to the name of the variable being assigned to.
|
||||
function identity(fn) {
|
||||
return fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Policies that describe methods in `ReactClassInterface`.
|
||||
*/
|
||||
type SpecPolicy =
|
||||
/**
|
||||
* These methods may be defined only once by the class specification or mixin.
|
||||
*/
|
||||
| 'DEFINE_ONCE' /**
|
||||
* These methods may be defined by both the class specification and mixins.
|
||||
* Subsequent definitions will be chained. These methods must return void.
|
||||
*/
|
||||
| 'DEFINE_MANY' /**
|
||||
* These methods are overriding the base class.
|
||||
*/
|
||||
| 'OVERRIDE_BASE' /**
|
||||
* These methods are similar to DEFINE_MANY, except we assume they return
|
||||
* objects. We try to merge the keys of the return values of all the mixed in
|
||||
* functions. If there is a key conflict we throw.
|
||||
*/
|
||||
| 'DEFINE_MANY_MERGED';
|
||||
|
||||
/**
|
||||
* Composite components are higher-level components that compose other composite
|
||||
* or host components.
|
||||
*
|
||||
* To create a new type of `ReactClass`, pass a specification of
|
||||
* your new class to `React.createClass`. The only requirement of your class
|
||||
* specification is that you implement a `render` method.
|
||||
*
|
||||
* var MyComponent = React.createClass({
|
||||
* render: function() {
|
||||
* return <div>Hello World</div>;
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* The class specification supports a specific protocol of methods that have
|
||||
* special meaning (e.g. `render`). See `ReactClassInterface` for
|
||||
* more the comprehensive protocol. Any other properties and methods in the
|
||||
* class specification will be available on the prototype.
|
||||
*
|
||||
* @interface ReactClassInterface
|
||||
* @internal
|
||||
*/
|
||||
var ReactClassInterface: {[key: string]: SpecPolicy} = {
|
||||
/**
|
||||
* An array of Mixin objects to include when defining your component.
|
||||
*
|
||||
* @type {array}
|
||||
* @optional
|
||||
*/
|
||||
mixins: 'DEFINE_MANY',
|
||||
|
||||
/**
|
||||
* An object containing properties and methods that should be defined on
|
||||
* the component's constructor instead of its prototype (static methods).
|
||||
*
|
||||
* @type {object}
|
||||
* @optional
|
||||
*/
|
||||
statics: 'DEFINE_MANY',
|
||||
|
||||
/**
|
||||
* Definition of prop types for this component.
|
||||
*
|
||||
* @type {object}
|
||||
* @optional
|
||||
*/
|
||||
propTypes: 'DEFINE_MANY',
|
||||
|
||||
/**
|
||||
* Definition of context types for this component.
|
||||
*
|
||||
* @type {object}
|
||||
* @optional
|
||||
*/
|
||||
contextTypes: 'DEFINE_MANY',
|
||||
|
||||
/**
|
||||
* Definition of context types this component sets for its children.
|
||||
*
|
||||
* @type {object}
|
||||
* @optional
|
||||
*/
|
||||
childContextTypes: 'DEFINE_MANY',
|
||||
|
||||
// ==== Definition methods ====
|
||||
|
||||
/**
|
||||
* Invoked when the component is mounted. Values in the mapping will be set on
|
||||
* `this.props` if that prop is not specified (i.e. using an `in` check).
|
||||
*
|
||||
* This method is invoked before `getInitialState` and therefore cannot rely
|
||||
* on `this.state` or use `this.setState`.
|
||||
*
|
||||
* @return {object}
|
||||
* @optional
|
||||
*/
|
||||
getDefaultProps: 'DEFINE_MANY_MERGED',
|
||||
|
||||
/**
|
||||
* Invoked once before the component is mounted. The return value will be used
|
||||
* as the initial value of `this.state`.
|
||||
*
|
||||
* getInitialState: function() {
|
||||
* return {
|
||||
* isOn: false,
|
||||
* fooBaz: new BazFoo()
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* @return {object}
|
||||
* @optional
|
||||
*/
|
||||
getInitialState: 'DEFINE_MANY_MERGED',
|
||||
|
||||
/**
|
||||
* @return {object}
|
||||
* @optional
|
||||
*/
|
||||
getChildContext: 'DEFINE_MANY_MERGED',
|
||||
|
||||
/**
|
||||
* Uses props from `this.props` and state from `this.state` to render the
|
||||
* structure of the component.
|
||||
*
|
||||
* No guarantees are made about when or how often this method is invoked, so
|
||||
* it must not have side effects.
|
||||
*
|
||||
* render: function() {
|
||||
* var name = this.props.name;
|
||||
* return <div>Hello, {name}!</div>;
|
||||
* }
|
||||
*
|
||||
* @return {ReactComponent}
|
||||
* @required
|
||||
*/
|
||||
render: 'DEFINE_ONCE',
|
||||
|
||||
// ==== Delegate methods ====
|
||||
|
||||
/**
|
||||
* Invoked when the component is initially created and about to be mounted.
|
||||
* This may have side effects, but any external subscriptions or data created
|
||||
* by this method must be cleaned up in `componentWillUnmount`.
|
||||
*
|
||||
* @optional
|
||||
*/
|
||||
componentWillMount: 'DEFINE_MANY',
|
||||
|
||||
/**
|
||||
* Invoked when the component has been mounted and has a DOM representation.
|
||||
* However, there is no guarantee that the DOM node is in the document.
|
||||
*
|
||||
* Use this as an opportunity to operate on the DOM when the component has
|
||||
* been mounted (initialized and rendered) for the first time.
|
||||
*
|
||||
* @param {DOMElement} rootNode DOM element representing the component.
|
||||
* @optional
|
||||
*/
|
||||
componentDidMount: 'DEFINE_MANY',
|
||||
|
||||
/**
|
||||
* Invoked before the component receives new props.
|
||||
*
|
||||
* Use this as an opportunity to react to a prop transition by updating the
|
||||
* state using `this.setState`. Current props are accessed via `this.props`.
|
||||
*
|
||||
* componentWillReceiveProps: function(nextProps, nextContext) {
|
||||
* this.setState({
|
||||
* likesIncreasing: nextProps.likeCount > this.props.likeCount
|
||||
* });
|
||||
* }
|
||||
*
|
||||
* NOTE: There is no equivalent `componentWillReceiveState`. An incoming prop
|
||||
* transition may cause a state change, but the opposite is not true. If you
|
||||
* need it, you are probably looking for `componentWillUpdate`.
|
||||
*
|
||||
* @param {object} nextProps
|
||||
* @optional
|
||||
*/
|
||||
componentWillReceiveProps: 'DEFINE_MANY',
|
||||
|
||||
/**
|
||||
* Invoked while deciding if the component should be updated as a result of
|
||||
* receiving new props, state and/or context.
|
||||
*
|
||||
* Use this as an opportunity to `return false` when you're certain that the
|
||||
* transition to the new props/state/context will not require a component
|
||||
* update.
|
||||
*
|
||||
* shouldComponentUpdate: function(nextProps, nextState, nextContext) {
|
||||
* return !equal(nextProps, this.props) ||
|
||||
* !equal(nextState, this.state) ||
|
||||
* !equal(nextContext, this.context);
|
||||
* }
|
||||
*
|
||||
* @param {object} nextProps
|
||||
* @param {?object} nextState
|
||||
* @param {?object} nextContext
|
||||
* @return {boolean} True if the component should update.
|
||||
* @optional
|
||||
*/
|
||||
shouldComponentUpdate: 'DEFINE_ONCE',
|
||||
|
||||
/**
|
||||
* Invoked when the component is about to update due to a transition from
|
||||
* `this.props`, `this.state` and `this.context` to `nextProps`, `nextState`
|
||||
* and `nextContext`.
|
||||
*
|
||||
* Use this as an opportunity to perform preparation before an update occurs.
|
||||
*
|
||||
* NOTE: You **cannot** use `this.setState()` in this method.
|
||||
*
|
||||
* @param {object} nextProps
|
||||
* @param {?object} nextState
|
||||
* @param {?object} nextContext
|
||||
* @param {ReactReconcileTransaction} transaction
|
||||
* @optional
|
||||
*/
|
||||
componentWillUpdate: 'DEFINE_MANY',
|
||||
|
||||
/**
|
||||
* Invoked when the component's DOM representation has been updated.
|
||||
*
|
||||
* Use this as an opportunity to operate on the DOM when the component has
|
||||
* been updated.
|
||||
*
|
||||
* @param {object} prevProps
|
||||
* @param {?object} prevState
|
||||
* @param {?object} prevContext
|
||||
* @param {DOMElement} rootNode DOM element representing the component.
|
||||
* @optional
|
||||
*/
|
||||
componentDidUpdate: 'DEFINE_MANY',
|
||||
|
||||
/**
|
||||
* Invoked when the component is about to be removed from its parent and have
|
||||
* its DOM representation destroyed.
|
||||
*
|
||||
* Use this as an opportunity to deallocate any external resources.
|
||||
*
|
||||
* NOTE: There is no `componentDidUnmount` since your component will have been
|
||||
* destroyed by that point.
|
||||
*
|
||||
* @optional
|
||||
*/
|
||||
componentWillUnmount: 'DEFINE_MANY',
|
||||
|
||||
// ==== Advanced methods ====
|
||||
|
||||
/**
|
||||
* Updates the component's currently mounted DOM representation.
|
||||
*
|
||||
* By default, this implements React's rendering and reconciliation algorithm.
|
||||
* Sophisticated clients may wish to override this.
|
||||
*
|
||||
* @param {ReactReconcileTransaction} transaction
|
||||
* @internal
|
||||
* @overridable
|
||||
*/
|
||||
updateComponent: 'OVERRIDE_BASE',
|
||||
};
|
||||
|
||||
/**
|
||||
* Mapping from class specification keys to special processing functions.
|
||||
*
|
||||
* Although these are declared like instance properties in the specification
|
||||
* when defining classes using `React.createClass`, they are actually static
|
||||
* and are accessible on the constructor instead of the prototype. Despite
|
||||
* being static, they must be defined outside of the "statics" key under
|
||||
* which all other static methods are defined.
|
||||
*/
|
||||
var RESERVED_SPEC_KEYS = {
|
||||
displayName: function(Constructor, displayName) {
|
||||
Constructor.displayName = displayName;
|
||||
},
|
||||
mixins: function(Constructor, mixins) {
|
||||
if (mixins) {
|
||||
for (var i = 0; i < mixins.length; i++) {
|
||||
mixSpecIntoComponent(Constructor, mixins[i]);
|
||||
}
|
||||
}
|
||||
},
|
||||
childContextTypes: function(Constructor, childContextTypes) {
|
||||
if (__DEV__) {
|
||||
validateTypeDef(Constructor, childContextTypes, 'child context');
|
||||
}
|
||||
Constructor.childContextTypes = Object.assign(
|
||||
{},
|
||||
Constructor.childContextTypes,
|
||||
childContextTypes,
|
||||
);
|
||||
},
|
||||
contextTypes: function(Constructor, contextTypes) {
|
||||
if (__DEV__) {
|
||||
validateTypeDef(Constructor, contextTypes, 'context');
|
||||
}
|
||||
Constructor.contextTypes = Object.assign(
|
||||
{},
|
||||
Constructor.contextTypes,
|
||||
contextTypes,
|
||||
);
|
||||
},
|
||||
/**
|
||||
* Special case getDefaultProps which should move into statics but requires
|
||||
* automatic merging.
|
||||
*/
|
||||
getDefaultProps: function(Constructor, getDefaultProps) {
|
||||
if (Constructor.getDefaultProps) {
|
||||
Constructor.getDefaultProps = createMergedResultFunction(
|
||||
Constructor.getDefaultProps,
|
||||
getDefaultProps,
|
||||
);
|
||||
} else {
|
||||
Constructor.getDefaultProps = getDefaultProps;
|
||||
}
|
||||
},
|
||||
propTypes: function(Constructor, propTypes) {
|
||||
if (__DEV__) {
|
||||
validateTypeDef(Constructor, propTypes, 'prop');
|
||||
}
|
||||
Constructor.propTypes = Object.assign({}, Constructor.propTypes, propTypes);
|
||||
},
|
||||
statics: function(Constructor, statics) {
|
||||
mixStaticSpecIntoComponent(Constructor, statics);
|
||||
},
|
||||
autobind: function() {}, // noop
|
||||
};
|
||||
|
||||
function validateTypeDef(Constructor, typeDef, location: string) {
|
||||
for (var propName in typeDef) {
|
||||
if (typeDef.hasOwnProperty(propName)) {
|
||||
// use a warning instead of an invariant so components
|
||||
// don't show up in prod but only in __DEV__
|
||||
warning(
|
||||
typeof typeDef[propName] === 'function',
|
||||
'%s: %s type `%s` is invalid; it must be a function, usually from ' +
|
||||
'React.PropTypes.',
|
||||
Constructor.displayName || 'ReactClass',
|
||||
location,
|
||||
propName,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function validateMethodOverride(isAlreadyDefined, name) {
|
||||
var specPolicy = ReactClassInterface.hasOwnProperty(name)
|
||||
? ReactClassInterface[name]
|
||||
: null;
|
||||
|
||||
// Disallow overriding of base class methods unless explicitly allowed.
|
||||
if (ReactClassMixin.hasOwnProperty(name)) {
|
||||
invariant(
|
||||
specPolicy === 'OVERRIDE_BASE',
|
||||
'ReactClassInterface: You are attempting to override ' +
|
||||
'`%s` from your class specification. Ensure that your method names ' +
|
||||
'do not overlap with React methods.',
|
||||
name,
|
||||
);
|
||||
}
|
||||
|
||||
// Disallow defining methods more than once unless explicitly allowed.
|
||||
if (isAlreadyDefined) {
|
||||
invariant(
|
||||
specPolicy === 'DEFINE_MANY' || specPolicy === 'DEFINE_MANY_MERGED',
|
||||
'ReactClassInterface: You are attempting to define ' +
|
||||
'`%s` on your component more than once. This conflict may be due ' +
|
||||
'to a mixin.',
|
||||
name,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mixin helper which handles policy validation and reserved
|
||||
* specification keys when building React classes.
|
||||
*/
|
||||
function mixSpecIntoComponent(Constructor, spec) {
|
||||
if (!spec) {
|
||||
if (__DEV__) {
|
||||
var typeofSpec = typeof spec;
|
||||
var isMixinValid = typeofSpec === 'object' && spec !== null;
|
||||
|
||||
warning(
|
||||
isMixinValid,
|
||||
"%s: You're attempting to include a mixin that is either null " +
|
||||
'or not an object. Check the mixins included by the component, ' +
|
||||
'as well as any mixins they include themselves. ' +
|
||||
'Expected object but got %s.',
|
||||
Constructor.displayName || 'ReactClass',
|
||||
spec === null ? null : typeofSpec,
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
invariant(
|
||||
typeof spec !== 'function',
|
||||
"ReactClass: You're attempting to " +
|
||||
'use a component class or function as a mixin. Instead, just use a ' +
|
||||
'regular object.',
|
||||
);
|
||||
invariant(
|
||||
!ReactElement.isValidElement(spec),
|
||||
"ReactClass: You're attempting to " +
|
||||
'use a component as a mixin. Instead, just use a regular object.',
|
||||
);
|
||||
|
||||
var proto = Constructor.prototype;
|
||||
var autoBindPairs = proto.__reactAutoBindPairs;
|
||||
|
||||
// By handling mixins before any other properties, we ensure the same
|
||||
// chaining order is applied to methods with DEFINE_MANY policy, whether
|
||||
// mixins are listed before or after these methods in the spec.
|
||||
if (spec.hasOwnProperty(MIXINS_KEY)) {
|
||||
RESERVED_SPEC_KEYS.mixins(Constructor, spec.mixins);
|
||||
}
|
||||
|
||||
for (var name in spec) {
|
||||
if (!spec.hasOwnProperty(name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (name === MIXINS_KEY) {
|
||||
// We have already handled mixins in a special case above.
|
||||
continue;
|
||||
}
|
||||
|
||||
var property = spec[name];
|
||||
var isAlreadyDefined = proto.hasOwnProperty(name);
|
||||
validateMethodOverride(isAlreadyDefined, name);
|
||||
|
||||
if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) {
|
||||
RESERVED_SPEC_KEYS[name](Constructor, property);
|
||||
} else {
|
||||
// Setup methods on prototype:
|
||||
// The following member methods should not be automatically bound:
|
||||
// 1. Expected ReactClass methods (in the "interface").
|
||||
// 2. Overridden methods (that were mixed in).
|
||||
var isReactClassMethod = ReactClassInterface.hasOwnProperty(name);
|
||||
var isFunction = typeof property === 'function';
|
||||
var shouldAutoBind = isFunction &&
|
||||
!isReactClassMethod &&
|
||||
!isAlreadyDefined &&
|
||||
spec.autobind !== false;
|
||||
|
||||
if (shouldAutoBind) {
|
||||
autoBindPairs.push(name, property);
|
||||
proto[name] = property;
|
||||
} else {
|
||||
if (isAlreadyDefined) {
|
||||
var specPolicy = ReactClassInterface[name];
|
||||
|
||||
// These cases should already be caught by validateMethodOverride.
|
||||
invariant(
|
||||
isReactClassMethod &&
|
||||
(specPolicy === 'DEFINE_MANY_MERGED' ||
|
||||
specPolicy === 'DEFINE_MANY'),
|
||||
'ReactClass: Unexpected spec policy %s for key %s ' +
|
||||
'when mixing in component specs.',
|
||||
specPolicy,
|
||||
name,
|
||||
);
|
||||
|
||||
// For methods which are defined more than once, call the existing
|
||||
// methods before calling the new property, merging if appropriate.
|
||||
if (specPolicy === 'DEFINE_MANY_MERGED') {
|
||||
proto[name] = createMergedResultFunction(proto[name], property);
|
||||
} else if (specPolicy === 'DEFINE_MANY') {
|
||||
proto[name] = createChainedFunction(proto[name], property);
|
||||
}
|
||||
} else {
|
||||
proto[name] = property;
|
||||
if (__DEV__) {
|
||||
// Add verbose displayName to the function, which helps when looking
|
||||
// at profiling tools.
|
||||
if (typeof property === 'function' && spec.displayName) {
|
||||
proto[name].displayName = spec.displayName + '_' + name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function mixStaticSpecIntoComponent(Constructor, statics) {
|
||||
if (!statics) {
|
||||
return;
|
||||
}
|
||||
for (var name in statics) {
|
||||
var property = statics[name];
|
||||
if (!statics.hasOwnProperty(name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var isReserved = name in RESERVED_SPEC_KEYS;
|
||||
invariant(
|
||||
!isReserved,
|
||||
'ReactClass: You are attempting to define a reserved ' +
|
||||
'property, `%s`, that shouldn\'t be on the "statics" key. Define it ' +
|
||||
'as an instance property instead; it will still be accessible on the ' +
|
||||
'constructor.',
|
||||
name,
|
||||
);
|
||||
|
||||
var isInherited = name in Constructor;
|
||||
invariant(
|
||||
!isInherited,
|
||||
'ReactClass: You are attempting to define ' +
|
||||
'`%s` on your component more than once. This conflict may be ' +
|
||||
'due to a mixin.',
|
||||
name,
|
||||
);
|
||||
Constructor[name] = property;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge two objects, but throw if both contain the same key.
|
||||
*
|
||||
* @param {object} one The first object, which is mutated.
|
||||
* @param {object} two The second object
|
||||
* @return {object} one after it has been mutated to contain everything in two.
|
||||
*/
|
||||
function mergeIntoWithNoDuplicateKeys(one, two) {
|
||||
invariant(
|
||||
one && two && typeof one === 'object' && typeof two === 'object',
|
||||
'mergeIntoWithNoDuplicateKeys(): Cannot merge non-objects.',
|
||||
);
|
||||
|
||||
for (var key in two) {
|
||||
if (two.hasOwnProperty(key)) {
|
||||
invariant(
|
||||
one[key] === undefined,
|
||||
'mergeIntoWithNoDuplicateKeys(): ' +
|
||||
'Tried to merge two objects with the same key: `%s`. 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.',
|
||||
key,
|
||||
);
|
||||
one[key] = two[key];
|
||||
}
|
||||
}
|
||||
return one;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a function that invokes two functions and merges their return values.
|
||||
*
|
||||
* @param {function} one Function to invoke first.
|
||||
* @param {function} two Function to invoke second.
|
||||
* @return {function} Function that invokes the two argument functions.
|
||||
* @private
|
||||
*/
|
||||
function createMergedResultFunction(one, two) {
|
||||
return function mergedResult() {
|
||||
var a = one.apply(this, arguments);
|
||||
var b = two.apply(this, arguments);
|
||||
if (a == null) {
|
||||
return b;
|
||||
} else if (b == null) {
|
||||
return a;
|
||||
}
|
||||
var c = {};
|
||||
mergeIntoWithNoDuplicateKeys(c, a);
|
||||
mergeIntoWithNoDuplicateKeys(c, b);
|
||||
return c;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a function that invokes two functions and ignores their return vales.
|
||||
*
|
||||
* @param {function} one Function to invoke first.
|
||||
* @param {function} two Function to invoke second.
|
||||
* @return {function} Function that invokes the two argument functions.
|
||||
* @private
|
||||
*/
|
||||
function createChainedFunction(one, two) {
|
||||
return function chainedFunction() {
|
||||
one.apply(this, arguments);
|
||||
two.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds a method to the component.
|
||||
*
|
||||
* @param {object} component Component whose method is going to be bound.
|
||||
* @param {function} method Method to be bound.
|
||||
* @return {function} The bound method.
|
||||
*/
|
||||
function bindAutoBindMethod(component, method) {
|
||||
var boundMethod = method.bind(component);
|
||||
if (__DEV__) {
|
||||
boundMethod.__reactBoundContext = component;
|
||||
boundMethod.__reactBoundMethod = method;
|
||||
boundMethod.__reactBoundArguments = null;
|
||||
var componentName = component.constructor.displayName;
|
||||
var _bind = boundMethod.bind;
|
||||
boundMethod.bind = function(newThis, ...args) {
|
||||
// User is trying to bind() an autobound method; we effectively will
|
||||
// ignore the value of "this" that the user is trying to use, so
|
||||
// let's warn.
|
||||
if (newThis !== component && newThis !== null) {
|
||||
warning(
|
||||
false,
|
||||
'bind(): React component methods may only be bound to the ' +
|
||||
'component instance.\n\nSee %s',
|
||||
componentName,
|
||||
);
|
||||
} else if (!args.length) {
|
||||
warning(
|
||||
false,
|
||||
'bind(): You are binding a component method to the component. ' +
|
||||
'React does this for you automatically in a high-performance ' +
|
||||
'way, so you can safely remove this call.\n\nSee %s',
|
||||
componentName,
|
||||
);
|
||||
return boundMethod;
|
||||
}
|
||||
var reboundMethod = _bind.apply(boundMethod, arguments);
|
||||
reboundMethod.__reactBoundContext = component;
|
||||
reboundMethod.__reactBoundMethod = method;
|
||||
reboundMethod.__reactBoundArguments = args;
|
||||
return reboundMethod;
|
||||
};
|
||||
}
|
||||
return boundMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds all auto-bound methods in a component.
|
||||
*
|
||||
* @param {object} component Component whose method is going to be bound.
|
||||
*/
|
||||
function bindAutoBindMethods(component) {
|
||||
var pairs = component.__reactAutoBindPairs;
|
||||
for (var i = 0; i < pairs.length; i += 2) {
|
||||
var autoBindKey = pairs[i];
|
||||
var method = pairs[i + 1];
|
||||
component[autoBindKey] = bindAutoBindMethod(component, method);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add more to the ReactClass base class. These are all legacy features and
|
||||
* therefore not already part of the modern ReactComponent.
|
||||
*/
|
||||
var ReactClassMixin = {
|
||||
/**
|
||||
* TODO: This will be deprecated because state should always keep a consistent
|
||||
* type signature and the only use case for this, is to avoid that.
|
||||
*/
|
||||
replaceState: function(newState, callback) {
|
||||
this.updater.enqueueReplaceState(this, newState, callback, 'replaceState');
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks whether or not this composite component is mounted.
|
||||
* @return {boolean} True if mounted, false otherwise.
|
||||
* @protected
|
||||
* @final
|
||||
*/
|
||||
isMounted: function() {
|
||||
return this.updater.isMounted(this);
|
||||
},
|
||||
};
|
||||
|
||||
var ReactClassComponent = function() {};
|
||||
Object.assign(
|
||||
ReactClassComponent.prototype,
|
||||
ReactComponent.prototype,
|
||||
ReactClassMixin,
|
||||
);
|
||||
|
||||
/**
|
||||
* Module for creating composite components.
|
||||
*
|
||||
* @class ReactClass
|
||||
*/
|
||||
var ReactClass = {
|
||||
/**
|
||||
* Creates a composite component class given a class specification.
|
||||
* See https://facebook.github.io/react/docs/react-api.html#createclass
|
||||
*
|
||||
* @param {object} spec Class specification (which must define `render`).
|
||||
* @return {function} Component constructor function.
|
||||
* @public
|
||||
*/
|
||||
createClass: function(spec) {
|
||||
// To keep our warnings more understandable, we'll use a little hack here to
|
||||
// ensure that Constructor.name !== 'Constructor'. This makes sure we don't
|
||||
// unnecessarily identify a class without displayName as 'Constructor'.
|
||||
var Constructor = identity(function(props, context, updater) {
|
||||
// This constructor gets overridden by mocks. The argument is used
|
||||
// by mocks to assert on what gets mounted.
|
||||
|
||||
if (__DEV__) {
|
||||
warning(
|
||||
this instanceof Constructor,
|
||||
'Something is calling a React component directly. Use a factory or ' +
|
||||
'JSX instead. See: https://fb.me/react-legacyfactory',
|
||||
);
|
||||
}
|
||||
|
||||
// Wire up auto-binding
|
||||
if (this.__reactAutoBindPairs.length) {
|
||||
bindAutoBindMethods(this);
|
||||
}
|
||||
|
||||
this.props = props;
|
||||
this.context = context;
|
||||
this.refs = emptyObject;
|
||||
this.updater = updater || ReactNoopUpdateQueue;
|
||||
|
||||
this.state = null;
|
||||
|
||||
// ReactClasses doesn't have constructors. Instead, they use the
|
||||
// getInitialState and componentWillMount methods for initialization.
|
||||
|
||||
var initialState = this.getInitialState ? this.getInitialState() : null;
|
||||
if (__DEV__) {
|
||||
// We allow auto-mocks to proceed as if they're returning null.
|
||||
if (
|
||||
initialState === undefined && this.getInitialState._isMockFunction
|
||||
) {
|
||||
// This is probably bad practice. Consider warning here and
|
||||
// deprecating this convenience.
|
||||
initialState = null;
|
||||
}
|
||||
}
|
||||
invariant(
|
||||
typeof initialState === 'object' && !Array.isArray(initialState),
|
||||
'%s.getInitialState(): must return an object or null',
|
||||
Constructor.displayName || 'ReactCompositeComponent',
|
||||
);
|
||||
|
||||
this.state = initialState;
|
||||
});
|
||||
Constructor.prototype = new ReactClassComponent();
|
||||
Constructor.prototype.constructor = Constructor;
|
||||
Constructor.prototype.__reactAutoBindPairs = [];
|
||||
|
||||
mixSpecIntoComponent(Constructor, spec);
|
||||
|
||||
// Initialize the defaultProps property after all mixins have been merged.
|
||||
if (Constructor.getDefaultProps) {
|
||||
Constructor.defaultProps = Constructor.getDefaultProps();
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
// This is a tag to indicate that the use of these method names is ok,
|
||||
// since it's used with createClass. If it's not, then it's likely a
|
||||
// mistake so we'll warn you to use the static property, property
|
||||
// initializer or constructor respectively.
|
||||
if (Constructor.getDefaultProps) {
|
||||
Constructor.getDefaultProps.isReactClassApproved = {};
|
||||
}
|
||||
if (Constructor.prototype.getInitialState) {
|
||||
Constructor.prototype.getInitialState.isReactClassApproved = {};
|
||||
}
|
||||
}
|
||||
|
||||
invariant(
|
||||
Constructor.prototype.render,
|
||||
'createClass(...): Class specification must implement a `render` method.',
|
||||
);
|
||||
|
||||
if (__DEV__) {
|
||||
warning(
|
||||
!Constructor.prototype.componentShouldUpdate,
|
||||
'%s 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.',
|
||||
spec.displayName || 'A component',
|
||||
);
|
||||
warning(
|
||||
!Constructor.prototype.componentWillRecieveProps,
|
||||
'%s has a method called ' +
|
||||
'componentWillRecieveProps(). Did you mean componentWillReceiveProps()?',
|
||||
spec.displayName || 'A component',
|
||||
);
|
||||
}
|
||||
|
||||
// Reduce time spent doing lookups by setting these on the prototype.
|
||||
for (var methodName in ReactClassInterface) {
|
||||
if (!Constructor.prototype[methodName]) {
|
||||
Constructor.prototype[methodName] = null;
|
||||
}
|
||||
}
|
||||
|
||||
return Constructor;
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = ReactClass;
|
||||
@@ -1,152 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
/*global global:true*/
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var ReactTestUtils = require('ReactTestUtils');
|
||||
|
||||
// TODO: Test render and all stock methods.
|
||||
describe('autobinding', () => {
|
||||
it('Holds reference to instance', () => {
|
||||
var mouseDidEnter = jest.fn();
|
||||
var mouseDidLeave = jest.fn();
|
||||
var mouseDidClick = jest.fn();
|
||||
|
||||
var TestBindComponent = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {something: 'hi'};
|
||||
},
|
||||
onMouseEnter: mouseDidEnter,
|
||||
onMouseLeave: mouseDidLeave,
|
||||
onClick: mouseDidClick,
|
||||
|
||||
// auto binding only occurs on top level functions in class defs.
|
||||
badIdeas: {
|
||||
badBind: function() {
|
||||
void this.state.something;
|
||||
},
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<div
|
||||
ref="child"
|
||||
onMouseOver={this.onMouseEnter}
|
||||
onMouseOut={this.onMouseLeave}
|
||||
onClick={this.onClick}
|
||||
/>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
var instance1 = ReactTestUtils.renderIntoDocument(<TestBindComponent />);
|
||||
var rendered1 = instance1.refs.child;
|
||||
|
||||
var instance2 = ReactTestUtils.renderIntoDocument(<TestBindComponent />);
|
||||
var rendered2 = instance2.refs.child;
|
||||
|
||||
expect(function() {
|
||||
var badIdea = instance1.badIdeas.badBind;
|
||||
badIdea();
|
||||
}).toThrow();
|
||||
|
||||
expect(instance1.onClick).not.toBe(instance2.onClick);
|
||||
|
||||
ReactTestUtils.Simulate.click(rendered1);
|
||||
expect(mouseDidClick.mock.instances.length).toBe(1);
|
||||
expect(mouseDidClick.mock.instances[0]).toBe(instance1);
|
||||
|
||||
ReactTestUtils.Simulate.click(rendered2);
|
||||
expect(mouseDidClick.mock.instances.length).toBe(2);
|
||||
expect(mouseDidClick.mock.instances[1]).toBe(instance2);
|
||||
|
||||
ReactTestUtils.Simulate.mouseOver(rendered1);
|
||||
expect(mouseDidEnter.mock.instances.length).toBe(1);
|
||||
expect(mouseDidEnter.mock.instances[0]).toBe(instance1);
|
||||
|
||||
ReactTestUtils.Simulate.mouseOver(rendered2);
|
||||
expect(mouseDidEnter.mock.instances.length).toBe(2);
|
||||
expect(mouseDidEnter.mock.instances[1]).toBe(instance2);
|
||||
|
||||
ReactTestUtils.Simulate.mouseOut(rendered1);
|
||||
expect(mouseDidLeave.mock.instances.length).toBe(1);
|
||||
expect(mouseDidLeave.mock.instances[0]).toBe(instance1);
|
||||
|
||||
ReactTestUtils.Simulate.mouseOut(rendered2);
|
||||
expect(mouseDidLeave.mock.instances.length).toBe(2);
|
||||
expect(mouseDidLeave.mock.instances[1]).toBe(instance2);
|
||||
});
|
||||
|
||||
it('works with mixins', () => {
|
||||
var mouseDidClick = jest.fn();
|
||||
|
||||
var TestMixin = {
|
||||
onClick: mouseDidClick,
|
||||
};
|
||||
|
||||
var TestBindComponent = React.createClass({
|
||||
mixins: [TestMixin],
|
||||
|
||||
render: function() {
|
||||
return <div ref="child" onClick={this.onClick} />;
|
||||
},
|
||||
});
|
||||
|
||||
var instance1 = ReactTestUtils.renderIntoDocument(<TestBindComponent />);
|
||||
var rendered1 = instance1.refs.child;
|
||||
|
||||
ReactTestUtils.Simulate.click(rendered1);
|
||||
expect(mouseDidClick.mock.instances.length).toBe(1);
|
||||
expect(mouseDidClick.mock.instances[0]).toBe(instance1);
|
||||
});
|
||||
|
||||
it('warns if you try to bind to this', () => {
|
||||
spyOn(console, 'error');
|
||||
|
||||
var TestBindComponent = React.createClass({
|
||||
handleClick: function() {},
|
||||
render: function() {
|
||||
return <div onClick={this.handleClick.bind(this)} />;
|
||||
},
|
||||
});
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<TestBindComponent />);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: bind(): You are binding a component method to the component. ' +
|
||||
'React does this for you automatically in a high-performance ' +
|
||||
'way, so you can safely remove this call.\n\nSee TestBindComponent',
|
||||
);
|
||||
});
|
||||
|
||||
it('does not warn if you pass an auto-bound method to setState', () => {
|
||||
spyOn(console, 'error');
|
||||
|
||||
var TestBindComponent = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {foo: 1};
|
||||
},
|
||||
componentDidMount: function() {
|
||||
this.setState({foo: 2}, this.handleUpdate);
|
||||
},
|
||||
handleUpdate: function() {},
|
||||
render: function() {
|
||||
return <div onClick={this.handleClick} />;
|
||||
},
|
||||
});
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<TestBindComponent />);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -1,206 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
/*global global:true*/
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var ReactTestUtils = require('ReactTestUtils');
|
||||
|
||||
// TODO: Test render and all stock methods.
|
||||
describe('autobind optout', () => {
|
||||
it('should work with manual binding', () => {
|
||||
var mouseDidEnter = jest.fn();
|
||||
var mouseDidLeave = jest.fn();
|
||||
var mouseDidClick = jest.fn();
|
||||
|
||||
var TestBindComponent = React.createClass({
|
||||
autobind: false,
|
||||
getInitialState: function() {
|
||||
return {something: 'hi'};
|
||||
},
|
||||
onMouseEnter: mouseDidEnter,
|
||||
onMouseLeave: mouseDidLeave,
|
||||
onClick: mouseDidClick,
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<div
|
||||
ref="child"
|
||||
onMouseOver={this.onMouseEnter.bind(this)}
|
||||
onMouseOut={this.onMouseLeave.bind(this)}
|
||||
onClick={this.onClick.bind(this)}
|
||||
/>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
var instance1 = ReactTestUtils.renderIntoDocument(<TestBindComponent />);
|
||||
var rendered1 = instance1.refs.child;
|
||||
|
||||
var instance2 = ReactTestUtils.renderIntoDocument(<TestBindComponent />);
|
||||
var rendered2 = instance2.refs.child;
|
||||
|
||||
ReactTestUtils.Simulate.click(rendered1);
|
||||
expect(mouseDidClick.mock.instances.length).toBe(1);
|
||||
expect(mouseDidClick.mock.instances[0]).toBe(instance1);
|
||||
|
||||
ReactTestUtils.Simulate.click(rendered2);
|
||||
expect(mouseDidClick.mock.instances.length).toBe(2);
|
||||
expect(mouseDidClick.mock.instances[1]).toBe(instance2);
|
||||
|
||||
ReactTestUtils.Simulate.mouseOver(rendered1);
|
||||
expect(mouseDidEnter.mock.instances.length).toBe(1);
|
||||
expect(mouseDidEnter.mock.instances[0]).toBe(instance1);
|
||||
|
||||
ReactTestUtils.Simulate.mouseOver(rendered2);
|
||||
expect(mouseDidEnter.mock.instances.length).toBe(2);
|
||||
expect(mouseDidEnter.mock.instances[1]).toBe(instance2);
|
||||
|
||||
ReactTestUtils.Simulate.mouseOut(rendered1);
|
||||
expect(mouseDidLeave.mock.instances.length).toBe(1);
|
||||
expect(mouseDidLeave.mock.instances[0]).toBe(instance1);
|
||||
|
||||
ReactTestUtils.Simulate.mouseOut(rendered2);
|
||||
expect(mouseDidLeave.mock.instances.length).toBe(2);
|
||||
expect(mouseDidLeave.mock.instances[1]).toBe(instance2);
|
||||
});
|
||||
|
||||
it('should not hold reference to instance', () => {
|
||||
var mouseDidClick = function() {
|
||||
void this.state.something;
|
||||
};
|
||||
|
||||
var TestBindComponent = React.createClass({
|
||||
autobind: false,
|
||||
getInitialState: function() {
|
||||
return {something: 'hi'};
|
||||
},
|
||||
onClick: mouseDidClick,
|
||||
|
||||
// auto binding only occurs on top level functions in class defs.
|
||||
badIdeas: {
|
||||
badBind: function() {
|
||||
void this.state.something;
|
||||
},
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <div ref="child" onClick={this.onClick} />;
|
||||
},
|
||||
});
|
||||
|
||||
var instance1 = ReactTestUtils.renderIntoDocument(<TestBindComponent />);
|
||||
var rendered1 = instance1.refs.child;
|
||||
|
||||
var instance2 = ReactTestUtils.renderIntoDocument(<TestBindComponent />);
|
||||
var rendered2 = instance2.refs.child;
|
||||
|
||||
expect(function() {
|
||||
var badIdea = instance1.badIdeas.badBind;
|
||||
badIdea();
|
||||
}).toThrow();
|
||||
|
||||
expect(instance1.onClick).toBe(instance2.onClick);
|
||||
|
||||
expect(function() {
|
||||
ReactTestUtils.Simulate.click(rendered1);
|
||||
}).toThrow();
|
||||
|
||||
expect(function() {
|
||||
ReactTestUtils.Simulate.click(rendered2);
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('works with mixins that have not opted out of autobinding', () => {
|
||||
var mouseDidClick = jest.fn();
|
||||
|
||||
var TestMixin = {
|
||||
onClick: mouseDidClick,
|
||||
};
|
||||
|
||||
var TestBindComponent = React.createClass({
|
||||
mixins: [TestMixin],
|
||||
|
||||
render: function() {
|
||||
return <div ref="child" onClick={this.onClick} />;
|
||||
},
|
||||
});
|
||||
|
||||
var instance1 = ReactTestUtils.renderIntoDocument(<TestBindComponent />);
|
||||
var rendered1 = instance1.refs.child;
|
||||
|
||||
ReactTestUtils.Simulate.click(rendered1);
|
||||
expect(mouseDidClick.mock.instances.length).toBe(1);
|
||||
expect(mouseDidClick.mock.instances[0]).toBe(instance1);
|
||||
});
|
||||
|
||||
it('works with mixins that have opted out of autobinding', () => {
|
||||
var mouseDidClick = jest.fn();
|
||||
|
||||
var TestMixin = {
|
||||
autobind: false,
|
||||
onClick: mouseDidClick,
|
||||
};
|
||||
|
||||
var TestBindComponent = React.createClass({
|
||||
mixins: [TestMixin],
|
||||
|
||||
render: function() {
|
||||
return <div ref="child" onClick={this.onClick.bind(this)} />;
|
||||
},
|
||||
});
|
||||
|
||||
var instance1 = ReactTestUtils.renderIntoDocument(<TestBindComponent />);
|
||||
var rendered1 = instance1.refs.child;
|
||||
|
||||
ReactTestUtils.Simulate.click(rendered1);
|
||||
expect(mouseDidClick.mock.instances.length).toBe(1);
|
||||
expect(mouseDidClick.mock.instances[0]).toBe(instance1);
|
||||
});
|
||||
|
||||
it('does not warn if you try to bind to this', () => {
|
||||
spyOn(console, 'error');
|
||||
|
||||
var TestBindComponent = React.createClass({
|
||||
autobind: false,
|
||||
handleClick: function() {},
|
||||
render: function() {
|
||||
return <div onClick={this.handleClick.bind(this)} />;
|
||||
},
|
||||
});
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<TestBindComponent />);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
});
|
||||
|
||||
it('does not warn if you pass an manually bound method to setState', () => {
|
||||
spyOn(console, 'error');
|
||||
|
||||
var TestBindComponent = React.createClass({
|
||||
autobind: false,
|
||||
getInitialState: function() {
|
||||
return {foo: 1};
|
||||
},
|
||||
componentDidMount: function() {
|
||||
this.setState({foo: 2}, this.handleUpdate.bind(this));
|
||||
},
|
||||
handleUpdate: function() {},
|
||||
render: function() {
|
||||
return <div />;
|
||||
},
|
||||
});
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<TestBindComponent />);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -1,348 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React;
|
||||
var ReactDOM;
|
||||
var ReactTestUtils;
|
||||
|
||||
describe('ReactClass-spec', () => {
|
||||
beforeEach(() => {
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
ReactTestUtils = require('ReactTestUtils');
|
||||
});
|
||||
|
||||
it('should throw when `render` is not specified', () => {
|
||||
expect(function() {
|
||||
React.createClass({});
|
||||
}).toThrowError(
|
||||
'createClass(...): Class specification must implement a `render` method.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should copy `displayName` onto the Constructor', () => {
|
||||
var TestComponent = React.createClass({
|
||||
render: function() {
|
||||
return <div />;
|
||||
},
|
||||
});
|
||||
|
||||
expect(TestComponent.displayName).toBe('TestComponent');
|
||||
});
|
||||
|
||||
it('should copy prop types onto the Constructor', () => {
|
||||
var propValidator = jest.fn();
|
||||
var TestComponent = React.createClass({
|
||||
propTypes: {
|
||||
value: propValidator,
|
||||
},
|
||||
render: function() {
|
||||
return <div />;
|
||||
},
|
||||
});
|
||||
|
||||
expect(TestComponent.propTypes).toBeDefined();
|
||||
expect(TestComponent.propTypes.value).toBe(propValidator);
|
||||
});
|
||||
|
||||
it('should warn on invalid prop types', () => {
|
||||
spyOn(console, 'error');
|
||||
React.createClass({
|
||||
displayName: 'Component',
|
||||
propTypes: {
|
||||
prop: null,
|
||||
},
|
||||
render: function() {
|
||||
return <span>{this.props.prop}</span>;
|
||||
},
|
||||
});
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Component: prop type `prop` is invalid; ' +
|
||||
'it must be a function, usually from React.PropTypes.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should warn on invalid context types', () => {
|
||||
spyOn(console, 'error');
|
||||
React.createClass({
|
||||
displayName: 'Component',
|
||||
contextTypes: {
|
||||
prop: null,
|
||||
},
|
||||
render: function() {
|
||||
return <span>{this.props.prop}</span>;
|
||||
},
|
||||
});
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Component: context type `prop` is invalid; ' +
|
||||
'it must be a function, usually from React.PropTypes.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw on invalid child context types', () => {
|
||||
spyOn(console, 'error');
|
||||
React.createClass({
|
||||
displayName: 'Component',
|
||||
childContextTypes: {
|
||||
prop: null,
|
||||
},
|
||||
render: function() {
|
||||
return <span>{this.props.prop}</span>;
|
||||
},
|
||||
});
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Component: child context type `prop` is invalid; ' +
|
||||
'it must be a function, usually from React.PropTypes.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should warn when mispelling shouldComponentUpdate', () => {
|
||||
spyOn(console, 'error');
|
||||
|
||||
React.createClass({
|
||||
componentShouldUpdate: function() {
|
||||
return false;
|
||||
},
|
||||
render: function() {
|
||||
return <div />;
|
||||
},
|
||||
});
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: 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.',
|
||||
);
|
||||
|
||||
React.createClass({
|
||||
displayName: 'NamedComponent',
|
||||
componentShouldUpdate: function() {
|
||||
return false;
|
||||
},
|
||||
render: function() {
|
||||
return <div />;
|
||||
},
|
||||
});
|
||||
expectDev(console.error.calls.count()).toBe(2);
|
||||
expectDev(console.error.calls.argsFor(1)[0]).toBe(
|
||||
'Warning: 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.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should warn when mispelling componentWillReceiveProps', () => {
|
||||
spyOn(console, 'error');
|
||||
React.createClass({
|
||||
componentWillRecieveProps: function() {
|
||||
return false;
|
||||
},
|
||||
render: function() {
|
||||
return <div />;
|
||||
},
|
||||
});
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: A component has a method called componentWillRecieveProps(). Did you ' +
|
||||
'mean componentWillReceiveProps()?',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw if a reserved property is in statics', () => {
|
||||
expect(function() {
|
||||
React.createClass({
|
||||
statics: {
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
foo: 0,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <span />;
|
||||
},
|
||||
});
|
||||
}).toThrowError(
|
||||
'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', () => {
|
||||
spyOn(console, 'error');
|
||||
React.createClass({
|
||||
mixins: [{}],
|
||||
propTypes: {
|
||||
foo: React.PropTypes.string,
|
||||
},
|
||||
contextTypes: {
|
||||
foo: React.PropTypes.string,
|
||||
},
|
||||
childContextTypes: {
|
||||
foo: React.PropTypes.string,
|
||||
},
|
||||
render: function() {
|
||||
return <div />;
|
||||
},
|
||||
});
|
||||
expectDev(console.error.calls.count()).toBe(4);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'createClass(...): `mixins` is now a static property and should ' +
|
||||
'be defined inside "statics".',
|
||||
);
|
||||
expectDev(console.error.calls.argsFor(1)[0]).toBe(
|
||||
'createClass(...): `propTypes` is now a static property and should ' +
|
||||
'be defined inside "statics".',
|
||||
);
|
||||
expectDev(console.error.calls.argsFor(2)[0]).toBe(
|
||||
'createClass(...): `contextTypes` is now a static property and ' +
|
||||
'should be defined inside "statics".',
|
||||
);
|
||||
expectDev(console.error.calls.argsFor(3)[0]).toBe(
|
||||
'createClass(...): `childContextTypes` is now a static property and ' +
|
||||
'should be defined inside "statics".',
|
||||
);
|
||||
});
|
||||
|
||||
it('should support statics', () => {
|
||||
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', () => {
|
||||
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('renders based on context getInitialState', () => {
|
||||
var Foo = React.createClass({
|
||||
contextTypes: {
|
||||
className: React.PropTypes.string,
|
||||
},
|
||||
getInitialState() {
|
||||
return {className: this.context.className};
|
||||
},
|
||||
render() {
|
||||
return <span className={this.state.className} />;
|
||||
},
|
||||
});
|
||||
|
||||
var Outer = React.createClass({
|
||||
childContextTypes: {
|
||||
className: React.PropTypes.string,
|
||||
},
|
||||
getChildContext() {
|
||||
return {className: 'foo'};
|
||||
},
|
||||
render() {
|
||||
return <Foo />;
|
||||
},
|
||||
});
|
||||
|
||||
var container = document.createElement('div');
|
||||
ReactDOM.render(<Outer />, container);
|
||||
expect(container.firstChild.className).toBe('foo');
|
||||
});
|
||||
|
||||
it('should throw with non-object getInitialState() return values', () => {
|
||||
[['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);
|
||||
}).toThrowError(
|
||||
'Component.getInitialState(): must return an object or null',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should work with a null getInitialState() return value', () => {
|
||||
var Component = React.createClass({
|
||||
getInitialState: function() {
|
||||
return null;
|
||||
},
|
||||
render: function() {
|
||||
return <span />;
|
||||
},
|
||||
});
|
||||
expect(() =>
|
||||
ReactTestUtils.renderIntoDocument(<Component />)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should throw when using legacy factories', () => {
|
||||
spyOn(console, 'error');
|
||||
var Component = React.createClass({
|
||||
render() {
|
||||
return <div />;
|
||||
},
|
||||
});
|
||||
|
||||
expect(() => Component()).toThrow();
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toBe(
|
||||
'Warning: Something is calling a React component directly. Use a ' +
|
||||
'factory or JSX instead. See: https://fb.me/react-legacyfactory',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,532 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React;
|
||||
var ReactTestUtils;
|
||||
|
||||
var TestComponent;
|
||||
var TestComponentWithPropTypes;
|
||||
var TestComponentWithReverseSpec;
|
||||
var mixinPropValidator;
|
||||
var componentPropValidator;
|
||||
|
||||
describe('ReactClass-mixin', () => {
|
||||
beforeEach(() => {
|
||||
React = require('react');
|
||||
ReactTestUtils = require('ReactTestUtils');
|
||||
mixinPropValidator = jest.fn();
|
||||
componentPropValidator = jest.fn();
|
||||
|
||||
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', () => {
|
||||
var listener = jest.fn();
|
||||
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', () => {
|
||||
var listener = jest.fn();
|
||||
var instance = <TestComponent listener={listener} />;
|
||||
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', () => {
|
||||
var listener = jest.fn();
|
||||
var instance = <TestComponentWithReverseSpec listener={listener} />;
|
||||
ReactTestUtils.renderIntoDocument(instance);
|
||||
|
||||
expect(listener.mock.calls).toEqual([
|
||||
['MixinA didMount'],
|
||||
['MixinBWithReverseSpec didMount'],
|
||||
['MixinC didMount'],
|
||||
['Component didMount'],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should validate prop types via mixins', () => {
|
||||
expect(TestComponent.propTypes).toBeDefined();
|
||||
expect(TestComponent.propTypes.value).toBe(mixinPropValidator);
|
||||
});
|
||||
|
||||
it('should override mixin prop types with class prop types', () => {
|
||||
// Sanity check...
|
||||
expect(componentPropValidator).not.toBe(mixinPropValidator);
|
||||
// Actually check...
|
||||
expect(TestComponentWithPropTypes.propTypes).toBeDefined();
|
||||
expect(TestComponentWithPropTypes.propTypes.value).not.toBe(
|
||||
mixinPropValidator,
|
||||
);
|
||||
expect(TestComponentWithPropTypes.propTypes.value).toBe(
|
||||
componentPropValidator,
|
||||
);
|
||||
});
|
||||
|
||||
it('should support mixins with getInitialState()', () => {
|
||||
var Mixin = {
|
||||
getInitialState: function() {
|
||||
return {mixin: true};
|
||||
},
|
||||
};
|
||||
var Component = React.createClass({
|
||||
mixins: [Mixin],
|
||||
getInitialState: function() {
|
||||
return {component: true};
|
||||
},
|
||||
render: function() {
|
||||
return <span />;
|
||||
},
|
||||
});
|
||||
var instance = ReactTestUtils.renderIntoDocument(<Component />);
|
||||
expect(instance.state.component).toBe(true);
|
||||
expect(instance.state.mixin).toBe(true);
|
||||
});
|
||||
|
||||
it('should throw with conflicting getInitialState() methods', () => {
|
||||
var Mixin = {
|
||||
getInitialState: function() {
|
||||
return {x: true};
|
||||
},
|
||||
};
|
||||
var Component = React.createClass({
|
||||
mixins: [Mixin],
|
||||
getInitialState: function() {
|
||||
return {x: true};
|
||||
},
|
||||
render: function() {
|
||||
return <span />;
|
||||
},
|
||||
});
|
||||
expect(function() {
|
||||
ReactTestUtils.renderIntoDocument(<Component />);
|
||||
}).toThrowError(
|
||||
'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()', () => {
|
||||
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', () => {
|
||||
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", () => {
|
||||
expect(function() {
|
||||
var Mixin = {
|
||||
statics: {
|
||||
abc: 'foo',
|
||||
},
|
||||
};
|
||||
React.createClass({
|
||||
mixins: [Mixin],
|
||||
|
||||
statics: {
|
||||
abc: 'bar',
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <span />;
|
||||
},
|
||||
});
|
||||
}).toThrowError(
|
||||
'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', () => {
|
||||
expect(function() {
|
||||
var Mixin = {
|
||||
statics: {
|
||||
abc: function() {
|
||||
console.log('foo');
|
||||
},
|
||||
},
|
||||
};
|
||||
React.createClass({
|
||||
mixins: [Mixin],
|
||||
|
||||
statics: {
|
||||
abc: function() {
|
||||
console.log('bar');
|
||||
},
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <span />;
|
||||
},
|
||||
});
|
||||
}).toThrowError(
|
||||
'ReactClass: You are attempting to define `abc` on your component ' +
|
||||
'more than once. This conflict may be due to a mixin.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should warn if the mixin is undefined', () => {
|
||||
spyOn(console, 'error');
|
||||
|
||||
React.createClass({
|
||||
mixins: [undefined],
|
||||
|
||||
render: function() {
|
||||
return <span />;
|
||||
},
|
||||
});
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toBe(
|
||||
"Warning: ReactClass: You're attempting to include a mixin that is " +
|
||||
'either null or not an object. Check the mixins included by the ' +
|
||||
'component, as well as any mixins they include themselves. ' +
|
||||
'Expected object but got undefined.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should warn if the mixin is null', () => {
|
||||
spyOn(console, 'error');
|
||||
|
||||
React.createClass({
|
||||
mixins: [null],
|
||||
|
||||
render: function() {
|
||||
return <span />;
|
||||
},
|
||||
});
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toBe(
|
||||
"Warning: ReactClass: You're attempting to include a mixin that is " +
|
||||
'either null or not an object. Check the mixins included by the ' +
|
||||
'component, as well as any mixins they include themselves. ' +
|
||||
'Expected object but got null.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should warn if an undefined mixin is included in another mixin', () => {
|
||||
spyOn(console, 'error');
|
||||
|
||||
var mixinA = {
|
||||
mixins: [undefined],
|
||||
};
|
||||
|
||||
React.createClass({
|
||||
mixins: [mixinA],
|
||||
|
||||
render: function() {
|
||||
return <span />;
|
||||
},
|
||||
});
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toBe(
|
||||
"Warning: ReactClass: You're attempting to include a mixin that is " +
|
||||
'either null or not an object. Check the mixins included by the ' +
|
||||
'component, as well as any mixins they include themselves. ' +
|
||||
'Expected object but got undefined.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should warn if a null mixin is included in another mixin', () => {
|
||||
spyOn(console, 'error');
|
||||
|
||||
var mixinA = {
|
||||
mixins: [null],
|
||||
};
|
||||
|
||||
React.createClass({
|
||||
mixins: [mixinA],
|
||||
|
||||
render: function() {
|
||||
return <span />;
|
||||
},
|
||||
});
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(console.error.calls.argsFor(0)[0]).toBe(
|
||||
"Warning: ReactClass: You're attempting to include a mixin that is " +
|
||||
'either null or not an object. Check the mixins included by the ' +
|
||||
'component, as well as any mixins they include themselves. ' +
|
||||
'Expected object but got null.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw if the mixin is a React component', () => {
|
||||
expect(function() {
|
||||
React.createClass({
|
||||
mixins: [<div />],
|
||||
|
||||
render: function() {
|
||||
return <span />;
|
||||
},
|
||||
});
|
||||
}).toThrowError(
|
||||
"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', () => {
|
||||
expect(function() {
|
||||
var Component = React.createClass({
|
||||
render: function() {
|
||||
return <span />;
|
||||
},
|
||||
});
|
||||
|
||||
React.createClass({
|
||||
mixins: [Component],
|
||||
|
||||
render: function() {
|
||||
return <span />;
|
||||
},
|
||||
});
|
||||
}).toThrowError(
|
||||
"ReactClass: You're attempting to use a component class or function " +
|
||||
'as a mixin. Instead, just use a regular object.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should have bound the mixin methods to the component', () => {
|
||||
var mixin = {
|
||||
mixinFunc: function() {
|
||||
return this;
|
||||
},
|
||||
};
|
||||
|
||||
var Component = React.createClass({
|
||||
mixins: [mixin],
|
||||
componentDidMount: function() {
|
||||
expect(this.mixinFunc()).toBe(this);
|
||||
},
|
||||
render: function() {
|
||||
return <span />;
|
||||
},
|
||||
});
|
||||
ReactTestUtils.renderIntoDocument(<Component />);
|
||||
});
|
||||
|
||||
it('should include the mixin keys in even if their values are falsy', () => {
|
||||
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 />;
|
||||
},
|
||||
});
|
||||
ReactTestUtils.renderIntoDocument(<Component />);
|
||||
});
|
||||
|
||||
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 = ReactTestUtils.renderIntoDocument(<Component />);
|
||||
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 = ReactTestUtils.renderIntoDocument(<Component />);
|
||||
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 = ReactTestUtils.renderIntoDocument(<Component />);
|
||||
expect(instance.state).toEqual({foo: 'bar', x: true});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user