react / react-0.13.3 / examples / basic-commonjs / node_modules / reactify / node_modules / react-tools / src / core / __tests__ / ReactComponentLifeCycle-test.js
81155 views/**1* Copyright 2013-2014, Facebook, Inc.2* All rights reserved.3*4* This source code is licensed under the BSD-style license found in the5* LICENSE file in the root directory of this source tree. An additional grant6* of patent rights can be found in the PATENTS file in the same directory.7*8* @emails react-core9*/1011"use strict";1213var React;14var ReactTestUtils;15var ReactComponent;16var ReactCompositeComponent;17var ComponentLifeCycle;18var CompositeComponentLifeCycle;1920var clone = function(o) {21return JSON.parse(JSON.stringify(o));22};232425var GET_INIT_STATE_RETURN_VAL = {26hasWillMountCompleted: false,27hasRenderCompleted: false,28hasDidMountCompleted: false,29hasWillUnmountCompleted: false30};3132var INIT_RENDER_STATE = {33hasWillMountCompleted: true,34hasRenderCompleted: false,35hasDidMountCompleted: false,36hasWillUnmountCompleted: false37};3839var DID_MOUNT_STATE = {40hasWillMountCompleted: true,41hasRenderCompleted: true,42hasDidMountCompleted: false,43hasWillUnmountCompleted: false44};4546var NEXT_RENDER_STATE = {47hasWillMountCompleted: true,48hasRenderCompleted: true,49hasDidMountCompleted: true,50hasWillUnmountCompleted: false51};5253var WILL_UNMOUNT_STATE = {54hasWillMountCompleted: true,55hasDidMountCompleted: true,56hasRenderCompleted: true,57hasWillUnmountCompleted: false58};5960var POST_WILL_UNMOUNT_STATE = {61hasWillMountCompleted: true,62hasDidMountCompleted: true,63hasRenderCompleted: true,64hasWillUnmountCompleted: true65};6667/**68* TODO: We should make any setState calls fail in69* `getInitialState` and `componentWillMount`. They will usually fail70* anyways because `this._renderedComponent` is empty, however, if a component71* is *reused*, then that won't be the case and things will appear to work in72* some cases. Better to just block all updates in initialization.73*/74describe('ReactComponentLifeCycle', function() {75beforeEach(function() {76require('mock-modules').dumpCache();77React = require('React');78ReactTestUtils = require('ReactTestUtils');79ReactComponent = require('ReactComponent');80ReactCompositeComponent = require('ReactCompositeComponent');81ComponentLifeCycle = ReactComponent.LifeCycle;82CompositeComponentLifeCycle = ReactCompositeComponent.LifeCycle;83});8485it('should not reuse an instance when it has been unmounted', function() {86var container = document.createElement('div');87var StatefulComponent = React.createClass({88getInitialState: function() {89return { };90},91render: function() {92return (93<div></div>94);95}96});97var element = <StatefulComponent />;98var firstInstance = React.render(element, container);99React.unmountComponentAtNode(container);100var secondInstance = React.render(element, container);101expect(firstInstance).not.toBe(secondInstance);102});103104/**105* If a state update triggers rerendering that in turn fires an onDOMReady,106* that second onDOMReady should not fail.107*/108it('it should fire onDOMReady when already in onDOMReady', function() {109110var _testJournal = [];111112var Child = React.createClass({113componentDidMount: function() {114_testJournal.push('Child:onDOMReady');115},116render: function() {117return <div></div>;118}119});120121var SwitcherParent = React.createClass({122getInitialState: function() {123_testJournal.push('SwitcherParent:getInitialState');124return {showHasOnDOMReadyComponent: false};125},126componentDidMount: function() {127_testJournal.push('SwitcherParent:onDOMReady');128this.switchIt();129},130switchIt: function() {131this.setState({showHasOnDOMReadyComponent: true});132},133render: function() {134return (135<div>{136this.state.showHasOnDOMReadyComponent ?137<Child /> :138<div> </div>139}</div>140);141}142});143144var instance = <SwitcherParent />;145instance = ReactTestUtils.renderIntoDocument(instance);146expect(_testJournal).toEqual([147'SwitcherParent:getInitialState',148'SwitcherParent:onDOMReady',149'Child:onDOMReady'150]);151});152153// You could assign state here, but not access members of it, unless you154// had provided a getInitialState method.155it('throws when accessing state in componentWillMount', function() {156var StatefulComponent = React.createClass({157componentWillMount: function() {158this.state.yada;159},160render: function() {161return (162<div></div>163);164}165});166var instance = <StatefulComponent />;167expect(function() {168instance = ReactTestUtils.renderIntoDocument(instance);169}).toThrow();170});171172it('should allow update state inside of componentWillMount', function() {173var StatefulComponent = React.createClass({174componentWillMount: function() {175this.setState({stateField: 'something'});176},177render: function() {178return (179<div></div>180);181}182});183var instance = <StatefulComponent />;184expect(function() {185instance = ReactTestUtils.renderIntoDocument(instance);186}).not.toThrow();187});188189it('should allow update state inside of getInitialState', function() {190var StatefulComponent = React.createClass({191getInitialState: function() {192this.setState({stateField: 'something'});193194return {stateField: 'somethingelse'};195},196render: function() {197return (198<div></div>199);200}201});202var instance = <StatefulComponent />;203expect(function() {204instance = ReactTestUtils.renderIntoDocument(instance);205}).not.toThrow();206207// The return value of getInitialState overrides anything from setState208expect(instance.state.stateField).toEqual('somethingelse');209});210211212it('should carry through each of the phases of setup', function() {213var LifeCycleComponent = React.createClass({214getInitialState: function() {215this._testJournal = {};216var initState = {217hasWillMountCompleted: false,218hasDidMountCompleted: false,219hasRenderCompleted: false,220hasWillUnmountCompleted: false221};222this._testJournal.returnedFromGetInitialState = clone(initState);223this._testJournal.lifeCycleAtStartOfGetInitialState =224this._lifeCycleState;225this._testJournal.compositeLifeCycleAtStartOfGetInitialState =226this._compositeLifeCycleState;227return initState;228},229230componentWillMount: function() {231this._testJournal.stateAtStartOfWillMount = clone(this.state);232this._testJournal.lifeCycleAtStartOfWillMount =233this._lifeCycleState;234this._testJournal.compositeLifeCycleAtStartOfWillMount =235this._compositeLifeCycleState;236this.state.hasWillMountCompleted = true;237},238239componentDidMount: function() {240this._testJournal.stateAtStartOfDidMount = clone(this.state);241this._testJournal.lifeCycleAtStartOfDidMount =242this._lifeCycleState;243this.setState({hasDidMountCompleted: true});244},245246render: function() {247var isInitialRender = !this.state.hasRenderCompleted;248if (isInitialRender) {249this._testJournal.stateInInitialRender = clone(this.state);250this._testJournal.lifeCycleInInitialRender = this._lifeCycleState;251this._testJournal.compositeLifeCycleInInitialRender =252this._compositeLifeCycleState;253} else {254this._testJournal.stateInLaterRender = clone(this.state);255this._testJournal.lifeCycleInLaterRender = this._lifeCycleState;256}257// you would *NEVER* do anything like this in real code!258this.state.hasRenderCompleted = true;259return (260<div ref="theDiv">261I am the inner DIV262</div>263);264},265266componentWillUnmount: function() {267this._testJournal.stateAtStartOfWillUnmount = clone(this.state);268this._testJournal.lifeCycleAtStartOfWillUnmount =269this._lifeCycleState;270this.state.hasWillUnmountCompleted = true;271}272});273274// A component that is merely "constructed" (as in "constructor") but not275// yet initialized, or rendered.276//277var instance = ReactTestUtils.renderIntoDocument(<LifeCycleComponent />);278279// getInitialState280expect(instance._testJournal.returnedFromGetInitialState).toEqual(281GET_INIT_STATE_RETURN_VAL282);283expect(instance._testJournal.lifeCycleAtStartOfGetInitialState)284.toBe(ComponentLifeCycle.MOUNTED);285expect(instance._testJournal.compositeLifeCycleAtStartOfGetInitialState)286.toBe(CompositeComponentLifeCycle.MOUNTING);287288// componentWillMount289expect(instance._testJournal.stateAtStartOfWillMount).toEqual(290instance._testJournal.returnedFromGetInitialState291);292expect(instance._testJournal.lifeCycleAtStartOfWillMount)293.toBe(ComponentLifeCycle.MOUNTED);294expect(instance._testJournal.compositeLifeCycleAtStartOfWillMount)295.toBe(CompositeComponentLifeCycle.MOUNTING);296297// componentDidMount298expect(instance._testJournal.stateAtStartOfDidMount)299.toEqual(DID_MOUNT_STATE);300expect(instance._testJournal.lifeCycleAtStartOfDidMount).toBe(301ComponentLifeCycle.MOUNTED302);303304// render305expect(instance._testJournal.stateInInitialRender)306.toEqual(INIT_RENDER_STATE);307expect(instance._testJournal.lifeCycleInInitialRender).toBe(308ComponentLifeCycle.MOUNTED309);310expect(instance._testJournal.compositeLifeCycleInInitialRender).toBe(311CompositeComponentLifeCycle.MOUNTING312);313314expect(instance._lifeCycleState).toBe(ComponentLifeCycle.MOUNTED);315316// Now *update the component*317instance.forceUpdate();318319// render 2nd time320expect(instance._testJournal.stateInLaterRender)321.toEqual(NEXT_RENDER_STATE);322expect(instance._testJournal.lifeCycleInLaterRender).toBe(323ComponentLifeCycle.MOUNTED324);325326expect(instance._lifeCycleState).toBe(ComponentLifeCycle.MOUNTED);327328// Now *unmountComponent*329instance.unmountComponent();330331expect(instance._testJournal.stateAtStartOfWillUnmount)332.toEqual(WILL_UNMOUNT_STATE);333// componentWillUnmount called right before unmount.334expect(instance._testJournal.lifeCycleAtStartOfWillUnmount).toBe(335ComponentLifeCycle.MOUNTED336);337338// But the current lifecycle of the component is unmounted.339expect(instance._lifeCycleState).toBe(ComponentLifeCycle.UNMOUNTED);340expect(instance.state).toEqual(POST_WILL_UNMOUNT_STATE);341});342343it('should throw when calling setProps() on an owned component', function() {344/**345* calls setProps in an componentDidMount.346*/347var PropsUpdaterInOnDOMReady = React.createClass({348componentDidMount: function() {349this.refs.theSimpleComponent.setProps({350valueToUseInitially: this.props.valueToUseInOnDOMReady351});352},353render: function() {354return (355<div356className={this.props.valueToUseInitially}357ref="theSimpleComponent"358/>359);360}361});362var instance =363<PropsUpdaterInOnDOMReady364valueToUseInitially="hello"365valueToUseInOnDOMReady="goodbye"366/>;367expect(function() {368instance = ReactTestUtils.renderIntoDocument(instance);369}).toThrow(370'Invariant Violation: replaceProps(...): You called `setProps` or ' +371'`replaceProps` on a component with a parent. This is an anti-pattern ' +372'since props will get reactively updated when rendered. Instead, ' +373'change the owner\'s `render` method to pass the correct value as ' +374'props to the component where it is created.'375);376});377378it('should not throw when updating an auxiliary component', function() {379var Tooltip = React.createClass({380render: function() {381return <div>{this.props.children}</div>;382},383componentDidMount: function() {384this.container = document.createElement('div');385this.updateTooltip();386},387componentDidUpdate: function() {388this.updateTooltip();389},390updateTooltip: function() {391// Even though this.props.tooltip has an owner, updating it shouldn't392// throw here because it's mounted as a root component393React.render(this.props.tooltip, this.container);394}395});396var Component = React.createClass({397render: function() {398return (399<Tooltip400ref="tooltip"401tooltip={<div>{this.props.tooltipText}</div>}>402{this.props.text}403</Tooltip>404);405}406});407408var container = document.createElement('div');409var instance = React.render(410<Component text="uno" tooltipText="one" />,411container412);413414// Since `instance` is a root component, we can set its props. This also415// makes Tooltip rerender the tooltip component, which shouldn't throw.416instance.setProps({text: "dos", tooltipText: "two"});417});418419it('should not allow setProps() called on an unmounted element',420function() {421var PropsToUpdate = React.createClass({422render: function() {423return <div className={this.props.value} ref="theSimpleComponent" />;424}425});426var instance = <PropsToUpdate value="hello" />;427expect(instance.setProps).not.toBeDefined();428});429430it('should allow state updates in componentDidMount', function() {431/**432* calls setState in an componentDidMount.433*/434var SetStateInComponentDidMount = React.createClass({435getInitialState: function() {436return {437stateField: this.props.valueToUseInitially438};439},440componentDidMount: function() {441this.setState({stateField: this.props.valueToUseInOnDOMReady});442},443render: function() {444return (<div></div>);445}446});447var instance =448<SetStateInComponentDidMount449valueToUseInitially="hello"450valueToUseInOnDOMReady="goodbye"451/>;452instance = ReactTestUtils.renderIntoDocument(instance);453expect(instance.state.stateField).toBe('goodbye');454});455456it('should call nested lifecycle methods in the right order', function() {457var log;458var logger = function(msg) {459return function() {460// return true for shouldComponentUpdate461log.push(msg);462return true;463};464};465var Outer = React.createClass({466render: function() {467return <div><Inner x={this.props.x} /></div>;468},469componentWillMount: logger('outer componentWillMount'),470componentDidMount: logger('outer componentDidMount'),471componentWillReceiveProps: logger('outer componentWillReceiveProps'),472shouldComponentUpdate: logger('outer shouldComponentUpdate'),473componentWillUpdate: logger('outer componentWillUpdate'),474componentDidUpdate: logger('outer componentDidUpdate'),475componentWillUnmount: logger('outer componentWillUnmount')476});477var Inner = React.createClass({478render: function() {479return <span>{this.props.x}</span>;480},481componentWillMount: logger('inner componentWillMount'),482componentDidMount: logger('inner componentDidMount'),483componentWillReceiveProps: logger('inner componentWillReceiveProps'),484shouldComponentUpdate: logger('inner shouldComponentUpdate'),485componentWillUpdate: logger('inner componentWillUpdate'),486componentDidUpdate: logger('inner componentDidUpdate'),487componentWillUnmount: logger('inner componentWillUnmount')488});489var instance;490491log = [];492instance = ReactTestUtils.renderIntoDocument(<Outer x={17} />);493expect(log).toEqual([494'outer componentWillMount',495'inner componentWillMount',496'inner componentDidMount',497'outer componentDidMount'498]);499500log = [];501instance.setProps({x: 42});502expect(log).toEqual([503'outer componentWillReceiveProps',504'outer shouldComponentUpdate',505'outer componentWillUpdate',506'inner componentWillReceiveProps',507'inner shouldComponentUpdate',508'inner componentWillUpdate',509'inner componentDidUpdate',510'outer componentDidUpdate'511]);512513log = [];514instance.unmountComponent();515expect(log).toEqual([516'outer componentWillUnmount',517'inner componentWillUnmount'518]);519});520});521522523524