@samitouri / QOS-React-2 / commits / 199a7d6903

Drop Legacy Factories Around Classes

Classes are now pure classes without a legacy factory around them. Since classes will become just any function that returns a valid instance, let's drop isValidClass. There's some hacks in here for auto-mocking frameworks (jest) that mock the prototype of these classes. These hacks allow these classes to be mounted.

Sebastian Markbage committed Oct 24, 2014 at 09:21 UTC 199a7d6903ed2e7c28fb32c6a28ed7f26148e13d
14 files changed +207 -439
src/browser/ReactDOM.js
+2 -7
@@ -14,7 +14,6 @@
14
15 var ReactElement = require('ReactElement');
16 var ReactElementValidator = require('ReactElementValidator');
17 -var ReactLegacyElement = require('ReactLegacyElement');
17
18 var mapObject = require('mapObject');
19
@@ -26,13 +25,9 @@ var mapObject = require('mapObject');
25 */
26 function createDOMFactory(tag) {
27 if (__DEV__) {
29 - return ReactLegacyElement.markNonLegacyFactory(
30 - ReactElementValidator.createFactory(tag)
31 - );
28 + return ReactElementValidator.createFactory(tag);
29 }
33 - return ReactLegacyElement.markNonLegacyFactory(
34 - ReactElement.createFactory(tag)
35 - );
30 + return ReactElement.createFactory(tag);
31 }
32
33 /**
src/browser/__tests__/ReactDOM-test.js
-20
@@ -114,24 +114,4 @@ describe('ReactDOM', function() {
114 expect(element.type).toBe('div');
115 expect(console.warn.argsForCall.length).toBe(0);
116 });
117 -
118 - it('warns but allow dom factories to be used in createFactory', function() {
119 - spyOn(console, 'warn');
120 - var factory = React.createFactory(React.DOM.div);
121 - expect(factory().type).toBe('div');
122 - expect(console.warn.argsForCall.length).toBe(1);
123 - expect(console.warn.argsForCall[0][0]).toContain(
124 - 'Do not pass React.DOM.div'
125 - );
126 - });
127 -
128 - it('warns but allow dom factories to be used in createElement', function() {
129 - spyOn(console, 'warn');
130 - var element = React.createElement(React.DOM.div);
131 - expect(element.type).toBe('div');
132 - expect(console.warn.argsForCall.length).toBe(1);
133 - expect(console.warn.argsForCall[0][0]).toContain(
134 - 'Do not pass React.DOM.div'
135 - );
136 - });
117 });
src/browser/ui/React.js
-10
@@ -24,7 +24,6 @@ var ReactDOM = require('ReactDOM');
24 var ReactDOMComponent = require('ReactDOMComponent');
25 var ReactDefaultInjection = require('ReactDefaultInjection');
26 var ReactInstanceHandles = require('ReactInstanceHandles');
27 -var ReactLegacyElement = require('ReactLegacyElement');
27 var ReactMount = require('ReactMount');
28 var ReactMultiChild = require('ReactMultiChild');
29 var ReactPerf = require('ReactPerf');
@@ -46,14 +45,6 @@ if (__DEV__) {
45 createFactory = ReactElementValidator.createFactory;
46 }
47
49 -// TODO: Drop legacy elements once classes no longer export these factories
50 -createElement = ReactLegacyElement.wrapCreateElement(
51 - createElement
52 -);
53 -createFactory = ReactLegacyElement.wrapCreateFactory(
54 - createFactory
55 -);
56 -
48 var render = ReactPerf.measure('React', 'render', ReactMount.render);
49
50 var React = {
@@ -77,7 +68,6 @@ var React = {
68 renderToString: ReactServerRendering.renderToString,
69 renderToStaticMarkup: ReactServerRendering.renderToStaticMarkup,
70 unmountComponentAtNode: ReactMount.unmountComponentAtNode,
80 - isValidClass: ReactLegacyElement.isValidClass,
71 isValidElement: ReactElement.isValidElement,
72 withContext: ReactContext.withContext,
73
src/browser/ui/ReactMount.js
+2 -7
@@ -15,7 +15,6 @@ var DOMProperty = require('DOMProperty');
15 var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
16 var ReactCurrentOwner = require('ReactCurrentOwner');
17 var ReactElement = require('ReactElement');
18 -var ReactLegacyElement = require('ReactLegacyElement');
18 var ReactInstanceHandles = require('ReactInstanceHandles');
19 var ReactPerf = require('ReactPerf');
20
@@ -27,10 +26,6 @@ var invariant = require('invariant');
26 var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
27 var warning = require('warning');
28
30 -var createElement = ReactLegacyElement.wrapCreateElement(
31 - ReactElement.createElement
32 -);
33 -
29 var SEPARATOR = ReactInstanceHandles.SEPARATOR;
30
31 var ATTR_NAME = DOMProperty.ID_ATTRIBUTE_NAME;
@@ -342,7 +337,7 @@ var ReactMount = {
337 typeof nextElement === 'string' ?
338 ' Instead of passing an element string, make sure to instantiate ' +
339 'it by passing it to React.createElement.' :
345 - ReactLegacyElement.isValidFactory(nextElement) ?
340 + typeof nextElement === 'function' ?
341 ' Instead of passing a component class, make sure to instantiate ' +
342 'it by passing it to React.createElement.' :
343 // Check if it quacks like a element
@@ -394,7 +389,7 @@ var ReactMount = {
389 * @return {ReactComponent} Component instance rendered in `container`.
390 */
391 constructAndRenderComponent: function(constructor, props, container) {
397 - var element = createElement(constructor, props);
392 + var element = ReactElement.createElement(constructor, props);
393 return ReactMount.render(element, container);
394 },
395
src/class/ReactClass.js
+4 -10
@@ -13,8 +13,6 @@
13
14 var ReactCompositeComponent = require('ReactCompositeComponent');
15 var ReactElement = require('ReactElement');
16 -var ReactElementValidator = require('ReactElementValidator');
17 -var ReactLegacyElement = require('ReactLegacyElement');
16 var ReactPropTypeLocations = require('ReactPropTypeLocations');
17 var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames');
18
@@ -679,14 +677,10 @@ var ReactClass = {
677 }
678 }
679
682 - if (__DEV__) {
683 - return ReactLegacyElement.wrapFactory(
684 - ReactElementValidator.createFactory(Constructor)
685 - );
686 - }
687 - return ReactLegacyElement.wrapFactory(
688 - ReactElement.createFactory(Constructor)
689 - );
680 + // Legacy hook TODO: Warn if this is accessed
681 + Constructor.type = Constructor;
682 +
683 + return Constructor;
684 },
685
686 injection: {
src/core/ReactCompositeComponent.js
+24
@@ -171,6 +171,17 @@ var ReactCompositeComponentMixin = {
171 this.props = this._processProps(this.props);
172
173 this.state = this.getInitialState ? this.getInitialState() : null;
174 +
175 + if (__DEV__) {
176 + // We allow auto-mocks to proceed as if they're returning null.
177 + if (typeof this.state === 'undefined' &&
178 + this.getInitialState && this.getInitialState._isMockFunction) {
179 + // This is probably bad practice. Consider warning here and
180 + // deprecating this convenience.
181 + this.state = null;
182 + }
183 + }
184 +
185 invariant(
186 typeof this.state === 'object' && !Array.isArray(this.state),
187 '%s.getInitialState(): must return an object or null',
@@ -675,6 +686,15 @@ var ReactCompositeComponentMixin = {
686 ReactCurrentOwner.current = this;
687 try {
688 renderedComponent = this.render();
689 + if (__DEV__) {
690 + // We allow auto-mocks to proceed as if they're returning null.
691 + if (typeof renderedComponent === 'undefined' &&
692 + this.render._isMockFunction) {
693 + // This is probably bad practice. Consider warning here and
694 + // deprecating this convenience.
695 + renderedComponent = null;
696 + }
697 + }
698 if (renderedComponent === null || renderedComponent === false) {
699 renderedComponent = ReactEmptyComponent.getEmptyComponent();
700 ReactEmptyComponent.registerNullComponentID(this._rootNodeID);
@@ -781,4 +801,8 @@ var ReactCompositeComponent = {
801
802 };
803
804 +// Temporary injection.
805 +// TODO: Delete this hack once implementation details are hidden.
806 +instantiateReactComponent._compositeBase = ReactCompositeComponentBase;
807 +
808 module.exports = ReactCompositeComponent;
src/core/ReactElement.js
+1
@@ -199,6 +199,7 @@ ReactElement.createFactory = function(type) {
199 // easily accessed on elements. E.g. <Foo />.type === Foo.type.
200 // This should not be named `constructor` since this may not be the function
201 // that created the element, and it may not even be a constructor.
202 + // Legacy hook TODO: Warn if this is accessed
203 factory.type = type;
204 return factory;
205 };
src/core/ReactElementValidator.js
+1
@@ -273,6 +273,7 @@ var ReactElementValidator = {
273 null,
274 type
275 );
276 + // Legacy hook TODO: Warn if this is accessed
277 validatedFactory.type = type;
278 return validatedFactory;
279 }
src/core/ReactLegacyElement.js deleted
-243
@@ -1,243 +0,0 @@
1 -/**
2 - * Copyright 2014, Facebook, Inc.
3 - * All rights reserved.
4 - *
5 - * This source code is licensed under the BSD-style license found in the
6 - * LICENSE file in the root directory of this source tree. An additional grant
7 - * of patent rights can be found in the PATENTS file in the same directory.
8 - *
9 - * @providesModule ReactLegacyElement
10 - */
11 -
12 -"use strict";
13 -
14 -var ReactCurrentOwner = require('ReactCurrentOwner');
15 -
16 -var invariant = require('invariant');
17 -var monitorCodeUse = require('monitorCodeUse');
18 -var warning = require('warning');
19 -
20 -var legacyFactoryLogs = {};
21 -function warnForLegacyFactoryCall() {
22 - if (!ReactLegacyElementFactory._isLegacyCallWarningEnabled) {
23 - return;
24 - }
25 - var owner = ReactCurrentOwner.current;
26 - var name = owner && owner.constructor ? owner.constructor.displayName : '';
27 - if (!name) {
28 - name = 'Something';
29 - }
30 - if (legacyFactoryLogs.hasOwnProperty(name)) {
31 - return;
32 - }
33 - legacyFactoryLogs[name] = true;
34 - warning(
35 - false,
36 - name + ' is calling a React component directly. ' +
37 - 'Use a factory or JSX instead. See: http://fb.me/react-legacyfactory'
38 - );
39 - monitorCodeUse('react_legacy_factory_call', { version: 3, name: name });
40 -}
41 -
42 -function warnForPlainFunctionType(type) {
43 - var isReactClass =
44 - type.prototype &&
45 - typeof type.prototype.mountComponent === 'function' &&
46 - typeof type.prototype.receiveComponent === 'function';
47 - if (isReactClass) {
48 - warning(
49 - false,
50 - 'Did not expect to get a React class here. Use `Component` instead ' +
51 - 'of `Component.type` or `this.constructor`.'
52 - );
53 - } else {
54 - if (!type._reactWarnedForThisType) {
55 - try {
56 - type._reactWarnedForThisType = true;
57 - } catch (x) {
58 - // just incase this is a frozen object or some special object
59 - }
60 - monitorCodeUse(
61 - 'react_non_component_in_jsx',
62 - { version: 3, name: type.name }
63 - );
64 - }
65 - warning(
66 - false,
67 - 'This JSX uses a plain function. Only React components are ' +
68 - 'valid in React\'s JSX transform.'
69 - );
70 - }
71 -}
72 -
73 -function warnForNonLegacyFactory(type) {
74 - warning(
75 - false,
76 - 'Do not pass React.DOM.' + type.type + ' to JSX or createFactory. ' +
77 - 'Use the string "' + type.type + '" instead.'
78 - );
79 -}
80 -
81 -/**
82 - * Transfer static properties from the source to the target. Functions are
83 - * rebound to have this reflect the original source.
84 - */
85 -function proxyStaticMethods(target, source) {
86 - if (typeof source !== 'function') {
87 - return;
88 - }
89 - for (var key in source) {
90 - if (source.hasOwnProperty(key)) {
91 - var value = source[key];
92 - if (typeof value === 'function') {
93 - var bound = value.bind(source);
94 - // Copy any properties defined on the function, such as `isRequired` on
95 - // a PropTypes validator.
96 - for (var k in value) {
97 - if (value.hasOwnProperty(k)) {
98 - bound[k] = value[k];
99 - }
100 - }
101 - target[key] = bound;
102 - } else {
103 - target[key] = value;
104 - }
105 - }
106 - }
107 -}
108 -
109 -// We use an object instead of a boolean because booleans are ignored by our
110 -// mocking libraries when these factories gets mocked.
111 -var LEGACY_MARKER = {};
112 -var NON_LEGACY_MARKER = {};
113 -
114 -var ReactLegacyElementFactory = {};
115 -
116 -ReactLegacyElementFactory.wrapCreateFactory = function(createFactory) {
117 - var legacyCreateFactory = function(type) {
118 - if (typeof type !== 'function') {
119 - // Non-function types cannot be legacy factories
120 - return createFactory(type);
121 - }
122 -
123 - if (type.isReactNonLegacyFactory) {
124 - // This is probably a factory created by ReactDOM we unwrap it to get to
125 - // the underlying string type. It shouldn't have been passed here so we
126 - // warn.
127 - if (__DEV__) {
128 - warnForNonLegacyFactory(type);
129 - }
130 - return createFactory(type.type);
131 - }
132 -
133 - if (type.isReactLegacyFactory) {
134 - // This is probably a legacy factory created by ReactCompositeComponent.
135 - // We unwrap it to get to the underlying class.
136 - return createFactory(type.type);
137 - }
138 -
139 - if (__DEV__) {
140 - warnForPlainFunctionType(type);
141 - }
142 -
143 - // Unless it's a legacy factory, then this is probably a plain function,
144 - // that is expecting to be invoked by JSX. We can just return it as is.
145 - return type;
146 - };
147 - return legacyCreateFactory;
148 -};
149 -
150 -ReactLegacyElementFactory.wrapCreateElement = function(createElement) {
151 - var legacyCreateElement = function(type, props, children) {
152 - if (typeof type !== 'function') {
153 - // Non-function types cannot be legacy factories
154 - return createElement.apply(this, arguments);
155 - }
156 -
157 - var args;
158 -
159 - if (type.isReactNonLegacyFactory) {
160 - // This is probably a factory created by ReactDOM we unwrap it to get to
161 - // the underlying string type. It shouldn't have been passed here so we
162 - // warn.
163 - if (__DEV__) {
164 - warnForNonLegacyFactory(type);
165 - }
166 - args = Array.prototype.slice.call(arguments, 0);
167 - args[0] = type.type;
168 - return createElement.apply(this, args);
169 - }
170 -
171 - if (type.isReactLegacyFactory) {
172 - // This is probably a legacy factory created by ReactCompositeComponent.
173 - // We unwrap it to get to the underlying class.
174 - if (type._isMockFunction) {
175 - // If this is a mock function, people will expect it to be called. We
176 - // will actually call the original mock factory function instead. This
177 - // future proofs unit testing that assume that these are classes.
178 - type.type._mockedReactClassConstructor = type;
179 - }
180 - args = Array.prototype.slice.call(arguments, 0);
181 - args[0] = type.type;
182 - return createElement.apply(this, args);
183 - }
184 -
185 - if (__DEV__) {
186 - warnForPlainFunctionType(type);
187 - }
188 -
189 - // This is being called with a plain function we should invoke it
190 - // immediately as if this was used with legacy JSX.
191 - return type.apply(null, Array.prototype.slice.call(arguments, 1));
192 - };
193 - return legacyCreateElement;
194 -};
195 -
196 -ReactLegacyElementFactory.wrapFactory = function(factory) {
197 - invariant(
198 - typeof factory === 'function',
199 - 'This is suppose to accept a element factory'
200 - );
201 - var legacyElementFactory = function(config, children) {
202 - // This factory should not be called when JSX is used. Use JSX instead.
203 - if (__DEV__) {
204 - warnForLegacyFactoryCall();
205 - }
206 - return factory.apply(this, arguments);
207 - };
208 - proxyStaticMethods(legacyElementFactory, factory.type);
209 - legacyElementFactory.isReactLegacyFactory = LEGACY_MARKER;
210 - legacyElementFactory.type = factory.type;
211 - return legacyElementFactory;
212 -};
213 -
214 -// This is used to mark a factory that will remain. E.g. we're allowed to call
215 -// it as a function. However, you're not suppose to pass it to createElement
216 -// or createFactory, so it will warn you if you do.
217 -ReactLegacyElementFactory.markNonLegacyFactory = function(factory) {
218 - factory.isReactNonLegacyFactory = NON_LEGACY_MARKER;
219 - return factory;
220 -};
221 -
222 -// Checks if a factory function is actually a legacy factory pretending to
223 -// be a class.
224 -ReactLegacyElementFactory.isValidFactory = function(factory) {
225 - // TODO: This will be removed and moved into a class validator or something.
226 - return typeof factory === 'function' &&
227 - factory.isReactLegacyFactory === LEGACY_MARKER;
228 -};
229 -
230 -ReactLegacyElementFactory.isValidClass = function(factory) {
231 - if (__DEV__) {
232 - warning(
233 - false,
234 - 'isValidClass is deprecated and will be removed in a future release. ' +
235 - 'Use a more specific validator instead.'
236 - );
237 - }
238 - return ReactLegacyElementFactory.isValidFactory(factory);
239 -};
240 -
241 -ReactLegacyElementFactory._isLegacyCallWarningEnabled = true;
242 -
243 -module.exports = ReactLegacyElementFactory;
src/core/__tests__/ReactCompositeComponent-test.js
-45
@@ -878,51 +878,6 @@ describe('ReactCompositeComponent', function() {
878 expect(ReactMount.purgeID.callCount).toBe(4);
879 });
880
881 - it('should warn but detect valid CompositeComponent classes', function() {
882 - var warn = console.warn;
883 - console.warn = mocks.getMockFunction();
884 -
885 - var Component = React.createClass({
886 - render: function() {
887 - return <div/>;
888 - }
889 - });
890 -
891 - expect(React.isValidClass(Component)).toBe(true);
892 -
893 - expect(console.warn.mock.calls.length).toBe(1);
894 - expect(console.warn.mock.calls[0][0]).toContain(
895 - 'isValidClass is deprecated and will be removed in a future release'
896 - );
897 - });
898 -
899 - it('should warn but detect invalid CompositeComponent classes', function() {
900 - var warn = console.warn;
901 - console.warn = mocks.getMockFunction();
902 -
903 - var FnComponent = function() {
904 - return false;
905 - };
906 -
907 - var NullComponent = null;
908 -
909 - var TrickFnComponent = function() {
910 - return true;
911 - };
912 - TrickFnComponent.componentConstructor = true;
913 -
914 - expect(React.isValidClass(FnComponent)).toBe(false);
915 - expect(React.isValidClass(NullComponent)).toBe(false);
916 - expect(React.isValidClass(TrickFnComponent)).toBe(false);
917 -
918 - expect(console.warn.mock.calls.length).toBe(3);
919 - console.warn.mock.calls.forEach(function(call) {
920 - expect(call[0]).toContain(
921 - 'isValidClass is deprecated and will be removed in a future release'
922 - );
923 - });
924 - });
925 -
881 it('should warn when shouldComponentUpdate() returns undefined', function() {
882 var warn = console.warn;
883 console.warn = mocks.getMockFunction();
src/core/__tests__/ReactElement-test.js
-39
@@ -338,45 +338,6 @@ describe('ReactElement', function() {
338 expect(ReactElement.isValidElement(Component)).toEqual(false);
339 });
340
341 - it('warns but allow a plain function in a factory to be invoked', function() {
342 - spyOn(console, 'warn');
343 - // This is a temporary helper to allow JSX with plain functions.
344 - // This allow you to track down these callers and replace them with regular
345 - // function calls.
346 - var factory = React.createFactory(function (x) {
347 - return 21 + x;
348 - });
349 - expect(factory(21)).toBe(42);
350 - expect(console.warn.argsForCall.length).toBe(1);
351 - expect(console.warn.argsForCall[0][0]).toContain(
352 - 'This JSX uses a plain function.'
353 - );
354 - });
355 -
356 - it('warns but allow a plain function to be immediately invoked', function() {
357 - spyOn(console, 'warn');
358 - var result = React.createElement(function (x, y) {
359 - return 21 + x + y;
360 - }, 11, 10);
361 - expect(result).toBe(42);
362 - expect(console.warn.argsForCall.length).toBe(1);
363 - expect(console.warn.argsForCall[0][0]).toContain(
364 - 'This JSX uses a plain function.'
365 - );
366 - });
367 -
368 - it('warns but does not fail on undefined results', function() {
369 - spyOn(console, 'warn');
370 - var fn = function () { };
371 - var result = React.createElement(fn, 1, 2, null);
372 - expect(result).toBe(undefined);
373 - expect(console.warn.argsForCall.length).toBe(1);
374 - expect(console.warn.argsForCall[0][0]).toContain(
375 - 'This JSX uses a plain function.'
376 - );
377 - });
378 -
379 -
341 it('should expose the underlying class from a legacy factory', function() {
342 var Legacy = React.createClass({ render: function() { } });
343 var factory = React.createFactory(Legacy);
src/core/__tests__/ReactMockedComponent-test.js new
+138
@@ -0,0 +1,138 @@
1 +/**
2 + * Copyright 2013-2014, Facebook, Inc.
3 + * All rights reserved.
4 + *
5 + * This source code is licensed under the BSD-style license found in the
6 + * LICENSE file in the root directory of this source tree. An additional grant
7 + * of patent rights can be found in the PATENTS file in the same directory.
8 + *
9 + * @emails react-core
10 + */
11 +
12 +"use strict";
13 +
14 +var React;
15 +var ReactComponent;
16 +var ReactTestUtils;
17 +
18 +var reactComponentExpect;
19 +var mocks;
20 +
21 +var OriginalComponent;
22 +var AutoMockedComponent;
23 +var MockedComponent;
24 +
25 +describe('ReactMockedComponent', function() {
26 +
27 + beforeEach(function() {
28 + mocks = require('mocks');
29 +
30 + React = require('React');
31 + ReactComponent = require('ReactComponent');
32 + ReactTestUtils = require('ReactTestUtils');
33 +
34 + OriginalComponent = React.createClass({
35 +
36 + getInitialState: function() {
37 + return { foo: 'bar' };
38 + },
39 +
40 + hasCustomMethod: function() {
41 + return true;
42 + },
43 +
44 + render: function() {
45 + return <span />;
46 + }
47 +
48 + });
49 +
50 + var metaData = mocks.getMetadata(OriginalComponent);
51 +
52 + AutoMockedComponent = mocks.generateFromMetadata(metaData);
53 + MockedComponent = mocks.generateFromMetadata(metaData);
54 +
55 + ReactTestUtils.mockComponent(MockedComponent);
56 + });
57 +
58 + it('should allow an implicitly mocked component to be rendered', () => {
59 + ReactTestUtils.renderIntoDocument(<AutoMockedComponent />);
60 + });
61 +
62 + it('should allow an implicitly mocked component to be updated', () => {
63 + var Wrapper = React.createClass({
64 +
65 + getInitialState: function() {
66 + return { foo: 1 };
67 + },
68 +
69 + update: function() {
70 + this.setState({ foo: 2 });
71 + },
72 +
73 + render: function() {
74 + return <AutoMockedComponent prop={this.state.foo} />;
75 + }
76 +
77 + });
78 + var instance = ReactTestUtils.renderIntoDocument(<Wrapper />);
79 + instance.update();
80 + });
81 +
82 + it('should find an implicitly mocked component in the tree', function() {
83 + var instance = ReactTestUtils.renderIntoDocument(
84 + <div><span><AutoMockedComponent prop="1" /></span></div>
85 + );
86 + var found = ReactTestUtils.findRenderedComponentWithType(
87 + instance,
88 + AutoMockedComponent
89 + );
90 + expect(typeof found).toBe('object');
91 + });
92 +
93 + it('has custom methods on the implicitly mocked component', () => {
94 + var instance = ReactTestUtils.renderIntoDocument(<AutoMockedComponent />);
95 + expect(typeof instance.hasCustomMethod).toBe('function');
96 + });
97 +
98 + it('should allow an explicitly mocked component to be rendered', () => {
99 + ReactTestUtils.renderIntoDocument(<MockedComponent />);
100 + });
101 +
102 + it('should allow an explicitly mocked component to be updated', () => {
103 + var Wrapper = React.createClass({
104 +
105 + getInitialState: function() {
106 + return { foo: 1 };
107 + },
108 +
109 + update: function() {
110 + this.setState({ foo: 2 });
111 + },
112 +
113 + render: function() {
114 + return <MockedComponent prop={this.state.foo} />;
115 + }
116 +
117 + });
118 + var instance = ReactTestUtils.renderIntoDocument(<Wrapper />);
119 + instance.update();
120 + });
121 +
122 + it('should find an explicitly mocked component in the tree', function() {
123 + var instance = ReactTestUtils.renderIntoDocument(
124 + <div><span><MockedComponent prop="1" /></span></div>
125 + );
126 + var found = ReactTestUtils.findRenderedComponentWithType(
127 + instance,
128 + MockedComponent
129 + );
130 + expect(typeof found).toBe('object');
131 + });
132 +
133 + it('has custom methods on the explicitly mocked component', () => {
134 + var instance = ReactTestUtils.renderIntoDocument(<MockedComponent />);
135 + expect(typeof instance.hasCustomMethod).toBe('function');
136 + });
137 +
138 +});
src/core/instantiateReactComponent.js
+27 -43
@@ -15,9 +15,29 @@
15 var warning = require('warning');
16
17 var ReactElement = require('ReactElement');
18 -var ReactLegacyElement = require('ReactLegacyElement');
18 var ReactNativeComponent = require('ReactNativeComponent');
20 -var ReactEmptyComponent = require('ReactEmptyComponent');
19 +
20 +// This is temporary until we've hidden all the implementation details
21 +// TODO: Delete this hack once implementation details are hidden
22 +var publicAPIs = {
23 + forceUpdate: true,
24 + replaceState: true,
25 + setProps: true,
26 + setState: true,
27 + getDOMNode: true
28 + // Public APIs used internally:
29 + // isMounted: true,
30 + // replaceProps: true,
31 +};
32 +
33 +function unmockImplementationDetails(mockInstance) {
34 + var ReactCompositeComponentBase = instantiateReactComponent._compositeBase;
35 + for (var key in ReactCompositeComponentBase.prototype) {
36 + if (!publicAPIs.hasOwnProperty(key)) {
37 + mockInstance[key] = ReactCompositeComponentBase.prototype[key];
38 + }
39 + }
40 +}
41
42 /**
43 * Given an `element` create an instance that will actually be mounted.
@@ -36,47 +56,6 @@ function instantiateReactComponent(element, parentCompositeType) {
56 typeof element.type === 'string'),
57 'Only functions or strings can be mounted as React components.'
58 );
39 -
40 - // Resolve mock instances
41 - if (element.type._mockedReactClassConstructor) {
42 - // If this is a mocked class, we treat the legacy factory as if it was the
43 - // class constructor for future proofing unit tests. Because this might
44 - // be mocked as a legacy factory, we ignore any warnings triggerd by
45 - // this temporary hack.
46 - ReactLegacyElement._isLegacyCallWarningEnabled = false;
47 - try {
48 - instance = new element.type._mockedReactClassConstructor(
49 - element.props
50 - );
51 - } finally {
52 - ReactLegacyElement._isLegacyCallWarningEnabled = true;
53 - }
54 -
55 - // If the mock implementation was a legacy factory, then it returns a
56 - // element. We need to turn this into a real component instance.
57 - if (ReactElement.isValidElement(instance)) {
58 - instance = new instance.type(instance.props);
59 - }
60 -
61 - var render = instance.render;
62 - if (!render) {
63 - // For auto-mocked factories, the prototype isn't shimmed and therefore
64 - // there is no render function on the instance. We replace the whole
65 - // component with an empty component instance instead.
66 - element = ReactEmptyComponent.getEmptyComponent();
67 - } else {
68 - if (render._isMockFunction && !render._getMockImplementation()) {
69 - // Auto-mocked components may have a prototype with a mocked render
70 - // function. For those, we'll need to mock the result of the render
71 - // since we consider undefined to be invalid results from render.
72 - render.mockImplementation(
73 - ReactEmptyComponent.getEmptyComponent
74 - );
75 - }
76 - instance.construct(element);
77 - return instance;
78 - }
79 - }
59 }
60
61 // Special case string values
@@ -92,6 +71,11 @@ function instantiateReactComponent(element, parentCompositeType) {
71 }
72
73 if (__DEV__) {
74 + if (element.type._isMockFunction) {
75 + // TODO: Remove this special case
76 + unmockImplementationDetails(instance);
77 + }
78 +
79 warning(
80 typeof instance.construct === 'function' &&
81 typeof instance.mountComponent === 'function' &&
src/test/ReactTestUtils.js
+8 -15
@@ -55,7 +55,7 @@ var ReactTestUtils = {
55 isElementOfType: function(inst, convenienceConstructor) {
56 return (
57 ReactElement.isValidElement(inst) &&
58 - inst.type === convenienceConstructor.type
58 + inst.type === convenienceConstructor
59 );
60 },
61
@@ -76,7 +76,7 @@ var ReactTestUtils = {
76
77 isCompositeComponentWithType: function(inst, type) {
78 return !!(ReactTestUtils.isCompositeComponent(inst) &&
79 - (inst.constructor === type.type));
79 + (inst.constructor === type));
80 },
81
82 isCompositeComponentElement: function(inst) {
@@ -231,21 +231,14 @@ var ReactTestUtils = {
231 mockComponent: function(module, mockTagName) {
232 mockTagName = mockTagName || module.mockTagName || "div";
233
234 - var ConvenienceConstructor = React.createClass({
235 - render: function() {
236 - return React.createElement(
237 - mockTagName,
238 - null,
239 - this.props.children
240 - );
241 - }
234 + module.prototype.render.mockImplementation(function() {
235 + return React.createElement(
236 + mockTagName,
237 + null,
238 + this.props.children
239 + );
240 });
241
244 - module.mockImplementation(ConvenienceConstructor);
245 -
246 - module.type = ConvenienceConstructor.type;
247 - module.isReactLegacyFactory = true;
248 -
242 return this;
243 },
244