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