diff --git a/src/browser/__tests__/ReactDOM-test.js b/src/browser/__tests__/ReactDOM-test.js
index 68405b68f6..24fd941f67 100644
--- a/src/browser/__tests__/ReactDOM-test.js
+++ b/src/browser/__tests__/ReactDOM-test.js
@@ -47,6 +47,12 @@ describe('ReactDOM', function() {
});
*/
+ it("allows a DOM element to be used with a string", function() {
+ var element = React.createElement('div', { className: 'foo' });
+ var instance = ReactTestUtils.renderIntoDocument(element);
+ expect(instance.getDOMNode().tagName).toBe('DIV');
+ });
+
it("should allow children to be passed as an argument", function() {
var argDiv = ReactTestUtils.renderIntoDocument(
div(null, 'child')
diff --git a/src/classic/element/ReactElementValidator.js b/src/classic/element/ReactElementValidator.js
index 55624f8e89..7c262243c8 100644
--- a/src/classic/element/ReactElementValidator.js
+++ b/src/classic/element/ReactElementValidator.js
@@ -41,6 +41,24 @@ var loggedTypeFailures = {};
var NUMERIC_PROPERTY_REGEX = /^\d+$/;
+/**
+ * Gets the instance's name for use in warnings.
+ *
+ * @internal
+ * @return {?string} Display name or undefined
+ */
+function getName(instance) {
+ var publicInstance = instance && instance.getPublicInstance();
+ if (!publicInstance) {
+ return undefined;
+ }
+ var constructor = publicInstance.constructor;
+ if (!constructor) {
+ return undefined;
+ }
+ return constructor.displayName || constructor.name || undefined;
+}
+
/**
* Gets the current owner's displayName for use in warnings.
*
@@ -50,7 +68,7 @@ var NUMERIC_PROPERTY_REGEX = /^\d+$/;
function getCurrentOwnerDisplayName() {
var current = ReactCurrentOwner.current;
return (
- current && current.getPublicInstance().constructor.displayName || undefined
+ current && getName(current) || undefined
);
}
@@ -110,7 +128,7 @@ function validatePropertyKey(name, element, parentType) {
*/
function warnAndMonitorForKeyUse(warningID, message, element, parentType) {
var ownerName = getCurrentOwnerDisplayName();
- var parentName = parentType.displayName;
+ var parentName = parentType.displayName || parentType.name;
var useName = ownerName || parentName;
var memoizer = ownerHasKeyUseWarning[warningID];
@@ -131,8 +149,7 @@ function warnAndMonitorForKeyUse(warningID, message, element, parentType) {
element._owner &&
element._owner !== ReactCurrentOwner.current) {
// Name of the component that originally created this child.
- childOwnerName =
- element._owner.getPublicInstance().constructor.displayName;
+ childOwnerName = getName(element._owner);
message += ` It was passed a child from ${childOwnerName}.`;
}
@@ -262,7 +279,7 @@ var ReactElementValidator = {
}
if (type) {
- var name = type.displayName;
+ var name = type.displayName || type.name;
if (type.propTypes) {
checkPropTypes(
name,
diff --git a/src/classic/element/__tests__/ReactElement-test.js b/src/classic/element/__tests__/ReactElement-test.js
index 0f64f24d8b..633fb56ef7 100644
--- a/src/classic/element/__tests__/ReactElement-test.js
+++ b/src/classic/element/__tests__/ReactElement-test.js
@@ -11,10 +11,12 @@
"use strict";
+// NOTE: We're explicitly not using JSX in this file. This is intended to test
+// classic JS without JSX.
+
var mocks;
var React;
-var ReactElement;
var ReactTestUtils;
describe('ReactElement', function() {
@@ -26,10 +28,9 @@ describe('ReactElement', function() {
mocks = require('mocks');
React = require('React');
- ReactElement = require('ReactElement');
ReactTestUtils = require('ReactTestUtils');
ComponentClass = React.createClass({
- render: function() { return
; }
+ render: function() { return React.createElement('div'); }
});
});
@@ -102,7 +103,9 @@ describe('ReactElement', function() {
}
});
- ReactTestUtils.renderIntoDocument();
+ ReactTestUtils.renderIntoDocument(
+ React.createElement(Wrapper)
+ );
expect(element._context).toEqual({ foo: 'bar' });
});
@@ -118,7 +121,9 @@ describe('ReactElement', function() {
}
});
- var instance = ReactTestUtils.renderIntoDocument();
+ var instance = ReactTestUtils.renderIntoDocument(
+ React.createElement(Wrapper)
+ );
expect(element._owner.getPublicInstance()).toBe(instance);
});
@@ -159,157 +164,6 @@ describe('ReactElement', function() {
expect(console.warn.argsForCall.length).toBe(0);
});
- it('warns for keys for arrays of elements in rest args', function() {
- spyOn(console, 'warn');
- var Component = React.createFactory(ComponentClass);
-
- Component(null, [ Component(), Component() ]);
-
- expect(console.warn.argsForCall.length).toBe(1);
- expect(console.warn.argsForCall[0][0]).toContain(
- 'Each child in an array or iterator should have a unique "key" prop.'
- );
- });
-
- it('warns for keys for arrays of elements in rest args', function() {
- spyOn(console, 'warn');
- var Component = React.createFactory(ComponentClass);
-
- var InnerClass = React.createClass({
- displayName: 'InnerClass',
- render: function() {
- return Component(null, this.props.childSet);
- }
- });
-
- var InnerComponent = React.createFactory(InnerClass);
-
- var ComponentWrapper = React.createClass({
- displayName: 'ComponentWrapper',
- render: function() {
- return InnerComponent({ childSet: [ Component(), Component() ] });
- }
- });
-
- ReactTestUtils.renderIntoDocument();
-
- expect(console.warn.argsForCall.length).toBe(1);
- expect(console.warn.argsForCall[0][0]).toContain(
- 'Each child in an array or iterator should have a unique "key" prop. ' +
- 'Check the render method of InnerClass. ' +
- 'It was passed a child from ComponentWrapper. '
- );
- });
-
- it('warns for keys for iterables of elements in rest args', function() {
- spyOn(console, 'warn');
- var Component = React.createFactory(ComponentClass);
-
- var iterable = {
- '@@iterator': function() {
- var i = 0;
- return {
- next: function() {
- var done = ++i > 2;
- return { value: done ? undefined : Component(), done: done };
- }
- };
- }
- };
-
- Component(null, iterable);
-
- expect(console.warn.argsForCall.length).toBe(1);
- expect(console.warn.argsForCall[0][0]).toContain(
- 'Each child in an array or iterator should have a unique "key" prop.'
- );
- });
-
- it('does not warns for arrays of elements with keys', function() {
- spyOn(console, 'warn');
- var Component = React.createFactory(ComponentClass);
-
- Component(null, [ Component({key: '#1'}), Component({key: '#2'}) ]);
-
- expect(console.warn.argsForCall.length).toBe(0);
- });
-
- it('does not warns for iterable elements with keys', function() {
- spyOn(console, 'warn');
- var Component = React.createFactory(ComponentClass);
-
- var iterable = {
- '@@iterator': function() {
- var i = 0;
- return {
- next: function() {
- var done = ++i > 2;
- return {
- value: done ? undefined : Component({key: '#' + i}),
- done: done
- };
- }
- };
- }
- };
-
- Component(null, iterable);
-
- expect(console.warn.argsForCall.length).toBe(0);
- });
-
- it('warns for numeric keys on objects in rest args', function() {
- spyOn(console, 'warn');
- var Component = React.createFactory(ComponentClass);
-
- Component(null, { 1: Component(), 2: Component() });
-
- expect(console.warn.argsForCall.length).toBe(1);
- expect(console.warn.argsForCall[0][0]).toContain(
- 'Child objects should have non-numeric keys so ordering is preserved.'
- );
- });
-
- it('does not warn for numeric keys in entry iterables in rest args', function() {
- spyOn(console, 'warn');
- var Component = React.createFactory(ComponentClass);
-
- var iterable = {
- '@@iterator': function() {
- var i = 0;
- return {
- next: function() {
- var done = ++i > 2;
- return { value: done ? undefined : [i, Component()], done: done };
- }
- };
- }
- };
- iterable.entries = iterable['@@iterator'];
-
- Component(null, iterable);
-
- expect(console.warn.argsForCall.length).toBe(0);
- });
-
- it('does not warn when the element is directly in rest args', function() {
- spyOn(console, 'warn');
- var Component = React.createFactory(ComponentClass);
-
- Component(null, Component(), Component());
-
- expect(console.warn.argsForCall.length).toBe(0);
- });
-
- it('does not warn when the array contains a non-element', function() {
- spyOn(console, 'warn');
- var Component = React.createFactory(ComponentClass);
-
- Component(null, [ {}, {} ]);
-
- expect(console.warn.argsForCall.length).toBe(0);
- });
-
it('allows static methods to be called using the type property', function() {
spyOn(console, 'warn');
@@ -323,11 +177,11 @@ describe('ReactElement', function() {
return {valueToReturn: 'hi'};
},
render: function() {
- return ;
+ return React.createElement('div');
}
});
- var element = ;
+ var element = React.createElement(ComponentClass);
expect(element.type.someStaticMethod()).toBe('someReturnValue');
expect(console.warn.argsForCall.length).toBe(0);
});
@@ -335,22 +189,27 @@ describe('ReactElement', function() {
it('identifies valid elements', function() {
var Component = React.createClass({
render: function() {
- return ;
+ return React.createElement('div');
}
});
- expect(ReactElement.isValidElement()).toEqual(true);
- expect(ReactElement.isValidElement()).toEqual(true);
+ expect(React.isValidElement(React.createElement('div')))
+ .toEqual(true);
+ expect(React.isValidElement(React.createElement(Component)))
+ .toEqual(true);
- expect(ReactElement.isValidElement(null)).toEqual(false);
- expect(ReactElement.isValidElement(true)).toEqual(false);
- expect(ReactElement.isValidElement({})).toEqual(false);
- expect(ReactElement.isValidElement("string")).toEqual(false);
- expect(ReactElement.isValidElement(React.DOM.div)).toEqual(false);
- expect(ReactElement.isValidElement(Component)).toEqual(false);
+ expect(React.isValidElement(null)).toEqual(false);
+ expect(React.isValidElement(true)).toEqual(false);
+ expect(React.isValidElement({})).toEqual(false);
+ expect(React.isValidElement("string")).toEqual(false);
+ expect(React.isValidElement(React.DOM.div)).toEqual(false);
+ expect(React.isValidElement(Component)).toEqual(false);
});
it('allows the use of PropTypes validators in statics', function() {
+ // TODO: This test was added to cover a special case where we proxied
+ // methods. However, we don't do that any more so this test can probably
+ // be removed. Leaving it in classic as a safety precausion.
var Component = React.createClass({
render: () => null,
statics: {
@@ -362,12 +221,6 @@ describe('ReactElement', function() {
expect(typeof Component.specialType.isRequired).toBe("function");
});
- it('allows a DOM element to be used with a string', function() {
- var element = React.createElement('div', { className: 'foo' });
- var instance = ReactTestUtils.renderIntoDocument(element);
- expect(instance.getDOMNode().tagName).toBe('DIV');
- });
-
it('is indistinguishable from a plain object', function() {
var element = React.createElement('div', { className: 'foo' });
var object = {};
@@ -380,50 +233,40 @@ describe('ReactElement', function() {
return {fruit: 'persimmon'};
},
render: function() {
- return ;
+ return React.createElement('span');
}
});
var container = document.createElement('div');
var instance = React.render(
- ,
+ React.createElement(Component, { fruit: 'mango' }),
container
);
expect(instance.props.fruit).toBe('mango');
- React.render(, container);
+ React.render(React.createElement(Component), container);
expect(instance.props.fruit).toBe('persimmon');
});
it('should normalize props with default values', function() {
- var warn = console.warn;
- console.warn = mocks.getMockFunction();
-
var Component = React.createClass({
- propTypes: {prop: React.PropTypes.string.isRequired},
getDefaultProps: function() {
return {prop: 'testKey'};
},
- getInitialState: function() {
- return {prop: this.props.prop + 'State'};
- },
render: function() {
- return {this.props.prop};
+ return React.createElement('span', null, this.props.prop);
}
});
- var instance = ReactTestUtils.renderIntoDocument();
- expect(instance.props.prop).toBe('testKey');
- expect(instance.state.prop).toBe('testKeyState');
-
- ReactTestUtils.renderIntoDocument();
-
- expect(console.warn.mock.calls.length).toBe(1);
- expect(console.warn.mock.calls[0][0]).toBe(
- 'Warning: Required prop `prop` was not specified in `Component`.'
+ var instance = ReactTestUtils.renderIntoDocument(
+ React.createElement(Component)
);
+ expect(instance.props.prop).toBe('testKey');
- console.warn = warn;
+ var inst2 = ReactTestUtils.renderIntoDocument(
+ React.createElement(Component, { prop: null })
+ );
+ expect(inst2.props.prop).toBe(null);
});
});
diff --git a/src/classic/element/__tests__/ReactElementValidator-test.js b/src/classic/element/__tests__/ReactElementValidator-test.js
index 67198ac605..bccfa09c7c 100644
--- a/src/classic/element/__tests__/ReactElementValidator-test.js
+++ b/src/classic/element/__tests__/ReactElementValidator-test.js
@@ -11,6 +11,9 @@
"use strict";
+// NOTE: We're explicitly not using JSX in this file. This is intended to test
+// classic JS without JSX.
+
var React;
var ReactTestUtils;
@@ -23,10 +26,163 @@ describe('ReactElementValidator', function() {
React = require('React');
ReactTestUtils = require('ReactTestUtils');
ComponentClass = React.createClass({
- render: function() { return ; }
+ render: function() { return React.createElement('div'); }
});
});
+ it('warns for keys for arrays of elements in rest args', function() {
+ spyOn(console, 'warn');
+ var Component = React.createFactory(ComponentClass);
+
+ Component(null, [ Component(), Component() ]);
+
+ expect(console.warn.argsForCall.length).toBe(1);
+ expect(console.warn.argsForCall[0][0]).toContain(
+ 'Each child in an array or iterator should have a unique "key" prop.'
+ );
+ });
+
+ it('warns for keys for arrays of elements with owner info', function() {
+ spyOn(console, 'warn');
+ var Component = React.createFactory(ComponentClass);
+
+ var InnerClass = React.createClass({
+ displayName: 'InnerClass',
+ render: function() {
+ return Component(null, this.props.childSet);
+ }
+ });
+
+ var InnerComponent = React.createFactory(InnerClass);
+
+ var ComponentWrapper = React.createClass({
+ displayName: 'ComponentWrapper',
+ render: function() {
+ return InnerComponent({ childSet: [ Component(), Component() ] });
+ }
+ });
+
+ ReactTestUtils.renderIntoDocument(
+ React.createElement(ComponentWrapper)
+ );
+
+ expect(console.warn.argsForCall.length).toBe(1);
+ expect(console.warn.argsForCall[0][0]).toContain(
+ 'Each child in an array or iterator should have a unique "key" prop. ' +
+ 'Check the render method of InnerClass. ' +
+ 'It was passed a child from ComponentWrapper. '
+ );
+ });
+
+ it('warns for keys for iterables of elements in rest args', function() {
+ spyOn(console, 'warn');
+ var Component = React.createFactory(ComponentClass);
+
+ var iterable = {
+ '@@iterator': function() {
+ var i = 0;
+ return {
+ next: function() {
+ var done = ++i > 2;
+ return { value: done ? undefined : Component(), done: done };
+ }
+ };
+ }
+ };
+
+ Component(null, iterable);
+
+ expect(console.warn.argsForCall.length).toBe(1);
+ expect(console.warn.argsForCall[0][0]).toContain(
+ 'Each child in an array or iterator should have a unique "key" prop.'
+ );
+ });
+
+ it('does not warns for arrays of elements with keys', function() {
+ spyOn(console, 'warn');
+ var Component = React.createFactory(ComponentClass);
+
+ Component(null, [ Component({key: '#1'}), Component({key: '#2'}) ]);
+
+ expect(console.warn.argsForCall.length).toBe(0);
+ });
+
+ it('does not warns for iterable elements with keys', function() {
+ spyOn(console, 'warn');
+ var Component = React.createFactory(ComponentClass);
+
+ var iterable = {
+ '@@iterator': function() {
+ var i = 0;
+ return {
+ next: function() {
+ var done = ++i > 2;
+ return {
+ value: done ? undefined : Component({key: '#' + i}),
+ done: done
+ };
+ }
+ };
+ }
+ };
+
+ Component(null, iterable);
+
+ expect(console.warn.argsForCall.length).toBe(0);
+ });
+
+ it('warns for numeric keys on objects in rest args', function() {
+ spyOn(console, 'warn');
+ var Component = React.createFactory(ComponentClass);
+
+ Component(null, { 1: Component(), 2: Component() });
+
+ expect(console.warn.argsForCall.length).toBe(1);
+ expect(console.warn.argsForCall[0][0]).toContain(
+ 'Child objects should have non-numeric keys so ordering is preserved.'
+ );
+ });
+
+ it('does not warn for numeric keys in entry iterables in rest args', function() {
+ spyOn(console, 'warn');
+ var Component = React.createFactory(ComponentClass);
+
+ var iterable = {
+ '@@iterator': function() {
+ var i = 0;
+ return {
+ next: function() {
+ var done = ++i > 2;
+ return { value: done ? undefined : [i, Component()], done: done };
+ }
+ };
+ }
+ };
+ iterable.entries = iterable['@@iterator'];
+
+ Component(null, iterable);
+
+ expect(console.warn.argsForCall.length).toBe(0);
+ });
+
+ it('does not warn when the element is directly in rest args', function() {
+ spyOn(console, 'warn');
+ var Component = React.createFactory(ComponentClass);
+
+ Component(null, Component(), Component());
+
+ expect(console.warn.argsForCall.length).toBe(0);
+ });
+
+ it('does not warn when the array contains a non-element', function() {
+ spyOn(console, 'warn');
+ var Component = React.createFactory(ComponentClass);
+
+ Component(null, [ {}, {} ]);
+
+ expect(console.warn.argsForCall.length).toBe(0);
+ });
+
// TODO: These warnings currently come from the composite component, but
// they should be moved into the ReactElementValidator.
@@ -40,15 +196,15 @@ describe('ReactElementValidator', function() {
color: React.PropTypes.string
},
render: function() {
- return My color is {this.color}
;
+ return React.createElement('div', null, 'My color is ' + this.color);
}
});
var ParentComp = React.createClass({
render: function() {
- return ;
+ return React.createElement(MyComp, { color: 123 });
}
});
- ReactTestUtils.renderIntoDocument();
+ ReactTestUtils.renderIntoDocument(React.createElement(ParentComp));
expect(console.warn.calls[0].args[0]).toBe(
'Warning: Invalid prop `color` of type `number` supplied to `MyComp`, ' +
'expected `string`. Check the render method of `ParentComp`.'
@@ -72,11 +228,8 @@ describe('ReactElementValidator', function() {
);
React.createElement('div');
expect(console.warn.calls.length).toBe(2);
-
- expect(() => React.createElement(undefined)).not.toThrow()
});
-
it('should check default prop values', function() {
spyOn(console, 'warn');
@@ -86,11 +239,34 @@ describe('ReactElementValidator', function() {
return {prop: null};
},
render: function() {
- return {this.props.prop};
+ return React.createElement('span', null, this.props.prop);
}
});
- ReactTestUtils.renderIntoDocument();
+ ReactTestUtils.renderIntoDocument(React.createElement(Component));
+
+ expect(console.warn.calls.length).toBe(1);
+ expect(console.warn.calls[0].args[0]).toBe(
+ 'Warning: Required prop `prop` was not specified in `Component`.'
+ );
+ });
+
+ it('should not check the default for explicit null', function() {
+ spyOn(console, 'warn');
+
+ var Component = React.createClass({
+ propTypes: {prop: React.PropTypes.string.isRequired},
+ getDefaultProps: function() {
+ return {prop: 'text'};
+ },
+ render: function() {
+ return React.createElement('span', null, this.props.prop);
+ }
+ });
+
+ ReactTestUtils.renderIntoDocument(
+ React.createElement(Component, {prop:null})
+ );
expect(console.warn.calls.length).toBe(1);
expect(console.warn.calls[0].args[0]).toBe(
@@ -106,12 +282,16 @@ describe('ReactElementValidator', function() {
prop: React.PropTypes.string.isRequired
},
render: function() {
- return {this.props.prop};
+ return React.createElement('span', null, this.props.prop);
}
});
- ReactTestUtils.renderIntoDocument();
- ReactTestUtils.renderIntoDocument();
+ ReactTestUtils.renderIntoDocument(
+ React.createElement(Component)
+ );
+ ReactTestUtils.renderIntoDocument(
+ React.createElement(Component, {prop: 42})
+ );
expect(console.warn.calls.length).toBe(2);
expect(console.warn.calls[0].args[0]).toBe(
@@ -123,7 +303,9 @@ describe('ReactElementValidator', function() {
'`Component`, expected `string`.'
);
- ReactTestUtils.renderIntoDocument();
+ ReactTestUtils.renderIntoDocument(
+ React.createElement(Component, {prop: 'string'})
+ );
// Should not error for strings
expect(console.warn.calls.length).toBe(2);
diff --git a/src/core/ReactCompositeComponent.js b/src/core/ReactCompositeComponent.js
index f71de95c92..7875c3bdbe 100644
--- a/src/core/ReactCompositeComponent.js
+++ b/src/core/ReactCompositeComponent.js
@@ -32,8 +32,9 @@ function getDeclarationErrorAddendum(component) {
var owner = component._currentElement._owner || null;
if (owner) {
var constructor = owner._instance.constructor;
- if (constructor && constructor.displayName) {
- return ' Check the render method of `' + constructor.displayName + '`.';
+ var name = constructor && (constructor.displayName || constructor.name);
+ if (name) {
+ return ' Check the render method of `' + name + '`.';
}
}
return '';
@@ -510,7 +511,8 @@ var ReactCompositeComponentMixin = assign({},
_checkPropTypes: function(propTypes, props, location) {
// TODO: Stop validating prop types here and only use the element
// validation.
- var componentName = this._instance.constructor.displayName;
+ var componentName = this._instance.constructor.displayName ||
+ this._instance.constructor.name;
for (var propName in propTypes) {
if (propTypes.hasOwnProperty(propName)) {
var error =
diff --git a/src/modern/element/__tests__/ReactJSXElement-test.js b/src/modern/element/__tests__/ReactJSXElement-test.js
new file mode 100644
index 0000000000..8044a7be69
--- /dev/null
+++ b/src/modern/element/__tests__/ReactJSXElement-test.js
@@ -0,0 +1,192 @@
+/**
+ * Copyright 2013-2014, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * @emails react-core
+ */
+
+"use strict";
+
+var mocks;
+
+var React;
+var ReactTestUtils;
+
+describe('ReactJSXElement', function() {
+ var Component;
+
+ beforeEach(function() {
+ require('mock-modules').dumpCache();
+
+ mocks = require('mocks');
+
+ React = require('React');
+ ReactTestUtils = require('ReactTestUtils');
+ Component = class {
+ render() { return ; }
+ };
+ });
+
+ it('returns a complete element according to spec', function() {
+ var element = ;
+ expect(element.type).toBe(Component);
+ expect(element.key).toBe(null);
+ expect(element.ref).toBe(null);
+ expect(element.props).toEqual({});
+ });
+
+ it('allows a lower-case to be passed as the string type', function() {
+ var element = ;
+ expect(element.type).toBe('div');
+ expect(element.key).toBe(null);
+ expect(element.ref).toBe(null);
+ expect(element.props).toEqual({});
+ });
+
+ it('allows a string to be passed as the type', function() {
+ var TagName = 'div';
+ var element = ;
+ expect(element.type).toBe('div');
+ expect(element.key).toBe(null);
+ expect(element.ref).toBe(null);
+ expect(element.props).toEqual({});
+ });
+
+ it('returns an immutable element', function() {
+ var element = ;
+ expect(() => element.type = 'div').toThrow();
+ });
+
+ it('does not reuse the object that is spread into props', function() {
+ var config = { foo: 1 };
+ var element = ;
+ expect(element.props.foo).toBe(1);
+ config.foo = 2;
+ expect(element.props.foo).toBe(1);
+ });
+
+ it('extracts key and ref from the rest of the props', function() {
+ var element = ;
+ expect(element.type).toBe(Component);
+ expect(element.key).toBe('12');
+ expect(element.ref).toBe('34');
+ expect(element.props).toEqual({foo:'56'});
+ });
+
+ it('coerces the key to a string', function() {
+ var element = ;
+ expect(element.type).toBe(Component);
+ expect(element.key).toBe('12');
+ expect(element.ref).toBe(null);
+ expect(element.props).toEqual({foo:'56'});
+ });
+
+ it('merges JSX children onto the children prop', function() {
+ spyOn(console, 'warn');
+ var a = 1;
+ var element = {a};
+ expect(element.props.children).toBe(a);
+ expect(console.warn.argsForCall.length).toBe(0);
+ });
+
+ it('does not override children if no JSX children are provided', function() {
+ spyOn(console, 'warn');
+ var element = ;
+ expect(element.props.children).toBe('text');
+ expect(console.warn.argsForCall.length).toBe(0);
+ });
+
+ it('overrides children if null is provided as a JSX child', function() {
+ spyOn(console, 'warn');
+ var element = {null};
+ expect(element.props.children).toBe(null);
+ expect(console.warn.argsForCall.length).toBe(0);
+ });
+
+ it('merges JSX children onto the children prop in an array', function() {
+ spyOn(console, 'warn');
+ var a = 1, b = 2, c = 3;
+ var element = {a}{b}{c};
+ expect(element.props.children).toEqual([1, 2, 3]);
+ expect(console.warn.argsForCall.length).toBe(0);
+ });
+
+ it('allows static methods to be called using the type property', function() {
+ spyOn(console, 'warn');
+
+ class Component {
+ static someStaticMethod() {
+ return 'someReturnValue';
+ }
+ render() {
+ return ;
+ }
+ }
+
+ var element = ;
+ expect(element.type.someStaticMethod()).toBe('someReturnValue');
+ expect(console.warn.argsForCall.length).toBe(0);
+ });
+
+ it('identifies valid elements', function() {
+ class Component {
+ render() {
+ return ;
+ }
+ }
+
+ expect(React.isValidElement()).toEqual(true);
+ expect(React.isValidElement()).toEqual(true);
+
+ expect(React.isValidElement(null)).toEqual(false);
+ expect(React.isValidElement(true)).toEqual(false);
+ expect(React.isValidElement({})).toEqual(false);
+ expect(React.isValidElement("string")).toEqual(false);
+ expect(React.isValidElement(Component)).toEqual(false);
+ });
+
+ it('is indistinguishable from a plain object', function() {
+ var element = ;
+ var object = {};
+ expect(element.constructor).toBe(object.constructor);
+ });
+
+ it('should use default prop value when removing a prop', function() {
+ class Component {
+ render() {
+ return ;
+ }
+ }
+ Component.defaultProps = { fruit: 'persimmon' };
+
+ var container = document.createElement('div');
+ var instance = React.render(
+ ,
+ container
+ );
+ expect(instance.props.fruit).toBe('mango');
+
+ React.render(, container);
+ expect(instance.props.fruit).toBe('persimmon');
+ });
+
+ it('should normalize props with default values', function() {
+ class Component {
+ render() {
+ return {this.props.prop};
+ }
+ }
+ Component.defaultProps = { prop: 'testKey' };
+
+ var instance = ReactTestUtils.renderIntoDocument();
+ expect(instance.props.prop).toBe('testKey');
+
+ var inst2 = ReactTestUtils.renderIntoDocument();
+ expect(inst2.props.prop).toBe(null);
+ });
+
+});
diff --git a/src/modern/element/__tests__/ReactJSXElementValidator-test.js b/src/modern/element/__tests__/ReactJSXElementValidator-test.js
new file mode 100644
index 0000000000..779c72fbfc
--- /dev/null
+++ b/src/modern/element/__tests__/ReactJSXElementValidator-test.js
@@ -0,0 +1,293 @@
+/**
+ * Copyright 2013-2014, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * @emails react-core
+ */
+
+"use strict";
+
+// TODO: All these warnings should become static errors using Flow instead
+// of dynamic errors when using JSX with Flow.
+
+var React;
+var ReactTestUtils;
+
+describe('ReactJSXElementValidator', function() {
+ var Component;
+
+ beforeEach(function() {
+ require('mock-modules').dumpCache();
+
+ React = require('React');
+ ReactTestUtils = require('ReactTestUtils');
+
+ Component = class {
+ render() { return ; }
+ };
+ });
+
+ it('warns for keys for arrays of elements in children position', function() {
+ spyOn(console, 'warn');
+
+ {[, ]};
+
+ expect(console.warn.argsForCall.length).toBe(1);
+ expect(console.warn.argsForCall[0][0]).toContain(
+ 'Each child in an array or iterator should have a unique "key" prop.'
+ );
+ });
+
+ it('warns for keys for arrays of elements with owner info', function() {
+ spyOn(console, 'warn');
+
+ class InnerComponent {
+ render() {
+ return {this.props.childSet};
+ }
+ };
+
+ class ComponentWrapper {
+ render() {
+ return (
+ , ]}
+ />
+ );
+ }
+ };
+
+ ReactTestUtils.renderIntoDocument();
+
+ expect(console.warn.argsForCall.length).toBe(1);
+ expect(console.warn.argsForCall[0][0]).toContain(
+ 'Each child in an array or iterator should have a unique "key" prop. ' +
+ 'Check the render method of InnerComponent. ' +
+ 'It was passed a child from ComponentWrapper. '
+ );
+ });
+
+ it('warns for keys for iterables of elements in rest args', function() {
+ spyOn(console, 'warn');
+
+ var iterable = {
+ '@@iterator': function() {
+ var i = 0;
+ return {
+ next: function() {
+ var done = ++i > 2;
+ return { value: done ? undefined : , done: done };
+ }
+ };
+ }
+ };
+
+ {iterable};
+
+ expect(console.warn.argsForCall.length).toBe(1);
+ expect(console.warn.argsForCall[0][0]).toContain(
+ 'Each child in an array or iterator should have a unique "key" prop.'
+ );
+ });
+
+ it('does not warns for arrays of elements with keys', function() {
+ spyOn(console, 'warn');
+
+ {[ , ]};
+
+ expect(console.warn.argsForCall.length).toBe(0);
+ });
+
+ it('does not warns for iterable elements with keys', function() {
+ spyOn(console, 'warn');
+
+ var iterable = {
+ '@@iterator': function() {
+ var i = 0;
+ return {
+ next: function() {
+ var done = ++i > 2;
+ return {
+ value: done ? undefined : ,
+ done: done
+ };
+ }
+ };
+ }
+ };
+
+ {iterable}
+
+ expect(console.warn.argsForCall.length).toBe(0);
+ });
+
+ it('warns for numeric keys on objects as children', function() {
+ spyOn(console, 'warn');
+
+ {{ 1: , 2: }};
+
+ expect(console.warn.argsForCall.length).toBe(1);
+ expect(console.warn.argsForCall[0][0]).toContain(
+ 'Child objects should have non-numeric keys so ordering is preserved.'
+ );
+ });
+
+ it('does not warn for numeric keys in entry iterable as a child', function() {
+ spyOn(console, 'warn');
+
+ var iterable = {
+ '@@iterator': function() {
+ var i = 0;
+ return {
+ next: function() {
+ var done = ++i > 2;
+ return { value: done ? undefined : [i, ], done: done };
+ }
+ };
+ }
+ };
+ iterable.entries = iterable['@@iterator'];
+
+ {iterable};
+
+ expect(console.warn.argsForCall.length).toBe(0);
+ });
+
+ it('does not warn when the element is directly as children', function() {
+ spyOn(console, 'warn');
+
+ ;
+
+ expect(console.warn.argsForCall.length).toBe(0);
+ });
+
+ it('does not warn when the child array contains non-elements', function() {
+ spyOn(console, 'warn');
+
+ {[ {}, {} ]};
+
+ expect(console.warn.argsForCall.length).toBe(0);
+ });
+
+ // TODO: These warnings currently come from the composite component, but
+ // they should be moved into the ReactElementValidator.
+
+ it('should give context for PropType errors in nested components.', () => {
+ // In this test, we're making sure that if a proptype error is found in a
+ // component, we give a small hint as to which parent instantiated that
+ // component as per warnings about key usage in ReactElementValidator.
+ spyOn(console, 'warn');
+ class MyComp {
+ render() {
+ return My color is {this.color}
;
+ }
+ }
+ MyComp.propTypes = {
+ color: React.PropTypes.string
+ };
+ class ParentComp {
+ render() {
+ return ;
+ }
+ }
+ ReactTestUtils.renderIntoDocument();
+ expect(console.warn.calls[0].args[0]).toBe(
+ 'Warning: Invalid prop `color` of type `number` supplied to `MyComp`, ' +
+ 'expected `string`. Check the render method of `ParentComp`.'
+ );
+ });
+
+ it('gives a helpful error when passing null or undefined', function() {
+ var Undefined = undefined;
+ var Null = null;
+ var Div = 'div';
+ spyOn(console, 'warn');
+ ;
+ ;
+ expect(console.warn.calls.length).toBe(2);
+ expect(console.warn.calls[0].args[0]).toContain(
+ 'type should not be null or undefined. It should be a string (for ' +
+ 'DOM elements) or a ReactClass (for composite components).'
+ );
+ expect(console.warn.calls[1].args[0]).toContain(
+ 'type should not be null or undefined. It should be a string (for ' +
+ 'DOM elements) or a ReactClass (for composite components).'
+ );
+ ;
+ expect(console.warn.calls.length).toBe(2);
+ });
+
+ it('should check default prop values', function() {
+ spyOn(console, 'warn');
+
+ class Component {
+ render() {
+ return {this.props.prop};
+ }
+ }
+ Component.defaultProps = { prop: null };
+ Component.propTypes = { prop: React.PropTypes.string.isRequired };
+
+ ReactTestUtils.renderIntoDocument();
+
+ expect(console.warn.calls.length).toBe(1);
+ expect(console.warn.calls[0].args[0]).toBe(
+ 'Warning: Required prop `prop` was not specified in `Component`.'
+ );
+ });
+
+ it('should not check the default for explicit null', function() {
+ spyOn(console, 'warn');
+
+ class Component {
+ render() {
+ return {this.props.prop};
+ }
+ }
+ Component.defaultProps = { prop: 'text' };
+ Component.propTypes = { prop: React.PropTypes.string.isRequired };
+
+ ReactTestUtils.renderIntoDocument();
+
+ expect(console.warn.calls.length).toBe(1);
+ expect(console.warn.calls[0].args[0]).toBe(
+ 'Warning: Required prop `prop` was not specified in `Component`.'
+ );
+ });
+
+ it('should check declared prop types', function() {
+ spyOn(console, 'warn');
+
+ class Component {
+ render() {
+ return {this.props.prop};
+ }
+ }
+ Component.propTypes = {
+ prop: React.PropTypes.string.isRequired
+ };
+
+ ReactTestUtils.renderIntoDocument();
+ ReactTestUtils.renderIntoDocument();
+
+ expect(console.warn.calls.length).toBe(2);
+ expect(console.warn.calls[0].args[0]).toBe(
+ 'Warning: Required prop `prop` was not specified in `Component`.'
+ );
+
+ expect(console.warn.calls[1].args[0]).toBe(
+ 'Warning: Invalid prop `prop` of type `number` supplied to ' +
+ '`Component`, expected `string`.'
+ );
+
+ ReactTestUtils.renderIntoDocument();
+
+ // Should not error for strings
+ expect(console.warn.calls.length).toBe(2);
+ });
+
+});