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); + }); + +});