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.
This commit is contained in:
Sebastian Markbage
2014-12-18 16:13:29 -08:00
parent 60b2241ad4
commit cea2c38733
7 changed files with 750 additions and 215 deletions
+6
View File
@@ -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')
+22 -5
View File
@@ -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,
@@ -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 <div />; }
render: function() { return React.createElement('div'); }
});
});
@@ -102,7 +103,9 @@ describe('ReactElement', function() {
}
});
ReactTestUtils.renderIntoDocument(<Wrapper />);
ReactTestUtils.renderIntoDocument(
React.createElement(Wrapper)
);
expect(element._context).toEqual({ foo: 'bar' });
});
@@ -118,7 +121,9 @@ describe('ReactElement', function() {
}
});
var instance = ReactTestUtils.renderIntoDocument(<Wrapper />);
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(<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);
});
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 <div></div>;
return React.createElement('div');
}
});
var element = <ComponentClass />;
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 <div />;
return React.createElement('div');
}
});
expect(ReactElement.isValidElement(<div />)).toEqual(true);
expect(ReactElement.isValidElement(<Component />)).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 <span />;
return React.createElement('span');
}
});
var container = document.createElement('div');
var instance = React.render(
<Component fruit="mango" />,
React.createElement(Component, { fruit: 'mango' }),
container
);
expect(instance.props.fruit).toBe('mango');
React.render(<Component />, 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 <span>{this.props.prop}</span>;
return React.createElement('span', null, this.props.prop);
}
});
var instance = ReactTestUtils.renderIntoDocument(<Component />);
expect(instance.props.prop).toBe('testKey');
expect(instance.state.prop).toBe('testKeyState');
ReactTestUtils.renderIntoDocument(<Component prop={null} />);
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);
});
});
@@ -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 <div />; }
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 <div>My color is {this.color}</div>;
return React.createElement('div', null, 'My color is ' + this.color);
}
});
var ParentComp = React.createClass({
render: function() {
return <MyComp color={123} />;
return React.createElement(MyComp, { color: 123 });
}
});
ReactTestUtils.renderIntoDocument(<ParentComp />);
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 <span>{this.props.prop}</span>;
return React.createElement('span', null, this.props.prop);
}
});
ReactTestUtils.renderIntoDocument(<Component />);
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 <span>{this.props.prop}</span>;
return React.createElement('span', null, this.props.prop);
}
});
ReactTestUtils.renderIntoDocument(<Component />);
ReactTestUtils.renderIntoDocument(<Component prop={42} />);
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(<Component prop="string" />);
ReactTestUtils.renderIntoDocument(
React.createElement(Component, {prop: 'string'})
);
// Should not error for strings
expect(console.warn.calls.length).toBe(2);
+5 -3
View File
@@ -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 =
@@ -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 <div />; }
};
});
it('returns a complete element according to spec', function() {
var element = <Component />;
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 = <div />;
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 = <TagName />;
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 = <Component />;
expect(() => element.type = 'div').toThrow();
});
it('does not reuse the object that is spread into props', function() {
var config = { foo: 1 };
var element = <Component {...config} />;
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 = <Component key="12" ref="34" foo="56" />;
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 = <Component key={12} foo="56" />;
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 = <Component children="text">{a}</Component>;
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 = <Component children="text" />;
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 = <Component children="text">{null}</Component>;
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 = <Component>{a}{b}{c}</Component>;
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 <div></div>;
}
}
var element = <Component />;
expect(element.type.someStaticMethod()).toBe('someReturnValue');
expect(console.warn.argsForCall.length).toBe(0);
});
it('identifies valid elements', function() {
class Component {
render() {
return <div />;
}
}
expect(React.isValidElement(<div />)).toEqual(true);
expect(React.isValidElement(<Component />)).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 = <div className="foo" />;
var object = {};
expect(element.constructor).toBe(object.constructor);
});
it('should use default prop value when removing a prop', function() {
class Component {
render() {
return <span />;
}
}
Component.defaultProps = { fruit: 'persimmon' };
var container = document.createElement('div');
var instance = React.render(
<Component fruit="mango" />,
container
);
expect(instance.props.fruit).toBe('mango');
React.render(<Component />, container);
expect(instance.props.fruit).toBe('persimmon');
});
it('should normalize props with default values', function() {
class Component {
render() {
return <span>{this.props.prop}</span>;
}
}
Component.defaultProps = { prop: 'testKey' };
var instance = ReactTestUtils.renderIntoDocument(<Component />);
expect(instance.props.prop).toBe('testKey');
var inst2 = ReactTestUtils.renderIntoDocument(<Component prop={null} />);
expect(inst2.props.prop).toBe(null);
});
});
@@ -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 <div />; }
};
});
it('warns for keys for arrays of elements in children position', function() {
spyOn(console, 'warn');
<Component>{[<Component />, <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');
class InnerComponent {
render() {
return <Component>{this.props.childSet}</Component>;
}
};
class ComponentWrapper {
render() {
return (
<InnerComponent
childSet={[ <Component />, <Component /> ]}
/>
);
}
};
ReactTestUtils.renderIntoDocument(<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 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 : <Component />, done: done };
}
};
}
};
<Component>{iterable}</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('does not warns for arrays of elements with keys', function() {
spyOn(console, 'warn');
<Component>{[ <Component key="#1" />, <Component key="#2" /> ]}</Component>;
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 : <Component key={'#' + i} />,
done: done
};
}
};
}
};
<Component>{iterable}</Component>
expect(console.warn.argsForCall.length).toBe(0);
});
it('warns for numeric keys on objects as children', function() {
spyOn(console, 'warn');
<Component>{{ 1: <Component />, 2: <Component /> }}</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 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, <Component />], done: done };
}
};
}
};
iterable.entries = iterable['@@iterator'];
<Component>{iterable}</Component>;
expect(console.warn.argsForCall.length).toBe(0);
});
it('does not warn when the element is directly as children', function() {
spyOn(console, 'warn');
<Component><Component /><Component /></Component>;
expect(console.warn.argsForCall.length).toBe(0);
});
it('does not warn when the child array contains non-elements', function() {
spyOn(console, 'warn');
<Component>{[ {}, {} ]}</Component>;
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 <div>My color is {this.color}</div>;
}
}
MyComp.propTypes = {
color: React.PropTypes.string
};
class ParentComp {
render() {
return <MyComp color={123} />;
}
}
ReactTestUtils.renderIntoDocument(<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`.'
);
});
it('gives a helpful error when passing null or undefined', function() {
var Undefined = undefined;
var Null = null;
var Div = 'div';
spyOn(console, 'warn');
<Undefined />;
<Null />;
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).'
);
<Div />;
expect(console.warn.calls.length).toBe(2);
});
it('should check default prop values', function() {
spyOn(console, 'warn');
class Component {
render() {
return <span>{this.props.prop}</span>;
}
}
Component.defaultProps = { prop: null };
Component.propTypes = { prop: React.PropTypes.string.isRequired };
ReactTestUtils.renderIntoDocument(<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');
class Component {
render() {
return <span>{this.props.prop}</span>;
}
}
Component.defaultProps = { prop: 'text' };
Component.propTypes = { prop: React.PropTypes.string.isRequired };
ReactTestUtils.renderIntoDocument(<Component prop={null} />);
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 <span>{this.props.prop}</span>;
}
}
Component.propTypes = {
prop: React.PropTypes.string.isRequired
};
ReactTestUtils.renderIntoDocument(<Component />);
ReactTestUtils.renderIntoDocument(<Component prop={42} />);
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(<Component prop="string" />);
// Should not error for strings
expect(console.warn.calls.length).toBe(2);
});
});