react / react-0.13.3 / examples / basic-commonjs / node_modules / reactify / node_modules / react-tools / src / core / __tests__ / ReactUpdates-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 ReactUpdates;1617describe('ReactUpdates', function() {18beforeEach(function() {19React = require('React');20ReactTestUtils = require('ReactTestUtils');21ReactUpdates = require('ReactUpdates');22});2324it('should batch state when updating state twice', function() {25var updateCount = 0;26var Component = React.createClass({27getInitialState: function() {28return {x: 0};29},30componentDidUpdate: function() {31updateCount++;32},33render: function() {34return <div>{this.state.x}</div>;35}36});3738var instance = ReactTestUtils.renderIntoDocument(<Component />);39expect(instance.state.x).toBe(0);4041ReactUpdates.batchedUpdates(function() {42instance.setState({x: 1});43instance.setState({x: 2});44expect(instance.state.x).toBe(0);45expect(updateCount).toBe(0);46});4748expect(instance.state.x).toBe(2);49expect(updateCount).toBe(1);50});5152it('should batch state when updating two different state keys', function() {53var updateCount = 0;54var Component = React.createClass({55getInitialState: function() {56return {x: 0, y: 0};57},58componentDidUpdate: function() {59updateCount++;60},61render: function() {62return <div>({this.state.x}, {this.state.y})</div>;63}64});6566var instance = ReactTestUtils.renderIntoDocument(<Component />);67expect(instance.state.x).toBe(0);68expect(instance.state.y).toBe(0);6970ReactUpdates.batchedUpdates(function() {71instance.setState({x: 1});72instance.setState({y: 2});73expect(instance.state.x).toBe(0);74expect(instance.state.y).toBe(0);75expect(updateCount).toBe(0);76});7778expect(instance.state.x).toBe(1);79expect(instance.state.y).toBe(2);80expect(updateCount).toBe(1);81});8283it('should batch state and props together', function() {84var updateCount = 0;85var Component = React.createClass({86getInitialState: function() {87return {y: 0};88},89componentDidUpdate: function() {90updateCount++;91},92render: function() {93return <div>({this.props.x}, {this.state.y})</div>;94}95});9697var instance = ReactTestUtils.renderIntoDocument(<Component x={0} />);98expect(instance.props.x).toBe(0);99expect(instance.state.y).toBe(0);100101ReactUpdates.batchedUpdates(function() {102instance.setProps({x: 1});103instance.setState({y: 2});104expect(instance.props.x).toBe(0);105expect(instance.state.y).toBe(0);106expect(updateCount).toBe(0);107});108109expect(instance.props.x).toBe(1);110expect(instance.state.y).toBe(2);111expect(updateCount).toBe(1);112});113114it('should batch parent/child state updates together', function() {115var parentUpdateCount = 0;116var Parent = React.createClass({117getInitialState: function() {118return {x: 0};119},120componentDidUpdate: function() {121parentUpdateCount++;122},123render: function() {124return <div><Child ref="child" x={this.state.x} /></div>;125}126});127var childUpdateCount = 0;128var Child = React.createClass({129getInitialState: function() {130return {y: 0};131},132componentDidUpdate: function() {133childUpdateCount++;134},135render: function() {136return <div>{this.props.x + this.state.y}</div>;137}138});139140var instance = ReactTestUtils.renderIntoDocument(<Parent />);141var child = instance.refs.child;142expect(instance.state.x).toBe(0);143expect(child.state.y).toBe(0);144145ReactUpdates.batchedUpdates(function() {146instance.setState({x: 1});147child.setState({y: 2});148expect(instance.state.x).toBe(0);149expect(child.state.y).toBe(0);150expect(parentUpdateCount).toBe(0);151expect(childUpdateCount).toBe(0);152});153154expect(instance.state.x).toBe(1);155expect(child.state.y).toBe(2);156expect(parentUpdateCount).toBe(1);157expect(childUpdateCount).toBe(1);158});159160it('should batch child/parent state updates together', function() {161var parentUpdateCount = 0;162var Parent = React.createClass({163getInitialState: function() {164return {x: 0};165},166componentDidUpdate: function() {167parentUpdateCount++;168},169render: function() {170return <div><Child ref="child" x={this.state.x} /></div>;171}172});173var childUpdateCount = 0;174var Child = React.createClass({175getInitialState: function() {176return {y: 0};177},178componentDidUpdate: function() {179childUpdateCount++;180},181render: function() {182return <div>{this.props.x + this.state.y}</div>;183}184});185186var instance = ReactTestUtils.renderIntoDocument(<Parent />);187var child = instance.refs.child;188expect(instance.state.x).toBe(0);189expect(child.state.y).toBe(0);190191ReactUpdates.batchedUpdates(function() {192child.setState({y: 2});193instance.setState({x: 1});194expect(instance.state.x).toBe(0);195expect(child.state.y).toBe(0);196expect(parentUpdateCount).toBe(0);197expect(childUpdateCount).toBe(0);198});199200expect(instance.state.x).toBe(1);201expect(child.state.y).toBe(2);202expect(parentUpdateCount).toBe(1);203204// Batching reduces the number of updates here to 1.205expect(childUpdateCount).toBe(1);206});207208it('should support chained state updates', function() {209var updateCount = 0;210var Component = React.createClass({211getInitialState: function() {212return {x: 0};213},214componentDidUpdate: function() {215updateCount++;216},217render: function() {218return <div>{this.state.x}</div>;219}220});221222var instance = ReactTestUtils.renderIntoDocument(<Component />);223expect(instance.state.x).toBe(0);224225var innerCallbackRun = false;226ReactUpdates.batchedUpdates(function() {227instance.setState({x: 1}, function() {228instance.setState({x: 2}, function() {229expect(this).toBe(instance);230innerCallbackRun = true;231expect(instance.state.x).toBe(2);232expect(updateCount).toBe(2);233});234expect(instance.state.x).toBe(1);235expect(updateCount).toBe(1);236});237expect(instance.state.x).toBe(0);238expect(updateCount).toBe(0);239});240241expect(innerCallbackRun).toBeTruthy();242expect(instance.state.x).toBe(2);243expect(updateCount).toBe(2);244});245246it('should batch forceUpdate together', function() {247var shouldUpdateCount = 0;248var updateCount = 0;249var Component = React.createClass({250getInitialState: function() {251return {x: 0};252},253shouldComponentUpdate: function() {254shouldUpdateCount++;255},256componentDidUpdate: function() {257updateCount++;258},259render: function() {260return <div>{this.state.x}</div>;261}262});263264var instance = ReactTestUtils.renderIntoDocument(<Component />);265expect(instance.state.x).toBe(0);266267var callbacksRun = 0;268ReactUpdates.batchedUpdates(function() {269instance.setState({x: 1}, function() {270callbacksRun++;271});272instance.forceUpdate(function() {273callbacksRun++;274});275expect(instance.state.x).toBe(0);276expect(updateCount).toBe(0);277});278279expect(callbacksRun).toBe(2);280// shouldComponentUpdate shouldn't be called since we're forcing281expect(shouldUpdateCount).toBe(0);282expect(instance.state.x).toBe(1);283expect(updateCount).toBe(1);284});285286it('should update children even if parent blocks updates', function() {287var parentRenderCount = 0;288var childRenderCount = 0;289290var Parent = React.createClass({291shouldComponentUpdate: function() {292return false;293},294295render: function() {296parentRenderCount++;297return <Child ref="child" />;298}299});300301var Child = React.createClass({302render: function() {303childRenderCount++;304return <div />;305}306});307308expect(parentRenderCount).toBe(0);309expect(childRenderCount).toBe(0);310311var instance = <Parent />;312instance = ReactTestUtils.renderIntoDocument(instance);313314expect(parentRenderCount).toBe(1);315expect(childRenderCount).toBe(1);316317ReactUpdates.batchedUpdates(function() {318instance.setState({x: 1});319});320321expect(parentRenderCount).toBe(1);322expect(childRenderCount).toBe(1);323324ReactUpdates.batchedUpdates(function() {325instance.refs.child.setState({x: 1});326});327328expect(parentRenderCount).toBe(1);329expect(childRenderCount).toBe(2);330});331332it('should not reconcile children passed via props', function() {333var numMiddleRenders = 0;334var numBottomRenders = 0;335336var Top = React.createClass({337render: function() {338return <Middle><Bottom /></Middle>;339}340});341342var Middle = React.createClass({343componentDidMount: function() {344this.forceUpdate();345},346347render: function() {348numMiddleRenders++;349return <div>{this.props.children}</div>;350}351});352353var Bottom = React.createClass({354render: function() {355numBottomRenders++;356return <span />;357}358});359360ReactTestUtils.renderIntoDocument(<Top />);361expect(numMiddleRenders).toBe(2);362expect(numBottomRenders).toBe(1);363});364365it('should flow updates correctly', function() {366var willUpdates = [];367var didUpdates = [];368369var UpdateLoggingMixin = {370componentWillUpdate: function() {371willUpdates.push(this.constructor.displayName);372},373componentDidUpdate: function() {374didUpdates.push(this.constructor.displayName);375}376};377378var Box = React.createClass({379mixins: [UpdateLoggingMixin],380381render: function() {382return <div ref="boxDiv">{this.props.children}</div>;383}384});385386var Child = React.createClass({387mixins: [UpdateLoggingMixin],388389render: function() {390return <span ref="span">child</span>;391}392});393394var Switcher = React.createClass({395mixins: [UpdateLoggingMixin],396397getInitialState: function() {398return {tabKey: 'hello'};399},400401render: function() {402var child = this.props.children;403404return (405<Box ref="box">406<div407ref="switcherDiv"408style={{409display: this.state.tabKey === child.key ? '' : 'none'410}}>411{child}412</div>413</Box>414);415}416});417418var App = React.createClass({419mixins: [UpdateLoggingMixin],420421render: function() {422return (423<Switcher ref="switcher">424<Child key="hello" ref="child" />425</Switcher>426);427}428});429430var root = <App />;431root = ReactTestUtils.renderIntoDocument(root);432433function expectUpdates(desiredWillUpdates, desiredDidUpdates) {434expect(willUpdates).toEqual(desiredWillUpdates);435expect(didUpdates).toEqual(desiredDidUpdates);436willUpdates.length = 0;437didUpdates.length = 0;438}439440function triggerUpdate(c) {441c.setState({x: 1});442}443444function testUpdates(components, desiredWillUpdates, desiredDidUpdates) {445var i;446447ReactUpdates.batchedUpdates(function() {448for (i = 0; i < components.length; i++) {449triggerUpdate(components[i]);450}451});452453expectUpdates(desiredWillUpdates, desiredDidUpdates);454455// Try them in reverse order456457ReactUpdates.batchedUpdates(function() {458for (i = components.length - 1; i >= 0; i--) {459triggerUpdate(components[i]);460}461});462463expectUpdates(desiredWillUpdates, desiredDidUpdates);464}465466testUpdates(467[root.refs.switcher.refs.box, root.refs.switcher],468// Owner-child relationships have inverse will and did469['Switcher', 'Box'],470['Box', 'Switcher']471);472473testUpdates(474[root.refs.child, root.refs.switcher.refs.box],475// Not owner-child so reconcile independently476['Box', 'Child'],477['Box', 'Child']478);479480testUpdates(481[root.refs.child, root.refs.switcher],482// Switcher owns Box and Child, Box does not own Child483['Switcher', 'Box', 'Child'],484['Box', 'Switcher', 'Child']485);486});487488it('should share reconcile transaction across different roots', function() {489var ReconcileTransaction = ReactUpdates.ReactReconcileTransaction;490spyOn(ReconcileTransaction, 'getPooled').andCallThrough();491492var Component = React.createClass({493render: function() {494return <div>{this.props.text}</div>;495}496});497498var containerA = document.createElement('div');499var containerB = document.createElement('div');500501// Initial renders aren't batched together yet...502ReactUpdates.batchedUpdates(function() {503React.render(<Component text="A1" />, containerA);504React.render(<Component text="B1" />, containerB);505});506expect(ReconcileTransaction.getPooled.calls.length).toBe(2);507508// ...but updates are! Here only one more transaction is used, which means509// we only have to initialize and close the wrappers once.510ReactUpdates.batchedUpdates(function() {511React.render(<Component text="A2" />, containerA);512React.render(<Component text="B2" />, containerB);513});514expect(ReconcileTransaction.getPooled.calls.length).toBe(3);515});516517it('should queue mount-ready handlers across different roots', function() {518// We'll define two components A and B, then update both of them. When A's519// componentDidUpdate handlers is called, B's DOM should already have been520// updated.521522var a;523var b;524525var aUpdated = false;526527var A = React.createClass({528getInitialState: function() {529return {x: 0};530},531componentDidUpdate: function() {532expect(b.getDOMNode().textContent).toBe("B1");533aUpdated = true;534},535render: function() {536return <div>A{this.state.x}</div>;537}538});539540var B = React.createClass({541getInitialState: function() {542return {x: 0};543},544render: function() {545return <div>B{this.state.x}</div>;546}547});548549a = ReactTestUtils.renderIntoDocument(<A />);550b = ReactTestUtils.renderIntoDocument(<B />);551552ReactUpdates.batchedUpdates(function() {553a.setState({x: 1});554b.setState({x: 1});555});556557expect(aUpdated).toBe(true);558});559560it('should flush updates in the correct order', function() {561var updates = [];562var Outer = React.createClass({563getInitialState: function() {564return {x: 0};565},566render: function() {567updates.push('Outer-render-' + this.state.x);568return <div><Inner x={this.state.x} ref="inner" /></div>;569},570componentDidUpdate: function() {571var x = this.state.x;572updates.push('Outer-didUpdate-' + x);573updates.push('Inner-setState-' + x);574this.refs.inner.setState({x: x}, function() {575updates.push('Inner-callback-' + x);576});577}578});579var Inner = React.createClass({580getInitialState: function() {581return {x: 0};582},583render: function() {584updates.push('Inner-render-' + this.props.x + '-' + this.state.x);585return <div />;586},587componentDidUpdate: function() {588updates.push('Inner-didUpdate-' + this.props.x + '-' + this.state.x);589}590});591592var instance = ReactTestUtils.renderIntoDocument(<Outer />);593594updates.push('Outer-setState-1');595instance.setState({x: 1}, function() {596updates.push('Outer-callback-1');597updates.push('Outer-setState-2');598instance.setState({x: 2}, function() {599updates.push('Outer-callback-2');600});601});602603expect(updates).toEqual([604'Outer-render-0',605'Inner-render-0-0',606607'Outer-setState-1',608'Outer-render-1',609'Inner-render-1-0',610'Inner-didUpdate-1-0',611'Outer-didUpdate-1',612'Inner-setState-1',613'Inner-render-1-1',614'Inner-didUpdate-1-1',615'Inner-callback-1',616'Outer-callback-1',617618'Outer-setState-2',619'Outer-render-2',620'Inner-render-2-1',621'Inner-didUpdate-2-1',622'Outer-didUpdate-2',623'Inner-setState-2',624'Inner-render-2-2',625'Inner-didUpdate-2-2',626'Inner-callback-2',627'Outer-callback-2'628]);629});630631it('should queue nested updates', function() {632// See https://github.com/facebook/react/issues/1147633634var X = React.createClass({635getInitialState: function() {636return {s: 0};637},638render: function() {639if (this.state.s === 0) {640return <div>641<span>0</span>642</div>;643} else {644return <div>1</div>;645}646},647go: function() {648this.setState({s: 1});649this.setState({s: 0});650this.setState({s: 1});651}652});653654var Y = React.createClass({655render: function() {656return <div>657<Z />658</div>;659}660});661662var Z = React.createClass({663render: function() { return <div />; },664componentWillUpdate: function() {665x.go();666}667});668669var x;670var y;671672x = ReactTestUtils.renderIntoDocument(<X />);673y = ReactTestUtils.renderIntoDocument(<Y />);674expect(x.getDOMNode().textContent).toBe('0');675676y.forceUpdate();677expect(x.getDOMNode().textContent).toBe('1');678});679680it('should queue updates from during mount', function() {681// See https://github.com/facebook/react/issues/1353682var a;683684var A = React.createClass({685getInitialState: function() {686return {x: 0};687},688componentWillMount: function() {689a = this;690},691render: function() {692return <div>A{this.state.x}</div>;693}694});695696var B = React.createClass({697componentWillMount: function() {698a.setState({x: 1});699},700render: function() {701return <div />;702}703});704705ReactUpdates.batchedUpdates(function() {706ReactTestUtils.renderIntoDocument(707<div>708<A />709<B />710</div>711);712});713714expect(a.state.x).toBe(1);715expect(a.getDOMNode().textContent).toBe('A1');716});717718it('calls componentWillReceiveProps setState callback properly', function() {719var callbackCount = 0;720var A = React.createClass({721getInitialState: function() {722return {x: this.props.x};723},724componentWillReceiveProps: function(nextProps) {725var newX = nextProps.x;726this.setState({x: newX}, function() {727// State should have updated by the time this callback gets called728expect(this.state.x).toBe(newX);729callbackCount++;730});731},732render: function() {733return <div>{this.state.x}</div>;734}735});736737var container = document.createElement('div');738React.render(<A x={1} />, container);739React.render(<A x={2} />, container);740expect(callbackCount).toBe(1);741});742743it('calls asap callbacks properly', function() {744var callbackCount = 0;745var A = React.createClass({746render: function() {747return <div />;748},749componentDidUpdate: function() {750var component = this;751ReactUpdates.asap(function() {752expect(this).toBe(component);753callbackCount++;754ReactUpdates.asap(function() {755callbackCount++;756});757expect(callbackCount).toBe(1);758}, this);759expect(callbackCount).toBe(0);760}761});762763var container = document.createElement('div');764var component = React.render(<A />, container);765component.forceUpdate();766expect(callbackCount).toBe(2);767});768769it('calls asap callbacks with queued updates', function() {770var log = [];771var A = React.createClass({772getInitialState: () => ({updates: 0}),773render: function() {774log.push('render-' + this.state.updates);775return <div />;776},777componentDidUpdate: function() {778if (this.state.updates === 1) {779ReactUpdates.asap(function() {780this.setState({updates: 2}, function() {781ReactUpdates.asap(function() {782log.push('asap-1.2');783});784log.push('setState-cb');785});786log.push('asap-1.1');787}, this);788} else if (this.state.updates === 2) {789ReactUpdates.asap(function() {790log.push('asap-2');791});792}793log.push('didUpdate-' + this.state.updates);794}795});796797var container = document.createElement('div');798var component = React.render(<A />, container);799component.setState({updates: 1});800expect(log).toEqual([801'render-0',802// We do the first update...803'render-1',804'didUpdate-1',805// ...which calls asap and enqueues a second update...806'asap-1.1',807// ...which runs and enqueues the asap-2 log in its didUpdate...808'render-2',809'didUpdate-2',810// ...and runs the setState callback, which enqueues the log for811// asap-1.2.812'setState-cb',813'asap-2',814'asap-1.2'815]);816});817});818819820