Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions src/core/ReactCompositeComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,73 @@ var ReactCompositeComponentMixin = {
}
),

/**
* Renders the component shallowly, returning a ReactElement.
*
* @param {string} rootID DOM ID of the root node.
* @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
* @param {number} mountDepth number of components in the owner hierarchy
* @return {ReactElement} Shallow rendering of the component.
* @final
* @internal
*/
_shallowMountComponent: function(rootID, transaction, mountDepth) {
ReactComponent.Mixin.mountComponent.call(
this,
rootID,
transaction,
mountDepth
);
this._compositeLifeCycleState = CompositeLifeCycle.MOUNTING;

if (this.__reactAutoBindMap) {
this._bindAutoBindMethods();
}

this.context = this._processContext(this._currentElement._context);
this.props = this._processProps(this.props);

this.state = this.getInitialState ? this.getInitialState() : null;

if (__DEV__) {
// We allow auto-mocks to proceed as if they're returning null.
if (typeof this.state === 'undefined' &&
this.getInitialState && this.getInitialState._isMockFunction) {
// This is probably bad practice. Consider warning here and
// deprecating this convenience.
this.state = null;
}
}

invariant(
typeof this.state === 'object' && !Array.isArray(this.state),
'%s.getInitialState(): must return an object or null',
this.constructor.displayName || 'ReactCompositeComponent'
);

this._pendingState = null;
this._pendingForceUpdate = false;

if (this.componentWillMount) {
this.componentWillMount();
// When mounting, calls to `setState` by `componentWillMount` will set
// `this._pendingState` without triggering a re-render.
if (this._pendingState) {
this.state = this._pendingState;
this._pendingState = null;
}
}

var renderedElement = this._renderValidatedComponent();
// In a regular mount, we would then call instantiateReactComponent() on
// the above. For a shallow mount though, we want to stop here.

// Done with mounting, `setState` will now trigger UI changes.
this._compositeLifeCycleState = null;

return renderedElement;
},

/**
* Releases any resources allocated by `mountComponent`.
*
Expand Down
80 changes: 80 additions & 0 deletions src/test/ReactTestUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ var EventPropagators = require('EventPropagators');
var React = require('React');
var ReactElement = require('ReactElement');
var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
var ReactInstanceHandles = require('ReactInstanceHandles');
var ReactMount = require('ReactMount');
var ReactTextComponent = require('ReactTextComponent');
var ReactUpdates = require('ReactUpdates');
var SyntheticEvent = require('SyntheticEvent');

var assign = require('Object.assign');
var instantiateReactComponent = require('instantiateReactComponent');

var topLevelTypes = EventConstants.topLevelTypes;

Expand Down Expand Up @@ -275,6 +277,46 @@ var ReactTestUtils = {
);
},

areElementsEquivalent: function(re1, re2) {
return (
re1.type === re2.type &&
re1.key === re2.key &&
re1.ref === re2.ref &&
ReactTestUtils._arePropsEquivalent(re1._store.props, re2._store.props)
);
},

_arePropsEquivalent: function(props1, props2) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First condition in this case will be totaly ignored. May use every and condition after them?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, thanks.

// Is every key in props1 (except children) also in props2 and equal?
Object.keys(props1).forEach(function(key) {
if (key !== 'children' && props1[key] !== props2[key]) {
return false;
}
});

// Are there no keys unique to props2?
if (Object.keys(props1).length !== Object.keys(props2).length) {
return false;
}

// If neither element's props has children, we're good.
if (props1.children == null && props2.children == null) {
return true;
}

// Lastly, if they do have children, compare each.
if (props1.children.length !== props2.children.length) {
return false;
}
for (var i = 0; i < props1.children.length; i++) {
if (!ReactTestUtils.areElementsEquivalent(props1.children[i],
props2.children[i])) {
return false;
}
}
return true;
},

nativeTouchData: function(x, y) {
return {
touches: [
Expand All @@ -283,10 +325,48 @@ var ReactTestUtils = {
};
},

createRenderer: function() {
return new ReactShallowRenderer();
},

Simulate: null,
SimulateNative: {}
};

/**
* @class ReactShallowRenderer
*/
var ReactShallowRenderer = function() {};

ReactShallowRenderer.prototype.getRenderOutput = function() {
return this._renderOutput;
};

ReactShallowRenderer.prototype.render = function(element) {
var instance = instantiateReactComponent(element, null);
var rootID = ReactInstanceHandles.createReactRootID();

// transaction stuff copied from ReactComponent, mountComponentIntoNode
var transaction = ReactUpdates.ReactReconcileTransaction.getPooled();
transaction.perform(
this._render,
this,
element,
instance,
rootID,
transaction
);
ReactUpdates.ReactReconcileTransaction.release(transaction);
};

ReactShallowRenderer.prototype._render = function(
element,
instance,
rootID,
transaction) {
this._renderOutput = instance._shallowMountComponent(rootID, transaction, 0);
};

/**
* Exports:
*
Expand Down
64 changes: 64 additions & 0 deletions src/test/__tests__/ReactTestUtils-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* Copyright 2013-2014, 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 mocks;
var warn;


describe('ReactTestUtils', function() {

beforeEach(function() {
mocks = require('mocks');

React = require('React');
ReactTestUtils = require('ReactTestUtils');

warn = console.warn;
console.warn = mocks.getMockFunction();
});

afterEach(function() {
console.warn = warn;
});

it('should have shallow rendering in the test utils', function() {
var SomeComponent = React.createClass({
render: function() {
return (
<div>
<span className="child1" />
<span className="child2" />
</div>
);
}
});

var shallowRenderer = ReactTestUtils.createRenderer();
shallowRenderer.render(<SomeComponent />);
// shallowRenderer.attachRef('myRefName', someMock);

var result = shallowRenderer.getRenderOutput();

var expected = (
<div>
<span className="child1" />
<span className="child2" />
</div>
);

expect(ReactTestUtils.areElementsEquivalent(result, expected)).toBe(true);
});
});