@samitouri / QOS-React-2 / commits / cea2c38733

Separate createElement and JSX tests

This essentially copies all classic element creation tests to the modern JSX tests. The classic tests doesn't use JSX and modern tests do. The idea is that the JSX tests can start dropping dynamic checks once we have Flow support for those features. JSX won't be necessary for dropping dynamic checks. Plain object will also work. Flow will also not be necessary for JSX. However, the tests should test for the suggested combination (JSX + Flow). This also moves some misplaced tests to ReactDOM and Validator. Note that the modern tests uses ES6 classes. I will add separate tests for those. However, these are not guaranteed to have .displayName so all our error messages should check .name if available instead. This should be better abstracted but I just adhoc fix this for now.

Sebastian Markbage committed Dec 18, 2014 at 16:13 UTC cea2c387336e80f308dca106a8b2ae17d248da7b
7 files changed +750 -215
src/browser/__tests__/ReactDOM-test.js
+6
@@ -47,6 +47,12 @@ describe('ReactDOM', function() {
47 });
48 */
49
50 + it("allows a DOM element to be used with a string", function() {
51 + var element = React.createElement('div', { className: 'foo' });
52 + var instance = ReactTestUtils.renderIntoDocument(element);
53 + expect(instance.getDOMNode().tagName).toBe('DIV');
54 + });
55 +
56 it("should allow children to be passed as an argument", function() {
57 var argDiv = ReactTestUtils.renderIntoDocument(
58 div(null, 'child')
src/classic/element/ReactElementValidator.js
+22 -5
@@ -41,6 +41,24 @@ var loggedTypeFailures = {};
41
42 var NUMERIC_PROPERTY_REGEX = /^\d+$/;
43
44 +/**
45 + * Gets the instance's name for use in warnings.
46 + *
47 + * @internal
48 + * @return {?string} Display name or undefined
49 + */
50 +function getName(instance) {
51 + var publicInstance = instance && instance.getPublicInstance();
52 + if (!publicInstance) {
53 + return undefined;
54 + }
55 + var constructor = publicInstance.constructor;
56 + if (!constructor) {
57 + return undefined;
58 + }
59 + return constructor.displayName || constructor.name || undefined;
60 +}
61 +
62 /**
63 * Gets the current owner's displayName for use in warnings.
64 *
@@ -50,7 +68,7 @@ var NUMERIC_PROPERTY_REGEX = /^\d+$/;
68 function getCurrentOwnerDisplayName() {
69 var current = ReactCurrentOwner.current;
70 return (
53 - current && current.getPublicInstance().constructor.displayName || undefined
71 + current && getName(current) || undefined
72 );
73 }
74
@@ -110,7 +128,7 @@ function validatePropertyKey(name, element, parentType) {
128 */
129 function warnAndMonitorForKeyUse(warningID, message, element, parentType) {
130 var ownerName = getCurrentOwnerDisplayName();
113 - var parentName = parentType.displayName;
131 + var parentName = parentType.displayName || parentType.name;
132
133 var useName = ownerName || parentName;
134 var memoizer = ownerHasKeyUseWarning[warningID];
@@ -131,8 +149,7 @@ function warnAndMonitorForKeyUse(warningID, message, element, parentType) {
149 element._owner &&
150 element._owner !== ReactCurrentOwner.current) {
151 // Name of the component that originally created this child.
134 - childOwnerName =
135 - element._owner.getPublicInstance().constructor.displayName;
152 + childOwnerName = getName(element._owner);
153
154 message += ` It was passed a child from ${childOwnerName}.`;
155 }
@@ -262,7 +279,7 @@ var ReactElementValidator = {
279 }
280
281 if (type) {
265 - var name = type.displayName;
282 + var name = type.displayName || type.name;
283 if (type.propTypes) {
284 checkPropTypes(
285 name,
src/classic/element/__tests__/ReactElement-test.js
+37 -194
@@ -11,10 +11,12 @@
11
12 "use strict";
13
14 +// NOTE: We're explicitly not using JSX in this file. This is intended to test
15 +// classic JS without JSX.
16 +
17 var mocks;
18
19 var React;
17 -var ReactElement;
20 var ReactTestUtils;
21
22 describe('ReactElement', function() {
@@ -26,10 +28,9 @@ describe('ReactElement', function() {
28 mocks = require('mocks');
29
30 React = require('React');
29 - ReactElement = require('ReactElement');
31 ReactTestUtils = require('ReactTestUtils');
32 ComponentClass = React.createClass({
32 - render: function() { return <div />; }
33 + render: function() { return React.createElement('div'); }
34 });
35 });
36
@@ -102,7 +103,9 @@ describe('ReactElement', function() {
103 }
104 });
105
105 - ReactTestUtils.renderIntoDocument(<Wrapper />);
106 + ReactTestUtils.renderIntoDocument(
107 + React.createElement(Wrapper)
108 + );
109
110 expect(element._context).toEqual({ foo: 'bar' });
111 });
@@ -118,7 +121,9 @@ describe('ReactElement', function() {
121 }
122 });
123
121 - var instance = ReactTestUtils.renderIntoDocument(<Wrapper />);
124 + var instance = ReactTestUtils.renderIntoDocument(
125 + React.createElement(Wrapper)
126 + );
127
128 expect(element._owner.getPublicInstance()).toBe(instance);
129 });
@@ -159,157 +164,6 @@ describe('ReactElement', function() {
164 expect(console.warn.argsForCall.length).toBe(0);
165 });
166
162 - it('warns for keys for arrays of elements in rest args', function() {
163 - spyOn(console, 'warn');
164 - var Component = React.createFactory(ComponentClass);
165 -
166 - Component(null, [ Component(), Component() ]);
167 -
168 - expect(console.warn.argsForCall.length).toBe(1);
169 - expect(console.warn.argsForCall[0][0]).toContain(
170 - 'Each child in an array or iterator should have a unique "key" prop.'
171 - );
172 - });
173 -
174 - it('warns for keys for arrays of elements in rest args', function() {
175 - spyOn(console, 'warn');
176 - var Component = React.createFactory(ComponentClass);
177 -
178 - var InnerClass = React.createClass({
179 - displayName: 'InnerClass',
180 - render: function() {
181 - return Component(null, this.props.childSet);
182 - }
183 - });
184 -
185 - var InnerComponent = React.createFactory(InnerClass);
186 -
187 - var ComponentWrapper = React.createClass({
188 - displayName: 'ComponentWrapper',
189 - render: function() {
190 - return InnerComponent({ childSet: [ Component(), Component() ] });
191 - }
192 - });
193 -
194 - ReactTestUtils.renderIntoDocument(<ComponentWrapper />);
195 -
196 - expect(console.warn.argsForCall.length).toBe(1);
197 - expect(console.warn.argsForCall[0][0]).toContain(
198 - 'Each child in an array or iterator should have a unique "key" prop. ' +
199 - 'Check the render method of InnerClass. ' +
200 - 'It was passed a child from ComponentWrapper. '
201 - );
202 - });
203 -
204 - it('warns for keys for iterables of elements in rest args', function() {
205 - spyOn(console, 'warn');
206 - var Component = React.createFactory(ComponentClass);
207 -
208 - var iterable = {
209 - '@@iterator': function() {
210 - var i = 0;
211 - return {
212 - next: function() {
213 - var done = ++i > 2;
214 - return { value: done ? undefined : Component(), done: done };
215 - }
216 - };
217 - }
218 - };
219 -
220 - Component(null, iterable);
221 -
222 - expect(console.warn.argsForCall.length).toBe(1);
223 - expect(console.warn.argsForCall[0][0]).toContain(
224 - 'Each child in an array or iterator should have a unique "key" prop.'
225 - );
226 - });
227 -
228 - it('does not warns for arrays of elements with keys', function() {
229 - spyOn(console, 'warn');
230 - var Component = React.createFactory(ComponentClass);
231 -
232 - Component(null, [ Component({key: '#1'}), Component({key: '#2'}) ]);
233 -
234 - expect(console.warn.argsForCall.length).toBe(0);
235 - });
236 -
237 - it('does not warns for iterable elements with keys', function() {
238 - spyOn(console, 'warn');
239 - var Component = React.createFactory(ComponentClass);
240 -
241 - var iterable = {
242 - '@@iterator': function() {
243 - var i = 0;
244 - return {
245 - next: function() {
246 - var done = ++i > 2;
247 - return {
248 - value: done ? undefined : Component({key: '#' + i}),
249 - done: done
250 - };
251 - }
252 - };
253 - }
254 - };
255 -
256 - Component(null, iterable);
257 -
258 - expect(console.warn.argsForCall.length).toBe(0);
259 - });
260 -
261 - it('warns for numeric keys on objects in rest args', function() {
262 - spyOn(console, 'warn');
263 - var Component = React.createFactory(ComponentClass);
264 -
265 - Component(null, { 1: Component(), 2: Component() });
266 -
267 - expect(console.warn.argsForCall.length).toBe(1);
268 - expect(console.warn.argsForCall[0][0]).toContain(
269 - 'Child objects should have non-numeric keys so ordering is preserved.'
270 - );
271 - });
272 -
273 - it('does not warn for numeric keys in entry iterables in rest args', function() {
274 - spyOn(console, 'warn');
275 - var Component = React.createFactory(ComponentClass);
276 -
277 - var iterable = {
278 - '@@iterator': function() {
279 - var i = 0;
280 - return {
281 - next: function() {
282 - var done = ++i > 2;
283 - return { value: done ? undefined : [i, Component()], done: done };
284 - }
285 - };
286 - }
287 - };
288 - iterable.entries = iterable['@@iterator'];
289 -
290 - Component(null, iterable);
291 -
292 - expect(console.warn.argsForCall.length).toBe(0);
293 - });
294 -
295 - it('does not warn when the element is directly in rest args', function() {
296 - spyOn(console, 'warn');
297 - var Component = React.createFactory(ComponentClass);
298 -
299 - Component(null, Component(), Component());
300 -
301 - expect(console.warn.argsForCall.length).toBe(0);
302 - });
303 -
304 - it('does not warn when the array contains a non-element', function() {
305 - spyOn(console, 'warn');
306 - var Component = React.createFactory(ComponentClass);
307 -
308 - Component(null, [ {}, {} ]);
309 -
310 - expect(console.warn.argsForCall.length).toBe(0);
311 - });
312 -
167 it('allows static methods to be called using the type property', function() {
168 spyOn(console, 'warn');
169
@@ -323,11 +177,11 @@ describe('ReactElement', function() {
177 return {valueToReturn: 'hi'};
178 },
179 render: function() {
326 - return <div></div>;
180 + return React.createElement('div');
181 }
182 });
183
330 - var element = <ComponentClass />;
184 + var element = React.createElement(ComponentClass);
185 expect(element.type.someStaticMethod()).toBe('someReturnValue');
186 expect(console.warn.argsForCall.length).toBe(0);
187 });
@@ -335,22 +189,27 @@ describe('ReactElement', function() {
189 it('identifies valid elements', function() {
190 var Component = React.createClass({
191 render: function() {
338 - return <div />;
192 + return React.createElement('div');
193 }
194 });
195
342 - expect(ReactElement.isValidElement(<div />)).toEqual(true);
343 - expect(ReactElement.isValidElement(<Component />)).toEqual(true);
344 -
345 - expect(ReactElement.isValidElement(null)).toEqual(false);
346 - expect(ReactElement.isValidElement(true)).toEqual(false);
347 - expect(ReactElement.isValidElement({})).toEqual(false);
348 - expect(ReactElement.isValidElement("string")).toEqual(false);
349 - expect(ReactElement.isValidElement(React.DOM.div)).toEqual(false);
350 - expect(ReactElement.isValidElement(Component)).toEqual(false);
196 + expect(React.isValidElement(React.createElement('div')))
197 + .toEqual(true);
198 + expect(React.isValidElement(React.createElement(Component)))
199 + .toEqual(true);
200 +
201 + expect(React.isValidElement(null)).toEqual(false);
202 + expect(React.isValidElement(true)).toEqual(false);
203 + expect(React.isValidElement({})).toEqual(false);
204 + expect(React.isValidElement("string")).toEqual(false);
205 + expect(React.isValidElement(React.DOM.div)).toEqual(false);
206 + expect(React.isValidElement(Component)).toEqual(false);
207 });
208
209 it('allows the use of PropTypes validators in statics', function() {
210 + // TODO: This test was added to cover a special case where we proxied
211 + // methods. However, we don't do that any more so this test can probably
212 + // be removed. Leaving it in classic as a safety precausion.
213 var Component = React.createClass({
214 render: () => null,
215 statics: {
@@ -362,12 +221,6 @@ describe('ReactElement', function() {
221 expect(typeof Component.specialType.isRequired).toBe("function");
222 });
223
365 - it('allows a DOM element to be used with a string', function() {
366 - var element = React.createElement('div', { className: 'foo' });
367 - var instance = ReactTestUtils.renderIntoDocument(element);
368 - expect(instance.getDOMNode().tagName).toBe('DIV');
369 - });
370 -
224 it('is indistinguishable from a plain object', function() {
225 var element = React.createElement('div', { className: 'foo' });
226 var object = {};
@@ -380,50 +233,40 @@ describe('ReactElement', function() {
233 return {fruit: 'persimmon'};
234 },
235 render: function() {
383 - return <span />;
236 + return React.createElement('span');
237 }
238 });
239
240 var container = document.createElement('div');
241 var instance = React.render(
389 - <Component fruit="mango" />,
242 + React.createElement(Component, { fruit: 'mango' }),
243 container
244 );
245 expect(instance.props.fruit).toBe('mango');
246
394 - React.render(<Component />, container);
247 + React.render(React.createElement(Component), container);
248 expect(instance.props.fruit).toBe('persimmon');
249 });
250
251 it('should normalize props with default values', function() {
399 - var warn = console.warn;
400 - console.warn = mocks.getMockFunction();
401 -
252 var Component = React.createClass({
403 - propTypes: {prop: React.PropTypes.string.isRequired},
253 getDefaultProps: function() {
254 return {prop: 'testKey'};
255 },
407 - getInitialState: function() {
408 - return {prop: this.props.prop + 'State'};
409 - },
256 render: function() {
411 - return <span>{this.props.prop}</span>;
257 + return React.createElement('span', null, this.props.prop);
258 }
259 });
260
415 - var instance = ReactTestUtils.renderIntoDocument(<Component />);
261 + var instance = ReactTestUtils.renderIntoDocument(
262 + React.createElement(Component)
263 + );
264 expect(instance.props.prop).toBe('testKey');
417 - expect(instance.state.prop).toBe('testKeyState');
265
419 - ReactTestUtils.renderIntoDocument(<Component prop={null} />);
420 -
421 - expect(console.warn.mock.calls.length).toBe(1);
422 - expect(console.warn.mock.calls[0][0]).toBe(
423 - 'Warning: Required prop `prop` was not specified in `Component`.'
266 + var inst2 = ReactTestUtils.renderIntoDocument(
267 + React.createElement(Component, { prop: null })
268 );
425 -
426 - console.warn = warn;
269 + expect(inst2.props.prop).toBe(null);
270 });
271
272 });
src/classic/element/__tests__/ReactElementValidator-test.js
+195 -13
@@ -11,6 +11,9 @@
11
12 "use strict";
13
14 +// NOTE: We're explicitly not using JSX in this file. This is intended to test
15 +// classic JS without JSX.
16 +
17 var React;
18 var ReactTestUtils;
19
@@ -23,10 +26,163 @@ describe('ReactElementValidator', function() {
26 React = require('React');
27 ReactTestUtils = require('ReactTestUtils');
28 ComponentClass = React.createClass({
26 - render: function() { return <div />; }
29 + render: function() { return React.createElement('div'); }
30 });
31 });
32
33 + it('warns for keys for arrays of elements in rest args', function() {
34 + spyOn(console, 'warn');
35 + var Component = React.createFactory(ComponentClass);
36 +
37 + Component(null, [ Component(), Component() ]);
38 +
39 + expect(console.warn.argsForCall.length).toBe(1);
40 + expect(console.warn.argsForCall[0][0]).toContain(
41 + 'Each child in an array or iterator should have a unique "key" prop.'
42 + );
43 + });
44 +
45 + it('warns for keys for arrays of elements with owner info', function() {
46 + spyOn(console, 'warn');
47 + var Component = React.createFactory(ComponentClass);
48 +
49 + var InnerClass = React.createClass({
50 + displayName: 'InnerClass',
51 + render: function() {
52 + return Component(null, this.props.childSet);
53 + }
54 + });
55 +
56 + var InnerComponent = React.createFactory(InnerClass);
57 +
58 + var ComponentWrapper = React.createClass({
59 + displayName: 'ComponentWrapper',
60 + render: function() {
61 + return InnerComponent({ childSet: [ Component(), Component() ] });
62 + }
63 + });
64 +
65 + ReactTestUtils.renderIntoDocument(
66 + React.createElement(ComponentWrapper)
67 + );
68 +
69 + expect(console.warn.argsForCall.length).toBe(1);
70 + expect(console.warn.argsForCall[0][0]).toContain(
71 + 'Each child in an array or iterator should have a unique "key" prop. ' +
72 + 'Check the render method of InnerClass. ' +
73 + 'It was passed a child from ComponentWrapper. '
74 + );
75 + });
76 +
77 + it('warns for keys for iterables of elements in rest args', function() {
78 + spyOn(console, 'warn');
79 + var Component = React.createFactory(ComponentClass);
80 +
81 + var iterable = {
82 + '@@iterator': function() {
83 + var i = 0;
84 + return {
85 + next: function() {
86 + var done = ++i > 2;
87 + return { value: done ? undefined : Component(), done: done };
88 + }
89 + };
90 + }
91 + };
92 +
93 + Component(null, iterable);
94 +
95 + expect(console.warn.argsForCall.length).toBe(1);
96 + expect(console.warn.argsForCall[0][0]).toContain(
97 + 'Each child in an array or iterator should have a unique "key" prop.'
98 + );
99 + });
100 +
101 + it('does not warns for arrays of elements with keys', function() {
102 + spyOn(console, 'warn');
103 + var Component = React.createFactory(ComponentClass);
104 +
105 + Component(null, [ Component({key: '#1'}), Component({key: '#2'}) ]);
106 +
107 + expect(console.warn.argsForCall.length).toBe(0);
108 + });
109 +
110 + it('does not warns for iterable elements with keys', function() {
111 + spyOn(console, 'warn');
112 + var Component = React.createFactory(ComponentClass);
113 +
114 + var iterable = {
115 + '@@iterator': function() {
116 + var i = 0;
117 + return {
118 + next: function() {
119 + var done = ++i > 2;
120 + return {
121 + value: done ? undefined : Component({key: '#' + i}),
122 + done: done
123 + };
124 + }
125 + };
126 + }
127 + };
128 +
129 + Component(null, iterable);
130 +
131 + expect(console.warn.argsForCall.length).toBe(0);
132 + });
133 +
134 + it('warns for numeric keys on objects in rest args', function() {
135 + spyOn(console, 'warn');
136 + var Component = React.createFactory(ComponentClass);
137 +
138 + Component(null, { 1: Component(), 2: Component() });
139 +
140 + expect(console.warn.argsForCall.length).toBe(1);
141 + expect(console.warn.argsForCall[0][0]).toContain(
142 + 'Child objects should have non-numeric keys so ordering is preserved.'
143 + );
144 + });
145 +
146 + it('does not warn for numeric keys in entry iterables in rest args', function() {
147 + spyOn(console, 'warn');
148 + var Component = React.createFactory(ComponentClass);
149 +
150 + var iterable = {
151 + '@@iterator': function() {
152 + var i = 0;
153 + return {
154 + next: function() {
155 + var done = ++i > 2;
156 + return { value: done ? undefined : [i, Component()], done: done };
157 + }
158 + };
159 + }
160 + };
161 + iterable.entries = iterable['@@iterator'];
162 +
163 + Component(null, iterable);
164 +
165 + expect(console.warn.argsForCall.length).toBe(0);
166 + });
167 +
168 + it('does not warn when the element is directly in rest args', function() {
169 + spyOn(console, 'warn');
170 + var Component = React.createFactory(ComponentClass);
171 +
172 + Component(null, Component(), Component());
173 +
174 + expect(console.warn.argsForCall.length).toBe(0);
175 + });
176 +
177 + it('does not warn when the array contains a non-element', function() {
178 + spyOn(console, 'warn');
179 + var Component = React.createFactory(ComponentClass);
180 +
181 + Component(null, [ {}, {} ]);
182 +
183 + expect(console.warn.argsForCall.length).toBe(0);
184 + });
185 +
186 // TODO: These warnings currently come from the composite component, but
187 // they should be moved into the ReactElementValidator.
188
@@ -40,15 +196,15 @@ describe('ReactElementValidator', function() {
196 color: React.PropTypes.string
197 },
198 render: function() {
43 - return <div>My color is {this.color}</div>;
199 + return React.createElement('div', null, 'My color is ' + this.color);
200 }
201 });
202 var ParentComp = React.createClass({
203 render: function() {
48 - return <MyComp color={123} />;
204 + return React.createElement(MyComp, { color: 123 });
205 }
206 });
51 - ReactTestUtils.renderIntoDocument(<ParentComp />);
207 + ReactTestUtils.renderIntoDocument(React.createElement(ParentComp));
208 expect(console.warn.calls[0].args[0]).toBe(
209 'Warning: Invalid prop `color` of type `number` supplied to `MyComp`, ' +
210 'expected `string`. Check the render method of `ParentComp`.'
@@ -72,11 +228,8 @@ describe('ReactElementValidator', function() {
228 );
229 React.createElement('div');
230 expect(console.warn.calls.length).toBe(2);
75 -
76 - expect(() => React.createElement(undefined)).not.toThrow()
231 });
232
79 -
233 it('should check default prop values', function() {
234 spyOn(console, 'warn');
235
@@ -86,11 +239,34 @@ describe('ReactElementValidator', function() {
239 return {prop: null};
240 },
241 render: function() {
89 - return <span>{this.props.prop}</span>;
242 + return React.createElement('span', null, this.props.prop);
243 + }
244 + });
245 +
246 + ReactTestUtils.renderIntoDocument(React.createElement(Component));
247 +
248 + expect(console.warn.calls.length).toBe(1);
249 + expect(console.warn.calls[0].args[0]).toBe(
250 + 'Warning: Required prop `prop` was not specified in `Component`.'
251 + );
252 + });
253 +
254 + it('should not check the default for explicit null', function() {
255 + spyOn(console, 'warn');
256 +
257 + var Component = React.createClass({
258 + propTypes: {prop: React.PropTypes.string.isRequired},
259 + getDefaultProps: function() {
260 + return {prop: 'text'};
261 + },
262 + render: function() {
263 + return React.createElement('span', null, this.props.prop);
264 }
265 });
266
93 - ReactTestUtils.renderIntoDocument(<Component />);
267 + ReactTestUtils.renderIntoDocument(
268 + React.createElement(Component, {prop:null})
269 + );
270
271 expect(console.warn.calls.length).toBe(1);
272 expect(console.warn.calls[0].args[0]).toBe(
@@ -106,12 +282,16 @@ describe('ReactElementValidator', function() {
282 prop: React.PropTypes.string.isRequired
283 },
284 render: function() {
109 - return <span>{this.props.prop}</span>;
285 + return React.createElement('span', null, this.props.prop);
286 }
287 });
288
113 - ReactTestUtils.renderIntoDocument(<Component />);
114 - ReactTestUtils.renderIntoDocument(<Component prop={42} />);
289 + ReactTestUtils.renderIntoDocument(
290 + React.createElement(Component)
291 + );
292 + ReactTestUtils.renderIntoDocument(
293 + React.createElement(Component, {prop: 42})
294 + );
295
296 expect(console.warn.calls.length).toBe(2);
297 expect(console.warn.calls[0].args[0]).toBe(
@@ -123,7 +303,9 @@ describe('ReactElementValidator', function() {
303 '`Component`, expected `string`.'
304 );
305
126 - ReactTestUtils.renderIntoDocument(<Component prop="string" />);
306 + ReactTestUtils.renderIntoDocument(
307 + React.createElement(Component, {prop: 'string'})
308 + );
309
310 // Should not error for strings
311 expect(console.warn.calls.length).toBe(2);
src/core/ReactCompositeComponent.js
+5 -3
@@ -32,8 +32,9 @@ function getDeclarationErrorAddendum(component) {
32 var owner = component._currentElement._owner || null;
33 if (owner) {
34 var constructor = owner._instance.constructor;
35 - if (constructor && constructor.displayName) {
36 - return ' Check the render method of `' + constructor.displayName + '`.';
35 + var name = constructor && (constructor.displayName || constructor.name);
36 + if (name) {
37 + return ' Check the render method of `' + name + '`.';
38 }
39 }
40 return '';
@@ -510,7 +511,8 @@ var ReactCompositeComponentMixin = assign({},
511 _checkPropTypes: function(propTypes, props, location) {
512 // TODO: Stop validating prop types here and only use the element
513 // validation.
513 - var componentName = this._instance.constructor.displayName;
514 + var componentName = this._instance.constructor.displayName ||
515 + this._instance.constructor.name;
516 for (var propName in propTypes) {
517 if (propTypes.hasOwnProperty(propName)) {
518 var error =
src/modern/element/__tests__/ReactJSXElement-test.js new
+192
@@ -0,0 +1,192 @@
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 mocks;
15 +
16 +var React;
17 +var ReactTestUtils;
18 +
19 +describe('ReactJSXElement', function() {
20 + var Component;
21 +
22 + beforeEach(function() {
23 + require('mock-modules').dumpCache();
24 +
25 + mocks = require('mocks');
26 +
27 + React = require('React');
28 + ReactTestUtils = require('ReactTestUtils');
29 + Component = class {
30 + render() { return <div />; }
31 + };
32 + });
33 +
34 + it('returns a complete element according to spec', function() {
35 + var element = <Component />;
36 + expect(element.type).toBe(Component);
37 + expect(element.key).toBe(null);
38 + expect(element.ref).toBe(null);
39 + expect(element.props).toEqual({});
40 + });
41 +
42 + it('allows a lower-case to be passed as the string type', function() {
43 + var element = <div />;
44 + expect(element.type).toBe('div');
45 + expect(element.key).toBe(null);
46 + expect(element.ref).toBe(null);
47 + expect(element.props).toEqual({});
48 + });
49 +
50 + it('allows a string to be passed as the type', function() {
51 + var TagName = 'div';
52 + var element = <TagName />;
53 + expect(element.type).toBe('div');
54 + expect(element.key).toBe(null);
55 + expect(element.ref).toBe(null);
56 + expect(element.props).toEqual({});
57 + });
58 +
59 + it('returns an immutable element', function() {
60 + var element = <Component />;
61 + expect(() => element.type = 'div').toThrow();
62 + });
63 +
64 + it('does not reuse the object that is spread into props', function() {
65 + var config = { foo: 1 };
66 + var element = <Component {...config} />;
67 + expect(element.props.foo).toBe(1);
68 + config.foo = 2;
69 + expect(element.props.foo).toBe(1);
70 + });
71 +
72 + it('extracts key and ref from the rest of the props', function() {
73 + var element = <Component key="12" ref="34" foo="56" />;
74 + expect(element.type).toBe(Component);
75 + expect(element.key).toBe('12');
76 + expect(element.ref).toBe('34');
77 + expect(element.props).toEqual({foo:'56'});
78 + });
79 +
80 + it('coerces the key to a string', function() {
81 + var element = <Component key={12} foo="56" />;
82 + expect(element.type).toBe(Component);
83 + expect(element.key).toBe('12');
84 + expect(element.ref).toBe(null);
85 + expect(element.props).toEqual({foo:'56'});
86 + });
87 +
88 + it('merges JSX children onto the children prop', function() {
89 + spyOn(console, 'warn');
90 + var a = 1;
91 + var element = <Component children="text">{a}</Component>;
92 + expect(element.props.children).toBe(a);
93 + expect(console.warn.argsForCall.length).toBe(0);
94 + });
95 +
96 + it('does not override children if no JSX children are provided', function() {
97 + spyOn(console, 'warn');
98 + var element = <Component children="text" />;
99 + expect(element.props.children).toBe('text');
100 + expect(console.warn.argsForCall.length).toBe(0);
101 + });
102 +
103 + it('overrides children if null is provided as a JSX child', function() {
104 + spyOn(console, 'warn');
105 + var element = <Component children="text">{null}</Component>;
106 + expect(element.props.children).toBe(null);
107 + expect(console.warn.argsForCall.length).toBe(0);
108 + });
109 +
110 + it('merges JSX children onto the children prop in an array', function() {
111 + spyOn(console, 'warn');
112 + var a = 1, b = 2, c = 3;
113 + var element = <Component>{a}{b}{c}</Component>;
114 + expect(element.props.children).toEqual([1, 2, 3]);
115 + expect(console.warn.argsForCall.length).toBe(0);
116 + });
117 +
118 + it('allows static methods to be called using the type property', function() {
119 + spyOn(console, 'warn');
120 +
121 + class Component {
122 + static someStaticMethod() {
123 + return 'someReturnValue';
124 + }
125 + render() {
126 + return <div></div>;
127 + }
128 + }
129 +
130 + var element = <Component />;
131 + expect(element.type.someStaticMethod()).toBe('someReturnValue');
132 + expect(console.warn.argsForCall.length).toBe(0);
133 + });
134 +
135 + it('identifies valid elements', function() {
136 + class Component {
137 + render() {
138 + return <div />;
139 + }
140 + }
141 +
142 + expect(React.isValidElement(<div />)).toEqual(true);
143 + expect(React.isValidElement(<Component />)).toEqual(true);
144 +
145 + expect(React.isValidElement(null)).toEqual(false);
146 + expect(React.isValidElement(true)).toEqual(false);
147 + expect(React.isValidElement({})).toEqual(false);
148 + expect(React.isValidElement("string")).toEqual(false);
149 + expect(React.isValidElement(Component)).toEqual(false);
150 + });
151 +
152 + it('is indistinguishable from a plain object', function() {
153 + var element = <div className="foo" />;
154 + var object = {};
155 + expect(element.constructor).toBe(object.constructor);
156 + });
157 +
158 + it('should use default prop value when removing a prop', function() {
159 + class Component {
160 + render() {
161 + return <span />;
162 + }
163 + }
164 + Component.defaultProps = { fruit: 'persimmon' };
165 +
166 + var container = document.createElement('div');
167 + var instance = React.render(
168 + <Component fruit="mango" />,
169 + container
170 + );
171 + expect(instance.props.fruit).toBe('mango');
172 +
173 + React.render(<Component />, container);
174 + expect(instance.props.fruit).toBe('persimmon');
175 + });
176 +
177 + it('should normalize props with default values', function() {
178 + class Component {
179 + render() {
180 + return <span>{this.props.prop}</span>;
181 + }
182 + }
183 + Component.defaultProps = { prop: 'testKey' };
184 +
185 + var instance = ReactTestUtils.renderIntoDocument(<Component />);
186 + expect(instance.props.prop).toBe('testKey');
187 +
188 + var inst2 = ReactTestUtils.renderIntoDocument(<Component prop={null} />);
189 + expect(inst2.props.prop).toBe(null);
190 + });
191 +
192 +});
src/modern/element/__tests__/ReactJSXElementValidator-test.js new
+293
@@ -0,0 +1,293 @@
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 +// TODO: All these warnings should become static errors using Flow instead
15 +// of dynamic errors when using JSX with Flow.
16 +
17 +var React;
18 +var ReactTestUtils;
19 +
20 +describe('ReactJSXElementValidator', function() {
21 + var Component;
22 +
23 + beforeEach(function() {
24 + require('mock-modules').dumpCache();
25 +
26 + React = require('React');
27 + ReactTestUtils = require('ReactTestUtils');
28 +
29 + Component = class {
30 + render() { return <div />; }
31 + };
32 + });
33 +
34 + it('warns for keys for arrays of elements in children position', function() {
35 + spyOn(console, 'warn');
36 +
37 + <Component>{[<Component />, <Component />]}</Component>;
38 +
39 + expect(console.warn.argsForCall.length).toBe(1);
40 + expect(console.warn.argsForCall[0][0]).toContain(
41 + 'Each child in an array or iterator should have a unique "key" prop.'
42 + );
43 + });
44 +
45 + it('warns for keys for arrays of elements with owner info', function() {
46 + spyOn(console, 'warn');
47 +
48 + class InnerComponent {
49 + render() {
50 + return <Component>{this.props.childSet}</Component>;
51 + }
52 + };
53 +
54 + class ComponentWrapper {
55 + render() {
56 + return (
57 + <InnerComponent
58 + childSet={[ <Component />, <Component /> ]}
59 + />
60 + );
61 + }
62 + };
63 +
64 + ReactTestUtils.renderIntoDocument(<ComponentWrapper />);
65 +
66 + expect(console.warn.argsForCall.length).toBe(1);
67 + expect(console.warn.argsForCall[0][0]).toContain(
68 + 'Each child in an array or iterator should have a unique "key" prop. ' +
69 + 'Check the render method of InnerComponent. ' +
70 + 'It was passed a child from ComponentWrapper. '
71 + );
72 + });
73 +
74 + it('warns for keys for iterables of elements in rest args', function() {
75 + spyOn(console, 'warn');
76 +
77 + var iterable = {
78 + '@@iterator': function() {
79 + var i = 0;
80 + return {
81 + next: function() {
82 + var done = ++i > 2;
83 + return { value: done ? undefined : <Component />, done: done };
84 + }
85 + };
86 + }
87 + };
88 +
89 + <Component>{iterable}</Component>;
90 +
91 + expect(console.warn.argsForCall.length).toBe(1);
92 + expect(console.warn.argsForCall[0][0]).toContain(
93 + 'Each child in an array or iterator should have a unique "key" prop.'
94 + );
95 + });
96 +
97 + it('does not warns for arrays of elements with keys', function() {
98 + spyOn(console, 'warn');
99 +
100 + <Component>{[ <Component key="#1" />, <Component key="#2" /> ]}</Component>;
101 +
102 + expect(console.warn.argsForCall.length).toBe(0);
103 + });
104 +
105 + it('does not warns for iterable elements with keys', function() {
106 + spyOn(console, 'warn');
107 +
108 + var iterable = {
109 + '@@iterator': function() {
110 + var i = 0;
111 + return {
112 + next: function() {
113 + var done = ++i > 2;
114 + return {
115 + value: done ? undefined : <Component key={'#' + i} />,
116 + done: done
117 + };
118 + }
119 + };
120 + }
121 + };
122 +
123 + <Component>{iterable}</Component>
124 +
125 + expect(console.warn.argsForCall.length).toBe(0);
126 + });
127 +
128 + it('warns for numeric keys on objects as children', function() {
129 + spyOn(console, 'warn');
130 +
131 + <Component>{{ 1: <Component />, 2: <Component /> }}</Component>;
132 +
133 + expect(console.warn.argsForCall.length).toBe(1);
134 + expect(console.warn.argsForCall[0][0]).toContain(
135 + 'Child objects should have non-numeric keys so ordering is preserved.'
136 + );
137 + });
138 +
139 + it('does not warn for numeric keys in entry iterable as a child', function() {
140 + spyOn(console, 'warn');
141 +
142 + var iterable = {
143 + '@@iterator': function() {
144 + var i = 0;
145 + return {
146 + next: function() {
147 + var done = ++i > 2;
148 + return { value: done ? undefined : [i, <Component />], done: done };
149 + }
150 + };
151 + }
152 + };
153 + iterable.entries = iterable['@@iterator'];
154 +
155 + <Component>{iterable}</Component>;
156 +
157 + expect(console.warn.argsForCall.length).toBe(0);
158 + });
159 +
160 + it('does not warn when the element is directly as children', function() {
161 + spyOn(console, 'warn');
162 +
163 + <Component><Component /><Component /></Component>;
164 +
165 + expect(console.warn.argsForCall.length).toBe(0);
166 + });
167 +
168 + it('does not warn when the child array contains non-elements', function() {
169 + spyOn(console, 'warn');
170 +
171 + <Component>{[ {}, {} ]}</Component>;
172 +
173 + expect(console.warn.argsForCall.length).toBe(0);
174 + });
175 +
176 + // TODO: These warnings currently come from the composite component, but
177 + // they should be moved into the ReactElementValidator.
178 +
179 + it('should give context for PropType errors in nested components.', () => {
180 + // In this test, we're making sure that if a proptype error is found in a
181 + // component, we give a small hint as to which parent instantiated that
182 + // component as per warnings about key usage in ReactElementValidator.
183 + spyOn(console, 'warn');
184 + class MyComp {
185 + render() {
186 + return <div>My color is {this.color}</div>;
187 + }
188 + }
189 + MyComp.propTypes = {
190 + color: React.PropTypes.string
191 + };
192 + class ParentComp {
193 + render() {
194 + return <MyComp color={123} />;
195 + }
196 + }
197 + ReactTestUtils.renderIntoDocument(<ParentComp />);
198 + expect(console.warn.calls[0].args[0]).toBe(
199 + 'Warning: Invalid prop `color` of type `number` supplied to `MyComp`, ' +
200 + 'expected `string`. Check the render method of `ParentComp`.'
201 + );
202 + });
203 +
204 + it('gives a helpful error when passing null or undefined', function() {
205 + var Undefined = undefined;
206 + var Null = null;
207 + var Div = 'div';
208 + spyOn(console, 'warn');
209 + <Undefined />;
210 + <Null />;
211 + expect(console.warn.calls.length).toBe(2);
212 + expect(console.warn.calls[0].args[0]).toContain(
213 + 'type should not be null or undefined. It should be a string (for ' +
214 + 'DOM elements) or a ReactClass (for composite components).'
215 + );
216 + expect(console.warn.calls[1].args[0]).toContain(
217 + 'type should not be null or undefined. It should be a string (for ' +
218 + 'DOM elements) or a ReactClass (for composite components).'
219 + );
220 + <Div />;
221 + expect(console.warn.calls.length).toBe(2);
222 + });
223 +
224 + it('should check default prop values', function() {
225 + spyOn(console, 'warn');
226 +
227 + class Component {
228 + render() {
229 + return <span>{this.props.prop}</span>;
230 + }
231 + }
232 + Component.defaultProps = { prop: null };
233 + Component.propTypes = { prop: React.PropTypes.string.isRequired };
234 +
235 + ReactTestUtils.renderIntoDocument(<Component />);
236 +
237 + expect(console.warn.calls.length).toBe(1);
238 + expect(console.warn.calls[0].args[0]).toBe(
239 + 'Warning: Required prop `prop` was not specified in `Component`.'
240 + );
241 + });
242 +
243 + it('should not check the default for explicit null', function() {
244 + spyOn(console, 'warn');
245 +
246 + class Component {
247 + render() {
248 + return <span>{this.props.prop}</span>;
249 + }
250 + }
251 + Component.defaultProps = { prop: 'text' };
252 + Component.propTypes = { prop: React.PropTypes.string.isRequired };
253 +
254 + ReactTestUtils.renderIntoDocument(<Component prop={null} />);
255 +
256 + expect(console.warn.calls.length).toBe(1);
257 + expect(console.warn.calls[0].args[0]).toBe(
258 + 'Warning: Required prop `prop` was not specified in `Component`.'
259 + );
260 + });
261 +
262 + it('should check declared prop types', function() {
263 + spyOn(console, 'warn');
264 +
265 + class Component {
266 + render() {
267 + return <span>{this.props.prop}</span>;
268 + }
269 + }
270 + Component.propTypes = {
271 + prop: React.PropTypes.string.isRequired
272 + };
273 +
274 + ReactTestUtils.renderIntoDocument(<Component />);
275 + ReactTestUtils.renderIntoDocument(<Component prop={42} />);
276 +
277 + expect(console.warn.calls.length).toBe(2);
278 + expect(console.warn.calls[0].args[0]).toBe(
279 + 'Warning: Required prop `prop` was not specified in `Component`.'
280 + );
281 +
282 + expect(console.warn.calls[1].args[0]).toBe(
283 + 'Warning: Invalid prop `prop` of type `number` supplied to ' +
284 + '`Component`, expected `string`.'
285 + );
286 +
287 + ReactTestUtils.renderIntoDocument(<Component prop="string" />);
288 +
289 + // Should not error for strings
290 + expect(console.warn.calls.length).toBe(2);
291 + });
292 +
293 +});