react / react-0.13.3 / examples / basic-commonjs / node_modules / reactify / node_modules / react-tools / src / core / __tests__ / ReactMultiChildReconcile-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";1213require('mock-modules');1415var React = require('React');16var ReactTestUtils = require('ReactTestUtils');17var ReactMount = require('ReactMount');1819var mapObject = require('mapObject');2021var stripEmptyValues = function(obj) {22var ret = {};23var name;24for (name in obj) {25if (!obj.hasOwnProperty(name)) {26continue;27}28if (obj[name] !== null && obj[name] !== undefined) {29ret[name] = obj[name];30}31}32return ret;33};3435/**36* Child key names are wrapped like '.$key:0'. We strip the extra chars out37* here. This relies on an implementation detail of the rendering system.38*/39var getOriginalKey = function(childName) {40var match = childName.match(/^\.\$([^.]+)\:0$/);41expect(match).not.toBeNull();42return match[1];43};4445/**46* Contains internal static internal state in order to test that updates to47* existing children won't reinitialize components, when moving children -48* reusing existing DOM/memory resources.49*/50var StatusDisplay = React.createClass({51getInitialState: function() {52return { internalState: Math.random() };53},5455getStatus: function() {56return this.props.status;57},5859getInternalState: function() {60return this.state.internalState;61},6263render: function() {64return (65<div>66{this.state.internalState}67</div>68);69}70});7172/**73* Displays friends statuses.74*/75var FriendsStatusDisplay = React.createClass({76/**77* Retrieves the rendered children in a nice format for comparing to the input78* `this.props.usernameToStatus`. Gets the order directly from each rendered79* child's `index` field. Refs are not maintained in the rendered order, and80* neither is `this._renderedChildren` (surprisingly).81*/82getStatusDisplays: function() {83var name;84var orderOfUsernames = [];85var statusDisplays = this._renderedComponent._renderedChildren;86for (name in statusDisplays) {87var child = statusDisplays[name];88var isPresent = !!child;89if (isPresent) {90orderOfUsernames[child._mountIndex] = getOriginalKey(name);91}92}93var res = {};94var i;95for (i = 0; i < orderOfUsernames.length; i++) {96var key = orderOfUsernames[i];97res[key] = this.refs[key];98}99return res;100},101render: function() {102var children = null;103var key;104for (key in this.props.usernameToStatus) {105var status = this.props.usernameToStatus[key];106children = children || {};107children[key] = !status ? null :108<StatusDisplay ref={key} status={status} />;109}110return (111<div>112{children}113</div>114);115}116});117118119function getInteralStateByUserName(statusDisplays) {120return mapObject(statusDisplays, function(statusDisplay, key) {121return statusDisplay.getInternalState();122});123}124125/**126* Verifies that the rendered `StatusDisplay` instances match the `props` that127* were responsible for allocating them. Checks the content of the user's status128* message as well as the order of them.129*/130function verifyStatuses(statusDisplays, props) {131var nonEmptyStatusDisplays = stripEmptyValues(statusDisplays);132var nonEmptyStatusProps = stripEmptyValues(props.usernameToStatus);133var username;134expect(Object.keys(nonEmptyStatusDisplays).length)135.toEqual(Object.keys(nonEmptyStatusProps).length);136for (username in nonEmptyStatusDisplays) {137if (!nonEmptyStatusDisplays.hasOwnProperty(username)) {138continue;139}140expect(nonEmptyStatusDisplays[username].getStatus())141.toEqual(nonEmptyStatusProps[username]);142}143144// now go the other way to make sure we got them all.145for (username in nonEmptyStatusProps) {146if (!nonEmptyStatusProps.hasOwnProperty(username)) {147continue;148}149expect(nonEmptyStatusDisplays[username].getStatus())150.toEqual(nonEmptyStatusProps[username]);151}152153expect(Object.keys(nonEmptyStatusDisplays))154.toEqual(Object.keys(nonEmptyStatusProps));155}156157/**158* For all statusDisplays that existed in the previous iteration of the159* sequence, verify that the state has been preserved. `StatusDisplay` contains160* a unique number that allows us to track internal state across ordering161* movements.162*/163function verifyStatesPreserved(lastInternalStates, statusDisplays) {164var key;165for (key in statusDisplays) {166if (!statusDisplays.hasOwnProperty(key)) {167continue;168}169if (lastInternalStates[key]) {170expect(lastInternalStates[key])171.toEqual(statusDisplays[key].getInternalState());172}173}174}175176177/**178* Verifies that the internal representation of a set of `renderedChildren`179* accurately reflects what is in the DOM.180*/181function verifyDomOrderingAccurate(parentInstance, statusDisplays) {182var containerNode = parentInstance.getDOMNode();183var statusDisplayNodes = containerNode.childNodes;184var i;185var orderedDomIds = [];186for (i=0; i < statusDisplayNodes.length; i++) {187orderedDomIds.push(ReactMount.getID(statusDisplayNodes[i]));188}189190var orderedLogicalIds = [];191var username;192for (username in statusDisplays) {193if (!statusDisplays.hasOwnProperty(username)) {194continue;195}196var statusDisplay = statusDisplays[username];197orderedLogicalIds.push(statusDisplay._rootNodeID);198}199expect(orderedDomIds).toEqual(orderedLogicalIds);200}201202/**203* Todo: Check that internal state is preserved across transitions204*/205function testPropsSequence(sequence) {206var i;207var parentInstance = ReactTestUtils.renderIntoDocument(208<FriendsStatusDisplay {...sequence[0]} />209);210var statusDisplays = parentInstance.getStatusDisplays();211var lastInternalStates = getInteralStateByUserName(statusDisplays);212verifyStatuses(statusDisplays, sequence[0]);213214for (i = 1; i < sequence.length; i++) {215parentInstance.replaceProps(sequence[i]);216statusDisplays = parentInstance.getStatusDisplays();217verifyStatuses(statusDisplays, sequence[i]);218verifyStatesPreserved(lastInternalStates, statusDisplays);219verifyDomOrderingAccurate(parentInstance, statusDisplays);220221lastInternalStates = getInteralStateByUserName(statusDisplays);222}223}224225describe('ReactMultiChildReconcile', function() {226beforeEach(function() {227require('mock-modules').dumpCache();228});229230it('should reset internal state if removed then readded', function() {231// Test basics.232var props = {233usernameToStatus: {234jcw: 'jcwStatus'235}236};237238var parentInstance = ReactTestUtils.renderIntoDocument(239<FriendsStatusDisplay {...props} />240);241var statusDisplays = parentInstance.getStatusDisplays();242var startingInternalState = statusDisplays.jcw.getInternalState();243244// Now remove the child.245parentInstance.replaceProps({ usernameToStatus: {} });246statusDisplays = parentInstance.getStatusDisplays();247expect(statusDisplays.jcw).toBeFalsy();248249// Now reset the props that cause there to be a child250parentInstance.replaceProps(props);251statusDisplays = parentInstance.getStatusDisplays();252expect(statusDisplays.jcw).toBeTruthy();253expect(statusDisplays.jcw.getInternalState())254.toNotBe(startingInternalState);255});256257it('should create unique identity', function() {258// Test basics.259var usernameToStatus = {260jcw: 'jcwStatus',261awalke: 'awalkeStatus',262bob: 'bobStatus'263};264265testPropsSequence([ { usernameToStatus: usernameToStatus } ]);266});267268it('should preserve order if children order has not changed', function() {269var PROPS_SEQUENCE = [270{271usernameToStatus: {272jcw: 'jcwStatus',273jordanjcw: 'jordanjcwStatus'274}275},276{277usernameToStatus: {278jcw: 'jcwstatus2',279jordanjcw: 'jordanjcwstatus2'280}281}282];283testPropsSequence(PROPS_SEQUENCE);284});285286it('should transition from zero to one children correctly', function() {287var PROPS_SEQUENCE = [288{ usernameToStatus: {} },289{290usernameToStatus: {291first: 'firstStatus'292}293}294];295testPropsSequence(PROPS_SEQUENCE);296});297298it('should transition from one to zero children correctly', function() {299var PROPS_SEQUENCE = [300{301usernameToStatus: {302first: 'firstStatus'303}304},305{ usernameToStatus: {} }306];307testPropsSequence(PROPS_SEQUENCE);308});309310it('should transition from one child to null children', function() {311testPropsSequence([312{313usernameToStatus: {314first: 'firstStatus'315}316},317{ }318]);319});320321it('should transition from null children to one child', function() {322testPropsSequence([323{ },324{325usernameToStatus: {326first: 'firstStatus'327}328}329]);330});331332it('should transition from zero children to null children', function() {333testPropsSequence([334{335usernameToStatus: { }336},337{ }338]);339});340341it('should transition from null children to zero children', function() {342testPropsSequence([343{ },344{345usernameToStatus: { }346}347]);348});349350351352/**353* `FriendsStatusDisplay` renders nulls as empty children (it's a convention354* of `FriendsStatusDisplay`, nothing related to React or these test cases.355*/356it('should remove nulled out children at the beginning', function() {357var PROPS_SEQUENCE = [358{359usernameToStatus: {360jcw: 'jcwStatus',361jordanjcw: 'jordanjcwStatus'362}363},364{365usernameToStatus: {366jcw: null,367jordanjcw: 'jordanjcwstatus2'368}369}370];371testPropsSequence(PROPS_SEQUENCE);372});373374it('should remove nulled out children at the end', function() {375var PROPS_SEQUENCE = [376{377usernameToStatus: {378jcw: 'jcwStatus',379jordanjcw: 'jordanjcwStatus'380}381},382{383usernameToStatus: {384jcw: 'jcwstatus2',385jordanjcw: null386}387}388];389testPropsSequence(PROPS_SEQUENCE);390});391392it('should reverse the order of two children', function() {393var PROPS_SEQUENCE = [394{395usernameToStatus: {396userOne: 'userOneStatus',397userTwo: 'userTwoStatus'398}399},400{401usernameToStatus: {402userTwo: 'userTwoStatus',403userOne: 'userOneStatus'404}405}406];407testPropsSequence(PROPS_SEQUENCE);408});409410it('should reverse the order of more than two children', function() {411var PROPS_SEQUENCE = [412{413usernameToStatus: {414userOne: 'userOneStatus',415userTwo: 'userTwoStatus',416userThree: 'userThreeStatus'417}418},419{420usernameToStatus: {421userThree: 'userThreeStatus',422userTwo: 'userTwoStatus',423userOne: 'userOneStatus'424}425}426];427testPropsSequence(PROPS_SEQUENCE);428});429430it('should cycle order correctly', function() {431var PROPS_SEQUENCE = [432{433usernameToStatus: {434userOne: 'userOneStatus',435userTwo: 'userTwoStatus',436userThree: 'userThreeStatus',437userFour: 'userFourStatus'438}439},440{441usernameToStatus: {442userTwo: 'userTwoStatus',443userThree: 'userThreeStatus',444userFour: 'userFourStatus',445userOne: 'userOneStatus'446}447},448{449usernameToStatus: {450userThree: 'userThreeStatus',451userFour: 'userFourStatus',452userOne: 'userOneStatus',453userTwo: 'userTwoStatus'454}455},456{457usernameToStatus: {458userFour: 'userFourStatus',459userOne: 'userOneStatus',460userTwo: 'userTwoStatus',461userThree: 'userThreeStatus'462}463},464{465usernameToStatus: { // Full circle!466userOne: 'userOneStatus',467userTwo: 'userTwoStatus',468userThree: 'userThreeStatus',469userFour: 'userFourStatus'470}471}472];473testPropsSequence(PROPS_SEQUENCE);474});475476it('should cycle order correctly in the other direction', function() {477var PROPS_SEQUENCE = [478{479usernameToStatus: {480userOne: 'userOneStatus',481userTwo: 'userTwoStatus',482userThree: 'userThreeStatus',483userFour: 'userFourStatus'484}485},486{487usernameToStatus: {488userFour: 'userFourStatus',489userOne: 'userOneStatus',490userTwo: 'userTwoStatus',491userThree: 'userThreeStatus'492}493},494{495usernameToStatus: {496userThree: 'userThreeStatus',497userFour: 'userFourStatus',498userOne: 'userOneStatus',499userTwo: 'userTwoStatus'500}501},502{503usernameToStatus: {504userTwo: 'userTwoStatus',505userThree: 'userThreeStatus',506userFour: 'userFourStatus',507userOne: 'userOneStatus'508}509},510{511usernameToStatus: { // Full circle!512userOne: 'userOneStatus',513userTwo: 'userTwoStatus',514userThree: 'userThreeStatus',515userFour: 'userFourStatus'516}517}518];519testPropsSequence(PROPS_SEQUENCE);520});521522523it('should remove nulled out children and ignore ' +524'new null children', function() {525var PROPS_SEQUENCE = [526{527usernameToStatus: {528jcw: 'jcwStatus',529jordanjcw: 'jordanjcwStatus'530}531},532{533usernameToStatus: {534jordanjcw: 'jordanjcwstatus2',535jcw: null,536another: null537}538}539];540testPropsSequence(PROPS_SEQUENCE);541});542543it('should remove nulled out children and reorder remaining', function() {544var PROPS_SEQUENCE = [545{546usernameToStatus: {547jcw: 'jcwStatus',548jordanjcw: 'jordanjcwStatus',549john: 'johnStatus', // john will go away550joe: 'joeStatus'551}552},553{554usernameToStatus: {555jordanjcw: 'jordanjcwStatus',556joe: 'joeStatus',557jcw: 'jcwStatus'558}559}560];561testPropsSequence(PROPS_SEQUENCE);562});563564it('should append children to the end', function() {565var PROPS_SEQUENCE = [566{567usernameToStatus: {568jcw: 'jcwStatus',569jordanjcw: 'jordanjcwStatus'570}571},572{573usernameToStatus: {574jcw: 'jcwStatus',575jordanjcw: 'jordanjcwStatus',576jordanjcwnew: 'jordanjcwnewStatus'577}578}579];580testPropsSequence(PROPS_SEQUENCE);581});582583it('should append multiple children to the end', function() {584var PROPS_SEQUENCE = [585{586usernameToStatus: {587jcw: 'jcwStatus',588jordanjcw: 'jordanjcwStatus'589}590},591{592usernameToStatus: {593jcw: 'jcwStatus',594jordanjcw: 'jordanjcwStatus',595jordanjcwnew: 'jordanjcwnewStatus',596jordanjcwnew2: 'jordanjcwnewStatus2'597}598}599];600testPropsSequence(PROPS_SEQUENCE);601});602603it('should prepend children to the beginning', function() {604var PROPS_SEQUENCE = [605{606usernameToStatus: {607jcw: 'jcwStatus',608jordanjcw: 'jordanjcwStatus'609}610},611{612usernameToStatus: {613newUsername: 'newUsernameStatus',614jcw: 'jcwStatus',615jordanjcw: 'jordanjcwStatus'616}617}618];619testPropsSequence(PROPS_SEQUENCE);620});621622it('should prepend multiple children to the beginning', function() {623var PROPS_SEQUENCE = [624{625usernameToStatus: {626jcw: 'jcwStatus',627jordanjcw: 'jordanjcwStatus'628}629},630{631usernameToStatus: {632newNewUsername: 'newNewUsernameStatus',633newUsername: 'newUsernameStatus',634jcw: 'jcwStatus',635jordanjcw: 'jordanjcwStatus'636}637}638];639testPropsSequence(PROPS_SEQUENCE);640});641642it('should not prepend an empty child to the beginning', function() {643var PROPS_SEQUENCE = [644{645usernameToStatus: {646jcw: 'jcwStatus',647jordanjcw: 'jordanjcwStatus'648}649},650{651usernameToStatus: {652emptyUsername: null,653jcw: 'jcwStatus',654jordanjcw: 'jordanjcwStatus'655}656}657];658testPropsSequence(PROPS_SEQUENCE);659});660661it('should not append an empty child to the end', function() {662var PROPS_SEQUENCE = [663{664usernameToStatus: {665jcw: 'jcwStatus',666jordanjcw: 'jordanjcwStatus'667}668},669{670usernameToStatus: {671jcw: 'jcwStatus',672jordanjcw: 'jordanjcwStatus',673emptyUsername: null674}675}676];677testPropsSequence(PROPS_SEQUENCE);678});679680it('should not insert empty children in the middle', function() {681var PROPS_SEQUENCE = [682{683usernameToStatus: {684jcw: 'jcwStatus',685jordanjcw: 'jordanjcwStatus'686}687},688{689usernameToStatus: {690jcw: 'jcwstatus2',691skipOverMe: null,692skipOverMeToo: null,693definitelySkipOverMe: null,694jordanjcw: 'jordanjcwstatus2'695}696}697];698testPropsSequence(PROPS_SEQUENCE);699});700701it('should insert one new child in the middle', function() {702var PROPS_SEQUENCE = [703{704usernameToStatus: {705jcw: 'jcwStatus',706jordanjcw: 'jordanjcwStatus'707}708},709{710usernameToStatus: {711jcw: 'jcwstatus2',712insertThis: 'insertThisStatus',713jordanjcw: 'jordanjcwstatus2'714}715}716];717testPropsSequence(PROPS_SEQUENCE);718});719720it('should insert multiple new truthy children in the middle', function() {721var PROPS_SEQUENCE = [722{723usernameToStatus: {724jcw: 'jcwStatus',725jordanjcw: 'jordanjcwStatus'726}727},728{729usernameToStatus: {730jcw: 'jcwstatus2',731insertThis: 'insertThisStatus',732insertThisToo: 'insertThisTooStatus',733definitelyInsertThisToo: 'definitelyInsertThisTooStatus',734jordanjcw: 'jordanjcwstatus2'735}736}737];738testPropsSequence(PROPS_SEQUENCE);739});740741it('should insert non-empty children in middle where nulls were', function() {742var PROPS_SEQUENCE = [743{744usernameToStatus: {745jcw: 'jcwStatus',746insertThis: null,747insertThisToo: null,748definitelyInsertThisToo: null,749jordanjcw: 'jordanjcwStatus'750}751},752{753usernameToStatus: {754jcw: 'jcwstatus2',755insertThis: 'insertThisStatus',756insertThisToo: 'insertThisTooStatus',757definitelyInsertThisToo: 'definitelyInsertThisTooStatus',758jordanjcw: 'jordanjcwstatus2'759}760}761];762testPropsSequence(PROPS_SEQUENCE);763});764});765766767