diff --git a/src/addons/transitions/ReactCSSTransitionGroup.js b/src/addons/transitions/ReactCSSTransitionGroup.js
new file mode 100644
index 0000000000..a388088c99
--- /dev/null
+++ b/src/addons/transitions/ReactCSSTransitionGroup.js
@@ -0,0 +1,65 @@
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @typechecks
+ * @providesModule ReactCSSTransitionGroup
+ * @jsx React.DOM
+ */
+
+"use strict";
+
+var React = require('React');
+
+var ReactTransitionGroup = require('ReactTransitionGroup');
+var ReactCSSTransitionGroupChild = require('ReactCSSTransitionGroupChild');
+
+var ReactCSSTransitionGroup = React.createClass({
+ propTypes: {
+ transitionName: React.PropTypes.string.isRequired,
+ transitionEnter: React.PropTypes.bool,
+ transitionLeave: React.PropTypes.bool
+ },
+
+ getDefaultProps: function() {
+ return {
+ transitionEnter: true,
+ transitionLeave: true
+ };
+ },
+
+ _wrapChild: function(child) {
+ // We need to provide this childFactory so that
+ // ReactCSSTransitionGroupChild can receive updates to name, enter, and
+ // leave while it is leaving.
+ return (
+
+ {child}
+
+ );
+ },
+
+ render: function() {
+ return this.transferPropsTo(
+
+ {this.props.children}
+
+ );
+ }
+});
+
+module.exports = ReactCSSTransitionGroup;
diff --git a/src/addons/transitions/ReactTransitionableChild.js b/src/addons/transitions/ReactCSSTransitionGroupChild.js
similarity index 60%
rename from src/addons/transitions/ReactTransitionableChild.js
rename to src/addons/transitions/ReactCSSTransitionGroupChild.js
index 23267726a6..a890152384 100644
--- a/src/addons/transitions/ReactTransitionableChild.js
+++ b/src/addons/transitions/ReactCSSTransitionGroupChild.js
@@ -13,15 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*
- * @providesModule ReactTransitionableChild
+ * @typechecks
+ * @providesModule ReactCSSTransitionGroupChild
*/
"use strict";
var React = require('React');
+
var CSSCore = require('CSSCore');
var ReactTransitionEvents = require('ReactTransitionEvents');
+var onlyChild = require('onlyChild');
+
// We don't remove the element from the DOM until we receive an animationend or
// transitionend event. If the user screws up and forgets to add an animation
// their node will be stuck in the DOM forever, so we detect if an animation
@@ -31,6 +35,7 @@ var NO_EVENT_TIMEOUT = 5000;
var noEventListener = null;
+
if (__DEV__) {
noEventListener = function() {
console.warn(
@@ -42,20 +47,8 @@ if (__DEV__) {
};
}
-/**
- * This component is simply responsible for watching when its single child
- * changes to undefined and animating the old child out. It does this by
- * recording its old child in savedChildren when it detects this event is about
- * to occur.
- */
-var ReactTransitionableChild = React.createClass({
- /**
- * Perform an actual DOM transition. This takes care of a few things:
- * - Adding the second CSS class to trigger the transition
- * - Listening for the finish event
- * - Cleaning up the css (unless noReset is true)
- */
- transition: function(animationType, noReset, finishCallback) {
+var ReactCSSTransitionGroupChild = React.createClass({
+ transition: function(animationType, finishCallback) {
var node = this.getDOMNode();
var className = this.props.name + '-' + animationType;
var activeClassName = className + '-active';
@@ -66,13 +59,8 @@ var ReactTransitionableChild = React.createClass({
clearTimeout(noEventTimeout);
}
- // If this gets invoked after the component is unmounted it's OK.
- if (!noReset) {
- // Usually this means you're about to remove the node if you want to
- // leave it in its animated state.
- CSSCore.removeClass(node, className);
- CSSCore.removeClass(node, activeClassName);
- }
+ CSSCore.removeClass(node, className);
+ CSSCore.removeClass(node, activeClassName);
ReactTransitionEvents.removeEndEventListener(node, endListener);
@@ -126,39 +114,25 @@ var ReactTransitionableChild = React.createClass({
}
},
- componentWillReceiveProps: function(nextProps) {
- if (!nextProps.children && this.props.children) {
- this.savedChildren = this.props.children;
- } else if (nextProps.children && !this.props.children) {
- // We're being told to re-add the child. Let's stop leaving!
- if (this.isMounted()) {
- var node = this.getDOMNode();
- var className = this.props.name;
- CSSCore.removeClass(node, className + '-leave');
- CSSCore.removeClass(node, className + '-leave-active');
- if (this.props.enter) {
- CSSCore.addClass(node, className + '-enter');
- CSSCore.addClass(node, className + '-enter-active');
- }
- }
- }
- },
-
- componentDidMount: function() {
+ componentWillEnter: function(done) {
if (this.props.enter) {
- this.transition('enter');
+ this.transition('enter', done);
+ } else {
+ done();
}
},
- componentDidUpdate: function(prevProps, prevState, prevContext) {
- if (prevProps.children && !this.props.children) {
- this.transition('leave', true, this.props.onDoneLeaving);
+ componentWillLeave: function(done) {
+ if (this.props.leave) {
+ this.transition('leave', done);
+ } else {
+ done();
}
},
render: function() {
- return this.props.children || this.savedChildren;
+ return onlyChild(this.props.children);
}
});
-module.exports = ReactTransitionableChild;
+module.exports = ReactCSSTransitionGroupChild;
diff --git a/src/addons/transitions/ReactTransitionKeySet.js b/src/addons/transitions/ReactTransitionChildMapping.js
similarity index 71%
rename from src/addons/transitions/ReactTransitionKeySet.js
rename to src/addons/transitions/ReactTransitionChildMapping.js
index 03fb177700..6b7515337c 100644
--- a/src/addons/transitions/ReactTransitionKeySet.js
+++ b/src/addons/transitions/ReactTransitionChildMapping.js
@@ -14,14 +14,14 @@
* limitations under the License.
*
* @typechecks static-only
- * @providesModule ReactTransitionKeySet
+ * @providesModule ReactTransitionChildMapping
*/
"use strict";
var ReactChildren = require('ReactChildren');
-var ReactTransitionKeySet = {
+var ReactTransitionChildMapping = {
/**
* Given `this.props.children`, return an object mapping key to child. Just
* simple syntactic sugar around ReactChildren.map().
@@ -35,22 +35,9 @@ var ReactTransitionKeySet = {
});
},
- /**
- * Simple syntactic sugar to get an object with keys of all of `children`.
- * Does not have references to the children themselves.
- *
- * @param {*} children `this.props.children`
- * @return {object} Mapping of key to the value "true"
- */
- getKeySet: function(children) {
- return ReactChildren.map(children, function() {
- return true;
- });
- },
-
/**
* When you're adding or removing children some may be added or removed in the
- * same render pass. We want to show *both* since we want to simultaneously
+ * same render pass. We want ot show *both* since we want to simultaneously
* animate elements in and out. This function takes a previous set of keys
* and a new set of keys and merges them with its best guess of the correct
* ordering. In the future we may expose some of the utilities in
@@ -58,17 +45,25 @@ var ReactTransitionKeySet = {
* directly have this concept of the union of prevChildren and nextChildren
* so we implement it here.
*
- * @param {object} prev prev child keys as returned from
- * `ReactTransitionKeySet.getKeySet()`.
- * @param {object} next next child keys as returned from
- * `ReactTransitionKeySet.getKeySet()`.
+ * @param {object} prev prev children as returned from
+ * `ReactTransitionChildMapping.getChildMapping()`.
+ * @param {object} next next children as returned from
+ * `ReactTransitionChildMapping.getChildMapping()`.
* @return {object} a key set that contains all keys in `prev` and all keys
* in `next` in a reasonable order.
*/
- mergeKeySets: function(prev, next) {
+ mergeChildMappings: function(prev, next) {
prev = prev || {};
next = next || {};
+ function getValueForKey(key) {
+ if (next.hasOwnProperty(key)) {
+ return next[key];
+ } else {
+ return prev[key];
+ }
+ }
+
// For each key of `next`, the list of keys to insert before that key in
// the combined list
var nextKeysPending = {};
@@ -86,23 +81,26 @@ var ReactTransitionKeySet = {
}
var i;
- var keySet = {};
+ var childMapping = {};
for (var nextKey in next) {
if (nextKeysPending[nextKey]) {
for (i = 0; i < nextKeysPending[nextKey].length; i++) {
- keySet[nextKeysPending[nextKey][i]] = true;
+ var pendingNextKey = nextKeysPending[nextKey][i];
+ childMapping[nextKeysPending[nextKey][i]] = getValueForKey(
+ pendingNextKey
+ );
}
}
- keySet[nextKey] = true;
+ childMapping[nextKey] = getValueForKey(nextKey);
}
// Finally, add the keys which didn't appear before any key in `next`
for (i = 0; i < pendingKeys.length; i++) {
- keySet[pendingKeys[i]] = true;
+ childMapping[pendingKeys[i]] = getValueForKey(pendingKeys[i]);
}
- return keySet;
+ return childMapping;
}
};
-module.exports = ReactTransitionKeySet;
+module.exports = ReactTransitionChildMapping;
diff --git a/src/addons/transitions/ReactTransitionGroup.js b/src/addons/transitions/ReactTransitionGroup.js
index 98ce8619e9..e022504841 100644
--- a/src/addons/transitions/ReactTransitionGroup.js
+++ b/src/addons/transitions/ReactTransitionGroup.js
@@ -19,103 +19,168 @@
"use strict";
var React = require('React');
-var ReactTransitionableChild = require('ReactTransitionableChild');
-var ReactTransitionKeySet = require('ReactTransitionKeySet');
+var ReactTransitionChildMapping = require('ReactTransitionChildMapping');
+
+var cloneWithProps = require('cloneWithProps');
+var emptyFunction = require('emptyFunction');
+var merge = require('merge');
var ReactTransitionGroup = React.createClass({
propTypes: {
- transitionName: React.PropTypes.string.isRequired,
- transitionEnter: React.PropTypes.bool,
- transitionLeave: React.PropTypes.bool,
- onTransition: React.PropTypes.func,
- component: React.PropTypes.func
+ component: React.PropTypes.func,
+ childFactory: React.PropTypes.func
},
getDefaultProps: function() {
return {
- transitionEnter: true,
- transitionLeave: true,
- component: React.DOM.span
+ component: React.DOM.span,
+ childFactory: emptyFunction.thatReturnsArgument
};
},
- componentWillMount: function() {
- // _transitionGroupCurrentKeys stores the union of previous *and* next keys.
- // If this were a component we'd store it as state, however, since this must
- // be a mixin, we need to keep the result of the union of keys in each
- // call to animateChildren() which happens in render(), so we can't
- // call setState() in there.
- this._transitionGroupCurrentKeys = {};
+ getInitialState: function() {
+ return {
+ children: ReactTransitionChildMapping.getChildMapping(this.props.children)
+ };
},
- componentDidUpdate: function() {
- if (this.props.onTransition) {
- this.props.onTransition();
- }
- },
-
- /**
- * Render some children in a transitionable way.
- */
- renderTransitionableChildren: function(sourceChildren) {
- var children = {};
- var childMapping = ReactTransitionKeySet.getChildMapping(sourceChildren);
-
- var prevKeys = this._transitionGroupCurrentKeys;
- var currentKeys = ReactTransitionKeySet.mergeKeySets(
- prevKeys,
- ReactTransitionKeySet.getKeySet(sourceChildren)
+ componentWillReceiveProps: function(nextProps) {
+ var nextChildMapping = ReactTransitionChildMapping.getChildMapping(
+ nextProps.children
);
+ var prevChildMapping = this.state.children;
- for (var key in currentKeys) {
- // Here is how we keep the nodes in the DOM. ReactTransitionableChild
- // knows how to hold onto its child if it changes to undefined. Here, we
- // may look up an old key in the new children, and it may switch to
- // undefined. React's reconciler will keep the ReactTransitionableChild
- // instance alive such that we can animate it.
- if (childMapping[key] || (this.props.transitionLeave && prevKeys[key])) {
- children[key] = ReactTransitionableChild({
- name: this.props.transitionName,
- enter: this.props.transitionEnter,
- onDoneLeaving: this._handleDoneLeaving.bind(this, key)
- }, childMapping[key]);
- } else {
- // If there's no leave transition and the child has been removed from
- // the source children list, we want to remove it immediately from the
- // _transitionGroupCurrentKeys cache because _handleDoneLeaving won't
- // be called. In normal cases, this prevents a small memory leak; in
- // the case of switching transitionLeave from false to true, it
- // prevents a confusing bug where ReactTransitionableChild.render()
- // returns nothing, throwing an error.
- delete currentKeys[key];
+ this.setState({
+ children: ReactTransitionChildMapping.mergeChildMappings(
+ prevChildMapping,
+ nextChildMapping
+ )
+ });
+
+ var key;
+
+ for (key in nextChildMapping) {
+ if (!prevChildMapping.hasOwnProperty(key) &&
+ !this.currentlyTransitioningKeys[key]) {
+ this.keysToEnter.push(key);
}
}
- this._transitionGroupCurrentKeys = currentKeys;
+ for (key in prevChildMapping) {
+ if (!nextChildMapping.hasOwnProperty(key) &&
+ !this.currentlyTransitioningKeys[key]) {
+ this.keysToLeave.push(key);
+ }
+ }
- return children;
+ // If we want to someday check for reordering, we could do it here.
+ },
+
+ componentWillMount: function() {
+ this.currentlyTransitioningKeys = {};
+ this.keysToEnter = [];
+ this.keysToLeave = [];
+ },
+
+ componentDidUpdate: function() {
+ var keysToEnter = this.keysToEnter;
+ this.keysToEnter = [];
+ keysToEnter.forEach(this.performEnter);
+
+ var keysToLeave = this.keysToLeave;
+ this.keysToLeave = [];
+ keysToLeave.forEach(this.performLeave);
+ },
+
+ performEnter: function(key) {
+ this.currentlyTransitioningKeys[key] = true;
+
+ var component = this.refs[key];
+
+ if (component.componentWillEnter) {
+ component.componentWillEnter(
+ this._handleDoneEntering.bind(this, key)
+ );
+ } else {
+ this._handleDoneEntering(key);
+ }
+ },
+
+ _handleDoneEntering: function(key) {
+ var component = this.refs[key];
+ if (component.componentDidEnter) {
+ component.componentDidEnter();
+ }
+
+ delete this.currentlyTransitioningKeys[key];
+
+ var currentChildMapping = ReactTransitionChildMapping.getChildMapping(
+ this.props.children
+ );
+
+ if (!currentChildMapping.hasOwnProperty(key)) {
+ // This was removed before it had fully entered. Remove it.
+ this.performLeave(key);
+ }
+ },
+
+ performLeave: function(key) {
+ this.currentlyTransitioningKeys[key] = true;
+
+ var component = this.refs[key];
+ if (component.componentWillLeave) {
+ component.componentWillLeave(this._handleDoneLeaving.bind(this, key));
+ } else {
+ // Note that this is somewhat dangerous b/c it calls setState()
+ // again, effectively mutating the component before all the work
+ // is done.
+ this._handleDoneLeaving(key);
+ }
},
_handleDoneLeaving: function(key) {
- // When the leave animation finishes, we should blow away the actual DOM
- // node.
- delete this._transitionGroupCurrentKeys[key];
- this.forceUpdate();
+ var component = this.refs[key];
+
+ if (component.componentDidLeave) {
+ component.componentDidLeave();
+ }
+
+ delete this.currentlyTransitioningKeys[key];
+
+ var currentChildMapping = ReactTransitionChildMapping.getChildMapping(
+ this.props.children
+ );
+
+ if (currentChildMapping.hasOwnProperty(key)) {
+ // This entered again before it fully left. Add it again.
+ this.performEnter(key);
+ } else {
+ var newChildren = merge(this.state.children);
+ delete newChildren[key];
+ this.setState({children: newChildren});
+ }
},
render: function() {
- return this.transferPropsTo(
- this.props.component(
- {
- transitionName: null,
- transitionEnter: null,
- transitionLeave: null,
- component: null
- },
- this.renderTransitionableChildren(this.props.children)
- )
- );
+ // TODO: we could get rid of the need for the wrapper node
+ // by cloning a single child
+ var childrenToRender = {};
+ for (var key in this.state.children) {
+ var child = this.state.children[key];
+ if (child) {
+ // You may need to apply reactive updates to a child as it is leaving.
+ // The normal React way to do it won't work since the child will have
+ // already been removed. In case you need this behavior you can provide
+ // a childFactory function to wrap every child, even the ones that are
+ // leaving.
+ childrenToRender[key] = cloneWithProps(
+ this.props.childFactory(child),
+ {ref: key}
+ );
+ }
+ }
+ return this.transferPropsTo(this.props.component(null, childrenToRender));
}
});
diff --git a/src/addons/transitions/__tests__/ReactCSSTransitionGroup-test.js b/src/addons/transitions/__tests__/ReactCSSTransitionGroup-test.js
new file mode 100644
index 0000000000..07e5c01f52
--- /dev/null
+++ b/src/addons/transitions/__tests__/ReactCSSTransitionGroup-test.js
@@ -0,0 +1,137 @@
+/**
+ * Copyright 2013 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @jsx React.DOM
+ * @emails react-core
+ */
+
+"use strict";
+
+var React;
+var ReactCSSTransitionGroup;
+var mocks;
+
+// Most of the real functionality is covered in other unit tests, this just
+// makes sure we're wired up correctly.
+describe('ReactCSSTransitionGroup', function() {
+ var container;
+
+ beforeEach(function() {
+ React = require('React');
+ ReactCSSTransitionGroup = require('ReactCSSTransitionGroup');
+ mocks = require('mocks');
+
+ container = document.createElement('div');
+ });
+
+ it('should warn after time with no transitionend', function() {
+ var a = React.renderComponent(
+
+
+ ,
+ container
+ );
+ expect(a.getDOMNode().childNodes.length).toBe(1);
+
+ setTimeout.mock.calls.length = 0;
+
+ React.renderComponent(
+
+
+ ,
+ container
+ );
+ expect(a.getDOMNode().childNodes.length).toBe(2);
+ expect(a.getDOMNode().childNodes[0].id).toBe('two');
+ expect(a.getDOMNode().childNodes[1].id).toBe('one');
+
+ console.warn = mocks.getMockFunction();
+ setTimeout.mock.calls[2][0]();
+
+ expect(a.getDOMNode().childNodes.length).toBe(2);
+ expect(console.warn.mock.calls.length).toBe(1);
+ });
+
+ it('should keep both sets of DOM nodes around', function() {
+ var a = React.renderComponent(
+
+
+ ,
+ container
+ );
+ expect(a.getDOMNode().childNodes.length).toBe(1);
+ React.renderComponent(
+
+
+ ,
+ container
+ );
+ expect(a.getDOMNode().childNodes.length).toBe(2);
+ expect(a.getDOMNode().childNodes[0].id).toBe('two');
+ expect(a.getDOMNode().childNodes[1].id).toBe('one');
+ });
+
+ it('should switch transitionLeave from false to true', function() {
+ var a = React.renderComponent(
+
+
+ ,
+ container
+ );
+ expect(a.getDOMNode().childNodes.length).toBe(1);
+ React.renderComponent(
+
+
+ ,
+ container
+ );
+ expect(a.getDOMNode().childNodes.length).toBe(1);
+ React.renderComponent(
+
+
+ ,
+ container
+ );
+ expect(a.getDOMNode().childNodes.length).toBe(2);
+ expect(a.getDOMNode().childNodes[0].id).toBe('three');
+ expect(a.getDOMNode().childNodes[1].id).toBe('two');
+ });
+ return;
+ it('should work with no children', function () {
+ React.renderComponent(
+
+ ,
+ container
+ );
+ });
+
+ it('should work with a null child', function () {
+ React.renderComponent(
+
+ {[null]}
+ ,
+ container
+ );
+ });
+});
diff --git a/src/addons/transitions/__tests__/ReactTransitionKeySet-test.js b/src/addons/transitions/__tests__/ReactTransitionChildMapping-test.js
similarity index 62%
rename from src/addons/transitions/__tests__/ReactTransitionKeySet-test.js
rename to src/addons/transitions/__tests__/ReactTransitionChildMapping-test.js
index 8e3954a5f3..9325c4daca 100644
--- a/src/addons/transitions/__tests__/ReactTransitionKeySet-test.js
+++ b/src/addons/transitions/__tests__/ReactTransitionChildMapping-test.js
@@ -20,12 +20,12 @@
"use strict";
var React;
-var ReactTransitionKeySet;
+var ReactTransitionChildMapping;
-describe('ReactTransitionKeySet', function() {
+describe('ReactTransitionChildMapping', function() {
beforeEach(function() {
React = require('React');
- ReactTransitionKeySet = require('ReactTransitionKeySet');
+ ReactTransitionChildMapping = require('ReactTransitionChildMapping');
});
it('should support getChildMapping', function() {
@@ -34,26 +34,15 @@ describe('ReactTransitionKeySet', function() {
var one =
{oneone}{onetwo}
;
var two = ;
var component = {one}{two}
;
- expect(ReactTransitionKeySet.getChildMapping(component.props.children))
- .toEqual({
- '.$one': one,
- '.$two': two
- });
- });
-
- it('should support getKeySet', function() {
- var oneone = ;
- var onetwo = ;
- var one = {oneone}{onetwo}
;
- var two = ;
- var component = {one}{two}
;
- expect(ReactTransitionKeySet.getKeySet(component.props.children)).toEqual({
- '.$one': true,
- '.$two': true
+ expect(
+ ReactTransitionChildMapping.getChildMapping(component.props.children)
+ ).toEqual({
+ '.$one': one,
+ '.$two': two
});
});
- it('should support mergeKeySets for adding keys', function() {
+ it('should support mergeChildMappings for adding keys', function() {
var prev = {
one: true,
two: true
@@ -63,14 +52,14 @@ describe('ReactTransitionKeySet', function() {
two: true,
three: true
};
- expect(ReactTransitionKeySet.mergeKeySets(prev, next)).toEqual({
+ expect(ReactTransitionChildMapping.mergeChildMappings(prev, next)).toEqual({
one: true,
two: true,
three: true
});
});
- it('should support mergeKeySets for removing keys', function() {
+ it('should support mergeChildMappings for removing keys', function() {
var prev = {
one: true,
two: true,
@@ -80,14 +69,14 @@ describe('ReactTransitionKeySet', function() {
one: true,
two: true
};
- expect(ReactTransitionKeySet.mergeKeySets(prev, next)).toEqual({
+ expect(ReactTransitionChildMapping.mergeChildMappings(prev, next)).toEqual({
one: true,
two: true,
three: true
});
});
- it('should support mergeKeySets for adding and removing', function() {
+ it('should support mergeChildMappings for adding and removing', function() {
var prev = {
one: true,
two: true,
@@ -98,7 +87,7 @@ describe('ReactTransitionKeySet', function() {
two: true,
four: true
};
- expect(ReactTransitionKeySet.mergeKeySets(prev, next)).toEqual({
+ expect(ReactTransitionChildMapping.mergeChildMappings(prev, next)).toEqual({
one: true,
two: true,
three: true,
@@ -119,7 +108,7 @@ describe('ReactTransitionKeySet', function() {
three: true,
five: true
};
- expect(ReactTransitionKeySet.mergeKeySets(prev, next)).toEqual({
+ expect(ReactTransitionChildMapping.mergeChildMappings(prev, next)).toEqual({
one: true,
two: true,
three: true,
@@ -128,7 +117,7 @@ describe('ReactTransitionKeySet', function() {
});
});
- it('should support mergeKeySets with undefined input', function () {
+ it('should support mergeChildMappings with undefined input', function () {
var prev = {
one: true,
two: true
@@ -136,7 +125,7 @@ describe('ReactTransitionKeySet', function() {
var next = undefined;
- expect(ReactTransitionKeySet.mergeKeySets(prev, next)).toEqual({
+ expect(ReactTransitionChildMapping.mergeChildMappings(prev, next)).toEqual({
one: true,
two: true
});
@@ -148,7 +137,7 @@ describe('ReactTransitionKeySet', function() {
four: true
};
- expect(ReactTransitionKeySet.mergeKeySets(prev, next)).toEqual({
+ expect(ReactTransitionChildMapping.mergeChildMappings(prev, next)).toEqual({
three: true,
four: true
});
diff --git a/src/addons/transitions/__tests__/ReactTransitionGroup-test.js b/src/addons/transitions/__tests__/ReactTransitionGroup-test.js
index ab0fb369ab..d7ee5343fb 100644
--- a/src/addons/transitions/__tests__/ReactTransitionGroup-test.js
+++ b/src/addons/transitions/__tests__/ReactTransitionGroup-test.js
@@ -36,102 +36,177 @@ describe('ReactTransitionGroup', function() {
container = document.createElement('div');
});
- it('should warn after time with no transitionend', function() {
- var a = React.renderComponent(
-
-
- ,
- container
- );
- expect(a.getDOMNode().childNodes.length).toBe(1);
- setTimeout.mock.calls.length = 0;
+ it('should handle willEnter correctly', function() {
+ var log = [];
- React.renderComponent(
-
-
- ,
- container
- );
- expect(a.getDOMNode().childNodes.length).toBe(2);
- expect(a.getDOMNode().childNodes[0].id).toBe('two');
- expect(a.getDOMNode().childNodes[1].id).toBe('one');
+ var Child = React.createClass({
+ componentDidMount: function() {
+ log.push('didMount');
+ },
+ componentWillEnter: function(cb) {
+ log.push('willEnter');
+ cb();
+ },
+ componentDidEnter: function() {
+ log.push('didEnter');
+ },
+ componentWillLeave: function(cb) {
+ log.push('willLeave');
+ cb();
+ },
+ componentDidLeave: function() {
+ log.push('didLeave');
+ },
+ componentWillUnmount: function() {
+ log.push('willUnmount');
+ },
+ render: function() {
+ return ;
+ }
+ });
- console.warn = mocks.getMockFunction();
- setTimeout.mock.calls[2][0]();
+ var Component = React.createClass({
+ getInitialState: function() {
+ return {count: 1};
+ },
+ render: function() {
+ var children = [];
+ for (var i = 0; i < this.state.count; i++) {
+ children.push();
+ }
+ return {children};
+ }
+ });
- expect(a.getDOMNode().childNodes.length).toBe(2);
- expect(console.warn.mock.calls.length).toBe(1);
+ var instance = React.renderComponent(, container);
+ expect(log).toEqual(['didMount']);
+
+ instance.setState({count: 2}, function() {
+ expect(log).toEqual(['didMount', 'didMount', 'willEnter', 'didEnter']);
+ instance.setState({count: 1}, function() {
+ expect(log).toEqual([
+ "didMount", "didMount", "willEnter", "didEnter",
+ "willLeave", "didLeave", "willUnmount"
+ ]);
+ });
+ });
});
- it('should keep both sets of DOM nodes around', function() {
- var a = React.renderComponent(
-
-
- ,
- container
- );
- expect(a.getDOMNode().childNodes.length).toBe(1);
- React.renderComponent(
-
-
- ,
- container
- );
- expect(a.getDOMNode().childNodes.length).toBe(2);
- expect(a.getDOMNode().childNodes[0].id).toBe('two');
- expect(a.getDOMNode().childNodes[1].id).toBe('one');
+ it('should handle enter/leave/enter/leave correctly', function() {
+ var log = [];
+ var cb;
+
+ var Child = React.createClass({
+ componentDidMount: function() {
+ log.push('didMount');
+ },
+ componentWillEnter: function(_cb) {
+ log.push('willEnter');
+ cb = _cb;
+ },
+ componentDidEnter: function() {
+ log.push('didEnter');
+ },
+ componentWillLeave: function(cb) {
+ log.push('willLeave');
+ cb();
+ },
+ componentDidLeave: function() {
+ log.push('didLeave');
+ },
+ componentWillUnmount: function() {
+ log.push('willUnmount');
+ },
+ render: function() {
+ return ;
+ }
+ });
+
+ var Component = React.createClass({
+ getInitialState: function() {
+ return {count: 1};
+ },
+ render: function() {
+ var children = [];
+ for (var i = 0; i < this.state.count; i++) {
+ children.push();
+ }
+ return {children};
+ }
+ });
+
+ var instance = React.renderComponent(, container);
+ expect(log).toEqual(['didMount']);
+ instance.setState({count: 2});
+ expect(log).toEqual(['didMount', 'didMount', 'willEnter']);
+ for (var i = 0; i < 5; i++) {
+ instance.setState({count: 2});
+ expect(log).toEqual(['didMount', 'didMount', 'willEnter']);
+ instance.setState({count: 1});
+ }
+ cb();
+ expect(log).toEqual([
+ 'didMount', 'didMount', 'willEnter',
+ 'didEnter', 'willLeave', 'didLeave', 'willUnmount'
+ ]);
});
- it('should switch transitionLeave from false to true', function() {
- var a = React.renderComponent(
-
-
- ,
- container
- );
- expect(a.getDOMNode().childNodes.length).toBe(1);
- React.renderComponent(
-
-
- ,
- container
- );
- expect(a.getDOMNode().childNodes.length).toBe(1);
- React.renderComponent(
-
-
- ,
- container
- );
- expect(a.getDOMNode().childNodes.length).toBe(2);
- expect(a.getDOMNode().childNodes[0].id).toBe('three');
- expect(a.getDOMNode().childNodes[1].id).toBe('two');
- });
+ it('should handle enter/leave/enter correctly', function() {
+ var log = [];
+ var cb;
- it('should work with no children', function () {
- React.renderComponent(
-
- ,
- container
- );
- });
+ var Child = React.createClass({
+ componentDidMount: function() {
+ log.push('didMount');
+ },
+ componentWillEnter: function(_cb) {
+ log.push('willEnter');
+ cb = _cb;
+ },
+ componentDidEnter: function() {
+ log.push('didEnter');
+ },
+ componentWillLeave: function(cb) {
+ log.push('willLeave');
+ cb();
+ },
+ componentDidLeave: function() {
+ log.push('didLeave');
+ },
+ componentWillUnmount: function() {
+ log.push('willUnmount');
+ },
+ render: function() {
+ return ;
+ }
+ });
- it('should work with a null child', function () {
- React.renderComponent(
-
- {[null]}
- ,
- container
- );
+ var Component = React.createClass({
+ getInitialState: function() {
+ return {count: 1};
+ },
+ render: function() {
+ var children = [];
+ for (var i = 0; i < this.state.count; i++) {
+ children.push();
+ }
+ return {children};
+ }
+ });
+
+ var instance = React.renderComponent(, container);
+ expect(log).toEqual(['didMount']);
+ instance.setState({count: 2});
+ expect(log).toEqual(['didMount', 'didMount', 'willEnter']);
+ for (var i = 0; i < 5; i++) {
+ instance.setState({count: 1});
+ expect(log).toEqual(['didMount', 'didMount', 'willEnter']);
+ instance.setState({count: 2});
+ }
+ cb();
+ expect(log).toEqual([
+ 'didMount', 'didMount', 'willEnter', 'didEnter'
+ ]);
});
});
diff --git a/src/addons/transitions/__tests__/ReactTransitionableChild-test.js b/src/addons/transitions/__tests__/ReactTransitionableChild-test.js
deleted file mode 100644
index 1d5155052b..0000000000
--- a/src/addons/transitions/__tests__/ReactTransitionableChild-test.js
+++ /dev/null
@@ -1,104 +0,0 @@
-/**
- * Copyright 2013 Facebook, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * @jsx React.DOM
- * @emails react-core
- */
-
-"use strict";
-
-var React;
-var ReactTransitionableChild;
-
-var mocks = require('mocks');
-
-describe('ReactTransitionableChild', function() {
- beforeEach(function() {
- React = require('React');
- ReactTransitionableChild = require('ReactTransitionableChild');
- });
-
- it('should keep the DOM node around', function() {
- var container = document.createElement('div');
- var ac = React.renderComponent(
- ,
- container
- );
- expect(ac.getDOMNode().id).toBe('test');
- ac = React.renderComponent(, container);
- expect(ac.getDOMNode().id).toBe('test');
- });
-
- it('should manage enter css classes correctly', function() {
- var runNextTick = mocks.getMockFunction();
- var container = document.createElement('div');
- var ac = React.renderComponent(
-
-
- ,
- container
- );
- expect(ac.getDOMNode().id).toBe('test');
- expect(ac.getDOMNode().className.trim()).toBe('myanim-enter');
- expect(runNextTick.mock.calls.length).toBe(1);
- runNextTick.mock.calls[0][0]();
- expect(ac.getDOMNode().className.trim()).toBe(
- 'myanim-enter myanim-enter-active'
- );
- expect(runNextTick.mock.calls.length).toBe(1);
- });
-
- it('should manage leave css classes correctly', function() {
- var runNextTick = mocks.getMockFunction();
- var container = document.createElement('div');
- var ac = React.renderComponent(
-
-
- ,
- container
- );
- runNextTick.mock.calls[0][0]();
- expect(ac.getDOMNode().className.trim()).toBe(
- 'myanim-enter myanim-enter-active'
- );
-
- // TODO: we should just trigger the CSS animation end event to
- // clean these up
- ac.getDOMNode().className = '';
- React.renderComponent(
- ,
- container
- );
- expect(ac.getDOMNode().className.trim()).toBe('myanim-leave');
- expect(runNextTick.mock.calls.length).toBe(2);
- runNextTick.mock.calls[1][0]();
- expect(ac.getDOMNode().className.trim()).toBe(
- 'myanim-leave myanim-leave-active'
- );
- expect(runNextTick.mock.calls.length).toBe(2);
- });
-});