From 801e95333444e1267331e6e765ca228161ce6e65 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Fri, 12 Dec 2014 17:54:35 -0800 Subject: [PATCH] Move ReactClass, ReactElement and ReactPropTypes into "traditional" This moves ReactClass, ReactElement and ReactPropTypes into a legacy folder but since it's not quite legacy yet, I call it "classic". These are "classic" because they are decoupled and can be replaced by ES6 classes, JSX and Flow respectively. This also extracts unit tests from ReactCompositeComponent, which was terribly overloaded, into the new corresponding test suites. There is one weird case for ReactContextValidator. This actually happens in core, and technically belongs to ReactCompositeComponent. I'm not sure we will be able to statically validate contexts so this might be a case for dynamic checks even in the future. Leaving the unit tests in classic until we can figure out what to do with them. --- .../__tests__/ReactContextValidator-test.js | 247 +++++ src/{ => classic}/class/ReactClass.js | 0 .../class}/ReactDoNotBindDeprecated.js | 0 .../class}/__tests__/ReactBind-test.js | 0 .../class/__tests__/ReactClass-test.js | 338 +++++++ .../class/__tests__/ReactClassMixin-test.js | 459 +++++++++ src/{core => classic/element}/ReactElement.js | 0 .../element}/ReactElementValidator.js | 0 .../element}/__tests__/ReactElement-test.js | 56 ++ .../__tests__/ReactElementValidator-test.js | 110 +++ .../propTypes}/ReactPropTypeLocationNames.js | 0 .../propTypes}/ReactPropTypeLocations.js | 0 .../propTypes}/ReactPropTypes.js | 0 .../__tests__/ReactPropTypes-test.js | 0 .../__tests__/ReactCompositeComponent-test.js | 898 +----------------- .../ReactCompositeComponentMixin-test.js | 171 ---- .../ReactCompositeComponentSpec-test.js | 63 -- 17 files changed, 1218 insertions(+), 1124 deletions(-) create mode 100644 src/classic/__tests__/ReactContextValidator-test.js rename src/{ => classic}/class/ReactClass.js (100%) rename src/{core => classic/class}/ReactDoNotBindDeprecated.js (100%) rename src/{core => classic/class}/__tests__/ReactBind-test.js (100%) create mode 100644 src/classic/class/__tests__/ReactClass-test.js create mode 100644 src/classic/class/__tests__/ReactClassMixin-test.js rename src/{core => classic/element}/ReactElement.js (100%) rename src/{core => classic/element}/ReactElementValidator.js (100%) rename src/{core => classic/element}/__tests__/ReactElement-test.js (87%) create mode 100644 src/classic/element/__tests__/ReactElementValidator-test.js rename src/{core => classic/propTypes}/ReactPropTypeLocationNames.js (100%) rename src/{core => classic/propTypes}/ReactPropTypeLocations.js (100%) rename src/{core => classic/propTypes}/ReactPropTypes.js (100%) rename src/{core => classic/propTypes}/__tests__/ReactPropTypes-test.js (100%) delete mode 100644 src/core/__tests__/ReactCompositeComponentMixin-test.js delete mode 100644 src/core/__tests__/ReactCompositeComponentSpec-test.js diff --git a/src/classic/__tests__/ReactContextValidator-test.js b/src/classic/__tests__/ReactContextValidator-test.js new file mode 100644 index 0000000000000..3f9f8c017b36e --- /dev/null +++ b/src/classic/__tests__/ReactContextValidator-test.js @@ -0,0 +1,247 @@ +/** + * 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 + */ + +// This test doesn't really have a good home yet. I'm leaving it here since this +// behavior belongs to the old propTypes system yet is currently implemented +// in the core ReactCompositeComponent. It should technically live in core's +// test suite but I'll leave it here to indicate that this is an issue that +// needs to be fixed. + +"use strict"; + +var React; +var ReactTestUtils; + +var reactComponentExpect; +var mocks; + +describe('ReactContextValidator', function() { + beforeEach(function() { + require('mock-modules').dumpCache(); + + React = require('React'); + ReactTestUtils = require('ReactTestUtils'); + reactComponentExpect = require('reactComponentExpect'); + mocks = require('mocks'); + + console.warn = mocks.getMockFunction(); + }); + + // TODO: This behavior creates a runtime dependency on propTypes. We should + // ensure that this is not required for ES6 classes with Flow. + + it('should filter out context not in contextTypes', function() { + var Component = React.createClass({ + contextTypes: { + foo: React.PropTypes.string + }, + + render: function() { + return
; + } + }); + + var ComponentInFooBarContext = React.createClass({ + childContextTypes: { + foo: React.PropTypes.string, + bar: React.PropTypes.number + }, + + getChildContext: function() { + return { + foo: 'abc', + bar: 123 + }; + }, + + render: function() { + return ; + } + }); + + var instance = ReactTestUtils.renderIntoDocument(); + reactComponentExpect(instance).expectRenderedChild().scalarContextEqual({foo: 'abc'}); + }); + + it('should filter context properly in callbacks', function() { + var actualComponentWillReceiveProps; + var actualShouldComponentUpdate; + var actualComponentWillUpdate; + var actualComponentDidUpdate; + + var Parent = React.createClass({ + childContextTypes: { + foo: React.PropTypes.string.isRequired, + bar: React.PropTypes.string.isRequired + }, + + getChildContext: function() { + return { + foo: this.props.foo, + bar: "bar" + }; + }, + + render: function() { + return ; + } + }); + + var Component = React.createClass({ + contextTypes: { + foo: React.PropTypes.string + }, + + componentWillReceiveProps: function(nextProps, nextContext) { + actualComponentWillReceiveProps = nextContext; + return true; + }, + + shouldComponentUpdate: function(nextProps, nextState, nextContext) { + actualShouldComponentUpdate = nextContext; + return true; + }, + + componentWillUpdate: function(nextProps, nextState, nextContext) { + actualComponentWillUpdate = nextContext; + }, + + componentDidUpdate: function(prevProps, prevState, prevContext) { + actualComponentDidUpdate = prevContext; + }, + + render: function() { + return
; + } + }); + + var instance = ; + instance = ReactTestUtils.renderIntoDocument(instance); + instance.replaceProps({foo: "def"}); + expect(actualComponentWillReceiveProps).toEqual({foo: 'def'}); + expect(actualShouldComponentUpdate).toEqual({foo: 'def'}); + expect(actualComponentWillUpdate).toEqual({foo: 'def'}); + expect(actualComponentDidUpdate).toEqual({foo: 'abc'}); + }); + + it('should check context types', function() { + var Component = React.createClass({ + contextTypes: { + foo: React.PropTypes.string.isRequired + }, + + render: function() { + return
; + } + }); + + ReactTestUtils.renderIntoDocument(); + + expect(console.warn.mock.calls.length).toBe(1); + expect(console.warn.mock.calls[0][0]).toBe( + 'Warning: Required context `foo` was not specified in `Component`.' + ); + + var ComponentInFooStringContext = React.createClass({ + childContextTypes: { + foo: React.PropTypes.string + }, + + getChildContext: function() { + return { + foo: this.props.fooValue + }; + }, + + render: function() { + return ; + } + }); + + ReactTestUtils.renderIntoDocument( + + ); + + // Previous call should not error + expect(console.warn.mock.calls.length).toBe(1); + + var ComponentInFooNumberContext = React.createClass({ + childContextTypes: { + foo: React.PropTypes.number + }, + + getChildContext: function() { + return { + foo: this.props.fooValue + }; + }, + + render: function() { + return ; + } + }); + + ReactTestUtils.renderIntoDocument(); + + expect(console.warn.mock.calls.length).toBe(2); + expect(console.warn.mock.calls[1][0]).toBe( + 'Warning: Invalid context `foo` of type `number` supplied ' + + 'to `Component`, expected `string`.' + + ' Check the render method of `ComponentInFooNumberContext`.' + ); + }); + + it('should check child context types', function() { + var Component = React.createClass({ + childContextTypes: { + foo: React.PropTypes.string.isRequired, + bar: React.PropTypes.number + }, + + getChildContext: function() { + return this.props.testContext; + }, + + render: function() { + return
; + } + }); + + ReactTestUtils.renderIntoDocument(); + expect(console.warn.mock.calls.length).toBe(2); + expect(console.warn.mock.calls[0][0]).toBe( + 'Warning: Required child context `foo` was not specified in `Component`.' + ); + expect(console.warn.mock.calls[1][0]).toBe( + 'Warning: Required child context `foo` was not specified in `Component`.' + ); + + ReactTestUtils.renderIntoDocument(); + + expect(console.warn.mock.calls.length).toBe(4); + expect(console.warn.mock.calls[3][0]).toBe( + 'Warning: Invalid child context `foo` of type `number` ' + + 'supplied to `Component`, expected `string`.' + ); + + ReactTestUtils.renderIntoDocument( + + ); + + ReactTestUtils.renderIntoDocument( + + ); + + // Previous calls should not log errors + expect(console.warn.mock.calls.length).toBe(4); + }); + +}); diff --git a/src/class/ReactClass.js b/src/classic/class/ReactClass.js similarity index 100% rename from src/class/ReactClass.js rename to src/classic/class/ReactClass.js diff --git a/src/core/ReactDoNotBindDeprecated.js b/src/classic/class/ReactDoNotBindDeprecated.js similarity index 100% rename from src/core/ReactDoNotBindDeprecated.js rename to src/classic/class/ReactDoNotBindDeprecated.js diff --git a/src/core/__tests__/ReactBind-test.js b/src/classic/class/__tests__/ReactBind-test.js similarity index 100% rename from src/core/__tests__/ReactBind-test.js rename to src/classic/class/__tests__/ReactBind-test.js diff --git a/src/classic/class/__tests__/ReactClass-test.js b/src/classic/class/__tests__/ReactClass-test.js new file mode 100644 index 0000000000000..edabe5a0571ab --- /dev/null +++ b/src/classic/class/__tests__/ReactClass-test.js @@ -0,0 +1,338 @@ +/** + * 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 = require('mocks'); + +var React; +var ReactTestUtils; +var reactComponentExpect; + +describe('ReactClass-spec', function() { + + beforeEach(function() { + React = require('React'); + ReactTestUtils = require('ReactTestUtils'); + reactComponentExpect = require('reactComponentExpect'); + }); + + it('should throw when `render` is not specified', function() { + expect(function() { + React.createClass({}); + }).toThrow( + 'Invariant Violation: createClass(...): Class specification must ' + + 'implement a `render` method.' + ); + }); + + it('should copy `displayName` onto the Constructor', function() { + var TestComponent = React.createClass({ + render: function() { + return
; + } + }); + + expect(TestComponent.displayName) + .toBe('TestComponent'); + }); + + it('should copy prop types onto the Constructor', function() { + var propValidator = mocks.getMockFunction(); + var TestComponent = React.createClass({ + propTypes: { + value: propValidator + }, + render: function() { + return
; + } + }); + + expect(TestComponent.propTypes).toBeDefined(); + expect(TestComponent.propTypes.value) + .toBe(propValidator); + }); + + it('should throw on invalid prop types', function() { + expect(function() { + React.createClass({ + displayName: 'Component', + propTypes: { + prop: null + }, + render: function() { + return {this.props.prop}; + } + }); + }).toThrow( + 'Invariant Violation: Component: prop type `prop` is invalid; ' + + 'it must be a function, usually from React.PropTypes.' + ); + }); + + it('should throw on invalid context types', function() { + expect(function() { + React.createClass({ + displayName: 'Component', + contextTypes: { + prop: null + }, + render: function() { + return {this.props.prop}; + } + }); + }).toThrow( + 'Invariant Violation: Component: context type `prop` is invalid; ' + + 'it must be a function, usually from React.PropTypes.' + ); + }); + + it('should throw on invalid child context types', function() { + expect(function() { + React.createClass({ + displayName: 'Component', + childContextTypes: { + prop: null + }, + render: function() { + return {this.props.prop}; + } + }); + }).toThrow( + 'Invariant Violation: Component: child context type `prop` is invalid; ' + + 'it must be a function, usually from React.PropTypes.' + ); + }); + + it('should warn when mispelling shouldComponentUpdate', function() { + var warn = console.warn; + console.warn = mocks.getMockFunction(); + + try { + React.createClass({ + componentShouldUpdate: function() { + return false; + }, + render: function() { + return
; + } + }); + expect(console.warn.mock.calls.length).toBe(1); + expect(console.warn.mock.calls[0][0]).toBe( + 'A component has a method called componentShouldUpdate(). Did you ' + + 'mean shouldComponentUpdate()? The name is phrased as a question ' + + 'because the function is expected to return a value.' + ); + + var NamedComponent = React.createClass({ + componentShouldUpdate: function() { + return false; + }, + render: function() { + return
; + } + }); + expect(console.warn.mock.calls.length).toBe(2); + expect(console.warn.mock.calls[1][0]).toBe( + 'NamedComponent has a method called componentShouldUpdate(). Did you ' + + 'mean shouldComponentUpdate()? The name is phrased as a question ' + + 'because the function is expected to return a value.' + ); + + ; // Shut up lint + } finally { + console.warn = warn; + } + }); + + it('should throw if a reserved property is in statics', function() { + expect(function() { + React.createClass({ + statics: { + getDefaultProps: function() { + return { + foo: 0 + }; + } + }, + + render: function() { + return ; + } + }); + }).toThrow( + 'Invariant Violation: ReactClass: You are attempting to ' + + 'define a reserved property, `getDefaultProps`, that shouldn\'t be on ' + + 'the "statics" key. Define it as an instance property instead; it ' + + 'will still be accessible on the constructor.' + ); + }); + + // TODO: Consider actually moving these to statics or drop this unit test. + + xit('should warn when using deprecated non-static spec keys', function() { + var warn = console.warn; + console.warn = mocks.getMockFunction(); + try { + React.createClass({ + mixins: [{}], + propTypes: { + foo: ReactPropTypes.string + }, + contextTypes: { + foo: ReactPropTypes.string + }, + childContextTypes: { + foo: ReactPropTypes.string + }, + render: function() { + return
; + } + }); + expect(console.warn.mock.calls.length).toBe(4); + expect(console.warn.mock.calls[0][0]).toBe( + 'createClass(...): `mixins` is now a static property and should ' + + 'be defined inside "statics".' + ); + expect(console.warn.mock.calls[1][0]).toBe( + 'createClass(...): `propTypes` is now a static property and should ' + + 'be defined inside "statics".' + ); + expect(console.warn.mock.calls[2][0]).toBe( + 'createClass(...): `contextTypes` is now a static property and ' + + 'should be defined inside "statics".' + ); + expect(console.warn.mock.calls[3][0]).toBe( + 'createClass(...): `childContextTypes` is now a static property and ' + + 'should be defined inside "statics".' + ); + } finally { + console.warn = warn; + } + }); + + it('should support statics', function() { + var Component = React.createClass({ + statics: { + abc: 'def', + def: 0, + ghi: null, + jkl: 'mno', + pqr: function() { + return this; + } + }, + + render: function() { + return ; + } + }); + var instance = ; + instance = ReactTestUtils.renderIntoDocument(instance); + expect(instance.constructor.abc).toBe('def'); + expect(Component.abc).toBe('def'); + expect(instance.constructor.def).toBe(0); + expect(Component.def).toBe(0); + expect(instance.constructor.ghi).toBe(null); + expect(Component.ghi).toBe(null); + expect(instance.constructor.jkl).toBe('mno'); + expect(Component.jkl).toBe('mno'); + expect(instance.constructor.pqr()).toBe(Component); + expect(Component.pqr()).toBe(Component); + }); + + it('should work with object getInitialState() return values', function() { + var Component = React.createClass({ + getInitialState: function() { + return { + occupation: 'clown' + }; + }, + render: function() { + return ; + } + }); + var instance = ; + instance = ReactTestUtils.renderIntoDocument(instance); + expect(instance.state.occupation).toEqual('clown'); + }); + + it('should throw with non-object getInitialState() return values', function() { + [['an array'], 'a string', 1234].forEach(function(state) { + var Component = React.createClass({ + getInitialState: function() { + return state; + }, + render: function() { + return ; + } + }); + var instance = ; + expect(function() { + instance = ReactTestUtils.renderIntoDocument(instance); + }).toThrow( + 'Invariant Violation: Component.getInitialState(): ' + + 'must return an object or null' + ); + }); + }); + + it('should work with a null getInitialState() return value', function() { + var Component = React.createClass({ + getInitialState: function() { + return null; + }, + render: function() { + return ; + } + }); + expect( + () => ReactTestUtils.renderIntoDocument() + ).not.toThrow(); + }); + + it('should work with object getInitialState() return values', function() { + var Component = React.createClass({ + getInitialState: function() { + return { + occupation: 'clown' + }; + }, + render: function() { + return ; + } + }); + var instance = ; + instance = ReactTestUtils.renderIntoDocument(instance); + expect(instance.state.occupation).toEqual('clown'); + }); + + it('should throw with non-object getInitialState() return values', function() { + [['an array'], 'a string', 1234].forEach(function(state) { + var Component = React.createClass({ + getInitialState: function() { + return state; + }, + render: function() { + return ; + } + }); + var instance = ; + expect(function() { + instance = ReactTestUtils.renderIntoDocument(instance); + }).toThrow( + 'Invariant Violation: Component.getInitialState(): ' + + 'must return an object or null' + ); + }); + }); + +}); diff --git a/src/classic/class/__tests__/ReactClassMixin-test.js b/src/classic/class/__tests__/ReactClassMixin-test.js new file mode 100644 index 0000000000000..1db776f59099b --- /dev/null +++ b/src/classic/class/__tests__/ReactClassMixin-test.js @@ -0,0 +1,459 @@ +/** + * 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 = require('mocks'); + +var React; +var ReactTestUtils; +var reactComponentExpect; + +var TestComponent; +var TestComponentWithPropTypes; +var TestComponentWithReverseSpec; +var mixinPropValidator; +var componentPropValidator; + +describe('ReactClass-mixin', function() { + + beforeEach(function() { + React = require('React'); + ReactTestUtils = require('ReactTestUtils'); + reactComponentExpect = require('reactComponentExpect'); + mixinPropValidator = mocks.getMockFunction(); + componentPropValidator = mocks.getMockFunction(); + + var MixinA = { + propTypes: { + propA: function() {} + }, + componentDidMount: function() { + this.props.listener('MixinA didMount'); + } + }; + + var MixinB = { + mixins: [MixinA], + propTypes: { + propB: function() {} + }, + componentDidMount: function() { + this.props.listener('MixinB didMount'); + } + }; + + var MixinBWithReverseSpec = { + componentDidMount: function() { + this.props.listener('MixinBWithReverseSpec didMount'); + }, + mixins: [MixinA] + }; + + var MixinC = { + statics: { + staticC: function() {} + }, + componentDidMount: function() { + this.props.listener('MixinC didMount'); + } + }; + + var MixinD = { + propTypes: { + value: mixinPropValidator + } + }; + + TestComponent = React.createClass({ + mixins: [MixinB, MixinC, MixinD], + statics: { + staticComponent: function() {} + }, + propTypes: { + propComponent: function() {} + }, + componentDidMount: function() { + this.props.listener('Component didMount'); + }, + render: function() { + return
; + } + }); + + TestComponentWithReverseSpec = React.createClass({ + render: function() { + return
; + }, + componentDidMount: function() { + this.props.listener('Component didMount'); + }, + mixins: [MixinBWithReverseSpec, MixinC, MixinD] + }); + + TestComponentWithPropTypes = React.createClass({ + mixins: [MixinD], + propTypes: { + value: componentPropValidator + }, + render: function() { + return
; + } + }); + }); + + it('should support merging propTypes and statics', function() { + var listener = mocks.getMockFunction(); + var instance = ; + instance = ReactTestUtils.renderIntoDocument(instance); + + var instancePropTypes = instance.constructor.propTypes; + + expect('propA' in instancePropTypes).toBe(true); + expect('propB' in instancePropTypes).toBe(true); + expect('propComponent' in instancePropTypes).toBe(true); + + expect('staticC' in TestComponent).toBe(true); + expect('staticComponent' in TestComponent).toBe(true); + }); + + it('should support chaining delegate functions', function() { + var listener = mocks.getMockFunction(); + var instance = ; + instance = ReactTestUtils.renderIntoDocument(instance); + + expect(listener.mock.calls).toEqual([ + ['MixinA didMount'], + ['MixinB didMount'], + ['MixinC didMount'], + ['Component didMount'] + ]); + }); + + it('should chain functions regardless of spec property order', function() { + var listener = mocks.getMockFunction(); + var instance = ; + instance = ReactTestUtils.renderIntoDocument(instance); + + expect(listener.mock.calls).toEqual([ + ['MixinA didMount'], + ['MixinBWithReverseSpec didMount'], + ['MixinC didMount'], + ['Component didMount'] + ]); + }); + + it('should validate prop types via mixins', function() { + expect(TestComponent.propTypes).toBeDefined(); + expect(TestComponent.propTypes.value) + .toBe(mixinPropValidator); + }); + + it('should override mixin prop types with class prop types', function() { + // Sanity check... + expect(componentPropValidator).toNotBe(mixinPropValidator); + // Actually check... + expect(TestComponentWithPropTypes.propTypes) + .toBeDefined(); + expect(TestComponentWithPropTypes.propTypes.value) + .toNotBe(mixinPropValidator); + expect(TestComponentWithPropTypes.propTypes.value) + .toBe(componentPropValidator); + }); + + + it('should support mixins with getInitialState()', function() { + var Mixin = { + getInitialState: function() { + return {mixin: true}; + } + }; + var Component = React.createClass({ + mixins: [Mixin], + getInitialState: function() { + return {component: true}; + }, + render: function() { + return ; + } + }); + var instance = ; + instance = ReactTestUtils.renderIntoDocument(instance); + expect(instance.state.component).toBe(true); + expect(instance.state.mixin).toBe(true); + }); + + it('should throw with conflicting getInitialState() methods', function() { + var Mixin = { + getInitialState: function() { + return {x: true}; + } + }; + var Component = React.createClass({ + mixins: [Mixin], + getInitialState: function() { + return {x: true}; + }, + render: function() { + return ; + } + }); + var instance = ; + expect(function() { + instance = ReactTestUtils.renderIntoDocument(instance); + }).toThrow( + 'Invariant Violation: mergeIntoWithNoDuplicateKeys(): ' + + 'Tried to merge two objects with the same key: `x`. This conflict ' + + 'may be due to a mixin; in particular, this may be caused by two ' + + 'getInitialState() or getDefaultProps() methods returning objects ' + + 'with clashing keys.' + ); + }); + + it('should not mutate objects returned by getInitialState()', function() { + var Mixin = { + getInitialState: function() { + return Object.freeze({mixin: true}); + } + }; + var Component = React.createClass({ + mixins: [Mixin], + getInitialState: function() { + return Object.freeze({component: true}); + }, + render: function() { + return ; + } + }); + expect(() => { + ReactTestUtils.renderIntoDocument(); + }).not.toThrow(); + }); + + it('should support statics in mixins', function() { + var Mixin = { + statics: { + foo: 'bar' + } + }; + var Component = React.createClass({ + mixins: [Mixin], + + statics: { + abc: 'def' + }, + + render: function() { + return ; + } + }); + var instance = ; + instance = ReactTestUtils.renderIntoDocument(instance); + expect(instance.constructor.foo).toBe('bar'); + expect(Component.foo).toBe('bar'); + expect(instance.constructor.abc).toBe('def'); + expect(Component.abc).toBe('def'); + }); + + it("should throw if mixins override each others' statics", function() { + expect(function() { + var Mixin = { + statics: { + abc: 'foo' + } + }; + React.createClass({ + mixins: [Mixin], + + statics: { + abc: 'bar' + }, + + render: function() { + return ; + } + }); + }).toThrow( + 'Invariant Violation: ReactClass: You are attempting to ' + + 'define `abc` on your component more than once. This conflict may be ' + + 'due to a mixin.' + ); + }); + + it("should throw if mixins override functions in statics", function() { + expect(function() { + var Mixin = { + statics: { + abc: function() { console.log('foo'); } + } + }; + React.createClass({ + mixins: [Mixin], + + statics: { + abc: function() { console.log('bar'); } + }, + + render: function() { + return ; + } + }); + }).toThrow( + 'Invariant Violation: ReactClass: You are attempting to ' + + 'define `abc` on your component more than once. This conflict may be ' + + 'due to a mixin.' + ); + }); + + it("should throw if the mixin is a React component", function() { + expect(function() { + React.createClass({ + mixins: [
], + + render: function() { + return ; + } + }); + }).toThrow( + 'Invariant Violation: ReactClass: You\'re attempting to ' + + 'use a component as a mixin. Instead, just use a regular object.' + ); + }); + + it("should throw if the mixin is a React component class", function() { + expect(function() { + var Component = React.createClass({ + render: function() { + return ; + } + }); + + React.createClass({ + mixins: [Component], + + render: function() { + return ; + } + }); + }).toThrow( + 'Invariant Violation: ReactClass: You\'re attempting to ' + + 'use a component class as a mixin. Instead, just use a regular object.' + ); + }); + + it('should have bound the mixin methods to the component', function() { + var mixin = { + mixinFunc: function() {return this;} + }; + + var Component = React.createClass({ + mixins: [mixin], + componentDidMount: function() { + expect(this.mixinFunc()).toBe(this); + }, + render: function() { + return ; + } + }); + var instance = ; + instance = ReactTestUtils.renderIntoDocument(instance); + }); + + it('should include the mixin keys in even if their values are falsy', + function() { + var mixin = { + keyWithNullValue: null, + randomCounter: 0 + }; + + var Component = React.createClass({ + mixins: [mixin], + componentDidMount: function() { + expect(this.randomCounter).toBe(0); + expect(this.keyWithNullValue).toBeNull(); + }, + render: function() { + return ; + } + }); + var instance = ; + instance = ReactTestUtils.renderIntoDocument(instance); + }); + + it('should work with a null getInitialState return value and a mixin', () => { + var Component; + var instance; + + var Mixin = { + getInitialState: function() { + return {foo: 'bar'}; + } + }; + Component = React.createClass({ + mixins: [Mixin], + getInitialState: function() { + return null; + }, + render: function() { + return ; + } + }); + expect( + () => ReactTestUtils.renderIntoDocument() + ).not.toThrow(); + + instance = ; + instance = ReactTestUtils.renderIntoDocument(instance); + expect(instance.state).toEqual({foo: 'bar'}); + + // Also the other way round should work + var Mixin2 = { + getInitialState: function() { + return null; + } + }; + Component = React.createClass({ + mixins: [Mixin2], + getInitialState: function() { + return {foo: 'bar'}; + }, + render: function() { + return ; + } + }); + expect( + () => ReactTestUtils.renderIntoDocument() + ).not.toThrow(); + + instance = ; + instance = ReactTestUtils.renderIntoDocument(instance); + expect(instance.state).toEqual({foo: 'bar'}); + + // Multiple mixins should be fine too + Component = React.createClass({ + mixins: [Mixin, Mixin2], + getInitialState: function() { + return {x: true}; + }, + render: function() { + return ; + } + }); + expect( + () => ReactTestUtils.renderIntoDocument() + ).not.toThrow(); + + instance = ; + instance = ReactTestUtils.renderIntoDocument(instance); + expect(instance.state).toEqual({foo: 'bar', x: true}); + }); + +}); diff --git a/src/core/ReactElement.js b/src/classic/element/ReactElement.js similarity index 100% rename from src/core/ReactElement.js rename to src/classic/element/ReactElement.js diff --git a/src/core/ReactElementValidator.js b/src/classic/element/ReactElementValidator.js similarity index 100% rename from src/core/ReactElementValidator.js rename to src/classic/element/ReactElementValidator.js diff --git a/src/core/__tests__/ReactElement-test.js b/src/classic/element/__tests__/ReactElement-test.js similarity index 87% rename from src/core/__tests__/ReactElement-test.js rename to src/classic/element/__tests__/ReactElement-test.js index c4c348fff107f..0f64f24d8bd2a 100644 --- a/src/core/__tests__/ReactElement-test.js +++ b/src/classic/element/__tests__/ReactElement-test.js @@ -11,6 +11,8 @@ "use strict"; +var mocks; + var React; var ReactElement; var ReactTestUtils; @@ -21,6 +23,8 @@ describe('ReactElement', function() { beforeEach(function() { require('mock-modules').dumpCache(); + mocks = require('mocks'); + React = require('React'); ReactElement = require('ReactElement'); ReactTestUtils = require('ReactTestUtils'); @@ -370,4 +374,56 @@ describe('ReactElement', function() { expect(element.constructor).toBe(object.constructor); }); + it('should use default prop value when removing a prop', function() { + var Component = React.createClass({ + getDefaultProps: function() { + return {fruit: 'persimmon'}; + }, + render: function() { + return ; + } + }); + + 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() { + 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}; + } + }); + + 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`.' + ); + + console.warn = warn; + }); + }); diff --git a/src/classic/element/__tests__/ReactElementValidator-test.js b/src/classic/element/__tests__/ReactElementValidator-test.js new file mode 100644 index 0000000000000..490dc9182fdc1 --- /dev/null +++ b/src/classic/element/__tests__/ReactElementValidator-test.js @@ -0,0 +1,110 @@ +/** + * 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 React; +var ReactTestUtils; + +describe('ReactElementValidator', function() { + var ComponentClass; + + beforeEach(function() { + require('mock-modules').dumpCache(); + + React = require('React'); + ReactTestUtils = require('ReactTestUtils'); + ComponentClass = React.createClass({ + render: function() { return
; } + }); + }); + + // 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'); + var MyComp = React.createClass({ + propTypes: { + color: React.PropTypes.string + }, + render: function() { + return
My color is {this.color}
; + } + }); + var ParentComp = React.createClass({ + render: function() { + 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('should check default prop values', function() { + spyOn(console, 'warn'); + + var Component = React.createClass({ + propTypes: {prop: React.PropTypes.string.isRequired}, + getDefaultProps: function() { + return {prop: null}; + }, + render: function() { + return {this.props.prop}; + } + }); + + 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'); + + var Component = React.createClass({ + propTypes: { + prop: React.PropTypes.string.isRequired + }, + render: function() { + return {this.props.prop}; + } + }); + + 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); + }); + +}); diff --git a/src/core/ReactPropTypeLocationNames.js b/src/classic/propTypes/ReactPropTypeLocationNames.js similarity index 100% rename from src/core/ReactPropTypeLocationNames.js rename to src/classic/propTypes/ReactPropTypeLocationNames.js diff --git a/src/core/ReactPropTypeLocations.js b/src/classic/propTypes/ReactPropTypeLocations.js similarity index 100% rename from src/core/ReactPropTypeLocations.js rename to src/classic/propTypes/ReactPropTypeLocations.js diff --git a/src/core/ReactPropTypes.js b/src/classic/propTypes/ReactPropTypes.js similarity index 100% rename from src/core/ReactPropTypes.js rename to src/classic/propTypes/ReactPropTypes.js diff --git a/src/core/__tests__/ReactPropTypes-test.js b/src/classic/propTypes/__tests__/ReactPropTypes-test.js similarity index 100% rename from src/core/__tests__/ReactPropTypes-test.js rename to src/classic/propTypes/__tests__/ReactPropTypes-test.js diff --git a/src/core/__tests__/ReactCompositeComponent-test.js b/src/core/__tests__/ReactCompositeComponent-test.js index 299a5ccf5c4ab..101f2218e61f3 100644 --- a/src/core/__tests__/ReactCompositeComponent-test.js +++ b/src/core/__tests__/ReactCompositeComponent-test.js @@ -85,31 +85,6 @@ describe('ReactCompositeComponent', function() { console.warn = warn; }); - 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'); - var MyComp = React.createClass({ - propTypes: { - color: ReactPropTypes.string - }, - render: function() { - return
My color is {this.color}
; - } - }); - var ParentComp = React.createClass({ - render: function() { - 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('should support rendering to different child types over time', function() { var instance = ; instance = ReactTestUtils.renderIntoDocument(instance); @@ -299,152 +274,6 @@ describe('ReactCompositeComponent', function() { expect(inputProps.prop).not.toBeDefined(); }); - it('should use default prop value when removing a prop', function() { - var Component = React.createClass({ - getDefaultProps: function() { - return {fruit: 'persimmon'}; - }, - render: function() { - return ; - } - }); - - 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() { - var Component = React.createClass({ - propTypes: {prop: ReactPropTypes.string.isRequired}, - getDefaultProps: function() { - return {prop: 'testKey'}; - }, - getInitialState: function() { - return {prop: this.props.prop + 'State'}; - }, - render: function() { - return {this.props.prop}; - } - }); - - var instance = ReactTestUtils.renderIntoDocument(); - reactComponentExpect(instance).scalarPropsEqual({prop: 'testKey'}); - reactComponentExpect(instance).scalarStateEqual({prop: '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`.' - ); - }); - - it('should check default prop values', function() { - var Component = React.createClass({ - propTypes: {prop: ReactPropTypes.string.isRequired}, - getDefaultProps: function() { - return {prop: null}; - }, - render: function() { - return {this.props.prop}; - } - }); - - 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`.' - ); - }); - - it('should check declared prop types', function() { - var Component = React.createClass({ - propTypes: { - prop: ReactPropTypes.string.isRequired - }, - render: function() { - return {this.props.prop}; - } - }); - - ReactTestUtils.renderIntoDocument(); - ReactTestUtils.renderIntoDocument(); - - expect(console.warn.mock.calls.length).toBe(2); - expect(console.warn.mock.calls[0][0]).toBe( - 'Warning: Required prop `prop` was not specified in `Component`.' - ); - - expect(console.warn.mock.calls[1][0]).toBe( - 'Warning: Invalid prop `prop` of type `number` supplied to ' + - '`Component`, expected `string`.' - ); - - ReactTestUtils.renderIntoDocument(); - - // Should not error for strings - expect(console.warn.mock.calls.length).toBe(2); - }); - - it('should throw on invalid prop types', function() { - expect(function() { - React.createClass({ - displayName: 'Component', - propTypes: { - prop: null - }, - render: function() { - return {this.props.prop}; - } - }); - }).toThrow( - 'Invariant Violation: Component: prop type `prop` is invalid; ' + - 'it must be a function, usually from React.PropTypes.' - ); - }); - - it('should throw on invalid context types', function() { - expect(function() { - React.createClass({ - displayName: 'Component', - contextTypes: { - prop: null - }, - render: function() { - return {this.props.prop}; - } - }); - }).toThrow( - 'Invariant Violation: Component: context type `prop` is invalid; ' + - 'it must be a function, usually from React.PropTypes.' - ); - }); - - it('should throw on invalid child context types', function() { - expect(function() { - React.createClass({ - displayName: 'Component', - childContextTypes: { - prop: null - }, - render: function() { - return {this.props.prop}; - } - }); - }).toThrow( - 'Invariant Violation: Component: child context type `prop` is invalid; ' + - 'it must be a function, usually from React.PropTypes.' - ); - }); - it('should not allow `forceUpdate` on unmounted components', function() { var container = document.createElement('div'); document.documentElement.appendChild(container); @@ -574,228 +403,6 @@ describe('ReactCompositeComponent', function() { expect(ReactCurrentOwner.current).toBe(null); }); - it('should support mixins with getInitialState()', function() { - var Mixin = { - getInitialState: function() { - return {mixin: true}; - } - }; - var Component = React.createClass({ - mixins: [Mixin], - getInitialState: function() { - return {component: true}; - }, - render: function() { - return ; - } - }); - var instance = ; - instance = ReactTestUtils.renderIntoDocument(instance); - expect(instance.state.component).toBe(true); - expect(instance.state.mixin).toBe(true); - }); - - it('should throw with conflicting getInitialState() methods', function() { - var Mixin = { - getInitialState: function() { - return {x: true}; - } - }; - var Component = React.createClass({ - mixins: [Mixin], - getInitialState: function() { - return {x: true}; - }, - render: function() { - return ; - } - }); - var instance = ; - expect(function() { - instance = ReactTestUtils.renderIntoDocument(instance); - }).toThrow( - 'Invariant Violation: mergeIntoWithNoDuplicateKeys(): ' + - 'Tried to merge two objects with the same key: `x`. This conflict ' + - 'may be due to a mixin; in particular, this may be caused by two ' + - 'getInitialState() or getDefaultProps() methods returning objects ' + - 'with clashing keys.' - ); - }); - - it('should not mutate objects returned by getInitialState()', function() { - var Mixin = { - getInitialState: function() { - return Object.freeze({mixin: true}); - } - }; - var Component = React.createClass({ - mixins: [Mixin], - getInitialState: function() { - return Object.freeze({component: true}); - }, - render: function() { - return ; - } - }); - expect(() => { - ReactTestUtils.renderIntoDocument(); - }).not.toThrow(); - }); - - it('should work with object getInitialState() return values', function() { - var Component = React.createClass({ - getInitialState: function() { - return { - occupation: 'clown' - }; - }, - render: function() { - return ; - } - }); - var instance = ; - instance = ReactTestUtils.renderIntoDocument(instance); - expect(instance.state.occupation).toEqual('clown'); - }); - - it('should throw with non-object getInitialState() return values', function() { - [['an array'], 'a string', 1234].forEach(function(state) { - var Component = React.createClass({ - getInitialState: function() { - return state; - }, - render: function() { - return ; - } - }); - var instance = ; - expect(function() { - instance = ReactTestUtils.renderIntoDocument(instance); - }).toThrow( - 'Invariant Violation: Component.getInitialState(): ' + - 'must return an object or null' - ); - }); - }); - - it('should work with a null getInitialState() return value', function() { - var Component = React.createClass({ - getInitialState: function() { - return null; - }, - render: function() { - return ; - } - }); - expect( - () => ReactTestUtils.renderIntoDocument() - ).not.toThrow(); - }); - - it('should work with a null getInitialState return value and a mixin', () => { - var Component; - var instance; - - var Mixin = { - getInitialState: function() { - return {foo: 'bar'}; - } - }; - Component = React.createClass({ - mixins: [Mixin], - getInitialState: function() { - return null; - }, - render: function() { - return ; - } - }); - expect( - () => ReactTestUtils.renderIntoDocument() - ).not.toThrow(); - - instance = ; - instance = ReactTestUtils.renderIntoDocument(instance); - expect(instance.state).toEqual({foo: 'bar'}); - - // Also the other way round should work - var Mixin2 = { - getInitialState: function() { - return null; - } - }; - Component = React.createClass({ - mixins: [Mixin2], - getInitialState: function() { - return {foo: 'bar'}; - }, - render: function() { - return ; - } - }); - expect( - () => ReactTestUtils.renderIntoDocument() - ).not.toThrow(); - - instance = ; - instance = ReactTestUtils.renderIntoDocument(instance); - expect(instance.state).toEqual({foo: 'bar'}); - - // Multiple mixins should be fine too - Component = React.createClass({ - mixins: [Mixin, Mixin2], - getInitialState: function() { - return {x: true}; - }, - render: function() { - return ; - } - }); - expect( - () => ReactTestUtils.renderIntoDocument() - ).not.toThrow(); - - instance = ; - instance = ReactTestUtils.renderIntoDocument(instance); - expect(instance.state).toEqual({foo: 'bar', x: true}); - }); - - it('should work with object getInitialState() return values', function() { - var Component = React.createClass({ - getInitialState: function() { - return { - occupation: 'clown' - }; - }, - render: function() { - return ; - } - }); - var instance = ; - instance = ReactTestUtils.renderIntoDocument(instance); - expect(instance.state.occupation).toEqual('clown'); - }); - - it('should throw with non-object getInitialState() return values', function() { - [['an array'], 'a string', 1234].forEach(function(state) { - var Component = React.createClass({ - getInitialState: function() { - return state; - }, - render: function() { - return ; - } - }); - var instance = ; - expect(function() { - instance = ReactTestUtils.renderIntoDocument(instance); - }).toThrow( - 'Invariant Violation: Component.getInitialState(): ' + - 'must return an object or null' - ); - }); - }); - it('should call componentWillUnmount before unmounting', function() { var container = document.createElement('div'); var innerUnmounted = false; @@ -866,97 +473,15 @@ describe('ReactCompositeComponent', function() { } }); - it('should warn when mispelling shouldComponentUpdate', function() { - var warn = console.warn; - console.warn = mocks.getMockFunction(); + it('should pass context', function() { + var childInstance = null; + var grandchildInstance = null; - try { - React.createClass({ - componentShouldUpdate: function() { - return false; - }, - render: function() { - return
; - } - }); - expect(console.warn.mock.calls.length).toBe(1); - expect(console.warn.mock.calls[0][0]).toBe( - 'A component has a method called componentShouldUpdate(). Did you ' + - 'mean shouldComponentUpdate()? The name is phrased as a question ' + - 'because the function is expected to return a value.' - ); - - var NamedComponent = React.createClass({ - componentShouldUpdate: function() { - return false; - }, - render: function() { - return
; - } - }); - expect(console.warn.mock.calls.length).toBe(2); - expect(console.warn.mock.calls[1][0]).toBe( - 'NamedComponent has a method called componentShouldUpdate(). Did you ' + - 'mean shouldComponentUpdate()? The name is phrased as a question ' + - 'because the function is expected to return a value.' - ); - - ; // Shut up lint - } finally { - console.warn = warn; - } - }); - - xit('should warn when using deprecated non-static spec keys', function() { - var warn = console.warn; - console.warn = mocks.getMockFunction(); - try { - React.createClass({ - mixins: [{}], - propTypes: { - foo: ReactPropTypes.string - }, - contextTypes: { - foo: ReactPropTypes.string - }, - childContextTypes: { - foo: ReactPropTypes.string - }, - render: function() { - return
; - } - }); - expect(console.warn.mock.calls.length).toBe(4); - expect(console.warn.mock.calls[0][0]).toBe( - 'createClass(...): `mixins` is now a static property and should ' + - 'be defined inside "statics".' - ); - expect(console.warn.mock.calls[1][0]).toBe( - 'createClass(...): `propTypes` is now a static property and should ' + - 'be defined inside "statics".' - ); - expect(console.warn.mock.calls[2][0]).toBe( - 'createClass(...): `contextTypes` is now a static property and ' + - 'should be defined inside "statics".' - ); - expect(console.warn.mock.calls[3][0]).toBe( - 'createClass(...): `childContextTypes` is now a static property and ' + - 'should be defined inside "statics".' - ); - } finally { - console.warn = warn; - } - }); - - it('should pass context', function() { - var childInstance = null; - var grandchildInstance = null; - - var Parent = React.createClass({ - childContextTypes: { - foo: ReactPropTypes.string, - depth: ReactPropTypes.number - }, + var Parent = React.createClass({ + childContextTypes: { + foo: ReactPropTypes.string, + depth: ReactPropTypes.number + }, getChildContext: function() { return { @@ -1074,413 +599,6 @@ describe('ReactCompositeComponent', function() { }); - it('should check context types', function() { - var Component = React.createClass({ - contextTypes: { - foo: ReactPropTypes.string.isRequired - }, - - render: function() { - return
; - } - }); - - ReactTestUtils.renderIntoDocument(); - - expect(console.warn.mock.calls.length).toBe(1); - expect(console.warn.mock.calls[0][0]).toBe( - 'Warning: Required context `foo` was not specified in `Component`.' - ); - - var ComponentInFooStringContext = React.createClass({ - childContextTypes: { - foo: ReactPropTypes.string - }, - - getChildContext: function() { - return { - foo: this.props.fooValue - }; - }, - - render: function() { - return ; - } - }); - - ReactTestUtils.renderIntoDocument(); - - // Previous call should not error - expect(console.warn.mock.calls.length).toBe(1); - - var ComponentInFooNumberContext = React.createClass({ - childContextTypes: { - foo: ReactPropTypes.number - }, - - getChildContext: function() { - return { - foo: this.props.fooValue - }; - }, - - render: function() { - return ; - } - }); - - ReactTestUtils.renderIntoDocument(); - - expect(console.warn.mock.calls.length).toBe(2); - expect(console.warn.mock.calls[1][0]).toBe( - 'Warning: Invalid context `foo` of type `number` supplied ' + - 'to `Component`, expected `string`.' + - ' Check the render method of `ComponentInFooNumberContext`.' - ); - }); - - it('should check child context types', function() { - var Component = React.createClass({ - childContextTypes: { - foo: ReactPropTypes.string.isRequired, - bar: ReactPropTypes.number - }, - - getChildContext: function() { - return this.props.testContext; - }, - - render: function() { - return
; - } - }); - - ReactTestUtils.renderIntoDocument(); - expect(console.warn.mock.calls.length).toBe(2); - expect(console.warn.mock.calls[0][0]).toBe( - 'Warning: Required child context `foo` was not specified in `Component`.' - ); - expect(console.warn.mock.calls[1][0]).toBe( - 'Warning: Required child context `foo` was not specified in `Component`.' - ); - - ReactTestUtils.renderIntoDocument(); - - expect(console.warn.mock.calls.length).toBe(4); - expect(console.warn.mock.calls[3][0]).toBe( - 'Warning: Invalid child context `foo` of type `number` ' + - 'supplied to `Component`, expected `string`.' - ); - - ReactTestUtils.renderIntoDocument( - - ); - - ReactTestUtils.renderIntoDocument( - - ); - - // Previous calls should not log errors - expect(console.warn.mock.calls.length).toBe(4); - }); - - it('should filter out context not in contextTypes', function() { - var Component = React.createClass({ - contextTypes: { - foo: ReactPropTypes.string - }, - - render: function() { - return
; - } - }); - - var ComponentInFooBarContext = React.createClass({ - childContextTypes: { - foo: ReactPropTypes.string, - bar: ReactPropTypes.number - }, - - getChildContext: function() { - return { - foo: 'abc', - bar: 123 - }; - }, - - render: function() { - return ; - } - }); - - var instance = ReactTestUtils.renderIntoDocument(); - reactComponentExpect(instance).expectRenderedChild().scalarContextEqual({foo: 'abc'}); - }); - - it('should filter context properly in callbacks', function() { - var actualComponentWillReceiveProps; - var actualShouldComponentUpdate; - var actualComponentWillUpdate; - var actualComponentDidUpdate; - - var Parent = React.createClass({ - childContextTypes: { - foo: ReactPropTypes.string.isRequired, - bar: ReactPropTypes.string.isRequired - }, - - getChildContext: function() { - return { - foo: this.props.foo, - bar: "bar" - }; - }, - - render: function() { - return ; - } - }); - - var Component = React.createClass({ - contextTypes: { - foo: ReactPropTypes.string - }, - - componentWillReceiveProps: function(nextProps, nextContext) { - actualComponentWillReceiveProps = nextContext; - return true; - }, - - shouldComponentUpdate: function(nextProps, nextState, nextContext) { - actualShouldComponentUpdate = nextContext; - return true; - }, - - componentWillUpdate: function(nextProps, nextState, nextContext) { - actualComponentWillUpdate = nextContext; - }, - - componentDidUpdate: function(prevProps, prevState, prevContext) { - actualComponentDidUpdate = prevContext; - }, - - render: function() { - return
; - } - }); - - var instance = ; - instance = ReactTestUtils.renderIntoDocument(instance); - instance.replaceProps({foo: "def"}); - expect(actualComponentWillReceiveProps).toEqual({foo: 'def'}); - expect(actualShouldComponentUpdate).toEqual({foo: 'def'}); - expect(actualComponentWillUpdate).toEqual({foo: 'def'}); - expect(actualComponentDidUpdate).toEqual({foo: 'abc'}); - }); - - it('should support statics', function() { - var Component = React.createClass({ - statics: { - abc: 'def', - def: 0, - ghi: null, - jkl: 'mno', - pqr: function() { - return this; - } - }, - - render: function() { - return ; - } - }); - var instance = ; - instance = ReactTestUtils.renderIntoDocument(instance); - expect(instance.constructor.abc).toBe('def'); - expect(Component.abc).toBe('def'); - expect(instance.constructor.def).toBe(0); - expect(Component.def).toBe(0); - expect(instance.constructor.ghi).toBe(null); - expect(Component.ghi).toBe(null); - expect(instance.constructor.jkl).toBe('mno'); - expect(Component.jkl).toBe('mno'); - expect(instance.constructor.pqr()).toBe(Component); - expect(Component.pqr()).toBe(Component); - }); - - it('should throw if a reserved property is in statics', function() { - expect(function() { - React.createClass({ - statics: { - getDefaultProps: function() { - return { - foo: 0 - }; - } - }, - - render: function() { - return ; - } - }); - }).toThrow( - 'Invariant Violation: ReactClass: You are attempting to ' + - 'define a reserved property, `getDefaultProps`, that shouldn\'t be on ' + - 'the "statics" key. Define it as an instance property instead; it ' + - 'will still be accessible on the constructor.' - ); - }); - - it('should support statics in mixins', function() { - var Mixin = { - statics: { - foo: 'bar' - } - }; - var Component = React.createClass({ - mixins: [Mixin], - - statics: { - abc: 'def' - }, - - render: function() { - return ; - } - }); - var instance = ; - instance = ReactTestUtils.renderIntoDocument(instance); - expect(instance.constructor.foo).toBe('bar'); - expect(Component.foo).toBe('bar'); - expect(instance.constructor.abc).toBe('def'); - expect(Component.abc).toBe('def'); - }); - - it("should throw if mixins override each others' statics", function() { - expect(function() { - var Mixin = { - statics: { - abc: 'foo' - } - }; - React.createClass({ - mixins: [Mixin], - - statics: { - abc: 'bar' - }, - - render: function() { - return ; - } - }); - }).toThrow( - 'Invariant Violation: ReactClass: You are attempting to ' + - 'define `abc` on your component more than once. This conflict may be ' + - 'due to a mixin.' - ); - }); - - it("should throw if mixins override functions in statics", function() { - expect(function() { - var Mixin = { - statics: { - abc: function() { console.log('foo'); } - } - }; - React.createClass({ - mixins: [Mixin], - - statics: { - abc: function() { console.log('bar'); } - }, - - render: function() { - return ; - } - }); - }).toThrow( - 'Invariant Violation: ReactClass: You are attempting to ' + - 'define `abc` on your component more than once. This conflict may be ' + - 'due to a mixin.' - ); - }); - - it("should throw if the mixin is a React component", function() { - expect(function() { - React.createClass({ - mixins: [
], - - render: function() { - return ; - } - }); - }).toThrow( - 'Invariant Violation: ReactClass: You\'re attempting to ' + - 'use a component as a mixin. Instead, just use a regular object.' - ); - }); - - it("should throw if the mixin is a React component class", function() { - expect(function() { - var Component = React.createClass({ - render: function() { - return ; - } - }); - - React.createClass({ - mixins: [Component], - - render: function() { - return ; - } - }); - }).toThrow( - 'Invariant Violation: ReactClass: You\'re attempting to ' + - 'use a component class as a mixin. Instead, just use a regular object.' - ); - }); - - it('should have bound the mixin methods to the component', function() { - var mixin = { - mixinFunc: function() {return this;} - }; - - var Component = React.createClass({ - mixins: [mixin], - componentDidMount: function() { - expect(this.mixinFunc()).toBe(this); - }, - render: function() { - return ; - } - }); - var instance = ; - instance = ReactTestUtils.renderIntoDocument(instance); - }); - - it('should include the mixin keys in even if their values are falsy', - function() { - var mixin = { - keyWithNullValue: null, - randomCounter: 0 - }; - - var Component = React.createClass({ - mixins: [mixin], - componentDidMount: function() { - expect(this.randomCounter).toBe(0); - expect(this.keyWithNullValue).toBeNull(); - }, - render: function() { - return ; - } - }); - var instance = ; - instance = ReactTestUtils.renderIntoDocument(instance); - }); - it('should disallow nested render calls', function() { spyOn(console, 'warn'); var Inner = React.createClass({ diff --git a/src/core/__tests__/ReactCompositeComponentMixin-test.js b/src/core/__tests__/ReactCompositeComponentMixin-test.js deleted file mode 100644 index f76e657b768a7..0000000000000 --- a/src/core/__tests__/ReactCompositeComponentMixin-test.js +++ /dev/null @@ -1,171 +0,0 @@ -/** - * 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 = require('mocks'); - -var React; -var ReactTestUtils; -var reactComponentExpect; - -var TestComponent; -var TestComponentWithPropTypes; -var TestComponentWithReverseSpec; -var mixinPropValidator; -var componentPropValidator; - -describe('ReactCompositeComponent-mixin', function() { - - beforeEach(function() { - React = require('React'); - ReactTestUtils = require('ReactTestUtils'); - reactComponentExpect = require('reactComponentExpect'); - mixinPropValidator = mocks.getMockFunction(); - componentPropValidator = mocks.getMockFunction(); - - var MixinA = { - propTypes: { - propA: function() {} - }, - componentDidMount: function() { - this.props.listener('MixinA didMount'); - } - }; - - var MixinB = { - mixins: [MixinA], - propTypes: { - propB: function() {} - }, - componentDidMount: function() { - this.props.listener('MixinB didMount'); - } - }; - - var MixinBWithReverseSpec = { - componentDidMount: function() { - this.props.listener('MixinBWithReverseSpec didMount'); - }, - mixins: [MixinA] - }; - - var MixinC = { - statics: { - staticC: function() {} - }, - componentDidMount: function() { - this.props.listener('MixinC didMount'); - } - }; - - var MixinD = { - propTypes: { - value: mixinPropValidator - } - }; - - TestComponent = React.createClass({ - mixins: [MixinB, MixinC, MixinD], - statics: { - staticComponent: function() {} - }, - propTypes: { - propComponent: function() {} - }, - componentDidMount: function() { - this.props.listener('Component didMount'); - }, - render: function() { - return
; - } - }); - - TestComponentWithReverseSpec = React.createClass({ - render: function() { - return
; - }, - componentDidMount: function() { - this.props.listener('Component didMount'); - }, - mixins: [MixinBWithReverseSpec, MixinC, MixinD] - }); - - TestComponentWithPropTypes = React.createClass({ - mixins: [MixinD], - propTypes: { - value: componentPropValidator - }, - render: function() { - return
; - } - }); - }); - - it('should support merging propTypes and statics', function() { - var listener = mocks.getMockFunction(); - var instance = ; - instance = ReactTestUtils.renderIntoDocument(instance); - - var instancePropTypes = instance.constructor.propTypes; - - expect('propA' in instancePropTypes).toBe(true); - expect('propB' in instancePropTypes).toBe(true); - expect('propComponent' in instancePropTypes).toBe(true); - - expect('staticC' in TestComponent).toBe(true); - expect('staticComponent' in TestComponent).toBe(true); - }); - - it('should support chaining delegate functions', function() { - var listener = mocks.getMockFunction(); - var instance = ; - instance = ReactTestUtils.renderIntoDocument(instance); - - expect(listener.mock.calls).toEqual([ - ['MixinA didMount'], - ['MixinB didMount'], - ['MixinC didMount'], - ['Component didMount'] - ]); - }); - - it('should chain functions regardless of spec property order', function() { - var listener = mocks.getMockFunction(); - var instance = ; - instance = ReactTestUtils.renderIntoDocument(instance); - - expect(listener.mock.calls).toEqual([ - ['MixinA didMount'], - ['MixinBWithReverseSpec didMount'], - ['MixinC didMount'], - ['Component didMount'] - ]); - }); - - it('should validate prop types via mixins', function() { - expect(TestComponent.propTypes).toBeDefined(); - expect(TestComponent.propTypes.value) - .toBe(mixinPropValidator); - }); - - it('should override mixin prop types with class prop types', function() { - // Sanity check... - expect(componentPropValidator).toNotBe(mixinPropValidator); - // Actually check... - expect(TestComponentWithPropTypes.propTypes) - .toBeDefined(); - expect(TestComponentWithPropTypes.propTypes.value) - .toNotBe(mixinPropValidator); - expect(TestComponentWithPropTypes.propTypes.value) - .toBe(componentPropValidator); - }); -}); diff --git a/src/core/__tests__/ReactCompositeComponentSpec-test.js b/src/core/__tests__/ReactCompositeComponentSpec-test.js deleted file mode 100644 index 63f5ff277eb33..0000000000000 --- a/src/core/__tests__/ReactCompositeComponentSpec-test.js +++ /dev/null @@ -1,63 +0,0 @@ -/** - * 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 = require('mocks'); - -var React; -var ReactTestUtils; -var reactComponentExpect; - -describe('ReactCompositeComponent-spec', function() { - - beforeEach(function() { - React = require('React'); - ReactTestUtils = require('ReactTestUtils'); - reactComponentExpect = require('reactComponentExpect'); - }); - - it('should throw when `render` is not specified', function() { - expect(function() { - React.createClass({}); - }).toThrow( - 'Invariant Violation: createClass(...): Class specification must ' + - 'implement a `render` method.' - ); - }); - - it('should copy `displayName` onto the Constructor', function() { - var TestComponent = React.createClass({ - render: function() { - return
; - } - }); - - expect(TestComponent.displayName) - .toBe('TestComponent'); - }); - - it('should copy prop types onto the Constructor', function() { - var propValidator = mocks.getMockFunction(); - var TestComponent = React.createClass({ - propTypes: { - value: propValidator - }, - render: function() { - return
; - } - }); - - expect(TestComponent.propTypes).toBeDefined(); - expect(TestComponent.propTypes.value) - .toBe(propValidator); - }); -});