From d9a4fa4f51c6da895e1655f32255cf72c0fe620e Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Tue, 21 Mar 2017 14:57:50 -0700 Subject: [PATCH] Replace createClass with external react-create-class dependency Warns when using React.createClass for the first time. Usages of createClass in tests have been converted to plain JavaScript classes. Tests that rely on replaceState or isMounted use updater.enqueueReplaceState and updater.isMounted. --- mocks/ReactElementTestChild.js | 8 +- mocks/ReactMockedComponentTestComponent.js | 21 +- package.json | 1 + .../ReactComponentWithPureRenderMixin-test.js | 145 --- .../link/__tests__/LinkedStateMixin-test.js | 46 - .../link/__tests__/ReactLinkPropTypes-test.js | 164 ---- .../__tests__/ReactCSSTransitionGroup-test.js | 330 ------- .../__tests__/ReactTransitionGroup-test.js | 321 ------- .../__tests__/ReactContextValidator-test.js | 167 ++-- src/isomorphic/classic/class/ReactClass.js | 875 +----------------- .../classic/class/__tests__/ReactBind-test.js | 166 ---- .../class/__tests__/ReactBindOptout-test.js | 233 ----- .../class/__tests__/ReactClass-test.js | 20 + .../class/__tests__/ReactClassMixin-test.js | 545 ----------- .../element/__tests__/ReactElement-test.js | 194 ++-- .../__tests__/ReactElementClone-test.js | 178 ++-- .../__tests__/ReactElementValidator-test.js | 185 ++-- .../dom/client/__tests__/ReactMount-test.js | 12 +- .../__tests__/BeforeInputEventPlugin-test.js | 8 +- .../__tests__/ReactServerRendering-test.js | 26 - .../__tests__/ReactDOMComponent-test.js | 56 +- .../shared/__tests__/ReactPerf-test.js | 20 +- .../fiber/__tests__/ReactIncremental-test.js | 14 +- .../__tests__/ReactComponentTreeHook-test.js | 11 +- .../__tests__/ReactComponentLifeCycle-test.js | 133 +-- .../__tests__/ReactCompositeComponent-test.js | 96 +- .../ReactCompositeComponentState-test.js | 66 +- .../__tests__/ReactMultiChild-test.js | 50 +- .../__tests__/ReactStateSetters-test.js | 152 --- .../reconciler/__tests__/ReactUpdates-test.js | 78 +- src/test/__tests__/ReactTestUtils-test.js | 10 +- yarn.lock | 29 +- 32 files changed, 628 insertions(+), 3732 deletions(-) delete mode 100644 src/addons/__tests__/ReactComponentWithPureRenderMixin-test.js delete mode 100644 src/addons/link/__tests__/LinkedStateMixin-test.js delete mode 100644 src/addons/link/__tests__/ReactLinkPropTypes-test.js delete mode 100644 src/addons/transitions/__tests__/ReactCSSTransitionGroup-test.js delete mode 100644 src/addons/transitions/__tests__/ReactTransitionGroup-test.js delete mode 100644 src/isomorphic/classic/class/__tests__/ReactBind-test.js delete mode 100644 src/isomorphic/classic/class/__tests__/ReactBindOptout-test.js delete mode 100644 src/isomorphic/classic/class/__tests__/ReactClassMixin-test.js delete mode 100644 src/renderers/shared/stack/reconciler/__tests__/ReactStateSetters-test.js diff --git a/mocks/ReactElementTestChild.js b/mocks/ReactElementTestChild.js index f2c94d5dd688..2e9d07693304 100644 --- a/mocks/ReactElementTestChild.js +++ b/mocks/ReactElementTestChild.js @@ -13,10 +13,10 @@ var React = require('React'); -var Child = React.createClass({ - render: function() { +class Child extends React.Component { + render() { return React.createElement('div'); - }, -}); + } +} module.exports = Child; diff --git a/mocks/ReactMockedComponentTestComponent.js b/mocks/ReactMockedComponentTestComponent.js index 903006487374..285265df1487 100644 --- a/mocks/ReactMockedComponentTestComponent.js +++ b/mocks/ReactMockedComponentTestComponent.js @@ -13,23 +13,18 @@ var React = require('React'); -var ReactMockedComponentTestComponent = React.createClass({ - getDefaultProps: function() { - return {bar: 'baz'}; - }, +class ReactMockedComponentTestComponent extends React.Component { + state = {foo: 'bar'}; - getInitialState: function() { - return {foo: 'bar'}; - }, - - hasCustomMethod: function() { + hasCustomMethod() { return true; - }, + } - render: function() { + render() { return ; - }, + } -}); +} +ReactMockedComponentTestComponent.defaultProps = {bar: 'baz'}; module.exports = ReactMockedComponentTestComponent; diff --git a/package.json b/package.json index e79c5278772e..c4ea09e12a68 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "merge-stream": "^1.0.0", "object-assign": "^4.1.1", "platform": "^1.1.0", + "react-create-class": "15.5.0-alpha.2", "run-sequence": "^1.1.4", "through2": "^2.0.0", "tmp": "~0.0.28", diff --git a/src/addons/__tests__/ReactComponentWithPureRenderMixin-test.js b/src/addons/__tests__/ReactComponentWithPureRenderMixin-test.js deleted file mode 100644 index 2f5c7a36c040..000000000000 --- a/src/addons/__tests__/ReactComponentWithPureRenderMixin-test.js +++ /dev/null @@ -1,145 +0,0 @@ -/** - * Copyright 2015-present, 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 ReactComponentWithPureRenderMixin; -var ReactTestUtils; - -describe('ReactComponentWithPureRenderMixin', () => { - - beforeEach(() => { - React = require('React'); - ReactComponentWithPureRenderMixin = - require('ReactComponentWithPureRenderMixin'); - ReactTestUtils = require('ReactTestUtils'); - }); - - it('provides a default shouldComponentUpdate implementation', () => { - var renderCalls = 0; - class PlasticWrap extends React.Component { - constructor(props, context) { - super(props, context); - this.state = { - color: 'green', - }; - } - - render() { - return ( - - ); - } - } - - var Apple = React.createClass({ - mixins: [ReactComponentWithPureRenderMixin], - - getInitialState: function() { - return { - cut: false, - slices: 1, - }; - }, - - cut: function() { - this.setState({ - cut: true, - slices: 10, - }); - }, - - eatSlice: function() { - this.setState({ - slices: this.state.slices - 1, - }); - }, - - render: function() { - renderCalls++; - return
; - }, - }); - - var instance = ReactTestUtils.renderIntoDocument(); - expect(renderCalls).toBe(1); - - // Do not re-render based on props - instance.setState({color: 'green'}); - expect(renderCalls).toBe(1); - - // Re-render based on props - instance.setState({color: 'red'}); - expect(renderCalls).toBe(2); - - // Re-render base on state - instance.refs.apple.cut(); - expect(renderCalls).toBe(3); - - // No re-render based on state - instance.refs.apple.cut(); - expect(renderCalls).toBe(3); - - // Re-render based on state again - instance.refs.apple.eatSlice(); - expect(renderCalls).toBe(4); - }); - - it('does not do a deep comparison', () => { - function getInitialState() { - return { - foo: [1, 2, 3], - bar: {a: 4, b: 5, c: 6}, - }; - } - - var renderCalls = 0; - var initialSettings = getInitialState(); - - var Component = React.createClass({ - mixins: [ReactComponentWithPureRenderMixin], - - getInitialState: function() { - return initialSettings; - }, - - render: function() { - renderCalls++; - return
; - }, - }); - - var instance = ReactTestUtils.renderIntoDocument(); - expect(renderCalls).toBe(1); - - // Do not re-render if state is equal - var settings = { - foo: initialSettings.foo, - bar: initialSettings.bar, - }; - instance.setState(settings); - expect(renderCalls).toBe(1); - - // Re-render because one field changed - initialSettings.foo = [1, 2, 3]; - instance.setState(initialSettings); - expect(renderCalls).toBe(2); - - // Re-render because the object changed - instance.setState(getInitialState()); - expect(renderCalls).toBe(3); - }); - -}); diff --git a/src/addons/link/__tests__/LinkedStateMixin-test.js b/src/addons/link/__tests__/LinkedStateMixin-test.js deleted file mode 100644 index 1d6fa18aff85..000000000000 --- a/src/addons/link/__tests__/LinkedStateMixin-test.js +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright 2013-present, 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'; - - -describe('LinkedStateMixin', () => { - var LinkedStateMixin; - var React; - var ReactTestUtils; - - beforeEach(() => { - LinkedStateMixin = require('LinkedStateMixin'); - React = require('React'); - ReactTestUtils = require('ReactTestUtils'); - }); - - it('should create a ReactLink for state', () => { - var Component = React.createClass({ - mixins: [LinkedStateMixin], - - getInitialState: function() { - return {value: 'initial value'}; - }, - - render: function() { - return value is {this.state.value}; - }, - }); - var component = ReactTestUtils.renderIntoDocument(); - var link = component.linkState('value'); - expect(component.state.value).toBe('initial value'); - expect(link.value).toBe('initial value'); - link.requestChange('new value'); - expect(component.state.value).toBe('new value'); - expect(component.linkState('value').value).toBe('new value'); - }); -}); diff --git a/src/addons/link/__tests__/ReactLinkPropTypes-test.js b/src/addons/link/__tests__/ReactLinkPropTypes-test.js deleted file mode 100644 index 71406cf77d11..000000000000 --- a/src/addons/link/__tests__/ReactLinkPropTypes-test.js +++ /dev/null @@ -1,164 +0,0 @@ -/** - * Copyright 2013-present, 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 emptyFunction = require('emptyFunction'); -var LinkPropTypes = require('ReactLink').PropTypes; -var React = require('React'); -var ReactPropTypesSecret = require('ReactPropTypesSecret'); - -var invalidMessage = 'Invalid prop `testProp` supplied to `testComponent`.'; -var requiredMessage = 'The prop `testProp` is marked as required in ' + - '`testComponent`, but its value is `undefined`.'; - -function typeCheckFail(declaration, value, message) { - var props = {testProp: value}; - var error = declaration( - props, - 'testProp', - 'testComponent', - 'prop', - null, - ReactPropTypesSecret - ); - expect(error instanceof Error).toBe(true); - expect(error.message).toBe(message); -} - -function typeCheckPass(declaration, value) { - var props = {testProp: value}; - var error = declaration( - props, - 'testProp', - 'testComponent', - 'prop', - null, - ReactPropTypesSecret - ); - expect(error).toBe(null); -} - -describe('ReactLink', () => { - it('should fail if the argument does not implement the Link API', () => { - typeCheckFail( - LinkPropTypes.link(React.PropTypes.any), - {}, - 'The prop `testProp.value` is marked as required in `testComponent`, ' + - 'but its value is `undefined`.' - ); - typeCheckFail( - LinkPropTypes.link(React.PropTypes.any), - {value: 123}, - 'The prop `testProp.requestChange` is marked as required in ' + - '`testComponent`, but its value is `undefined`.' - ); - typeCheckFail( - LinkPropTypes.link(React.PropTypes.any), - {requestChange: emptyFunction}, - 'The prop `testProp.value` is marked as required in `testComponent`, ' + - 'but its value is `undefined`.' - ); - typeCheckFail( - LinkPropTypes.link(React.PropTypes.any), - {value: null, requestChange: null}, - 'The prop `testProp.value` is marked as required in `testComponent`, ' + - 'but its value is `null`.' - ); - }); - - it('should allow valid links even if no type was specified', () => { - typeCheckPass( - LinkPropTypes.link(), - {value: 42, requestChange: emptyFunction} - ); - typeCheckPass( - LinkPropTypes.link(), - {value: {}, requestChange: emptyFunction} - ); - }); - - it('should allow no link to be passed at all', () => { - typeCheckPass( - LinkPropTypes.link(React.PropTypes.string), - undefined - ); - }); - - it('should allow valid links with correct value format', () => { - typeCheckPass( - LinkPropTypes.link(React.PropTypes.any), - {value: 42, requestChange: emptyFunction} - ); - typeCheckPass( - LinkPropTypes.link(React.PropTypes.number), - {value: 42, requestChange: emptyFunction} - ); - typeCheckPass( - LinkPropTypes.link(React.PropTypes.node), - {value: 42, requestChange: emptyFunction} - ); - }); - - it('should fail if the link`s value type does not match', () => { - typeCheckFail( - LinkPropTypes.link(React.PropTypes.string), - {value: 123, requestChange: emptyFunction}, - 'Invalid prop `testProp.value` of type `number` supplied to `testComponent`,' + - ' expected `string`.' - ); - }); - - it('should be implicitly optional and not warn without values', () => { - typeCheckPass(LinkPropTypes.link(), null); - typeCheckPass(LinkPropTypes.link(), undefined); - typeCheckPass(LinkPropTypes.link(React.PropTypes.string), null); - typeCheckPass(LinkPropTypes.link(React.PropTypes.string), undefined); - }); - - it('should warn for missing required values', () => { - var specifiedButIsNullMsg = 'The prop `testProp` is marked as required ' + - 'in `testComponent`, but its value is `null`.'; - typeCheckFail(LinkPropTypes.link().isRequired, null, specifiedButIsNullMsg); - typeCheckFail(LinkPropTypes.link().isRequired, undefined, requiredMessage); - typeCheckFail( - LinkPropTypes.link(React.PropTypes.string).isRequired, - null, - specifiedButIsNullMsg - ); - typeCheckFail( - LinkPropTypes.link(React.PropTypes.string).isRequired, - undefined, - requiredMessage - ); - }); - - it('should be compatible with React.PropTypes.oneOfType', () => { - typeCheckPass( - React.PropTypes.oneOfType([LinkPropTypes.link(React.PropTypes.number)]), - {value: 123, requestChange: emptyFunction} - ); - typeCheckFail( - React.PropTypes.oneOfType([LinkPropTypes.link(React.PropTypes.number)]), - 123, - invalidMessage - ); - typeCheckPass( - LinkPropTypes.link(React.PropTypes.oneOfType([React.PropTypes.number])), - {value: 123, requestChange: emptyFunction} - ); - typeCheckFail( - LinkPropTypes.link(React.PropTypes.oneOfType([React.PropTypes.number])), - {value: 'imastring', requestChange: emptyFunction}, - 'Invalid prop `testProp.value` supplied to `testComponent`.' - ); - }); -}); diff --git a/src/addons/transitions/__tests__/ReactCSSTransitionGroup-test.js b/src/addons/transitions/__tests__/ReactCSSTransitionGroup-test.js deleted file mode 100644 index 492b41e2e79a..000000000000 --- a/src/addons/transitions/__tests__/ReactCSSTransitionGroup-test.js +++ /dev/null @@ -1,330 +0,0 @@ -/** - * Copyright 2013-present, 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 CSSCore = require('CSSCore'); - -var React; -var ReactDOM; -var ReactCSSTransitionGroup; - -// Most of the real functionality is covered in other unit tests, this just -// makes sure we're wired up correctly. -describe('ReactCSSTransitionGroup', () => { - var container; - - beforeEach(() => { - jest.resetModuleRegistry(); - React = require('React'); - ReactDOM = require('ReactDOM'); - ReactCSSTransitionGroup = require('ReactCSSTransitionGroup'); - - container = document.createElement('div'); - spyOn(console, 'error'); - }); - - it('should warn if timeouts aren\'t specified', () => { - ReactDOM.render( - - - , - container - ); - - // Warning about the missing transitionLeaveTimeout prop - expect(console.error.calls.count()).toBe(1); - }); - - it('should not warn if timeouts is zero', () => { - ReactDOM.render( - - - , - container - ); - - expect(console.error.calls.count()).toBe(0); - }); - - it('should clean-up silently after the timeout elapses', () => { - var a = ReactDOM.render( - - - , - container - ); - expect(ReactDOM.findDOMNode(a).childNodes.length).toBe(1); - - setTimeout.mock.calls.length = 0; - - ReactDOM.render( - - - , - container - ); - expect(ReactDOM.findDOMNode(a).childNodes.length).toBe(2); - expect(ReactDOM.findDOMNode(a).childNodes[0].id).toBe('two'); - expect(ReactDOM.findDOMNode(a).childNodes[1].id).toBe('one'); - - // For some reason jst is adding extra setTimeout()s and grunt test isn't, - // so we need to do this disgusting hack. - for (var i = 0; i < setTimeout.mock.calls.length; i++) { - if (setTimeout.mock.calls[i][1] === 200) { - setTimeout.mock.calls[i][0](); - break; - } - } - - // No warnings - expect(console.error.calls.count()).toBe(0); - - // The leaving child has been removed - expect(ReactDOM.findDOMNode(a).childNodes.length).toBe(1); - expect(ReactDOM.findDOMNode(a).childNodes[0].id).toBe('two'); - }); - - it('should keep both sets of DOM nodes around', () => { - var a = ReactDOM.render( - - - , - container - ); - expect(ReactDOM.findDOMNode(a).childNodes.length).toBe(1); - ReactDOM.render( - - - , - container - ); - expect(ReactDOM.findDOMNode(a).childNodes.length).toBe(2); - expect(ReactDOM.findDOMNode(a).childNodes[0].id).toBe('two'); - expect(ReactDOM.findDOMNode(a).childNodes[1].id).toBe('one'); - }); - - it('should switch transitionLeave from false to true', () => { - var a = ReactDOM.render( - - - , - container - ); - expect(ReactDOM.findDOMNode(a).childNodes.length).toBe(1); - ReactDOM.render( - - - , - container - ); - expect(ReactDOM.findDOMNode(a).childNodes.length).toBe(1); - ReactDOM.render( - - - , - container - ); - expect(ReactDOM.findDOMNode(a).childNodes.length).toBe(2); - expect(ReactDOM.findDOMNode(a).childNodes[0].id).toBe('three'); - expect(ReactDOM.findDOMNode(a).childNodes[1].id).toBe('two'); - }); - - it('should work with no children', () => { - ReactDOM.render( - , - container - ); - }); - - it('should work with a null child', () => { - ReactDOM.render( - - {[null]} - , - container - ); - }); - - it('should transition from one to null', () => { - var a = ReactDOM.render( - - - , - container - ); - expect(ReactDOM.findDOMNode(a).childNodes.length).toBe(1); - ReactDOM.render( - - {null} - , - container - ); - // (Here, we expect the original child to stick around but test that no - // exception is thrown) - expect(ReactDOM.findDOMNode(a).childNodes.length).toBe(1); - expect(ReactDOM.findDOMNode(a).childNodes[0].id).toBe('one'); - }); - - it('should transition from false to one', () => { - var a = ReactDOM.render( - - {false} - , - container - ); - expect(ReactDOM.findDOMNode(a).childNodes.length).toBe(0); - ReactDOM.render( - - - , - container - ); - expect(ReactDOM.findDOMNode(a).childNodes.length).toBe(1); - expect(ReactDOM.findDOMNode(a).childNodes[0].id).toBe('one'); - }); - - it('should use transition-type specific names when they\'re provided', () => { - var customTransitionNames = { - enter: 'custom-entering', - leave: 'custom-leaving', - }; - - var a = ReactDOM.render( - - - , - container - ); - expect(ReactDOM.findDOMNode(a).childNodes.length).toBe(1); - - // Add an element - ReactDOM.render( - - - - , - container - ); - expect(ReactDOM.findDOMNode(a).childNodes.length).toBe(2); - - var enteringNode = ReactDOM.findDOMNode(a).childNodes[1]; - expect(CSSCore.hasClass(enteringNode, 'custom-entering')).toBe(true); - - // Remove an element - ReactDOM.render( - - - , - container - ); - expect(ReactDOM.findDOMNode(a).childNodes.length).toBe(2); - - var leavingNode = ReactDOM.findDOMNode(a).childNodes[0]; - expect(CSSCore.hasClass(leavingNode, 'custom-leaving')).toBe(true); - }); - - it('should clear transition timeouts when unmounted', () => { - class Component extends React.Component { - render() { - return ( - - {this.props.children} - - ); - } - } - - ReactDOM.render(, container); - ReactDOM.render(, container); - - ReactDOM.unmountComponentAtNode(container); - - // Testing that no exception is thrown here, as the timeout has been cleared. - jest.runAllTimers(); - }); - - it('should handle unmounted elements properly', () => { - class Child extends React.Component { - render() { - if (!this.props.show) { - return null; - } - return
; - } - } - - class Component extends React.Component { - state = { showChild: true }; - - componentDidMount() { - this.setState({ showChild: false }); - } - - render() { - return ( - - - - ); - } - } - - ReactDOM.render(, container); - - // Testing that no exception is thrown here, as the timeout has been cleared. - jest.runAllTimers(); - }); -}); diff --git a/src/addons/transitions/__tests__/ReactTransitionGroup-test.js b/src/addons/transitions/__tests__/ReactTransitionGroup-test.js deleted file mode 100644 index 25e6dfdcd1f0..000000000000 --- a/src/addons/transitions/__tests__/ReactTransitionGroup-test.js +++ /dev/null @@ -1,321 +0,0 @@ -/** - * Copyright 2013-present, 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 ReactDOM; -var ReactTransitionGroup; - -// Most of the real functionality is covered in other unit tests, this just -// makes sure we're wired up correctly. -describe('ReactTransitionGroup', () => { - var container; - - beforeEach(() => { - React = require('React'); - ReactDOM = require('ReactDOM'); - ReactTransitionGroup = require('ReactTransitionGroup'); - - container = document.createElement('div'); - }); - - - it('should handle willEnter correctly', () => { - var log = []; - - class Child extends React.Component { - componentDidMount() { - log.push('didMount'); - } - - componentWillAppear = (cb) => { - log.push('willAppear'); - cb(); - }; - - componentDidAppear = () => { - log.push('didAppear'); - }; - - componentWillEnter = (cb) => { - log.push('willEnter'); - cb(); - }; - - componentDidEnter = () => { - log.push('didEnter'); - }; - - componentWillLeave = (cb) => { - log.push('willLeave'); - cb(); - }; - - componentDidLeave = () => { - log.push('didLeave'); - }; - - componentWillUnmount() { - log.push('willUnmount'); - } - - render() { - return ; - } - } - - class Component extends React.Component { - state = {count: 1}; - - render() { - var children = []; - for (var i = 0; i < this.state.count; i++) { - children.push(); - } - return {children}; - } - } - - var instance = ReactDOM.render(, container); - expect(log).toEqual(['didMount', 'willAppear', 'didAppear']); - - log = []; - instance.setState({count: 2}, function() { - expect(log).toEqual(['didMount', 'willEnter', 'didEnter']); - - log = []; - instance.setState({count: 1}, function() { - expect(log).toEqual(['willLeave', 'didLeave', 'willUnmount']); - }); - }); - }); - - it('should handle enter/leave/enter/leave correctly', () => { - var log = []; - var willEnterCb; - - class Child extends React.Component { - componentDidMount() { - log.push('didMount'); - } - - componentWillEnter = (cb) => { - log.push('willEnter'); - willEnterCb = cb; - }; - - componentDidEnter = () => { - log.push('didEnter'); - }; - - componentWillLeave = (cb) => { - log.push('willLeave'); - cb(); - }; - - componentDidLeave = () => { - log.push('didLeave'); - }; - - componentWillUnmount() { - log.push('willUnmount'); - } - - render() { - return ; - } - } - - class Component extends React.Component { - state = {count: 1}; - - render() { - var children = []; - for (var i = 0; i < this.state.count; i++) { - children.push(); - } - return {children}; - } - } - - var instance = ReactDOM.render(, container); - expect(log).toEqual(['didMount']); - instance.setState({count: 2}); - expect(log).toEqual(['didMount', 'didMount', 'willEnter']); - for (var k = 0; k < 5; k++) { - instance.setState({count: 2}); - expect(log).toEqual(['didMount', 'didMount', 'willEnter']); - instance.setState({count: 1}); - } - // other animations are blocked until willEnterCb is called - willEnterCb(); - expect(log).toEqual([ - 'didMount', 'didMount', 'willEnter', - 'didEnter', 'willLeave', 'didLeave', 'willUnmount', - ]); - }); - - it('should handle enter/leave/enter correctly', () => { - var log = []; - var willEnterCb; - - class Child extends React.Component { - componentDidMount() { - log.push('didMount'); - } - - componentWillEnter = (cb) => { - log.push('willEnter'); - willEnterCb = cb; - }; - - componentDidEnter = () => { - log.push('didEnter'); - }; - - componentWillLeave = (cb) => { - log.push('willLeave'); - cb(); - }; - - componentDidLeave = () => { - log.push('didLeave'); - }; - - componentWillUnmount() { - log.push('willUnmount'); - } - - render() { - return ; - } - } - - class Component extends React.Component { - state = {count: 1}; - - render() { - var children = []; - for (var i = 0; i < this.state.count; i++) { - children.push(); - } - return {children}; - } - } - - var instance = ReactDOM.render(, container); - expect(log).toEqual(['didMount']); - instance.setState({count: 2}); - expect(log).toEqual(['didMount', 'didMount', 'willEnter']); - for (var k = 0; k < 5; k++) { - instance.setState({count: 1}); - expect(log).toEqual(['didMount', 'didMount', 'willEnter']); - instance.setState({count: 2}); - } - willEnterCb(); - expect(log).toEqual([ - 'didMount', 'didMount', 'willEnter', 'didEnter', - ]); - }); - - it('should handle entering/leaving several elements at once', () => { - var log = []; - - class Child extends React.Component { - componentDidMount() { - log.push('didMount' + this.props.id); - } - - componentWillEnter = (cb) => { - log.push('willEnter' + this.props.id); - cb(); - }; - - componentDidEnter = () => { - log.push('didEnter' + this.props.id); - }; - - componentWillLeave = (cb) => { - log.push('willLeave' + this.props.id); - cb(); - }; - - componentDidLeave = () => { - log.push('didLeave' + this.props.id); - }; - - componentWillUnmount() { - log.push('willUnmount' + this.props.id); - } - - render() { - return ; - } - } - - class Component extends React.Component { - state = {count: 1}; - - render() { - var children = []; - for (var i = 0; i < this.state.count; i++) { - children.push(); - } - return {children}; - } - } - - var instance = ReactDOM.render(, container); - expect(log).toEqual(['didMount0']); - log = []; - - instance.setState({count: 3}); - expect(log).toEqual([ - 'didMount1', 'didMount2', 'willEnter1', 'didEnter1', - 'willEnter2', 'didEnter2', - ]); - log = []; - - instance.setState({count: 0}); - expect(log).toEqual([ - 'willLeave0', 'didLeave0', 'willLeave1', 'didLeave1', - 'willLeave2', 'didLeave2', 'willUnmount0', 'willUnmount1', 'willUnmount2', - ]); - }); - - it('should warn for duplicated keys', () => { - spyOn(console, 'error'); - - class Component extends React.Component { - render() { - var children = [
,
]; - return {children}; - } - } - - ReactDOM.render(, container); - - expect(console.error.calls.count()).toBe(2); - expect(console.error.calls.argsFor(0)[0]).toBe( - 'Warning: flattenChildren(...): ' + - 'Encountered two children with the same key, `1`. ' + - 'Child keys must be unique; when two children share a key, ' + - 'only the first child will be used.' - ); - expect(console.error.calls.argsFor(1)[0]).toBe( - 'Warning: flattenChildren(...): ' + - 'Encountered two children with the same key, `1`. ' + - 'Child keys must be unique; when two children share a key, ' + - 'only the first child will be used.' - ); - }); -}); diff --git a/src/isomorphic/classic/__tests__/ReactContextValidator-test.js b/src/isomorphic/classic/__tests__/ReactContextValidator-test.js index bda23752f712..a02b9882252c 100644 --- a/src/isomorphic/classic/__tests__/ReactContextValidator-test.js +++ b/src/isomorphic/classic/__tests__/ReactContextValidator-test.js @@ -41,33 +41,31 @@ describe('ReactContextValidator', () => { // ensure that this is not required for ES6 classes with Flow. it('should filter out context not in contextTypes', () => { - var Component = React.createClass({ - contextTypes: { - foo: React.PropTypes.string, - }, - - render: function() { + class Component extends React.Component { + render() { return
; - }, - }); - - var ComponentInFooBarContext = React.createClass({ - childContextTypes: { - foo: React.PropTypes.string, - bar: React.PropTypes.number, - }, - - getChildContext: function() { + } + } + Component.contextTypes = { + foo: React.PropTypes.string, + }; + + class ComponentInFooBarContext extends React.Component { + getChildContext() { return { foo: 'abc', bar: 123, }; - }, + } - render: function() { + render() { return ; - }, - }); + } + } + ComponentInFooBarContext.childContextTypes = { + foo: React.PropTypes.string, + bar: React.PropTypes.number, + }; var instance = ReactTestUtils.renderIntoDocument(); reactComponentExpect(instance).expectRenderedChild().scalarContextEqual({foo: 'abc'}); @@ -79,51 +77,50 @@ describe('ReactContextValidator', () => { var actualComponentWillUpdate; var actualComponentDidUpdate; - var Parent = React.createClass({ - childContextTypes: { - foo: React.PropTypes.string.isRequired, - bar: React.PropTypes.string.isRequired, - }, - - getChildContext: function() { + class Parent extends React.Component { + getChildContext() { return { foo: this.props.foo, bar: 'bar', }; - }, + } - render: function() { + render() { return ; - }, - }); - - var Component = React.createClass({ - contextTypes: { - foo: React.PropTypes.string, - }, - - componentWillReceiveProps: function(nextProps, nextContext) { + } + } + Parent.childContextTypes = { + foo: React.PropTypes.string.isRequired, + bar: React.PropTypes.string.isRequired, + }; + + class Component extends React.Component { + componentWillReceiveProps(nextProps, nextContext) { actualComponentWillReceiveProps = nextContext; return true; - }, + } - shouldComponentUpdate: function(nextProps, nextState, nextContext) { + shouldComponentUpdate(nextProps, nextState, nextContext) { actualShouldComponentUpdate = nextContext; return true; - }, + } - componentWillUpdate: function(nextProps, nextState, nextContext) { + componentWillUpdate(nextProps, nextState, nextContext) { actualComponentWillUpdate = nextContext; - }, + } - componentDidUpdate: function(prevProps, prevState, prevContext) { + componentDidUpdate(prevProps, prevState, prevContext) { actualComponentDidUpdate = prevContext; - }, + } - render: function() { + render() { return
; - }, - }); + } + } + Component.contextTypes = { + foo: React.PropTypes.string, + }; + var container = document.createElement('div'); ReactDOM.render(, container); @@ -137,15 +134,14 @@ describe('ReactContextValidator', () => { it('should check context types', () => { spyOn(console, 'error'); - var Component = React.createClass({ - contextTypes: { - foo: React.PropTypes.string.isRequired, - }, - - render: function() { + class Component extends React.Component { + render() { return
; - }, - }); + } + } + Component.contextTypes = { + foo: React.PropTypes.string.isRequired, + }; ReactTestUtils.renderIntoDocument(); @@ -157,21 +153,20 @@ describe('ReactContextValidator', () => { ' in Component (at **)' ); - var ComponentInFooStringContext = React.createClass({ - childContextTypes: { - foo: React.PropTypes.string, - }, - - getChildContext: function() { + class ComponentInFooStringContext extends React.Component { + getChildContext() { return { foo: this.props.fooValue, }; - }, + } - render: function() { + render() { return ; - }, - }); + } + } + ComponentInFooStringContext.childContextTypes = { + foo: React.PropTypes.string, + }; ReactTestUtils.renderIntoDocument( @@ -180,21 +175,20 @@ describe('ReactContextValidator', () => { // Previous call should not error expect(console.error.calls.count()).toBe(1); - var ComponentInFooNumberContext = React.createClass({ - childContextTypes: { - foo: React.PropTypes.number, - }, - - getChildContext: function() { + class ComponentInFooNumberContext extends React.Component { + getChildContext() { return { foo: this.props.fooValue, }; - }, + } - render: function() { + render() { return ; - }, - }); + } + } + ComponentInFooNumberContext.childContextTypes = { + foo: React.PropTypes.number, + }; ReactTestUtils.renderIntoDocument(); @@ -211,20 +205,19 @@ describe('ReactContextValidator', () => { it('should check child context types', () => { spyOn(console, 'error'); - var Component = React.createClass({ - childContextTypes: { - foo: React.PropTypes.string.isRequired, - bar: React.PropTypes.number, - }, - - getChildContext: function() { + class Component extends React.Component { + getChildContext() { return this.props.testContext; - }, + } - render: function() { + render() { return
; - }, - }); + } + } + Component.childContextTypes = { + foo: React.PropTypes.string.isRequired, + bar: React.PropTypes.number, + }; ReactTestUtils.renderIntoDocument(); expect(console.error.calls.count()).toBe(1); diff --git a/src/isomorphic/classic/class/ReactClass.js b/src/isomorphic/classic/class/ReactClass.js index a387299fcc53..7bc995f5506d 100644 --- a/src/isomorphic/classic/class/ReactClass.js +++ b/src/isomorphic/classic/class/ReactClass.js @@ -13,877 +13,32 @@ var ReactComponent = require('ReactComponent'); var ReactElement = require('ReactElement'); -var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames'); var ReactNoopUpdateQueue = require('ReactNoopUpdateQueue'); -var emptyObject = require('emptyObject'); -var invariant = require('invariant'); var warning = require('warning'); -import type { ReactPropTypeLocations } from 'ReactPropTypeLocations'; +var factory = require('react-create-class/factory'); -var MIXINS_KEY = 'mixins'; - -// Helper function to allow the creation of anonymous functions which do not -// have .name set to the name of the variable being assigned to. -function identity(fn) { - return fn; -} - -/** - * Policies that describe methods in `ReactClassInterface`. - */ -type SpecPolicy = - /** - * These methods may be defined only once by the class specification or mixin. - */ - 'DEFINE_ONCE' | - /** - * These methods may be defined by both the class specification and mixins. - * Subsequent definitions will be chained. These methods must return void. - */ - 'DEFINE_MANY' | - /** - * These methods are overriding the base class. - */ - 'OVERRIDE_BASE' | - /** - * These methods are similar to DEFINE_MANY, except we assume they return - * objects. We try to merge the keys of the return values of all the mixed in - * functions. If there is a key conflict we throw. - */ - 'DEFINE_MANY_MERGED'; - - -var injectedMixins = []; - -/** - * Composite components are higher-level components that compose other composite - * or host components. - * - * To create a new type of `ReactClass`, pass a specification of - * your new class to `React.createClass`. The only requirement of your class - * specification is that you implement a `render` method. - * - * var MyComponent = React.createClass({ - * render: function() { - * return
Hello World
; - * } - * }); - * - * The class specification supports a specific protocol of methods that have - * special meaning (e.g. `render`). See `ReactClassInterface` for - * more the comprehensive protocol. Any other properties and methods in the - * class specification will be available on the prototype. - * - * @interface ReactClassInterface - * @internal - */ -var ReactClassInterface: {[key: string]: SpecPolicy} = { - - /** - * An array of Mixin objects to include when defining your component. - * - * @type {array} - * @optional - */ - mixins: 'DEFINE_MANY', - - /** - * An object containing properties and methods that should be defined on - * the component's constructor instead of its prototype (static methods). - * - * @type {object} - * @optional - */ - statics: 'DEFINE_MANY', - - /** - * Definition of prop types for this component. - * - * @type {object} - * @optional - */ - propTypes: 'DEFINE_MANY', - - /** - * Definition of context types for this component. - * - * @type {object} - * @optional - */ - contextTypes: 'DEFINE_MANY', - - /** - * Definition of context types this component sets for its children. - * - * @type {object} - * @optional - */ - childContextTypes: 'DEFINE_MANY', - - // ==== Definition methods ==== - - /** - * Invoked when the component is mounted. Values in the mapping will be set on - * `this.props` if that prop is not specified (i.e. using an `in` check). - * - * This method is invoked before `getInitialState` and therefore cannot rely - * on `this.state` or use `this.setState`. - * - * @return {object} - * @optional - */ - getDefaultProps: 'DEFINE_MANY_MERGED', - - /** - * Invoked once before the component is mounted. The return value will be used - * as the initial value of `this.state`. - * - * getInitialState: function() { - * return { - * isOn: false, - * fooBaz: new BazFoo() - * } - * } - * - * @return {object} - * @optional - */ - getInitialState: 'DEFINE_MANY_MERGED', - - /** - * @return {object} - * @optional - */ - getChildContext: 'DEFINE_MANY_MERGED', - - /** - * Uses props from `this.props` and state from `this.state` to render the - * structure of the component. - * - * No guarantees are made about when or how often this method is invoked, so - * it must not have side effects. - * - * render: function() { - * var name = this.props.name; - * return
Hello, {name}!
; - * } - * - * @return {ReactComponent} - * @nosideeffects - * @required - */ - render: 'DEFINE_ONCE', - - - - // ==== Delegate methods ==== - - /** - * Invoked when the component is initially created and about to be mounted. - * This may have side effects, but any external subscriptions or data created - * by this method must be cleaned up in `componentWillUnmount`. - * - * @optional - */ - componentWillMount: 'DEFINE_MANY', - - /** - * Invoked when the component has been mounted and has a DOM representation. - * However, there is no guarantee that the DOM node is in the document. - * - * Use this as an opportunity to operate on the DOM when the component has - * been mounted (initialized and rendered) for the first time. - * - * @param {DOMElement} rootNode DOM element representing the component. - * @optional - */ - componentDidMount: 'DEFINE_MANY', - - /** - * Invoked before the component receives new props. - * - * Use this as an opportunity to react to a prop transition by updating the - * state using `this.setState`. Current props are accessed via `this.props`. - * - * componentWillReceiveProps: function(nextProps, nextContext) { - * this.setState({ - * likesIncreasing: nextProps.likeCount > this.props.likeCount - * }); - * } - * - * NOTE: There is no equivalent `componentWillReceiveState`. An incoming prop - * transition may cause a state change, but the opposite is not true. If you - * need it, you are probably looking for `componentWillUpdate`. - * - * @param {object} nextProps - * @optional - */ - componentWillReceiveProps: 'DEFINE_MANY', - - /** - * Invoked while deciding if the component should be updated as a result of - * receiving new props, state and/or context. - * - * Use this as an opportunity to `return false` when you're certain that the - * transition to the new props/state/context will not require a component - * update. - * - * shouldComponentUpdate: function(nextProps, nextState, nextContext) { - * return !equal(nextProps, this.props) || - * !equal(nextState, this.state) || - * !equal(nextContext, this.context); - * } - * - * @param {object} nextProps - * @param {?object} nextState - * @param {?object} nextContext - * @return {boolean} True if the component should update. - * @optional - */ - shouldComponentUpdate: 'DEFINE_ONCE', - - /** - * Invoked when the component is about to update due to a transition from - * `this.props`, `this.state` and `this.context` to `nextProps`, `nextState` - * and `nextContext`. - * - * Use this as an opportunity to perform preparation before an update occurs. - * - * NOTE: You **cannot** use `this.setState()` in this method. - * - * @param {object} nextProps - * @param {?object} nextState - * @param {?object} nextContext - * @param {ReactReconcileTransaction} transaction - * @optional - */ - componentWillUpdate: 'DEFINE_MANY', - - /** - * Invoked when the component's DOM representation has been updated. - * - * Use this as an opportunity to operate on the DOM when the component has - * been updated. - * - * @param {object} prevProps - * @param {?object} prevState - * @param {?object} prevContext - * @param {DOMElement} rootNode DOM element representing the component. - * @optional - */ - componentDidUpdate: 'DEFINE_MANY', - - /** - * Invoked when the component is about to be removed from its parent and have - * its DOM representation destroyed. - * - * Use this as an opportunity to deallocate any external resources. - * - * NOTE: There is no `componentDidUnmount` since your component will have been - * destroyed by that point. - * - * @optional - */ - componentWillUnmount: 'DEFINE_MANY', - - - - // ==== Advanced methods ==== - - /** - * Updates the component's currently mounted DOM representation. - * - * By default, this implements React's rendering and reconciliation algorithm. - * Sophisticated clients may wish to override this. - * - * @param {ReactReconcileTransaction} transaction - * @internal - * @overridable - */ - updateComponent: 'OVERRIDE_BASE', - -}; - -/** - * Mapping from class specification keys to special processing functions. - * - * Although these are declared like instance properties in the specification - * when defining classes using `React.createClass`, they are actually static - * and are accessible on the constructor instead of the prototype. Despite - * being static, they must be defined outside of the "statics" key under - * which all other static methods are defined. - */ -var RESERVED_SPEC_KEYS = { - displayName: function(Constructor, displayName) { - Constructor.displayName = displayName; - }, - mixins: function(Constructor, mixins) { - if (mixins) { - for (var i = 0; i < mixins.length; i++) { - mixSpecIntoComponent(Constructor, mixins[i]); - } - } - }, - childContextTypes: function(Constructor, childContextTypes) { - if (__DEV__) { - validateTypeDef( - Constructor, - childContextTypes, - 'childContext' - ); - } - Constructor.childContextTypes = Object.assign( - {}, - Constructor.childContextTypes, - childContextTypes - ); - }, - contextTypes: function(Constructor, contextTypes) { - if (__DEV__) { - validateTypeDef( - Constructor, - contextTypes, - 'context' - ); - } - Constructor.contextTypes = Object.assign( - {}, - Constructor.contextTypes, - contextTypes - ); - }, - /** - * Special case getDefaultProps which should move into statics but requires - * automatic merging. - */ - getDefaultProps: function(Constructor, getDefaultProps) { - if (Constructor.getDefaultProps) { - Constructor.getDefaultProps = createMergedResultFunction( - Constructor.getDefaultProps, - getDefaultProps - ); - } else { - Constructor.getDefaultProps = getDefaultProps; - } - }, - propTypes: function(Constructor, propTypes) { - if (__DEV__) { - validateTypeDef( - Constructor, - propTypes, - 'prop' - ); - } - Constructor.propTypes = Object.assign( - {}, - Constructor.propTypes, - propTypes - ); - }, - statics: function(Constructor, statics) { - mixStaticSpecIntoComponent(Constructor, statics); - }, - autobind: function() {}, // noop -}; - -function validateTypeDef( - Constructor, - typeDef, - location: ReactPropTypeLocations, -) { - for (var propName in typeDef) { - if (typeDef.hasOwnProperty(propName)) { - // use a warning instead of an invariant so components - // don't show up in prod but only in __DEV__ - warning( - typeof typeDef[propName] === 'function', - '%s: %s type `%s` is invalid; it must be a function, usually from ' + - 'React.PropTypes.', - Constructor.displayName || 'ReactClass', - ReactPropTypeLocationNames[location], - propName - ); - } - } -} - -function validateMethodOverride(isAlreadyDefined, name) { - var specPolicy = ReactClassInterface.hasOwnProperty(name) ? - ReactClassInterface[name] : - null; - - // Disallow overriding of base class methods unless explicitly allowed. - if (ReactClassMixin.hasOwnProperty(name)) { - invariant( - specPolicy === 'OVERRIDE_BASE', - 'ReactClassInterface: You are attempting to override ' + - '`%s` from your class specification. Ensure that your method names ' + - 'do not overlap with React methods.', - name - ); - } - - // Disallow defining methods more than once unless explicitly allowed. - if (isAlreadyDefined) { - invariant( - specPolicy === 'DEFINE_MANY' || - specPolicy === 'DEFINE_MANY_MERGED', - 'ReactClassInterface: You are attempting to define ' + - '`%s` on your component more than once. This conflict may be due ' + - 'to a mixin.', - name - ); - } -} - -/** - * Mixin helper which handles policy validation and reserved - * specification keys when building React classes. - */ -function mixSpecIntoComponent(Constructor, spec) { - if (!spec) { - if (__DEV__) { - var typeofSpec = typeof spec; - var isMixinValid = typeofSpec === 'object' && spec !== null; - - warning( - isMixinValid, - '%s: You\'re attempting to include a mixin that is either null ' + - 'or not an object. Check the mixins included by the component, ' + - 'as well as any mixins they include themselves. ' + - 'Expected object but got %s.', - Constructor.displayName || 'ReactClass', - spec === null ? null : typeofSpec - ); - } - - return; - } - - invariant( - typeof spec !== 'function', - 'ReactClass: You\'re attempting to ' + - 'use a component class or function as a mixin. Instead, just use a ' + - 'regular object.' - ); - invariant( - !ReactElement.isValidElement(spec), - 'ReactClass: You\'re attempting to ' + - 'use a component as a mixin. Instead, just use a regular object.' - ); - - var proto = Constructor.prototype; - var autoBindPairs = proto.__reactAutoBindPairs; - - // By handling mixins before any other properties, we ensure the same - // chaining order is applied to methods with DEFINE_MANY policy, whether - // mixins are listed before or after these methods in the spec. - if (spec.hasOwnProperty(MIXINS_KEY)) { - RESERVED_SPEC_KEYS.mixins(Constructor, spec.mixins); - } - - for (var name in spec) { - if (!spec.hasOwnProperty(name)) { - continue; - } - - if (name === MIXINS_KEY) { - // We have already handled mixins in a special case above. - continue; - } - - var property = spec[name]; - var isAlreadyDefined = proto.hasOwnProperty(name); - validateMethodOverride(isAlreadyDefined, name); - - if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) { - RESERVED_SPEC_KEYS[name](Constructor, property); - } else { - // Setup methods on prototype: - // The following member methods should not be automatically bound: - // 1. Expected ReactClass methods (in the "interface"). - // 2. Overridden methods (that were mixed in). - var isReactClassMethod = - ReactClassInterface.hasOwnProperty(name); - var isFunction = typeof property === 'function'; - var shouldAutoBind = - isFunction && - !isReactClassMethod && - !isAlreadyDefined && - spec.autobind !== false; - - if (shouldAutoBind) { - autoBindPairs.push(name, property); - proto[name] = property; - } else { - if (isAlreadyDefined) { - var specPolicy = ReactClassInterface[name]; - - // These cases should already be caught by validateMethodOverride. - invariant( - isReactClassMethod && ( - specPolicy === 'DEFINE_MANY_MERGED' || - specPolicy === 'DEFINE_MANY' - ), - 'ReactClass: Unexpected spec policy %s for key %s ' + - 'when mixing in component specs.', - specPolicy, - name - ); - - // For methods which are defined more than once, call the existing - // methods before calling the new property, merging if appropriate. - if (specPolicy === 'DEFINE_MANY_MERGED') { - proto[name] = createMergedResultFunction(proto[name], property); - } else if (specPolicy === 'DEFINE_MANY') { - proto[name] = createChainedFunction(proto[name], property); - } - } else { - proto[name] = property; - if (__DEV__) { - // Add verbose displayName to the function, which helps when looking - // at profiling tools. - if (typeof property === 'function' && spec.displayName) { - proto[name].displayName = spec.displayName + '_' + name; - } - } - } - } - } - } -} - -function mixStaticSpecIntoComponent(Constructor, statics) { - if (!statics) { - return; - } - for (var name in statics) { - var property = statics[name]; - if (!statics.hasOwnProperty(name)) { - continue; - } - - var isReserved = name in RESERVED_SPEC_KEYS; - invariant( - !isReserved, - 'ReactClass: You are attempting to define a reserved ' + - 'property, `%s`, that shouldn\'t be on the "statics" key. Define it ' + - 'as an instance property instead; it will still be accessible on the ' + - 'constructor.', - name - ); - - var isInherited = name in Constructor; - invariant( - !isInherited, - 'ReactClass: You are attempting to define ' + - '`%s` on your component more than once. This conflict may be ' + - 'due to a mixin.', - name - ); - Constructor[name] = property; - } -} - -/** - * Merge two objects, but throw if both contain the same key. - * - * @param {object} one The first object, which is mutated. - * @param {object} two The second object - * @return {object} one after it has been mutated to contain everything in two. - */ -function mergeIntoWithNoDuplicateKeys(one, two) { - invariant( - one && two && typeof one === 'object' && typeof two === 'object', - 'mergeIntoWithNoDuplicateKeys(): Cannot merge non-objects.' - ); - - for (var key in two) { - if (two.hasOwnProperty(key)) { - invariant( - one[key] === undefined, - 'mergeIntoWithNoDuplicateKeys(): ' + - 'Tried to merge two objects with the same key: `%s`. 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.', - key - ); - one[key] = two[key]; - } - } - return one; -} - -/** - * Creates a function that invokes two functions and merges their return values. - * - * @param {function} one Function to invoke first. - * @param {function} two Function to invoke second. - * @return {function} Function that invokes the two argument functions. - * @private - */ -function createMergedResultFunction(one, two) { - return function mergedResult() { - var a = one.apply(this, arguments); - var b = two.apply(this, arguments); - if (a == null) { - return b; - } else if (b == null) { - return a; - } - var c = {}; - mergeIntoWithNoDuplicateKeys(c, a); - mergeIntoWithNoDuplicateKeys(c, b); - return c; - }; -} - -/** - * Creates a function that invokes two functions and ignores their return vales. - * - * @param {function} one Function to invoke first. - * @param {function} two Function to invoke second. - * @return {function} Function that invokes the two argument functions. - * @private - */ -function createChainedFunction(one, two) { - return function chainedFunction() { - one.apply(this, arguments); - two.apply(this, arguments); - }; -} - -/** - * Binds a method to the component. - * - * @param {object} component Component whose method is going to be bound. - * @param {function} method Method to be bound. - * @return {function} The bound method. - */ -function bindAutoBindMethod(component, method) { - var boundMethod = method.bind(component); - if (__DEV__) { - boundMethod.__reactBoundContext = component; - boundMethod.__reactBoundMethod = method; - boundMethod.__reactBoundArguments = null; - var componentName = component.constructor.displayName; - var _bind = boundMethod.bind; - boundMethod.bind = function(newThis, ...args) { - // User is trying to bind() an autobound method; we effectively will - // ignore the value of "this" that the user is trying to use, so - // let's warn. - if (newThis !== component && newThis !== null) { - warning( - false, - 'bind(): React component methods may only be bound to the ' + - 'component instance. See %s', - componentName - ); - } else if (!args.length) { - warning( - false, - 'bind(): You are binding a component method to the component. ' + - 'React does this for you automatically in a high-performance ' + - 'way, so you can safely remove this call. See %s', - componentName - ); - return boundMethod; - } - var reboundMethod = _bind.apply(boundMethod, arguments); - reboundMethod.__reactBoundContext = component; - reboundMethod.__reactBoundMethod = method; - reboundMethod.__reactBoundArguments = args; - return reboundMethod; - }; - } - return boundMethod; -} - -/** - * Binds all auto-bound methods in a component. - * - * @param {object} component Component whose method is going to be bound. - */ -function bindAutoBindMethods(component) { - var pairs = component.__reactAutoBindPairs; - for (var i = 0; i < pairs.length; i += 2) { - var autoBindKey = pairs[i]; - var method = pairs[i + 1]; - component[autoBindKey] = bindAutoBindMethod( - component, - method - ); - } -} - -/** - * Add more to the ReactClass base class. These are all legacy features and - * therefore not already part of the modern ReactComponent. - */ -var ReactClassMixin = { - - /** - * TODO: This will be deprecated because state should always keep a consistent - * type signature and the only use case for this, is to avoid that. - */ - replaceState: function(newState, callback) { - this.updater.enqueueReplaceState(this, newState); - if (callback) { - this.updater.enqueueCallback(this, callback, 'replaceState'); - } - }, - - /** - * Checks whether or not this composite component is mounted. - * @return {boolean} True if mounted, false otherwise. - * @protected - * @final - */ - isMounted: function() { - return this.updater.isMounted(this); - }, -}; - -var ReactClassComponent = function() {}; -Object.assign( - ReactClassComponent.prototype, - ReactComponent.prototype, - ReactClassMixin +var createClass = factory( + ReactComponent, + ReactElement.isValidElement, + ReactNoopUpdateQueue ); -/** - * Module for creating composite components. - * - * @class ReactClass - */ -var ReactClass = { - - /** - * Creates a composite component class given a class specification. - * See https://facebook.github.io/react/docs/top-level-api.html#react.createclass - * - * @param {object} spec Class specification (which must define `render`). - * @return {function} Component constructor function. - * @public - */ - createClass: function(spec) { - // To keep our warnings more understandable, we'll use a little hack here to - // ensure that Constructor.name !== 'Constructor'. This makes sure we don't - // unnecessarily identify a class without displayName as 'Constructor'. - var Constructor = identity(function(props, context, updater) { - // This constructor gets overridden by mocks. The argument is used - // by mocks to assert on what gets mounted. - - if (__DEV__) { - warning( - this instanceof Constructor, - 'Something is calling a React component directly. Use a factory or ' + - 'JSX instead. See: https://fb.me/react-legacyfactory' - ); - } - - // Wire up auto-binding - if (this.__reactAutoBindPairs.length) { - bindAutoBindMethods(this); - } - - this.props = props; - this.context = context; - this.refs = emptyObject; - this.updater = updater || ReactNoopUpdateQueue; - - this.state = null; - - // ReactClasses doesn't have constructors. Instead, they use the - // getInitialState and componentWillMount methods for initialization. - - var initialState = this.getInitialState ? this.getInitialState() : null; - if (__DEV__) { - // We allow auto-mocks to proceed as if they're returning null. - if (initialState === undefined && - this.getInitialState._isMockFunction) { - // This is probably bad practice. Consider warning here and - // deprecating this convenience. - initialState = null; - } - } - invariant( - typeof initialState === 'object' && !Array.isArray(initialState), - '%s.getInitialState(): must return an object or null', - Constructor.displayName || 'ReactCompositeComponent' - ); - - this.state = initialState; - }); - Constructor.prototype = new ReactClassComponent(); - Constructor.prototype.constructor = Constructor; - Constructor.prototype.__reactAutoBindPairs = []; - - injectedMixins.forEach( - mixSpecIntoComponent.bind(null, Constructor) - ); - - mixSpecIntoComponent(Constructor, spec); - - // Initialize the defaultProps property after all mixins have been merged. - if (Constructor.getDefaultProps) { - Constructor.defaultProps = Constructor.getDefaultProps(); - } - - if (__DEV__) { - // This is a tag to indicate that the use of these method names is ok, - // since it's used with createClass. If it's not, then it's likely a - // mistake so we'll warn you to use the static property, property - // initializer or constructor respectively. - if (Constructor.getDefaultProps) { - Constructor.getDefaultProps.isReactClassApproved = {}; - } - if (Constructor.prototype.getInitialState) { - Constructor.prototype.getInitialState.isReactClassApproved = {}; - } - } - - invariant( - Constructor.prototype.render, - 'createClass(...): Class specification must implement a `render` method.' - ); +let didWarnDeprecated = false; +module.exports = { + createClass(...args) { if (__DEV__) { warning( - !Constructor.prototype.componentShouldUpdate, - '%s 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.', - spec.displayName || 'A component' - ); - warning( - !Constructor.prototype.componentWillRecieveProps, - '%s has a method called ' + - 'componentWillRecieveProps(). Did you mean componentWillReceiveProps()?', - spec.displayName || 'A component' + didWarnDeprecated, + 'React.createClass is deprecated and will be removed in version 16. ' + + 'Use plain JavaScript classes instead. If you\'re not yet ready to ' + + 'migrate, react-create-class is available on npm as a ' + + 'drop-in replacement.' ); + didWarnDeprecated = true; } - - // Reduce time spent doing lookups by setting these on the prototype. - for (var methodName in ReactClassInterface) { - if (!Constructor.prototype[methodName]) { - Constructor.prototype[methodName] = null; - } - } - - return Constructor; + return createClass(...args); }, - - injection: { - injectMixin: function(mixin) { - injectedMixins.push(mixin); - }, - }, - }; - -module.exports = ReactClass; diff --git a/src/isomorphic/classic/class/__tests__/ReactBind-test.js b/src/isomorphic/classic/class/__tests__/ReactBind-test.js deleted file mode 100644 index a5b612eba3a7..000000000000 --- a/src/isomorphic/classic/class/__tests__/ReactBind-test.js +++ /dev/null @@ -1,166 +0,0 @@ -/** - * Copyright 2013-present, 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 - */ -/*global global:true*/ -'use strict'; - -var React = require('React'); -var ReactTestUtils = require('ReactTestUtils'); -var reactComponentExpect = require('reactComponentExpect'); - -// TODO: Test render and all stock methods. -describe('autobinding', () => { - - it('Holds reference to instance', () => { - - var mouseDidEnter = jest.fn(); - var mouseDidLeave = jest.fn(); - var mouseDidClick = jest.fn(); - - var TestBindComponent = React.createClass({ - getInitialState: function() { - return {something: 'hi'}; - }, - onMouseEnter: mouseDidEnter, - onMouseLeave: mouseDidLeave, - onClick: mouseDidClick, - - // auto binding only occurs on top level functions in class defs. - badIdeas: { - badBind: function() { - void this.state.something; - }, - }, - - render: function() { - return ( -
- ); - }, - }); - - var instance1 = ; - var mountedInstance1 = ReactTestUtils.renderIntoDocument(instance1); - var rendered1 = reactComponentExpect(mountedInstance1) - .expectRenderedChild() - .instance(); - - var instance2 = ; - var mountedInstance2 = ReactTestUtils.renderIntoDocument(instance2); - var rendered2 = reactComponentExpect(mountedInstance2) - .expectRenderedChild() - .instance(); - - expect(function() { - var badIdea = instance1.badIdeas.badBind; - badIdea(); - }).toThrow(); - - expect(mountedInstance1.onClick).not.toBe(mountedInstance2.onClick); - - ReactTestUtils.Simulate.click(rendered1); - expect(mouseDidClick.mock.instances.length).toBe(1); - expect(mouseDidClick.mock.instances[0]).toBe(mountedInstance1); - - ReactTestUtils.Simulate.click(rendered2); - expect(mouseDidClick.mock.instances.length).toBe(2); - expect(mouseDidClick.mock.instances[1]).toBe(mountedInstance2); - - ReactTestUtils.Simulate.mouseOver(rendered1); - expect(mouseDidEnter.mock.instances.length).toBe(1); - expect(mouseDidEnter.mock.instances[0]).toBe(mountedInstance1); - - ReactTestUtils.Simulate.mouseOver(rendered2); - expect(mouseDidEnter.mock.instances.length).toBe(2); - expect(mouseDidEnter.mock.instances[1]).toBe(mountedInstance2); - - ReactTestUtils.Simulate.mouseOut(rendered1); - expect(mouseDidLeave.mock.instances.length).toBe(1); - expect(mouseDidLeave.mock.instances[0]).toBe(mountedInstance1); - - ReactTestUtils.Simulate.mouseOut(rendered2); - expect(mouseDidLeave.mock.instances.length).toBe(2); - expect(mouseDidLeave.mock.instances[1]).toBe(mountedInstance2); - }); - - it('works with mixins', () => { - var mouseDidClick = jest.fn(); - - var TestMixin = { - onClick: mouseDidClick, - }; - - var TestBindComponent = React.createClass({ - mixins: [TestMixin], - - render: function() { - return
; - }, - }); - - var instance1 = ; - var mountedInstance1 = ReactTestUtils.renderIntoDocument(instance1); - var rendered1 = reactComponentExpect(mountedInstance1) - .expectRenderedChild() - .instance(); - - ReactTestUtils.Simulate.click(rendered1); - expect(mouseDidClick.mock.instances.length).toBe(1); - expect(mouseDidClick.mock.instances[0]).toBe(mountedInstance1); - }); - - it('warns if you try to bind to this', () => { - spyOn(console, 'error'); - - var TestBindComponent = React.createClass({ - handleClick: function() { }, - render: function() { - return
; - }, - }); - - ReactTestUtils.renderIntoDocument(); - - expect(console.error.calls.count()).toBe(1); - expect(console.error.calls.argsFor(0)[0]).toBe( - 'Warning: bind(): You are binding a component method to the component. ' + - 'React does this for you automatically in a high-performance ' + - 'way, so you can safely remove this call. See TestBindComponent' - ); - }); - - it('does not warn if you pass an auto-bound method to setState', () => { - spyOn(console, 'error'); - - var TestBindComponent = React.createClass({ - getInitialState: function() { - return {foo: 1}; - }, - componentDidMount: function() { - this.setState({foo: 2}, this.handleUpdate); - }, - handleUpdate: function() { - - }, - render: function() { - return
; - }, - }); - - ReactTestUtils.renderIntoDocument(); - - expect(console.error.calls.count()).toBe(0); - }); - -}); diff --git a/src/isomorphic/classic/class/__tests__/ReactBindOptout-test.js b/src/isomorphic/classic/class/__tests__/ReactBindOptout-test.js deleted file mode 100644 index 635b594dee27..000000000000 --- a/src/isomorphic/classic/class/__tests__/ReactBindOptout-test.js +++ /dev/null @@ -1,233 +0,0 @@ -/** - * Copyright 2013-present, 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 - */ -/*global global:true*/ -'use strict'; - -var React = require('React'); -var ReactTestUtils = require('ReactTestUtils'); -var reactComponentExpect = require('reactComponentExpect'); - -// TODO: Test render and all stock methods. -describe('autobind optout', () => { - - it('should work with manual binding', () => { - - var mouseDidEnter = jest.fn(); - var mouseDidLeave = jest.fn(); - var mouseDidClick = jest.fn(); - - var TestBindComponent = React.createClass({ - autobind: false, - getInitialState: function() { - return {something: 'hi'}; - }, - onMouseEnter: mouseDidEnter, - onMouseLeave: mouseDidLeave, - onClick: mouseDidClick, - - render: function() { - return ( -
- ); - }, - }); - - var instance1 = ; - var mountedInstance1 = ReactTestUtils.renderIntoDocument(instance1); - var rendered1 = reactComponentExpect(mountedInstance1) - .expectRenderedChild() - .instance(); - - var instance2 = ; - var mountedInstance2 = ReactTestUtils.renderIntoDocument(instance2); - var rendered2 = reactComponentExpect(mountedInstance2) - .expectRenderedChild() - .instance(); - - ReactTestUtils.Simulate.click(rendered1); - expect(mouseDidClick.mock.instances.length).toBe(1); - expect(mouseDidClick.mock.instances[0]).toBe(mountedInstance1); - - ReactTestUtils.Simulate.click(rendered2); - expect(mouseDidClick.mock.instances.length).toBe(2); - expect(mouseDidClick.mock.instances[1]).toBe(mountedInstance2); - - ReactTestUtils.Simulate.mouseOver(rendered1); - expect(mouseDidEnter.mock.instances.length).toBe(1); - expect(mouseDidEnter.mock.instances[0]).toBe(mountedInstance1); - - ReactTestUtils.Simulate.mouseOver(rendered2); - expect(mouseDidEnter.mock.instances.length).toBe(2); - expect(mouseDidEnter.mock.instances[1]).toBe(mountedInstance2); - - ReactTestUtils.Simulate.mouseOut(rendered1); - expect(mouseDidLeave.mock.instances.length).toBe(1); - expect(mouseDidLeave.mock.instances[0]).toBe(mountedInstance1); - - ReactTestUtils.Simulate.mouseOut(rendered2); - expect(mouseDidLeave.mock.instances.length).toBe(2); - expect(mouseDidLeave.mock.instances[1]).toBe(mountedInstance2); - }); - - it('should not hold reference to instance', () => { - var mouseDidClick = function() { - void this.state.something; - }; - - var TestBindComponent = React.createClass({ - autobind: false, - getInitialState: function() { - return {something: 'hi'}; - }, - onClick: mouseDidClick, - - // auto binding only occurs on top level functions in class defs. - badIdeas: { - badBind: function() { - void this.state.something; - }, - }, - - render: function() { - return ( -
- ); - }, - }); - - var instance1 = ; - var mountedInstance1 = ReactTestUtils.renderIntoDocument(instance1); - var rendered1 = reactComponentExpect(mountedInstance1) - .expectRenderedChild() - .instance(); - - var instance2 = ; - var mountedInstance2 = ReactTestUtils.renderIntoDocument(instance2); - var rendered2 = reactComponentExpect(mountedInstance2) - .expectRenderedChild() - .instance(); - - expect(function() { - var badIdea = instance1.badIdeas.badBind; - badIdea(); - }).toThrow(); - - expect(mountedInstance1.onClick).toBe(mountedInstance2.onClick); - - expect(function() { - ReactTestUtils.Simulate.click(rendered1); - }).toThrow(); - - expect(function() { - ReactTestUtils.Simulate.click(rendered2); - }).toThrow(); - }); - - it('works with mixins that have not opted out of autobinding', () => { - var mouseDidClick = jest.fn(); - - var TestMixin = { - onClick: mouseDidClick, - }; - - var TestBindComponent = React.createClass({ - mixins: [TestMixin], - - render: function() { - return
; - }, - }); - - var instance1 = ; - var mountedInstance1 = ReactTestUtils.renderIntoDocument(instance1); - var rendered1 = reactComponentExpect(mountedInstance1) - .expectRenderedChild() - .instance(); - - ReactTestUtils.Simulate.click(rendered1); - expect(mouseDidClick.mock.instances.length).toBe(1); - expect(mouseDidClick.mock.instances[0]).toBe(mountedInstance1); - }); - - it('works with mixins that have opted out of autobinding', () => { - var mouseDidClick = jest.fn(); - - var TestMixin = { - autobind: false, - onClick: mouseDidClick, - }; - - var TestBindComponent = React.createClass({ - mixins: [TestMixin], - - render: function() { - return
; - }, - }); - - var instance1 = ; - var mountedInstance1 = ReactTestUtils.renderIntoDocument(instance1); - var rendered1 = reactComponentExpect(mountedInstance1) - .expectRenderedChild() - .instance(); - - ReactTestUtils.Simulate.click(rendered1); - expect(mouseDidClick.mock.instances.length).toBe(1); - expect(mouseDidClick.mock.instances[0]).toBe(mountedInstance1); - }); - - it('does not warn if you try to bind to this', () => { - spyOn(console, 'error'); - - var TestBindComponent = React.createClass({ - autobind: false, - handleClick: function() { }, - render: function() { - return
; - }, - }); - - ReactTestUtils.renderIntoDocument(); - - expect(console.error.calls.count()).toBe(0); - }); - - it('does not warn if you pass an manually bound method to setState', () => { - spyOn(console, 'error'); - - var TestBindComponent = React.createClass({ - autobind: false, - getInitialState: function() { - return {foo: 1}; - }, - componentDidMount: function() { - this.setState({foo: 2}, this.handleUpdate.bind(this)); - }, - handleUpdate: function() { - - }, - render: function() { - return
; - }, - }); - - ReactTestUtils.renderIntoDocument(); - - expect(console.error.calls.count()).toBe(0); - }); - -}); diff --git a/src/isomorphic/classic/class/__tests__/ReactClass-test.js b/src/isomorphic/classic/class/__tests__/ReactClass-test.js index e56d3913ba8d..e992af7429b4 100644 --- a/src/isomorphic/classic/class/__tests__/ReactClass-test.js +++ b/src/isomorphic/classic/class/__tests__/ReactClass-test.js @@ -23,6 +23,26 @@ describe('ReactClass-spec', () => { ReactTestUtils = require('ReactTestUtils'); }); + it('should warn on first call to React.createClass', () => { + spyOn(console, 'error'); + const spec = { + render() { + return
; + }, + }; + React.createClass(spec); + React.createClass(spec); + expect(console.error.calls.count()).toEqual(1); + expect(console.error.calls.count()).toEqual(1); + expect(console.error.calls.argsFor(0)[0]).toBe( + 'Warning: React.createClass is deprecated and will be removed in ' + + 'version 16. Use plain JavaScript classes instead. If you\'re not yet ' + + 'ready to migrate, react-create-class is available on npm as a ' + + 'drop-in replacement.' + ); + console.error.calls.reset(); + }); + it('should throw when `render` is not specified', () => { expect(function() { React.createClass({}); diff --git a/src/isomorphic/classic/class/__tests__/ReactClassMixin-test.js b/src/isomorphic/classic/class/__tests__/ReactClassMixin-test.js deleted file mode 100644 index 3a3bb6288fa0..000000000000 --- a/src/isomorphic/classic/class/__tests__/ReactClassMixin-test.js +++ /dev/null @@ -1,545 +0,0 @@ -/** - * Copyright 2013-present, 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; - -var TestComponent; -var TestComponentWithPropTypes; -var TestComponentWithReverseSpec; -var mixinPropValidator; -var componentPropValidator; - -describe('ReactClass-mixin', () => { - - beforeEach(() => { - React = require('React'); - ReactTestUtils = require('ReactTestUtils'); - mixinPropValidator = jest.fn(); - componentPropValidator = jest.fn(); - - 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', () => { - var listener = jest.fn(); - 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', () => { - var listener = jest.fn(); - var 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', () => { - var listener = jest.fn(); - var instance = ; - ReactTestUtils.renderIntoDocument(instance); - - expect(listener.mock.calls).toEqual([ - ['MixinA didMount'], - ['MixinBWithReverseSpec didMount'], - ['MixinC didMount'], - ['Component didMount'], - ]); - }); - - it('should validate prop types via mixins', () => { - expect(TestComponent.propTypes).toBeDefined(); - expect(TestComponent.propTypes.value) - .toBe(mixinPropValidator); - }); - - it('should override mixin prop types with class prop types', () => { - // Sanity check... - expect(componentPropValidator).not.toBe(mixinPropValidator); - // Actually check... - expect(TestComponentWithPropTypes.propTypes) - .toBeDefined(); - expect(TestComponentWithPropTypes.propTypes.value) - .not.toBe(mixinPropValidator); - expect(TestComponentWithPropTypes.propTypes.value) - .toBe(componentPropValidator); - }); - - - it('should support mixins with getInitialState()', () => { - 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', () => { - 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); - }).toThrowError( - '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()', () => { - 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', () => { - 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", () => { - expect(function() { - var Mixin = { - statics: { - abc: 'foo', - }, - }; - React.createClass({ - mixins: [Mixin], - - statics: { - abc: 'bar', - }, - - render: function() { - return ; - }, - }); - }).toThrowError( - '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', () => { - expect(function() { - var Mixin = { - statics: { - abc: function() { - console.log('foo'); - }, - }, - }; - React.createClass({ - mixins: [Mixin], - - statics: { - abc: function() { - console.log('bar'); - }, - }, - - render: function() { - return ; - }, - }); - }).toThrowError( - 'ReactClass: You are attempting to define `abc` on your component ' + - 'more than once. This conflict may be due to a mixin.' - ); - }); - - it('should warn if the mixin is undefined', () => { - spyOn(console, 'error'); - - React.createClass({ - mixins: [undefined], - - render: function() { - return ; - }, - }); - - expect(console.error.calls.count()).toBe(1); - expect(console.error.calls.argsFor(0)[0]).toBe( - 'Warning: ReactClass: You\'re attempting to include a mixin that is ' + - 'either null or not an object. Check the mixins included by the ' + - 'component, as well as any mixins they include themselves. ' + - 'Expected object but got undefined.' - ); - }); - - it('should warn if the mixin is null', () => { - spyOn(console, 'error'); - - React.createClass({ - mixins: [null], - - render: function() { - return ; - }, - }); - - expect(console.error.calls.count()).toBe(1); - expect(console.error.calls.argsFor(0)[0]).toBe( - 'Warning: ReactClass: You\'re attempting to include a mixin that is ' + - 'either null or not an object. Check the mixins included by the ' + - 'component, as well as any mixins they include themselves. ' + - 'Expected object but got null.' - ); - }); - - it('should warn if an undefined mixin is included in another mixin', () => { - spyOn(console, 'error'); - - var mixinA = { - mixins: [undefined], - }; - - React.createClass({ - mixins: [mixinA], - - render: function() { - return ; - }, - }); - - expect(console.error.calls.count()).toBe(1); - expect(console.error.calls.argsFor(0)[0]).toBe( - 'Warning: ReactClass: You\'re attempting to include a mixin that is ' + - 'either null or not an object. Check the mixins included by the ' + - 'component, as well as any mixins they include themselves. ' + - 'Expected object but got undefined.' - ); - }); - - it('should warn if a null mixin is included in another mixin', () => { - spyOn(console, 'error'); - - var mixinA = { - mixins: [null], - }; - - React.createClass({ - mixins: [mixinA], - - render: function() { - return ; - }, - }); - - expect(console.error.calls.count()).toBe(1); - expect(console.error.calls.argsFor(0)[0]).toBe( - 'Warning: ReactClass: You\'re attempting to include a mixin that is ' + - 'either null or not an object. Check the mixins included by the ' + - 'component, as well as any mixins they include themselves. ' + - 'Expected object but got null.' - ); - }); - - it('should throw if the mixin is a React component', () => { - expect(function() { - React.createClass({ - mixins: [
], - - render: function() { - return ; - }, - }); - }).toThrowError( - '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', () => { - expect(function() { - var Component = React.createClass({ - render: function() { - return ; - }, - }); - - React.createClass({ - mixins: [Component], - - render: function() { - return ; - }, - }); - }).toThrowError( - 'ReactClass: You\'re attempting to use a component class or function ' + - 'as a mixin. Instead, just use a regular object.' - ); - }); - - it('should have bound the mixin methods to the component', () => { - var mixin = { - mixinFunc: function() { - return this; - }, - }; - - var Component = React.createClass({ - mixins: [mixin], - componentDidMount: function() { - expect(this.mixinFunc()).toBe(this); - }, - render: function() { - return ; - }, - }); - var instance = ; - ReactTestUtils.renderIntoDocument(instance); - }); - - it('should include the mixin keys in even if their values are falsy', () => { - 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 = ; - 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/isomorphic/classic/element/__tests__/ReactElement-test.js b/src/isomorphic/classic/element/__tests__/ReactElement-test.js index 36814982c3f1..cf82a953094a 100644 --- a/src/isomorphic/classic/element/__tests__/ReactElement-test.js +++ b/src/isomorphic/classic/element/__tests__/ReactElement-test.js @@ -32,11 +32,11 @@ describe('ReactElement', () => { ReactTestUtils = require('ReactTestUtils'); // NOTE: We're explicitly not using JSX here. This is intended to test // classic JS without JSX. - ComponentClass = React.createClass({ - render: function() { + ComponentClass = class extends React.Component { + render() { return React.createElement('div'); - }, - }); + } + }; }); afterEach(() => { @@ -57,37 +57,7 @@ describe('ReactElement', () => { expect(element.props).toEqual({}); }); - it('should warn when `key` is being accessed on createClass element', () => { - spyOn(console, 'error'); - var container = document.createElement('div'); - var Child = React.createClass({ - render: function() { - return
{this.props.key}
; - }, - }); - var Parent = React.createClass({ - render: function() { - return ( -
- - - -
- ); - }, - }); - expect(console.error.calls.count()).toBe(0); - ReactDOM.render(, container); - expect(console.error.calls.count()).toBe(1); - expect(console.error.calls.argsFor(0)[0]).toContain( - 'Child: `key` is not a prop. Trying to access it will result ' + - 'in `undefined` being returned. If you need to access the same ' + - 'value within the child component, you should pass it as a different ' + - 'prop. (https://fb.me/react-special-props)' - ); - }); - - it('should warn when `key` is being accessed on ES class element', () => { + it('should warn when `key` is being accessed on composite element', () => { spyOn(console, 'error'); var container = document.createElement('div'); class Child extends React.Component { @@ -95,8 +65,8 @@ describe('ReactElement', () => { return
{this.props.key}
; } } - var Parent = React.createClass({ - render: function() { + class Parent extends React.Component { + render() { return (
@@ -104,8 +74,8 @@ describe('ReactElement', () => {
); - }, - }); + } + } expect(console.error.calls.count()).toBe(0); ReactDOM.render(, container); expect(console.error.calls.count()).toBe(1); @@ -134,20 +104,20 @@ describe('ReactElement', () => { it('should warn when `ref` is being accessed', () => { spyOn(console, 'error'); var container = document.createElement('div'); - var Child = React.createClass({ - render: function() { + class Child extends React.Component { + render() { return
{this.props.ref}
; - }, - }); - var Parent = React.createClass({ - render: function() { + } + } + class Parent extends React.Component { + render() { return (
); - }, - }); + } + } expect(console.error.calls.count()).toBe(0); ReactDOM.render(, container); expect(console.error.calls.count()).toBe(1); @@ -255,12 +225,12 @@ describe('ReactElement', () => { var Component = React.createFactory(ComponentClass); var element; - var Wrapper = React.createClass({ - render: function() { + class Wrapper extends React.Component { + render() { element = Component(); return element; - }, - }); + } + } var instance = ReactTestUtils.renderIntoDocument( React.createElement(Wrapper) @@ -312,19 +282,12 @@ describe('ReactElement', () => { it('allows static methods to be called using the type property', () => { spyOn(console, 'error'); - var StaticMethodComponentClass = React.createClass({ - statics: { - someStaticMethod: function() { - return 'someReturnValue'; - }, - }, - getInitialState: function() { - return {valueToReturn: 'hi'}; - }, - render: function() { + class StaticMethodComponentClass extends React.Component { + render() { return React.createElement('div'); - }, - }); + } + } + StaticMethodComponentClass.someStaticMethod = () => 'someReturnValue'; var element = React.createElement(StaticMethodComponentClass); expect(element.type.someStaticMethod()).toBe('someReturnValue'); @@ -334,11 +297,11 @@ describe('ReactElement', () => { // NOTE: We're explicitly not using JSX here. This is intended to test // classic JS without JSX. it('identifies valid elements', () => { - var Component = React.createClass({ - render: function() { + class Component extends React.Component { + render() { return React.createElement('div'); - }, - }); + } + } expect(React.isValidElement(React.createElement('div'))) .toEqual(true); @@ -357,21 +320,6 @@ describe('ReactElement', () => { expect(React.isValidElement(JSON.parse(jsonElement))).toBe(true); }); - it('allows the use of PropTypes validators in statics', () => { - // 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 precaution. - var Component = React.createClass({ - render: () => null, - statics: { - specialType: React.PropTypes.shape({monkey: React.PropTypes.any}), - }, - }); - - expect(typeof Component.specialType).toBe('function'); - expect(typeof Component.specialType.isRequired).toBe('function'); - }); - // NOTE: We're explicitly not using JSX here. This is intended to test // classic JS without JSX. it('is indistinguishable from a plain object', () => { @@ -383,14 +331,12 @@ describe('ReactElement', () => { // NOTE: We're explicitly not using JSX here. This is intended to test // classic JS without JSX. it('should use default prop value when removing a prop', () => { - var Component = React.createClass({ - getDefaultProps: function() { - return {fruit: 'persimmon'}; - }, - render: function() { + class Component extends React.Component { + render() { return React.createElement('span'); - }, - }); + } + } + Component.defaultProps = {fruit: 'persimmon'}; var container = document.createElement('div'); var instance = ReactDOM.render( @@ -406,14 +352,12 @@ describe('ReactElement', () => { // NOTE: We're explicitly not using JSX here. This is intended to test // classic JS without JSX. it('should normalize props with default values', () => { - var Component = React.createClass({ - getDefaultProps: function() { - return {prop: 'testKey'}; - }, - render: function() { + class Component extends React.Component { + render() { return React.createElement('span', null, this.props.prop); - }, - }); + } + } + Component.defaultProps = {prop: 'testKey'}; var instance = ReactTestUtils.renderIntoDocument( React.createElement(Component) @@ -427,8 +371,8 @@ describe('ReactElement', () => { }); it('throws when changing a prop (in dev) after element creation', () => { - var Outer = React.createClass({ - render: function() { + class Outer extends React.Component { + render() { var el =
; expect(function() { @@ -437,17 +381,16 @@ describe('ReactElement', () => { expect(el.props.className).toBe('moo'); return el; - }, - }); + } + } var outer = ReactTestUtils.renderIntoDocument(); expect(ReactDOM.findDOMNode(outer).className).toBe('moo'); }); it('throws when adding a prop (in dev) after element creation', () => { var container = document.createElement('div'); - var Outer = React.createClass({ - getDefaultProps: () => ({sound: 'meow'}), - render: function() { + class Outer extends React.Component { + render() { var el =
{this.props.sound}
; expect(function() { @@ -457,8 +400,9 @@ describe('ReactElement', () => { expect(el.props.className).toBe(undefined); return el; - }, - }); + } + } + Outer.defaultProps = {sound: 'meow'}; var outer = ReactDOM.render(, container); expect(ReactDOM.findDOMNode(outer).textContent).toBe('meow'); expect(ReactDOM.findDOMNode(outer).className).toBe(''); @@ -466,11 +410,11 @@ describe('ReactElement', () => { it('does not warn for NaN props', () => { spyOn(console, 'error'); - var Test = React.createClass({ - render: function() { + class Test extends React.Component { + render() { return
; - }, - }); + } + } var test = ReactTestUtils.renderIntoDocument(); expect(test.props.value).toBeNaN(); expect(console.error.calls.count()).toBe(0); @@ -498,11 +442,11 @@ describe('ReactElement', () => { React = require('React'); - var Component = React.createClass({ - render: function() { + class Component extends React.Component { + render() { return React.createElement('div'); - }, - }); + } + } expect(React.isValidElement(React.createElement('div'))) .toEqual(true); @@ -538,15 +482,15 @@ describe('comparing jsx vs .createFactory() vs .createElement()', () => { describe('when using jsx only', () => { var Parent, instance; beforeEach(() => { - Parent = React.createClass({ - render: function() { + Parent = class extends React.Component { + render() { return (
children value
); - }, - }); + } + }; instance = ReactTestUtils.renderIntoDocument(); }); @@ -568,11 +512,11 @@ describe('comparing jsx vs .createFactory() vs .createElement()', () => { var factory, instance; beforeEach(() => { var childFactory = React.createFactory(Child); - var Parent = React.createClass({ - render: function() { + class Parent extends React.Component { + render() { return React.DOM.div({}, childFactory({ ref: 'child', foo: 'foo value' }, 'children value')); - }, - }); + } + } factory = React.createFactory(Parent); instance = ReactTestUtils.renderIntoDocument(factory()); }); @@ -594,11 +538,11 @@ describe('comparing jsx vs .createFactory() vs .createElement()', () => { describe('when using parent that uses .createElement()', () => { var factory, instance; beforeEach(() => { - var Parent = React.createClass({ - render: function() { + class Parent extends React.Component { + render() { return React.DOM.div({}, React.createElement(Child, { ref: 'child', foo: 'foo value' }, 'children value')); - }, - }); + } + } factory = React.createFactory(Parent); instance = ReactTestUtils.renderIntoDocument(factory()); }); diff --git a/src/isomorphic/classic/element/__tests__/ReactElementClone-test.js b/src/isomorphic/classic/element/__tests__/ReactElementClone-test.js index 50406d03456d..69e8984ca2f8 100644 --- a/src/isomorphic/classic/element/__tests__/ReactElementClone-test.js +++ b/src/isomorphic/classic/element/__tests__/ReactElementClone-test.js @@ -25,52 +25,52 @@ describe('ReactElementClone', () => { // NOTE: We're explicitly not using JSX here. This is intended to test // classic JS without JSX. - ComponentClass = React.createClass({ - render: function() { + ComponentClass = class extends React.Component { + render() { return React.createElement('div'); - }, - }); + } + }; }); it('should clone a DOM component with new props', () => { - var Grandparent = React.createClass({ - render: function() { + class Grandparent extends React.Component { + render() { return } />; - }, - }); - var Parent = React.createClass({ - render: function() { + } + } + class Parent extends React.Component { + render() { return (
{React.cloneElement(this.props.child, { className: 'xyz' })}
); - }, - }); + } + } var component = ReactTestUtils.renderIntoDocument(); expect(ReactDOM.findDOMNode(component).childNodes[0].className).toBe('xyz'); }); it('should clone a composite component with new props', () => { - var Child = React.createClass({ - render: function() { + class Child extends React.Component { + render() { return
; - }, - }); - var Grandparent = React.createClass({ - render: function() { + } + } + class Grandparent extends React.Component { + render() { return } />; - }, - }); - var Parent = React.createClass({ - render: function() { + } + } + class Parent extends React.Component { + render() { return (
{React.cloneElement(this.props.child, { className: 'xyz' })}
); - }, - }); + } + } var component = ReactTestUtils.renderIntoDocument(); expect(ReactDOM.findDOMNode(component).childNodes[0].className).toBe('xyz'); }); @@ -81,43 +81,43 @@ describe('ReactElementClone', () => { }); it('should keep the original ref if it is not overridden', () => { - var Grandparent = React.createClass({ - render: function() { + class Grandparent extends React.Component { + render() { return } />; - }, - }); + } + } - var Parent = React.createClass({ - render: function() { + class Parent extends React.Component { + render() { return (
{React.cloneElement(this.props.child, { className: 'xyz' })}
); - }, - }); + } + } var component = ReactTestUtils.renderIntoDocument(); expect(component.refs.yolo.tagName).toBe('DIV'); }); it('should transfer the key property', () => { - var Component = React.createClass({ - render: function() { + class Component extends React.Component { + render() { return null; - }, - }); + } + } var clone = React.cloneElement(, {key: 'xyz'}); expect(clone.key).toBe('xyz'); }); it('should transfer children', () => { - var Component = React.createClass({ - render: function() { + class Component extends React.Component { + render() { expect(this.props.children).toBe('xyz'); return
; - }, - }); + } + } ReactTestUtils.renderIntoDocument( React.cloneElement(, {children: 'xyz'}) @@ -125,12 +125,12 @@ describe('ReactElementClone', () => { }); it('should shallow clone children', () => { - var Component = React.createClass({ - render: function() { + class Component extends React.Component { + render() { expect(this.props.children).toBe('xyz'); return
; - }, - }); + } + } ReactTestUtils.renderIntoDocument( React.cloneElement(xyz, {}) @@ -138,11 +138,11 @@ describe('ReactElementClone', () => { }); it('should accept children as rest arguments', () => { - var Component = React.createClass({ - render: function() { + class Component extends React.Component { + render() { return null; - }, - }); + } + } var clone = React.cloneElement( xyz, @@ -170,39 +170,39 @@ describe('ReactElementClone', () => { }); it('should support keys and refs', () => { - var Parent = React.createClass({ - render: function() { + class Parent extends React.Component { + render() { var clone = React.cloneElement(this.props.children, {key: 'xyz', ref: 'xyz'}); expect(clone.key).toBe('xyz'); expect(clone.ref).toBe('xyz'); return
{clone}
; - }, - }); + } + } - var Grandparent = React.createClass({ - render: function() { + class Grandparent extends React.Component { + render() { return ; - }, - }); + } + } var component = ReactTestUtils.renderIntoDocument(); expect(component.refs.parent.refs.xyz.tagName).toBe('SPAN'); }); it('should steal the ref if a new ref is specified', () => { - var Parent = React.createClass({ - render: function() { + class Parent extends React.Component { + render() { var clone = React.cloneElement(this.props.children, {ref: 'xyz'}); return
{clone}
; - }, - }); + } + } - var Grandparent = React.createClass({ - render: function() { + class Grandparent extends React.Component { + render() { return ; - }, - }); + } + } var component = ReactTestUtils.renderIntoDocument(); expect(component.refs.child).toBeUndefined(); @@ -210,12 +210,12 @@ describe('ReactElementClone', () => { }); it('should overwrite props', () => { - var Component = React.createClass({ - render: function() { + class Component extends React.Component { + render() { expect(this.props.myprop).toBe('xyz'); return
; - }, - }); + } + } ReactTestUtils.renderIntoDocument( React.cloneElement(, {myprop: 'xyz'}) @@ -223,14 +223,12 @@ describe('ReactElementClone', () => { }); it('should normalize props with default values', () => { - var Component = React.createClass({ - getDefaultProps: function() { - return {prop: 'testKey'}; - }, - render: function() { + class Component extends React.Component { + render() { return ; - }, - }); + } + } + Component.defaultProps = {prop: 'testKey'}; var instance = React.createElement(Component); var clonedInstance = React.cloneElement(instance, {prop: undefined}); @@ -282,27 +280,27 @@ describe('ReactElementClone', () => { it('should check declared prop types after clone', () => { spyOn(console, 'error'); - var Component = React.createClass({ - propTypes: { - color: React.PropTypes.string.isRequired, - }, - render: function() { + class Component extends React.Component { + render() { return React.createElement('div', null, 'My color is ' + this.color); - }, - }); - var Parent = React.createClass({ - render: function() { + } + } + Component.propTypes = { + color: React.PropTypes.string.isRequired, + }; + class Parent extends React.Component { + render() { return React.cloneElement(this.props.child, {color: 123}); - }, - }); - var GrandParent = React.createClass({ - render: function() { + } + } + class GrandParent extends React.Component { + render() { return React.createElement( Parent, { child: React.createElement(Component, {color: 'red'}) } ); - }, - }); + } + } ReactTestUtils.renderIntoDocument(React.createElement(GrandParent)); expect(console.error.calls.count()).toBe(1); expect(console.error.calls.argsFor(0)[0]).toBe( diff --git a/src/isomorphic/classic/element/__tests__/ReactElementValidator-test.js b/src/isomorphic/classic/element/__tests__/ReactElementValidator-test.js index b36fa2f39c9a..17367f0439c9 100644 --- a/src/isomorphic/classic/element/__tests__/ReactElementValidator-test.js +++ b/src/isomorphic/classic/element/__tests__/ReactElementValidator-test.js @@ -31,11 +31,11 @@ describe('ReactElementValidator', () => { React = require('React'); ReactDOM = require('ReactDOM'); ReactTestUtils = require('ReactTestUtils'); - ComponentClass = React.createClass({ - render: function() { + ComponentClass = class extends React.Component { + render() { return React.createElement('div'); - }, - }); + } + }; }); it('warns for keys for arrays of elements in rest args', () => { @@ -54,21 +54,19 @@ describe('ReactElementValidator', () => { spyOn(console, 'error'); var Component = React.createFactory(ComponentClass); - var InnerClass = React.createClass({ - displayName: 'InnerClass', - render: function() { + class InnerClass extends React.Component { + render() { return Component(null, this.props.childSet); - }, - }); + } + } var InnerComponent = React.createFactory(InnerClass); - var ComponentWrapper = React.createClass({ - displayName: 'ComponentWrapper', - render: function() { + class ComponentWrapper extends React.Component { + render() { return InnerComponent({childSet: [Component(), Component()] }); - }, - }); + } + } ReactTestUtils.renderIntoDocument( React.createElement(ComponentWrapper) @@ -85,12 +83,10 @@ describe('ReactElementValidator', () => { it('warns for keys for arrays with no owner or parent info', () => { spyOn(console, 'error'); - var Anonymous = React.createClass({ - displayName: undefined, - render: function() { - return
; - }, - }); + function Anonymous() { + return
; + } + Object.defineProperty(Anonymous, 'name', { value: undefined }); var divs = [
, @@ -127,23 +123,17 @@ describe('ReactElementValidator', () => { it('warns for keys with component stack info', () => { spyOn(console, 'error'); - var Component = React.createClass({ - render: function() { - return
{[
,
]}
; - }, - }); + function Component() { + return
{[
,
]}
; + } - var Parent = React.createClass({ - render: function() { - return React.cloneElement(this.props.child); - }, - }); + function Parent(props) { + return React.cloneElement(props.child); + } - var GrandParent = React.createClass({ - render: function() { - return } />; - }, - }); + function GrandParent() { + return } />; + } ReactTestUtils.renderIntoDocument(); @@ -162,16 +152,14 @@ describe('ReactElementValidator', () => { it('does not warn for keys when passing children down', () => { spyOn(console, 'error'); - var Wrapper = React.createClass({ - render: function() { - return ( -
- {this.props.children} -
-
- ); - }, - }); + function Wrapper(props) { + return ( +
+ {props.children} +
+
+ ); + } ReactTestUtils.renderIntoDocument( @@ -266,19 +254,15 @@ describe('ReactElementValidator', () => { // component, we give a small hint as to which parent instantiated that // component as per warnings about key usage in ReactElementValidator. spyOn(console, 'error'); - var MyComp = React.createClass({ - propTypes: { - color: React.PropTypes.string, - }, - render: function() { - return React.createElement('div', null, 'My color is ' + this.color); - }, - }); - var ParentComp = React.createClass({ - render: function() { - return React.createElement(MyComp, {color: 123}); - }, - }); + function MyComp(props) { + return React.createElement('div', null, 'My color is ' + props.color); + } + MyComp.propTypes = { + color: React.PropTypes.string, + }; + function ParentComp() { + return React.createElement(MyComp, {color: 123}); + } ReactTestUtils.renderIntoDocument(React.createElement(ParentComp)); expect(console.error.calls.argsFor(0)[0]).toBe( 'Warning: Failed prop type: ' + @@ -336,11 +320,9 @@ describe('ReactElementValidator', () => { it('includes the owner name when passing null, undefined, boolean, or number', () => { spyOn(console, 'error'); - var ParentComp = React.createClass({ - render: function() { - return React.createElement(null); - }, - }); + function ParentComp() { + return React.createElement(null); + } expect(function() { ReactTestUtils.renderIntoDocument(React.createElement(ParentComp)); }).toThrowError( @@ -359,15 +341,13 @@ describe('ReactElementValidator', () => { it('should check default prop values', () => { spyOn(console, 'error'); - var Component = React.createClass({ - propTypes: {prop: React.PropTypes.string.isRequired}, - getDefaultProps: function() { - return {prop: null}; - }, - render: function() { + class Component extends React.Component { + render() { return React.createElement('span', null, this.props.prop); - }, - }); + } + } + Component.propTypes = {prop: React.PropTypes.string.isRequired}; + Component.defaultProps = {prop: null}; ReactTestUtils.renderIntoDocument(React.createElement(Component)); @@ -382,15 +362,13 @@ describe('ReactElementValidator', () => { it('should not check the default for explicit null', () => { spyOn(console, 'error'); - var Component = React.createClass({ - propTypes: {prop: React.PropTypes.string.isRequired}, - getDefaultProps: function() { - return {prop: 'text'}; - }, - render: function() { + class Component extends React.Component { + render() { return React.createElement('span', null, this.props.prop); - }, - }); + } + } + Component.propTypes = {prop: React.PropTypes.string.isRequired}; + Component.defaultProps = {prop: 'text'}; ReactTestUtils.renderIntoDocument( React.createElement(Component, {prop:null}) @@ -407,14 +385,14 @@ describe('ReactElementValidator', () => { it('should check declared prop types', () => { spyOn(console, 'error'); - var Component = React.createClass({ - propTypes: { - prop: React.PropTypes.string.isRequired, - }, - render: function() { + class Component extends React.Component { + render() { return React.createElement('span', null, this.props.prop); - }, - }); + } + } + Component.propTypes = { + prop: React.PropTypes.string.isRequired, + }; ReactTestUtils.renderIntoDocument( React.createElement(Component) @@ -449,14 +427,15 @@ describe('ReactElementValidator', () => { it('should warn if a PropType creator is used as a PropType', () => { spyOn(console, 'error'); - var Component = React.createClass({ - propTypes: { - myProp: React.PropTypes.shape, - }, - render: function() { + class Component extends React.Component { + render() { return React.createElement('span', null, this.props.myProp.value); - }, - }); + + } + } + Component.propTypes = { + myProp: React.PropTypes.shape, + }; ReactTestUtils.renderIntoDocument( React.createElement(Component, {myProp: {value: 'hi'}}) @@ -474,11 +453,9 @@ describe('ReactElementValidator', () => { it('should warn when accessing .type on an element factory', () => { spyOn(console, 'error'); - var TestComponent = React.createClass({ - render: function() { - return
; - }, - }); + function TestComponent() { + return
; + } var TestFactory = React.createFactory(TestComponent); expect(TestFactory.type).toBe(TestComponent); expect(console.error.calls.count()).toBe(1); @@ -493,14 +470,14 @@ describe('ReactElementValidator', () => { it('does not warn when using DOM node as children', () => { spyOn(console, 'error'); - var DOMContainer = React.createClass({ - render: function() { + class DOMContainer extends React.Component { + render() { return
; - }, - componentDidMount: function() { + } + componentDidMount() { ReactDOM.findDOMNode(this).appendChild(this.props.children); - }, - }); + } + } var node = document.createElement('div'); // This shouldn't cause a stack overflow or any other problems (#3883) diff --git a/src/renderers/dom/client/__tests__/ReactMount-test.js b/src/renderers/dom/client/__tests__/ReactMount-test.js index 7e574216bdc7..d9d9e5eff14a 100644 --- a/src/renderers/dom/client/__tests__/ReactMount-test.js +++ b/src/renderers/dom/client/__tests__/ReactMount-test.js @@ -91,13 +91,13 @@ describe('ReactMount', () => { var mockMount = jest.fn(); var mockUnmount = jest.fn(); - var Component = React.createClass({ - componentDidMount: mockMount, - componentWillUnmount: mockUnmount, - render: function() { + class Component extends React.Component { + componentDidMount = mockMount; + componentWillUnmount = mockUnmount; + render() { return {this.props.text}; - }, - }); + } + } expect(mockMount.mock.calls.length).toBe(0); expect(mockUnmount.mock.calls.length).toBe(0); diff --git a/src/renderers/dom/client/eventPlugins/__tests__/BeforeInputEventPlugin-test.js b/src/renderers/dom/client/eventPlugins/__tests__/BeforeInputEventPlugin-test.js index d39527f5c0a0..5d2ec7ab88bc 100644 --- a/src/renderers/dom/client/eventPlugins/__tests__/BeforeInputEventPlugin-test.js +++ b/src/renderers/dom/client/eventPlugins/__tests__/BeforeInputEventPlugin-test.js @@ -184,9 +184,11 @@ describe('BeforeInputEventPlugin', function() { function TestEditableReactComponent(Emulator, Scenario, ExpectedResult) { ModuleCache = new initialize(Emulator); - var EditableDiv = React.createClass({ - render: () => (
), - }); + class EditableDiv extends React.Component { + render() { + return
; + } + } var rendered = ReactTestUtils.renderIntoDocument(); var node = ModuleCache.ReactDOM.findDOMNode(rendered); diff --git a/src/renderers/dom/server/__tests__/ReactServerRendering-test.js b/src/renderers/dom/server/__tests__/ReactServerRendering-test.js index 26db5640693b..af931eb408d3 100644 --- a/src/renderers/dom/server/__tests__/ReactServerRendering-test.js +++ b/src/renderers/dom/server/__tests__/ReactServerRendering-test.js @@ -486,32 +486,6 @@ describe('ReactServerRendering', () => { expect(markup).toBe('
hello
'); }); - it('warns with a no-op when an async replaceState is triggered', () => { - var Bar = React.createClass({ - componentWillMount: function() { - this.replaceState({text: 'hello'}); - setTimeout(() => { - this.replaceState({text: 'error'}); - }); - }, - render: function() { - return
{}}>{this.state.text}
; - }, - }); - - spyOn(console, 'error'); - ReactServerRendering.renderToString(); - jest.runOnlyPendingTimers(); - expect(console.error.calls.count()).toBe(1); - expect(console.error.calls.mostRecent().args[0]).toBe( - 'Warning: replaceState(...): Can only update a mounting component. ' + - 'This usually means you called replaceState() outside componentWillMount() on the server. ' + - 'This is a no-op. Please check the code for the Bar component.' - ); - var markup = ReactServerRendering.renderToStaticMarkup(); - expect(markup).toBe('
hello
'); - }); - it('warns with a no-op when an async forceUpdate is triggered', () => { class Baz extends React.Component { componentWillMount() { diff --git a/src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js b/src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js index 34e14145a79c..71bb4cb1824b 100644 --- a/src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js +++ b/src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js @@ -187,18 +187,14 @@ describe('ReactDOMComponent', () => { spyOn(console, 'error'); var div = document.createElement('div'); - var One = React.createClass({ - render: function() { - return this.props.inline ? - : -
; - }, - }); - var Two = React.createClass({ - render: function() { - return
; - }, - }); + function One(props) { + return props.inline ? + : +
; + } + function Two() { + return
; + } ReactDOM.render(, div); expect(console.error.calls.count()).toBe(1); expect(console.error.calls.argsFor(0)[0]).toBe( @@ -1256,12 +1252,12 @@ describe('ReactDOMComponent', () => { it('gives useful context in warnings', () => { spyOn(console, 'error'); - var Row = React.createClass({ - render: () => , - }); - var FancyRow = React.createClass({ - render: () => , - }); + function Row() { + return ; + } + function FancyRow() { + return ; + } class Table extends React.Component { render() { @@ -1275,24 +1271,24 @@ describe('ReactDOMComponent', () => { } } - var Viz1 = React.createClass({ - render: () =>
, - }); - var App1 = React.createClass({ - render: () => , - }); + function Viz1() { + return
; + } + function App1() { + return ; + } ReactTestUtils.renderIntoDocument(); expect(console.error.calls.count()).toBe(1); expect(console.error.calls.argsFor(0)[0]).toContain( 'See Viz1 > table > FancyRow > Row > tr.' ); - var Viz2 = React.createClass({ - render: () => , - }); - var App2 = React.createClass({ - render: () => , - }); + function Viz2() { + return ; + } + function App2() { + return ; + } ReactTestUtils.renderIntoDocument(); expect(console.error.calls.count()).toBe(2); expect(console.error.calls.argsFor(1)[0]).toContain( diff --git a/src/renderers/shared/__tests__/ReactPerf-test.js b/src/renderers/shared/__tests__/ReactPerf-test.js index dfd1e517fc2d..25ffd77d7bc7 100644 --- a/src/renderers/shared/__tests__/ReactPerf-test.js +++ b/src/renderers/shared/__tests__/ReactPerf-test.js @@ -59,16 +59,16 @@ describe('ReactPerf', () => { } }; - LifeCycle = React.createClass({ - shouldComponentUpdate: emptyFunction.thatReturnsTrue, - componentWillMount: emptyFunction, - componentDidMount: emptyFunction, - componentWillReceiveProps: emptyFunction, - componentWillUpdate: emptyFunction, - componentDidUpdate: emptyFunction, - componentWillUnmount: emptyFunction, - render: emptyFunction.thatReturnsNull, - }); + LifeCycle = class extends React.Component { + shouldComponentUpdate = emptyFunction.thatReturnsTrue; + componentWillMount = emptyFunction; + componentDidMount = emptyFunction; + componentWillReceiveProps = emptyFunction; + componentWillUpdate = emptyFunction; + componentDidUpdate = emptyFunction; + componentWillUnmount = emptyFunction; + render = emptyFunction.thatReturnsNull; + }; }); afterEach(() => { diff --git a/src/renderers/shared/fiber/__tests__/ReactIncremental-test.js b/src/renderers/shared/fiber/__tests__/ReactIncremental-test.js index f13573c91fdf..24f0bddfa72c 100644 --- a/src/renderers/shared/fiber/__tests__/ReactIncremental-test.js +++ b/src/renderers/shared/fiber/__tests__/ReactIncremental-test.js @@ -751,15 +751,13 @@ describe('ReactIncremental', () => { it('can replaceState', () => { let instance; - const Bar = React.createClass({ - getInitialState() { - instance = this; - return { a: 'a' }; - }, + class Bar extends React.Component { + state = {a: 'a'}; render() { + instance = this; return
{this.props.children}
; - }, - }); + } + } function Foo() { return ( @@ -773,7 +771,7 @@ describe('ReactIncremental', () => { ReactNoop.flush(); instance.setState({ b: 'b' }); instance.setState({ c: 'c' }); - instance.replaceState({ d: 'd' }); + instance.updater.enqueueReplaceState(instance, {d: 'd'}); ReactNoop.flush(); expect(instance.state).toEqual({ d: 'd' }); }); diff --git a/src/renderers/shared/hooks/__tests__/ReactComponentTreeHook-test.js b/src/renderers/shared/hooks/__tests__/ReactComponentTreeHook-test.js index 61fd34902d2e..d639848ae2d6 100644 --- a/src/renderers/shared/hooks/__tests__/ReactComponentTreeHook-test.js +++ b/src/renderers/shared/hooks/__tests__/ReactComponentTreeHook-test.js @@ -1743,8 +1743,15 @@ describe('ReactComponentTreeHook', () => { return addendum.replace(/\(at .+?:\d+\)/g, '(at **)'); } - var Anon = React.createClass({displayName: null, render: () => null}); - var Orange = React.createClass({render: () => null}); + function Anon() { + return null; + } + Object.defineProperty(Anon, 'name', { + value: null, + }); + function Orange() { + return null; + } expect(getAddendum()).toBe( '' diff --git a/src/renderers/shared/stack/reconciler/__tests__/ReactComponentLifeCycle-test.js b/src/renderers/shared/stack/reconciler/__tests__/ReactComponentLifeCycle-test.js index a8a139b015b6..c1addcafd703 100644 --- a/src/renderers/shared/stack/reconciler/__tests__/ReactComponentLifeCycle-test.js +++ b/src/renderers/shared/stack/reconciler/__tests__/ReactComponentLifeCycle-test.js @@ -245,23 +245,28 @@ describe('ReactComponentLifeCycle', () => { it('should correctly determine if a component is mounted', () => { spyOn(console, 'error'); - var Component = React.createClass({ - componentWillMount: function() { - expect(this.isMounted()).toBeFalsy(); - }, - componentDidMount: function() { - expect(this.isMounted()).toBeTruthy(); - }, - render: function() { - expect(this.isMounted()).toBeFalsy(); + class Component extends React.Component { + _isMounted() { + // No longer a public API, but we can test that it works internally by + // reaching into the updater. + return this.updater.isMounted(this); + } + componentWillMount() { + expect(this._isMounted()).toBeFalsy(); + } + componentDidMount() { + expect(this._isMounted()).toBeTruthy(); + } + render() { + expect(this._isMounted()).toBeFalsy(); return
; - }, - }); + } + } var element = ; var instance = ReactTestUtils.renderIntoDocument(element); - expect(instance.isMounted()).toBeTruthy(); + expect(instance._isMounted()).toBeTruthy(); expect(console.error.calls.count()).toBe(1); expect(console.error.calls.argsFor(0)[0]).toContain( @@ -271,23 +276,28 @@ describe('ReactComponentLifeCycle', () => { it('should correctly determine if a null component is mounted', () => { spyOn(console, 'error'); - var Component = React.createClass({ - componentWillMount: function() { - expect(this.isMounted()).toBeFalsy(); - }, - componentDidMount: function() { - expect(this.isMounted()).toBeTruthy(); - }, - render: function() { - expect(this.isMounted()).toBeFalsy(); + class Component extends React.Component { + _isMounted() { + // No longer a public API, but we can test that it works internally by + // reaching into the updater. + return this.updater.isMounted(this); + } + componentWillMount() { + expect(this._isMounted()).toBeFalsy(); + } + componentDidMount() { + expect(this._isMounted()).toBeTruthy(); + } + render() { + expect(this._isMounted()).toBeFalsy(); return null; - }, - }); + } + } var element = ; var instance = ReactTestUtils.renderIntoDocument(element); - expect(instance.isMounted()).toBeTruthy(); + expect(instance._isMounted()).toBeTruthy(); expect(console.error.calls.count()).toBe(1); expect(console.error.calls.argsFor(0)[0]).toContain( @@ -296,38 +306,38 @@ describe('ReactComponentLifeCycle', () => { }); it('isMounted should return false when unmounted', () => { - var Component = React.createClass({ - render: function() { + class Component extends React.Component { + render() { return
; - }, - }); + } + } var container = document.createElement('div'); var instance = ReactDOM.render(, container); - expect(instance.isMounted()).toBe(true); + // No longer a public API, but we can test that it works internally by + // reaching into the updater. + expect(instance.updater.isMounted(instance)).toBe(true); ReactDOM.unmountComponentAtNode(container); - expect(instance.isMounted()).toBe(false); + expect(instance.updater.isMounted(instance)).toBe(false); }); it('warns if findDOMNode is used inside render', () => { spyOn(console, 'error'); - var Component = React.createClass({ - getInitialState: function() { - return {isMounted: false}; - }, - componentDidMount: function() { + class Component extends React.Component { + state = {isMounted: false}; + componentDidMount() { this.setState({isMounted: true}); - }, - render: function() { + } + render() { if (this.state.isMounted) { expect(ReactDOM.findDOMNode(this).tagName).toBe('DIV'); } return
; - }, - }); + } + } ReactTestUtils.renderIntoDocument(); expect(console.error.calls.count()).toBe(1); @@ -539,30 +549,31 @@ describe('ReactComponentLifeCycle', () => { return true; }; }; - var Outer = React.createClass({ - render: function() { + class Outer extends React.Component { + componentWillMount = logger('outer componentWillMount'); + componentDidMount = logger('outer componentDidMount'); + componentWillReceiveProps = logger('outer componentWillReceiveProps'); + shouldComponentUpdate = logger('outer shouldComponentUpdate'); + componentWillUpdate = logger('outer componentWillUpdate'); + componentDidUpdate = logger('outer componentDidUpdate'); + componentWillUnmount = logger('outer componentWillUnmount'); + render() { return
; - }, - componentWillMount: logger('outer componentWillMount'), - componentDidMount: logger('outer componentDidMount'), - componentWillReceiveProps: logger('outer componentWillReceiveProps'), - shouldComponentUpdate: logger('outer shouldComponentUpdate'), - componentWillUpdate: logger('outer componentWillUpdate'), - componentDidUpdate: logger('outer componentDidUpdate'), - componentWillUnmount: logger('outer componentWillUnmount'), - }); - var Inner = React.createClass({ - render: function() { + } + } + + class Inner extends React.Component { + componentWillMount = logger('inner componentWillMount'); + componentDidMount = logger('inner componentDidMount'); + componentWillReceiveProps = logger('inner componentWillReceiveProps'); + shouldComponentUpdate = logger('inner shouldComponentUpdate'); + componentWillUpdate = logger('inner componentWillUpdate'); + componentDidUpdate = logger('inner componentDidUpdate'); + componentWillUnmount = logger('inner componentWillUnmount'); + render() { return {this.props.x}; - }, - componentWillMount: logger('inner componentWillMount'), - componentDidMount: logger('inner componentDidMount'), - componentWillReceiveProps: logger('inner componentWillReceiveProps'), - shouldComponentUpdate: logger('inner shouldComponentUpdate'), - componentWillUpdate: logger('inner componentWillUpdate'), - componentDidUpdate: logger('inner componentDidUpdate'), - componentWillUnmount: logger('inner componentWillUnmount'), - }); + } + } var container = document.createElement('div'); diff --git a/src/renderers/shared/stack/reconciler/__tests__/ReactCompositeComponent-test.js b/src/renderers/shared/stack/reconciler/__tests__/ReactCompositeComponent-test.js index f38711594015..66323dc6ed8b 100644 --- a/src/renderers/shared/stack/reconciler/__tests__/ReactCompositeComponent-test.js +++ b/src/renderers/shared/stack/reconciler/__tests__/ReactCompositeComponent-test.js @@ -169,65 +169,6 @@ describe('ReactCompositeComponent', () => { expect(instance.getAnchor().className).toBe(''); }); - it('should auto bind methods and values correctly', () => { - spyOn(console, 'error'); - - var ComponentClass = React.createClass({ - getInitialState: function() { - return {valueToReturn: 'hi'}; - }, - methodToBeExplicitlyBound: function() { - return this; - }, - methodAutoBound: function() { - return this; - }, - render: function() { - return
; - }, - }); - var instance = ; - - // Next, prove that once mounted, the scope is bound correctly to the actual - // component. - var mountedInstance = ReactTestUtils.renderIntoDocument(instance); - - expect(function() { - mountedInstance.methodToBeExplicitlyBound.bind(instance)(); - }).not.toThrow(); - expect(function() { - mountedInstance.methodAutoBound(); - }).not.toThrow(); - - expect(console.error.calls.count()).toBe(1); - var explicitlyBound = mountedInstance.methodToBeExplicitlyBound.bind( - mountedInstance - ); - expect(console.error.calls.count()).toBe(2); - var autoBound = mountedInstance.methodAutoBound; - - var context = {}; - expect(explicitlyBound.call(context)).toBe(mountedInstance); - expect(autoBound.call(context)).toBe(mountedInstance); - - expect(explicitlyBound.call(mountedInstance)).toBe(mountedInstance); - expect(autoBound.call(mountedInstance)).toBe(mountedInstance); - - }); - - it('should not pass this to getDefaultProps', () => { - var Component = React.createClass({ - getDefaultProps: function() { - expect(this.render).not.toBeDefined(); - return {}; - }, - render: function() { - return
; - }, - }); - ReactTestUtils.renderIntoDocument(); - }); - it('should use default values for undefined props', () => { class Component extends React.Component { static defaultProps = {prop: 'testKey'}; @@ -1178,17 +1119,17 @@ describe('ReactCompositeComponent', () => { }); it('should replace state', () => { - var Moo = React.createClass({ - getInitialState: function() { - return {x: 1}; - }, - render: function() { + class Moo extends React.Component { + state = {x: 1}; + render() { return
; - }, - }); + } + } var moo = ReactTestUtils.renderIntoDocument(); - moo.replaceState({y: 2}); + // No longer a public API, but we can test that it works internally by + // reaching into the updater. + moo.updater.enqueueReplaceState(moo, {y: 2}); expect('x' in moo.state).toBe(false); expect(moo.state.y).toBe(2); }); @@ -1200,21 +1141,22 @@ describe('ReactCompositeComponent', () => { NotActuallyImmutable.prototype.amIImmutable = function() { return true; }; - var Moo = React.createClass({ - getInitialState: function() { - return new NotActuallyImmutable('first'); - }, - render: function() { + class Moo extends React.Component { + state = new NotActuallyImmutable('first'); + // No longer a public API, but we can test that it works internally by + // reaching into the updater. + _replaceState = update => this.updater.enqueueReplaceState(this, update); + render() { return
; - }, - }); + } + } var moo = ReactTestUtils.renderIntoDocument(); expect(moo.state.str).toBe('first'); expect(moo.state.amIImmutable()).toBe(true); var secondState = new NotActuallyImmutable('second'); - moo.replaceState(secondState); + moo._replaceState(secondState); expect(moo.state.str).toBe('second'); expect(moo.state.amIImmutable()).toBe(true); expect(moo.state).toBe(secondState); @@ -1228,14 +1170,14 @@ describe('ReactCompositeComponent', () => { var fifthState = new NotActuallyImmutable('fifth'); ReactUpdates.batchedUpdates(function() { moo.setState({str: 'fourth'}); - moo.replaceState(fifthState); + moo._replaceState(fifthState); }); expect(moo.state).toBe(fifthState); // When more than one state update is enqueued, we have the same behavior var sixthState = new NotActuallyImmutable('sixth'); ReactUpdates.batchedUpdates(function() { - moo.replaceState(sixthState); + moo._replaceState(sixthState); moo.setState({str: 'seventh'}); }); expect(moo.state.str).toBe('seventh'); diff --git a/src/renderers/shared/stack/reconciler/__tests__/ReactCompositeComponentState-test.js b/src/renderers/shared/stack/reconciler/__tests__/ReactCompositeComponentState-test.js index c758aa5d2e66..3385d7a4d527 100644 --- a/src/renderers/shared/stack/reconciler/__tests__/ReactCompositeComponentState-test.js +++ b/src/renderers/shared/stack/reconciler/__tests__/ReactCompositeComponentState-test.js @@ -23,34 +23,34 @@ describe('ReactCompositeComponent-state', () => { ReactDOM = require('ReactDOM'); - TestComponent = React.createClass({ - peekAtState: function(from, state) { - state = state || this.state; - this.props.stateListener(from, state && state.color); - }, + TestComponent = class extends React.Component { + constructor(props) { + super(props); + this.peekAtState('getInitialState', undefined, props); + this.state = {color: 'red'}; + } + + peekAtState = (from, state = this.state, props = this.props) => { + props.stateListener(from, state && state.color); + } - peekAtCallback: function(from) { + peekAtCallback = from => { return () => this.peekAtState(from); - }, + } - setFavoriteColor: function(nextColor) { + setFavoriteColor(nextColor) { this.setState( {color: nextColor}, this.peekAtCallback('setFavoriteColor') ); - }, - - getInitialState: function() { - this.peekAtState('getInitialState'); - return {color: 'red'}; - }, + } - render: function() { + render() { this.peekAtState('render'); return
{this.state.color}
; - }, + } - componentWillMount: function() { + componentWillMount() { this.peekAtState('componentWillMount-start'); this.setState(function(state) { this.peekAtState('before-setState-sunrise', state); @@ -71,25 +71,27 @@ describe('ReactCompositeComponent-state', () => { this.peekAtState('after-setState-orange', state); }); this.peekAtState('componentWillMount-end'); - }, + } - componentDidMount: function() { + componentDidMount() { this.peekAtState('componentDidMount-start'); this.setState( {color: 'yellow'}, this.peekAtCallback('setState-yellow') ); this.peekAtState('componentDidMount-end'); - }, + } - componentWillReceiveProps: function(newProps) { + componentWillReceiveProps(newProps) { this.peekAtState('componentWillReceiveProps-start'); if (newProps.nextColor) { this.setState(function(state) { this.peekAtState('before-setState-receiveProps', state); return {color: newProps.nextColor}; }); - this.replaceState({color: undefined}); + // No longer a public API, but we can test that it works internally by + // reaching into the updater. + this.updater.enqueueReplaceState(this, {color: undefined}); this.setState( function(state) { this.peekAtState('before-setState-again-receiveProps', state); @@ -102,28 +104,28 @@ describe('ReactCompositeComponent-state', () => { }); } this.peekAtState('componentWillReceiveProps-end'); - }, + } - shouldComponentUpdate: function(nextProps, nextState) { + shouldComponentUpdate(nextProps, nextState) { this.peekAtState('shouldComponentUpdate-currentState'); this.peekAtState('shouldComponentUpdate-nextState', nextState); return true; - }, + } - componentWillUpdate: function(nextProps, nextState) { + componentWillUpdate(nextProps, nextState) { this.peekAtState('componentWillUpdate-currentState'); this.peekAtState('componentWillUpdate-nextState', nextState); - }, + } - componentDidUpdate: function(prevProps, prevState) { + componentDidUpdate(prevProps, prevState) { this.peekAtState('componentDidUpdate-currentState'); this.peekAtState('componentDidUpdate-prevState', prevState); - }, + } - componentWillUnmount: function() { + componentWillUnmount() { this.peekAtState('componentWillUnmount'); - }, - }); + } + }; }); it('should support setting state', () => { diff --git a/src/renderers/shared/stack/reconciler/__tests__/ReactMultiChild-test.js b/src/renderers/shared/stack/reconciler/__tests__/ReactMultiChild-test.js index 4fa299e3fda2..4d9bffc42516 100644 --- a/src/renderers/shared/stack/reconciler/__tests__/ReactMultiChild-test.js +++ b/src/renderers/shared/stack/reconciler/__tests__/ReactMultiChild-test.js @@ -33,14 +33,14 @@ describe('ReactMultiChild', () => { var mockUpdate = jest.fn(); var mockUnmount = jest.fn(); - var MockComponent = React.createClass({ - componentDidMount: mockMount, - componentDidUpdate: mockUpdate, - componentWillUnmount: mockUnmount, - render: function() { + class MockComponent extends React.Component { + componentDidMount = mockMount; + componentDidUpdate = mockUpdate; + componentWillUnmount = mockUnmount; + render() { return ; - }, - }); + } + } expect(mockMount.mock.calls.length).toBe(0); expect(mockUpdate.mock.calls.length).toBe(0); @@ -65,13 +65,13 @@ describe('ReactMultiChild', () => { var mockMount = jest.fn(); var mockUnmount = jest.fn(); - var MockComponent = React.createClass({ - componentDidMount: mockMount, - componentWillUnmount: mockUnmount, - render: function() { + class MockComponent extends React.Component { + componentDidMount = mockMount; + componentWillUnmount = mockUnmount; + render() { return ; - }, - }); + } + } expect(mockMount.mock.calls.length).toBe(0); expect(mockUnmount.mock.calls.length).toBe(0); @@ -93,13 +93,13 @@ describe('ReactMultiChild', () => { var mockMount = jest.fn(); var mockUnmount = jest.fn(); - var MockComponent = React.createClass({ - componentDidMount: mockMount, - componentWillUnmount: mockUnmount, - render: function() { + class MockComponent extends React.Component { + componentDidMount = mockMount; + componentWillUnmount = mockUnmount; + render() { return ; - }, - }); + } + } class WrapperComponent extends React.Component { render() { @@ -130,13 +130,13 @@ describe('ReactMultiChild', () => { var mockMount = jest.fn(); var mockUnmount = jest.fn(); - var MockComponent = React.createClass({ - componentDidMount: mockMount, - componentWillUnmount: mockUnmount, - render: function() { + class MockComponent extends React.Component { + componentDidMount = mockMount; + componentWillUnmount = mockUnmount; + render() { return ; - }, - }); + } + } expect(mockMount.mock.calls.length).toBe(0); expect(mockUnmount.mock.calls.length).toBe(0); diff --git a/src/renderers/shared/stack/reconciler/__tests__/ReactStateSetters-test.js b/src/renderers/shared/stack/reconciler/__tests__/ReactStateSetters-test.js deleted file mode 100644 index c813dfe09c40..000000000000 --- a/src/renderers/shared/stack/reconciler/__tests__/ReactStateSetters-test.js +++ /dev/null @@ -1,152 +0,0 @@ -/** - * Copyright 2013-present, 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 = require('React'); -var ReactStateSetters = require('ReactStateSetters'); -var ReactTestUtils = require('ReactTestUtils'); - -var TestComponent; -var TestComponentWithMixin; - -describe('ReactStateSetters', () => { - beforeEach(() => { - jest.resetModuleRegistry(); - - TestComponent = class extends React.Component { - state = {foo: 'foo'}; - - render() { - return
; - } - }; - - TestComponentWithMixin = React.createClass({ - mixins: [ReactStateSetters.Mixin], - - getInitialState: function() { - return {foo: 'foo'}; - }, - - render: function() { - return
; - }, - }); - }); - - it('createStateSetter should update state', () => { - var instance = ; - instance = ReactTestUtils.renderIntoDocument(instance); - expect(instance.state).toEqual({foo: 'foo'}); - - var setter = ReactStateSetters.createStateSetter( - instance, - function(a, b, c) { - return { - foo: a + b + c, - bar: a * b * c, - }; - } - ); - expect(instance.state).toEqual({foo: 'foo'}); - - setter(1, 2, 3); - expect(instance.state).toEqual({foo: 6, bar: 6}); - - setter(10, 11, 12); - expect(instance.state).toEqual({foo: 33, bar: 1320}); - }); - - it('createStateKeySetter should update state', () => { - var instance = ; - instance = ReactTestUtils.renderIntoDocument(instance); - expect(instance.state).toEqual({foo: 'foo'}); - - var setter = ReactStateSetters.createStateKeySetter(instance, 'foo'); - - expect(instance.state).toEqual({foo: 'foo'}); - - setter('bar'); - expect(instance.state).toEqual({foo: 'bar'}); - - setter('baz'); - expect(instance.state).toEqual({foo: 'baz'}); - }); - - it('createStateKeySetter is memoized', () => { - var instance = ; - instance = ReactTestUtils.renderIntoDocument(instance); - expect(instance.state).toEqual({foo: 'foo'}); - - var foo1 = ReactStateSetters.createStateKeySetter(instance, 'foo'); - var bar1 = ReactStateSetters.createStateKeySetter(instance, 'bar'); - - var foo2 = ReactStateSetters.createStateKeySetter(instance, 'foo'); - var bar2 = ReactStateSetters.createStateKeySetter(instance, 'bar'); - - expect(foo2).toBe(foo1); - expect(bar2).toBe(bar1); - }); - - it('createStateSetter should update state from mixin', () => { - var instance = ; - instance = ReactTestUtils.renderIntoDocument(instance); - expect(instance.state).toEqual({foo: 'foo'}); - - var setter = instance.createStateSetter( - function(a, b, c) { - return { - foo: a + b + c, - bar: a * b * c, - }; - } - ); - expect(instance.state).toEqual({foo: 'foo'}); - - setter(1, 2, 3); - expect(instance.state).toEqual({foo: 6, bar: 6}); - - setter(10, 11, 12); - expect(instance.state).toEqual({foo: 33, bar: 1320}); - }); - - it('createStateKeySetter should update state with mixin', () => { - var instance = ; - instance = ReactTestUtils.renderIntoDocument(instance); - expect(instance.state).toEqual({foo: 'foo'}); - - var setter = instance.createStateKeySetter('foo'); - - expect(instance.state).toEqual({foo: 'foo'}); - - setter('bar'); - expect(instance.state).toEqual({foo: 'bar'}); - - setter('baz'); - expect(instance.state).toEqual({foo: 'baz'}); - }); - - it('createStateKeySetter is memoized with mixin', () => { - var instance = ; - instance = ReactTestUtils.renderIntoDocument(instance); - expect(instance.state).toEqual({foo: 'foo'}); - - var foo1 = instance.createStateKeySetter('foo'); - var bar1 = instance.createStateKeySetter('bar'); - - var foo2 = instance.createStateKeySetter('foo'); - var bar2 = instance.createStateKeySetter('bar'); - - expect(foo2).toBe(foo1); - expect(bar2).toBe(bar1); - }); -}); diff --git a/src/renderers/shared/stack/reconciler/__tests__/ReactUpdates-test.js b/src/renderers/shared/stack/reconciler/__tests__/ReactUpdates-test.js index 044e709b4b38..026a938eb24d 100644 --- a/src/renderers/shared/stack/reconciler/__tests__/ReactUpdates-test.js +++ b/src/renderers/shared/stack/reconciler/__tests__/ReactUpdates-test.js @@ -391,30 +391,23 @@ describe('ReactUpdates', () => { }, }; - var Box = React.createClass({ - mixins: [UpdateLoggingMixin], - - render: function() { + class Box extends React.Component { + render() { return
{this.props.children}
; - }, - }); - - var Child = React.createClass({ - mixins: [UpdateLoggingMixin], + } + } + Object.assign(Box.prototype, UpdateLoggingMixin); - render: function() { + class Child extends React.Component { + render() { return child; - }, - }); - - var Switcher = React.createClass({ - mixins: [UpdateLoggingMixin], - - getInitialState: function() { - return {tabKey: 'hello'}; - }, + } + } + Object.assign(Child.prototype, UpdateLoggingMixin); - render: function() { + class Switcher extends React.Component { + state = {tabKey: 'hello'}; + render() { var child = this.props.children; return ( @@ -428,20 +421,20 @@ describe('ReactUpdates', () => {
); - }, - }); - - var App = React.createClass({ - mixins: [UpdateLoggingMixin], + } + } + Object.assign(Switcher.prototype, UpdateLoggingMixin); - render: function() { + class App extends React.Component { + render() { return ( ); - }, - }); + } + } + Object.assign(App.prototype, UpdateLoggingMixin); var root = ; root = ReactTestUtils.renderIntoDocument(root); @@ -988,35 +981,6 @@ describe('ReactUpdates', () => { ); }); - it('throws in replaceState if the update callback is not a function', () => { - function Foo() { - this.a = 1; - this.b = 2; - } - var A = React.createClass({ - getInitialState: function() { - return {}; - }, - render: function() { - return
; - }, - }); - var component = ReactTestUtils.renderIntoDocument(); - - expect(() => component.replaceState({}, 'no')).toThrowError( - 'replaceState(...): Expected the last optional `callback` argument ' + - 'to be a function. Instead received: string.' - ); - expect(() => component.replaceState({}, {})).toThrowError( - 'replaceState(...): Expected the last optional `callback` argument ' + - 'to be a function. Instead received: Object.' - ); - expect(() => component.replaceState({}, new Foo())).toThrowError( - 'replaceState(...): Expected the last optional `callback` argument ' + - 'to be a function. Instead received: Foo (keys: a, b).' - ); - }); - it('throws in forceUpdate if the update callback is not a function', () => { function Foo() { this.a = 1; diff --git a/src/test/__tests__/ReactTestUtils-test.js b/src/test/__tests__/ReactTestUtils-test.js index fa202e7e9d8e..f63200f6fd74 100644 --- a/src/test/__tests__/ReactTestUtils-test.js +++ b/src/test/__tests__/ReactTestUtils-test.js @@ -91,12 +91,12 @@ describe('ReactTestUtils', () => { it('should have shallow unmounting', () => { var componentWillUnmount = jest.fn(); - var SomeComponent = React.createClass({ - render: function() { + class SomeComponent extends React.Component { + componentWillUnmount = componentWillUnmount; + render() { return
; - }, - componentWillUnmount, - }); + } + } var shallowRenderer = ReactTestUtils.createRenderer(); shallowRenderer.render(); diff --git a/yarn.lock b/yarn.lock index 240e586d2d61..bf09363a81aa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2060,6 +2060,18 @@ fbjs@^0.8.5: promise "^7.1.1" ua-parser-js "^0.7.9" +fbjs@^0.8.9: + version "0.8.9" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.9.tgz#180247fbd347dcc9004517b904f865400a0c8f14" + dependencies: + core-js "^1.0.0" + isomorphic-fetch "^2.1.1" + loose-envify "^1.0.0" + object-assign "^4.1.0" + promise "^7.1.1" + setimmediate "^1.0.5" + ua-parser-js "^0.7.9" + figures@^1.3.5: version "1.7.0" resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" @@ -3930,11 +3942,7 @@ object-assign@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2" -object-assign@^4.0.1, object-assign@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0" - -object-assign@^4.1.1: +object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -4257,6 +4265,13 @@ rc@~1.1.6: minimist "^1.2.0" strip-json-comments "~1.0.4" +react-create-class@15.5.0-alpha.2: + version "15.5.0-alpha.2" + resolved "https://registry.yarnpkg.com/react-create-class/-/react-create-class-15.5.0-alpha.2.tgz#b6e55133ecebc95cc0eb0a9a0acf1679a86b02d4" + dependencies: + fbjs "^0.8.9" + object-assign "^4.1.1" + read-only-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/read-only-stream/-/read-only-stream-2.0.0.tgz#2724fd6a8113d73764ac288d4386270c1dbf17f0" @@ -4585,6 +4600,10 @@ set-immediate-shim@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" +setimmediate@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + sha.js@^2.3.6, sha.js@~2.4.4: version "2.4.8" resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.8.tgz#37068c2c476b6baf402d14a49c67f597921f634f"