Merge pull request #2375 from sebmarkbage/droplegacyfactories

Drop Legacy Factories
This commit is contained in:
Sebastian Markbåge
2014-10-28 18:00:32 -07:00
25 changed files with 230 additions and 486 deletions
+2 -7
View File
@@ -14,7 +14,6 @@
var ReactElement = require('ReactElement');
var ReactElementValidator = require('ReactElementValidator');
var ReactLegacyElement = require('ReactLegacyElement');
var mapObject = require('mapObject');
@@ -26,13 +25,9 @@ var mapObject = require('mapObject');
*/
function createDOMFactory(tag) {
if (__DEV__) {
return ReactLegacyElement.markNonLegacyFactory(
ReactElementValidator.createFactory(tag)
);
return ReactElementValidator.createFactory(tag);
}
return ReactLegacyElement.markNonLegacyFactory(
ReactElement.createFactory(tag)
);
return ReactElement.createFactory(tag);
}
/**
-20
View File
@@ -114,24 +114,4 @@ describe('ReactDOM', function() {
expect(element.type).toBe('div');
expect(console.warn.argsForCall.length).toBe(0);
});
it('warns but allow dom factories to be used in createFactory', function() {
spyOn(console, 'warn');
var factory = React.createFactory(React.DOM.div);
expect(factory().type).toBe('div');
expect(console.warn.argsForCall.length).toBe(1);
expect(console.warn.argsForCall[0][0]).toContain(
'Do not pass React.DOM.div'
);
});
it('warns but allow dom factories to be used in createElement', function() {
spyOn(console, 'warn');
var element = React.createElement(React.DOM.div);
expect(element.type).toBe('div');
expect(console.warn.argsForCall.length).toBe(1);
expect(console.warn.argsForCall[0][0]).toContain(
'Do not pass React.DOM.div'
);
});
});
-10
View File
@@ -24,7 +24,6 @@ var ReactDOM = require('ReactDOM');
var ReactDOMComponent = require('ReactDOMComponent');
var ReactDefaultInjection = require('ReactDefaultInjection');
var ReactInstanceHandles = require('ReactInstanceHandles');
var ReactLegacyElement = require('ReactLegacyElement');
var ReactMount = require('ReactMount');
var ReactMultiChild = require('ReactMultiChild');
var ReactPerf = require('ReactPerf');
@@ -46,14 +45,6 @@ if (__DEV__) {
createFactory = ReactElementValidator.createFactory;
}
// TODO: Drop legacy elements once classes no longer export these factories
createElement = ReactLegacyElement.wrapCreateElement(
createElement
);
createFactory = ReactLegacyElement.wrapCreateFactory(
createFactory
);
var render = ReactPerf.measure('React', 'render', ReactMount.render);
var React = {
@@ -77,7 +68,6 @@ var React = {
renderToString: ReactServerRendering.renderToString,
renderToStaticMarkup: ReactServerRendering.renderToStaticMarkup,
unmountComponentAtNode: ReactMount.unmountComponentAtNode,
isValidClass: ReactLegacyElement.isValidClass,
isValidElement: ReactElement.isValidElement,
withContext: ReactContext.withContext,
+2 -7
View File
@@ -15,7 +15,6 @@ var DOMProperty = require('DOMProperty');
var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
var ReactCurrentOwner = require('ReactCurrentOwner');
var ReactElement = require('ReactElement');
var ReactLegacyElement = require('ReactLegacyElement');
var ReactInstanceHandles = require('ReactInstanceHandles');
var ReactPerf = require('ReactPerf');
@@ -27,10 +26,6 @@ var invariant = require('invariant');
var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
var warning = require('warning');
var createElement = ReactLegacyElement.wrapCreateElement(
ReactElement.createElement
);
var SEPARATOR = ReactInstanceHandles.SEPARATOR;
var ATTR_NAME = DOMProperty.ID_ATTRIBUTE_NAME;
@@ -342,7 +337,7 @@ var ReactMount = {
typeof nextElement === 'string' ?
' Instead of passing an element string, make sure to instantiate ' +
'it by passing it to React.createElement.' :
ReactLegacyElement.isValidFactory(nextElement) ?
typeof nextElement === 'function' ?
' Instead of passing a component class, make sure to instantiate ' +
'it by passing it to React.createElement.' :
// Check if it quacks like a element
@@ -394,7 +389,7 @@ var ReactMount = {
* @return {ReactComponent} Component instance rendered in `container`.
*/
constructAndRenderComponent: function(constructor, props, container) {
var element = createElement(constructor, props);
var element = ReactElement.createElement(constructor, props);
return ReactMount.render(element, container);
},
@@ -15,12 +15,10 @@ var AutoFocusMixin = require('AutoFocusMixin');
var ReactBrowserComponentMixin = require('ReactBrowserComponentMixin');
var ReactClass = require('ReactClass');
var ReactElement = require('ReactElement');
var ReactDOM = require('ReactDOM');
var keyMirror = require('keyMirror');
// Store a reference to the <button> `ReactDOMComponent`. TODO: use string
var button = ReactElement.createFactory(ReactDOM.button.type);
var button = ReactElement.createFactory('button');
var mouseListenerNames = keyMirror({
onClick: true,
@@ -16,10 +16,8 @@ var LocalEventTrapMixin = require('LocalEventTrapMixin');
var ReactBrowserComponentMixin = require('ReactBrowserComponentMixin');
var ReactClass = require('ReactClass');
var ReactElement = require('ReactElement');
var ReactDOM = require('ReactDOM');
// Store a reference to the <form> `ReactDOMComponent`. TODO: use string
var form = ReactElement.createFactory(ReactDOM.form.type);
var form = ReactElement.createFactory('form');
/**
* Since onSubmit doesn't bubble OR capture on the top level in IE8, we need
+1 -3
View File
@@ -16,10 +16,8 @@ var LocalEventTrapMixin = require('LocalEventTrapMixin');
var ReactBrowserComponentMixin = require('ReactBrowserComponentMixin');
var ReactClass = require('ReactClass');
var ReactElement = require('ReactElement');
var ReactDOM = require('ReactDOM');
// Store a reference to the <img> `ReactDOMComponent`. TODO: use string
var img = ReactElement.createFactory(ReactDOM.img.type);
var img = ReactElement.createFactory('img');
/**
* Since onLoad doesn't bubble OR capture on the top level in IE8, we need to
@@ -17,15 +17,13 @@ var LinkedValueUtils = require('LinkedValueUtils');
var ReactBrowserComponentMixin = require('ReactBrowserComponentMixin');
var ReactClass = require('ReactClass');
var ReactElement = require('ReactElement');
var ReactDOM = require('ReactDOM');
var ReactMount = require('ReactMount');
var ReactUpdates = require('ReactUpdates');
var assign = require('Object.assign');
var invariant = require('invariant');
// Store a reference to the <input> `ReactDOMComponent`. TODO: use string
var input = ReactElement.createFactory(ReactDOM.input.type);
var input = ReactElement.createFactory('input');
var instancesByReactID = {};
@@ -14,12 +14,10 @@
var ReactBrowserComponentMixin = require('ReactBrowserComponentMixin');
var ReactClass = require('ReactClass');
var ReactElement = require('ReactElement');
var ReactDOM = require('ReactDOM');
var warning = require('warning');
// Store a reference to the <option> `ReactDOMComponent`. TODO: use string
var option = ReactElement.createFactory(ReactDOM.option.type);
var option = ReactElement.createFactory('option');
/**
* Implements an <option> native component that warns when `selected` is set.
@@ -16,13 +16,11 @@ var LinkedValueUtils = require('LinkedValueUtils');
var ReactBrowserComponentMixin = require('ReactBrowserComponentMixin');
var ReactClass = require('ReactClass');
var ReactElement = require('ReactElement');
var ReactDOM = require('ReactDOM');
var ReactUpdates = require('ReactUpdates');
var assign = require('Object.assign');
// Store a reference to the <select> `ReactDOMComponent`. TODO: use string
var select = ReactElement.createFactory(ReactDOM.select.type);
var select = ReactElement.createFactory('select');
function updateWithPendingValueIfMounted() {
/*jshint validthis:true */
@@ -17,7 +17,6 @@ var LinkedValueUtils = require('LinkedValueUtils');
var ReactBrowserComponentMixin = require('ReactBrowserComponentMixin');
var ReactClass = require('ReactClass');
var ReactElement = require('ReactElement');
var ReactDOM = require('ReactDOM');
var ReactUpdates = require('ReactUpdates');
var assign = require('Object.assign');
@@ -25,8 +24,7 @@ var invariant = require('invariant');
var warning = require('warning');
// Store a reference to the <textarea> `ReactDOMComponent`. TODO: use string
var textarea = ReactElement.createFactory(ReactDOM.textarea.type);
var textarea = ReactElement.createFactory('textarea');
function forceUpdateIfMounted() {
/*jshint validthis:true */
+4 -10
View File
@@ -13,8 +13,6 @@
var ReactCompositeComponent = require('ReactCompositeComponent');
var ReactElement = require('ReactElement');
var ReactElementValidator = require('ReactElementValidator');
var ReactLegacyElement = require('ReactLegacyElement');
var ReactPropTypeLocations = require('ReactPropTypeLocations');
var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames');
@@ -679,14 +677,10 @@ var ReactClass = {
}
}
if (__DEV__) {
return ReactLegacyElement.wrapFactory(
ReactElementValidator.createFactory(Constructor)
);
}
return ReactLegacyElement.wrapFactory(
ReactElement.createFactory(Constructor)
);
// Legacy hook TODO: Warn if this is accessed
Constructor.type = Constructor;
return Constructor;
},
injection: {
+24
View File
@@ -171,6 +171,17 @@ var ReactCompositeComponentMixin = {
this.props = this._processProps(this.props);
this.state = this.getInitialState ? this.getInitialState() : null;
if (__DEV__) {
// We allow auto-mocks to proceed as if they're returning null.
if (typeof this.state === 'undefined' &&
this.getInitialState && this.getInitialState._isMockFunction) {
// This is probably bad practice. Consider warning here and
// deprecating this convenience.
this.state = null;
}
}
invariant(
typeof this.state === 'object' && !Array.isArray(this.state),
'%s.getInitialState(): must return an object or null',
@@ -675,6 +686,15 @@ var ReactCompositeComponentMixin = {
ReactCurrentOwner.current = this;
try {
renderedComponent = this.render();
if (__DEV__) {
// We allow auto-mocks to proceed as if they're returning null.
if (typeof renderedComponent === 'undefined' &&
this.render._isMockFunction) {
// This is probably bad practice. Consider warning here and
// deprecating this convenience.
renderedComponent = null;
}
}
if (renderedComponent === null || renderedComponent === false) {
renderedComponent = ReactEmptyComponent.getEmptyComponent();
ReactEmptyComponent.registerNullComponentID(this._rootNodeID);
@@ -781,4 +801,8 @@ var ReactCompositeComponent = {
};
// Temporary injection.
// TODO: Delete this hack once implementation details are hidden.
instantiateReactComponent._compositeBase = ReactCompositeComponentBase;
module.exports = ReactCompositeComponent;
+1
View File
@@ -199,6 +199,7 @@ ReactElement.createFactory = function(type) {
// easily accessed on elements. E.g. <Foo />.type === Foo.type.
// This should not be named `constructor` since this may not be the function
// that created the element, and it may not even be a constructor.
// Legacy hook TODO: Warn if this is accessed
factory.type = type;
return factory;
};
+1
View File
@@ -273,6 +273,7 @@ var ReactElementValidator = {
null,
type
);
// Legacy hook TODO: Warn if this is accessed
validatedFactory.type = type;
return validatedFactory;
}
-243
View File
@@ -1,243 +0,0 @@
/**
* Copyright 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.
*
* @providesModule ReactLegacyElement
*/
"use strict";
var ReactCurrentOwner = require('ReactCurrentOwner');
var invariant = require('invariant');
var monitorCodeUse = require('monitorCodeUse');
var warning = require('warning');
var legacyFactoryLogs = {};
function warnForLegacyFactoryCall() {
if (!ReactLegacyElementFactory._isLegacyCallWarningEnabled) {
return;
}
var owner = ReactCurrentOwner.current;
var name = owner && owner.constructor ? owner.constructor.displayName : '';
if (!name) {
name = 'Something';
}
if (legacyFactoryLogs.hasOwnProperty(name)) {
return;
}
legacyFactoryLogs[name] = true;
warning(
false,
name + ' is calling a React component directly. ' +
'Use a factory or JSX instead. See: http://fb.me/react-legacyfactory'
);
monitorCodeUse('react_legacy_factory_call', { version: 3, name: name });
}
function warnForPlainFunctionType(type) {
var isReactClass =
type.prototype &&
typeof type.prototype.mountComponent === 'function' &&
typeof type.prototype.receiveComponent === 'function';
if (isReactClass) {
warning(
false,
'Did not expect to get a React class here. Use `Component` instead ' +
'of `Component.type` or `this.constructor`.'
);
} else {
if (!type._reactWarnedForThisType) {
try {
type._reactWarnedForThisType = true;
} catch (x) {
// just incase this is a frozen object or some special object
}
monitorCodeUse(
'react_non_component_in_jsx',
{ version: 3, name: type.name }
);
}
warning(
false,
'This JSX uses a plain function. Only React components are ' +
'valid in React\'s JSX transform.'
);
}
}
function warnForNonLegacyFactory(type) {
warning(
false,
'Do not pass React.DOM.' + type.type + ' to JSX or createFactory. ' +
'Use the string "' + type.type + '" instead.'
);
}
/**
* Transfer static properties from the source to the target. Functions are
* rebound to have this reflect the original source.
*/
function proxyStaticMethods(target, source) {
if (typeof source !== 'function') {
return;
}
for (var key in source) {
if (source.hasOwnProperty(key)) {
var value = source[key];
if (typeof value === 'function') {
var bound = value.bind(source);
// Copy any properties defined on the function, such as `isRequired` on
// a PropTypes validator.
for (var k in value) {
if (value.hasOwnProperty(k)) {
bound[k] = value[k];
}
}
target[key] = bound;
} else {
target[key] = value;
}
}
}
}
// We use an object instead of a boolean because booleans are ignored by our
// mocking libraries when these factories gets mocked.
var LEGACY_MARKER = {};
var NON_LEGACY_MARKER = {};
var ReactLegacyElementFactory = {};
ReactLegacyElementFactory.wrapCreateFactory = function(createFactory) {
var legacyCreateFactory = function(type) {
if (typeof type !== 'function') {
// Non-function types cannot be legacy factories
return createFactory(type);
}
if (type.isReactNonLegacyFactory) {
// This is probably a factory created by ReactDOM we unwrap it to get to
// the underlying string type. It shouldn't have been passed here so we
// warn.
if (__DEV__) {
warnForNonLegacyFactory(type);
}
return createFactory(type.type);
}
if (type.isReactLegacyFactory) {
// This is probably a legacy factory created by ReactCompositeComponent.
// We unwrap it to get to the underlying class.
return createFactory(type.type);
}
if (__DEV__) {
warnForPlainFunctionType(type);
}
// Unless it's a legacy factory, then this is probably a plain function,
// that is expecting to be invoked by JSX. We can just return it as is.
return type;
};
return legacyCreateFactory;
};
ReactLegacyElementFactory.wrapCreateElement = function(createElement) {
var legacyCreateElement = function(type, props, children) {
if (typeof type !== 'function') {
// Non-function types cannot be legacy factories
return createElement.apply(this, arguments);
}
var args;
if (type.isReactNonLegacyFactory) {
// This is probably a factory created by ReactDOM we unwrap it to get to
// the underlying string type. It shouldn't have been passed here so we
// warn.
if (__DEV__) {
warnForNonLegacyFactory(type);
}
args = Array.prototype.slice.call(arguments, 0);
args[0] = type.type;
return createElement.apply(this, args);
}
if (type.isReactLegacyFactory) {
// This is probably a legacy factory created by ReactCompositeComponent.
// We unwrap it to get to the underlying class.
if (type._isMockFunction) {
// If this is a mock function, people will expect it to be called. We
// will actually call the original mock factory function instead. This
// future proofs unit testing that assume that these are classes.
type.type._mockedReactClassConstructor = type;
}
args = Array.prototype.slice.call(arguments, 0);
args[0] = type.type;
return createElement.apply(this, args);
}
if (__DEV__) {
warnForPlainFunctionType(type);
}
// This is being called with a plain function we should invoke it
// immediately as if this was used with legacy JSX.
return type.apply(null, Array.prototype.slice.call(arguments, 1));
};
return legacyCreateElement;
};
ReactLegacyElementFactory.wrapFactory = function(factory) {
invariant(
typeof factory === 'function',
'This is suppose to accept a element factory'
);
var legacyElementFactory = function(config, children) {
// This factory should not be called when JSX is used. Use JSX instead.
if (__DEV__) {
warnForLegacyFactoryCall();
}
return factory.apply(this, arguments);
};
proxyStaticMethods(legacyElementFactory, factory.type);
legacyElementFactory.isReactLegacyFactory = LEGACY_MARKER;
legacyElementFactory.type = factory.type;
return legacyElementFactory;
};
// This is used to mark a factory that will remain. E.g. we're allowed to call
// it as a function. However, you're not suppose to pass it to createElement
// or createFactory, so it will warn you if you do.
ReactLegacyElementFactory.markNonLegacyFactory = function(factory) {
factory.isReactNonLegacyFactory = NON_LEGACY_MARKER;
return factory;
};
// Checks if a factory function is actually a legacy factory pretending to
// be a class.
ReactLegacyElementFactory.isValidFactory = function(factory) {
// TODO: This will be removed and moved into a class validator or something.
return typeof factory === 'function' &&
factory.isReactLegacyFactory === LEGACY_MARKER;
};
ReactLegacyElementFactory.isValidClass = function(factory) {
if (__DEV__) {
warning(
false,
'isValidClass is deprecated and will be removed in a future release. ' +
'Use a more specific validator instead.'
);
}
return ReactLegacyElementFactory.isValidFactory(factory);
};
ReactLegacyElementFactory._isLegacyCallWarningEnabled = true;
module.exports = ReactLegacyElementFactory;
+1 -1
View File
@@ -58,7 +58,7 @@ function createInstanceForTag(tag, props, parentType) {
return new genericComponentClass(tag, props);
}
// Unwrap legacy factories
return new componentClass.type(props);
return new componentClass(props);
}
var ReactNativeComponent = {
@@ -878,51 +878,6 @@ describe('ReactCompositeComponent', function() {
expect(ReactMount.purgeID.callCount).toBe(4);
});
it('should warn but detect valid CompositeComponent classes', function() {
var warn = console.warn;
console.warn = mocks.getMockFunction();
var Component = React.createClass({
render: function() {
return <div/>;
}
});
expect(React.isValidClass(Component)).toBe(true);
expect(console.warn.mock.calls.length).toBe(1);
expect(console.warn.mock.calls[0][0]).toContain(
'isValidClass is deprecated and will be removed in a future release'
);
});
it('should warn but detect invalid CompositeComponent classes', function() {
var warn = console.warn;
console.warn = mocks.getMockFunction();
var FnComponent = function() {
return false;
};
var NullComponent = null;
var TrickFnComponent = function() {
return true;
};
TrickFnComponent.componentConstructor = true;
expect(React.isValidClass(FnComponent)).toBe(false);
expect(React.isValidClass(NullComponent)).toBe(false);
expect(React.isValidClass(TrickFnComponent)).toBe(false);
expect(console.warn.mock.calls.length).toBe(3);
console.warn.mock.calls.forEach(function(call) {
expect(call[0]).toContain(
'isValidClass is deprecated and will be removed in a future release'
);
});
});
it('should warn when shouldComponentUpdate() returns undefined', function() {
var warn = console.warn;
console.warn = mocks.getMockFunction();
@@ -1282,8 +1237,8 @@ describe('ReactCompositeComponent', function() {
expect(Component.ghi).toBe(null);
expect(instance.constructor.jkl).toBe('mno');
expect(Component.jkl).toBe('mno');
expect(instance.constructor.pqr()).toBe(Component.type);
expect(Component.pqr()).toBe(Component.type);
expect(instance.constructor.pqr()).toBe(Component);
expect(Component.pqr()).toBe(Component);
});
it('should throw if a reserved property is in statics', function() {
@@ -152,8 +152,8 @@ describe('ReactCompositeComponent-mixin', function() {
});
it('should validate prop types via mixins', function() {
expect(TestComponent.type.propTypes).toBeDefined();
expect(TestComponent.type.propTypes.value)
expect(TestComponent.propTypes).toBeDefined();
expect(TestComponent.propTypes.value)
.toBe(mixinPropValidator);
});
@@ -161,11 +161,11 @@ describe('ReactCompositeComponent-mixin', function() {
// Sanity check...
expect(componentPropValidator).toNotBe(mixinPropValidator);
// Actually check...
expect(TestComponentWithPropTypes.type.propTypes)
expect(TestComponentWithPropTypes.propTypes)
.toBeDefined();
expect(TestComponentWithPropTypes.type.propTypes.value)
expect(TestComponentWithPropTypes.propTypes.value)
.toNotBe(mixinPropValidator);
expect(TestComponentWithPropTypes.type.propTypes.value)
expect(TestComponentWithPropTypes.propTypes.value)
.toBe(componentPropValidator);
});
});
@@ -41,7 +41,7 @@ describe('ReactCompositeComponent-spec', function() {
}
});
expect(TestComponent.type.displayName)
expect(TestComponent.displayName)
.toBe('TestComponent');
});
@@ -56,8 +56,8 @@ describe('ReactCompositeComponent-spec', function() {
}
});
expect(TestComponent.type.propTypes).toBeDefined();
expect(TestComponent.type.propTypes.value)
expect(TestComponent.propTypes).toBeDefined();
expect(TestComponent.propTypes.value)
.toBe(propValidator);
});
});
+1 -47
View File
@@ -26,7 +26,7 @@ describe('ReactElement', function() {
ComponentFactory = React.createClass({
render: function() { return <div />; }
});
ComponentClass = ComponentFactory.type;
ComponentClass = ComponentFactory;
});
it('returns a complete element according to spec', function() {
@@ -338,52 +338,6 @@ describe('ReactElement', function() {
expect(ReactElement.isValidElement(Component)).toEqual(false);
});
it('warns but allow a plain function in a factory to be invoked', function() {
spyOn(console, 'warn');
// This is a temporary helper to allow JSX with plain functions.
// This allow you to track down these callers and replace them with regular
// function calls.
var factory = React.createFactory(function (x) {
return 21 + x;
});
expect(factory(21)).toBe(42);
expect(console.warn.argsForCall.length).toBe(1);
expect(console.warn.argsForCall[0][0]).toContain(
'This JSX uses a plain function.'
);
});
it('warns but allow a plain function to be immediately invoked', function() {
spyOn(console, 'warn');
var result = React.createElement(function (x, y) {
return 21 + x + y;
}, 11, 10);
expect(result).toBe(42);
expect(console.warn.argsForCall.length).toBe(1);
expect(console.warn.argsForCall[0][0]).toContain(
'This JSX uses a plain function.'
);
});
it('warns but does not fail on undefined results', function() {
spyOn(console, 'warn');
var fn = function () { };
var result = React.createElement(fn, 1, 2, null);
expect(result).toBe(undefined);
expect(console.warn.argsForCall.length).toBe(1);
expect(console.warn.argsForCall[0][0]).toContain(
'This JSX uses a plain function.'
);
});
it('should expose the underlying class from a legacy factory', function() {
var Legacy = React.createClass({ render: function() { } });
var factory = React.createFactory(Legacy);
expect(factory.type).toBe(Legacy.type);
expect(factory().type).toBe(Legacy.type);
});
it('allows the use of PropTypes validators in statics', function() {
var Component = React.createClass({
render: () => null,
@@ -0,0 +1,138 @@
/**
* 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 ReactComponent;
var ReactTestUtils;
var reactComponentExpect;
var mocks;
var OriginalComponent;
var AutoMockedComponent;
var MockedComponent;
describe('ReactMockedComponent', function() {
beforeEach(function() {
mocks = require('mocks');
React = require('React');
ReactComponent = require('ReactComponent');
ReactTestUtils = require('ReactTestUtils');
OriginalComponent = React.createClass({
getInitialState: function() {
return { foo: 'bar' };
},
hasCustomMethod: function() {
return true;
},
render: function() {
return <span />;
}
});
var metaData = mocks.getMetadata(OriginalComponent);
AutoMockedComponent = mocks.generateFromMetadata(metaData);
MockedComponent = mocks.generateFromMetadata(metaData);
ReactTestUtils.mockComponent(MockedComponent);
});
it('should allow an implicitly mocked component to be rendered', () => {
ReactTestUtils.renderIntoDocument(<AutoMockedComponent />);
});
it('should allow an implicitly mocked component to be updated', () => {
var Wrapper = React.createClass({
getInitialState: function() {
return { foo: 1 };
},
update: function() {
this.setState({ foo: 2 });
},
render: function() {
return <AutoMockedComponent prop={this.state.foo} />;
}
});
var instance = ReactTestUtils.renderIntoDocument(<Wrapper />);
instance.update();
});
it('should find an implicitly mocked component in the tree', function() {
var instance = ReactTestUtils.renderIntoDocument(
<div><span><AutoMockedComponent prop="1" /></span></div>
);
var found = ReactTestUtils.findRenderedComponentWithType(
instance,
AutoMockedComponent
);
expect(typeof found).toBe('object');
});
it('has custom methods on the implicitly mocked component', () => {
var instance = ReactTestUtils.renderIntoDocument(<AutoMockedComponent />);
expect(typeof instance.hasCustomMethod).toBe('function');
});
it('should allow an explicitly mocked component to be rendered', () => {
ReactTestUtils.renderIntoDocument(<MockedComponent />);
});
it('should allow an explicitly mocked component to be updated', () => {
var Wrapper = React.createClass({
getInitialState: function() {
return { foo: 1 };
},
update: function() {
this.setState({ foo: 2 });
},
render: function() {
return <MockedComponent prop={this.state.foo} />;
}
});
var instance = ReactTestUtils.renderIntoDocument(<Wrapper />);
instance.update();
});
it('should find an explicitly mocked component in the tree', function() {
var instance = ReactTestUtils.renderIntoDocument(
<div><span><MockedComponent prop="1" /></span></div>
);
var found = ReactTestUtils.findRenderedComponentWithType(
instance,
MockedComponent
);
expect(typeof found).toBe('object');
});
it('has custom methods on the explicitly mocked component', () => {
var instance = ReactTestUtils.renderIntoDocument(<MockedComponent />);
expect(typeof instance.hasCustomMethod).toBe('function');
});
});
+27 -43
View File
@@ -15,9 +15,29 @@
var warning = require('warning');
var ReactElement = require('ReactElement');
var ReactLegacyElement = require('ReactLegacyElement');
var ReactNativeComponent = require('ReactNativeComponent');
var ReactEmptyComponent = require('ReactEmptyComponent');
// This is temporary until we've hidden all the implementation details
// TODO: Delete this hack once implementation details are hidden
var publicAPIs = {
forceUpdate: true,
replaceState: true,
setProps: true,
setState: true,
getDOMNode: true
// Public APIs used internally:
// isMounted: true,
// replaceProps: true,
};
function unmockImplementationDetails(mockInstance) {
var ReactCompositeComponentBase = instantiateReactComponent._compositeBase;
for (var key in ReactCompositeComponentBase.prototype) {
if (!publicAPIs.hasOwnProperty(key)) {
mockInstance[key] = ReactCompositeComponentBase.prototype[key];
}
}
}
/**
* Given an `element` create an instance that will actually be mounted.
@@ -36,47 +56,6 @@ function instantiateReactComponent(element, parentCompositeType) {
typeof element.type === 'string'),
'Only functions or strings can be mounted as React components.'
);
// Resolve mock instances
if (element.type._mockedReactClassConstructor) {
// If this is a mocked class, we treat the legacy factory as if it was the
// class constructor for future proofing unit tests. Because this might
// be mocked as a legacy factory, we ignore any warnings triggerd by
// this temporary hack.
ReactLegacyElement._isLegacyCallWarningEnabled = false;
try {
instance = new element.type._mockedReactClassConstructor(
element.props
);
} finally {
ReactLegacyElement._isLegacyCallWarningEnabled = true;
}
// If the mock implementation was a legacy factory, then it returns a
// element. We need to turn this into a real component instance.
if (ReactElement.isValidElement(instance)) {
instance = new instance.type(instance.props);
}
var render = instance.render;
if (!render) {
// For auto-mocked factories, the prototype isn't shimmed and therefore
// there is no render function on the instance. We replace the whole
// component with an empty component instance instead.
element = ReactEmptyComponent.getEmptyComponent();
} else {
if (render._isMockFunction && !render._getMockImplementation()) {
// Auto-mocked components may have a prototype with a mocked render
// function. For those, we'll need to mock the result of the render
// since we consider undefined to be invalid results from render.
render.mockImplementation(
ReactEmptyComponent.getEmptyComponent
);
}
instance.construct(element);
return instance;
}
}
}
// Special case string values
@@ -92,6 +71,11 @@ function instantiateReactComponent(element, parentCompositeType) {
}
if (__DEV__) {
if (element.type._isMockFunction) {
// TODO: Remove this special case
unmockImplementationDetails(instance);
}
warning(
typeof instance.construct === 'function' &&
typeof instance.mountComponent === 'function' &&
+8 -15
View File
@@ -55,7 +55,7 @@ var ReactTestUtils = {
isElementOfType: function(inst, convenienceConstructor) {
return (
ReactElement.isValidElement(inst) &&
inst.type === convenienceConstructor.type
inst.type === convenienceConstructor
);
},
@@ -76,7 +76,7 @@ var ReactTestUtils = {
isCompositeComponentWithType: function(inst, type) {
return !!(ReactTestUtils.isCompositeComponent(inst) &&
(inst.constructor === type.type));
(inst.constructor === type));
},
isCompositeComponentElement: function(inst) {
@@ -231,21 +231,14 @@ var ReactTestUtils = {
mockComponent: function(module, mockTagName) {
mockTagName = mockTagName || module.mockTagName || "div";
var ConvenienceConstructor = React.createClass({
render: function() {
return React.createElement(
mockTagName,
null,
this.props.children
);
}
module.prototype.render.mockImplementation(function() {
return React.createElement(
mockTagName,
null,
this.props.children
);
});
module.mockImplementation(ConvenienceConstructor);
module.type = ConvenienceConstructor.type;
module.isReactLegacyFactory = true;
return this;
},
+4 -7
View File
@@ -97,12 +97,9 @@ assign(reactComponentExpect.prototype, {
// Matchers ------------------------------------------------------------------
toBeComponentOfType: function(convenienceConstructor) {
var type = typeof convenienceConstructor === 'string' ?
convenienceConstructor :
convenienceConstructor.type;
toBeComponentOfType: function(constructor) {
expect(
this.instance()._currentElement.type === type
this.instance()._currentElement.type === constructor
).toBe(true);
return this;
},
@@ -119,10 +116,10 @@ assign(reactComponentExpect.prototype, {
return this;
},
toBeCompositeComponentWithType: function(convenienceConstructor) {
toBeCompositeComponentWithType: function(constructor) {
this.toBeCompositeComponent();
expect(
this.instance()._currentElement.type === convenienceConstructor.type
this.instance()._currentElement.type === constructor
).toBe(true);
return this;
},