Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to render child custom component in shallow renderer #5513

Closed
wants to merge 1 commit into from
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
36 changes: 18 additions & 18 deletions src/renderers/shared/reconciler/ReactChildReconciler.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,29 +19,29 @@ var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
var traverseAllChildren = require('traverseAllChildren');
var warning = require('warning');

function instantiateChild(childInstances, child, name) {
// We found a component instance.
var keyUnique = (childInstances[name] === undefined);
if (__DEV__) {
warning(
keyUnique,
'flattenChildren(...): Encountered two children with the same key, ' +
'`%s`. Child keys must be unique; when two children share a key, only ' +
'the first child will be used.',
name
);
}
if (child != null && keyUnique) {
childInstances[name] = instantiateReactComponent(child, null);
}
}

/**
* ReactChildReconciler provides helpers for initializing or updating a set of
* children. Its output is suitable for passing it onto ReactMultiChild which
* does diffed reordering and insertion.
*/
var ReactChildReconciler = {
_instantiateReactComponent: instantiateReactComponent,
instantiateChild: function(childInstances, child, name) {
// We found a component instance.
var keyUnique = (childInstances[name] === undefined);
if (__DEV__) {
warning(
keyUnique,
'flattenChildren(...): Encountered two children with the same key, ' +
'`%s`. Child keys must be unique; when two children share a key, only ' +
'the first child will be used.',
name
);
}
if (child != null && keyUnique) {
childInstances[name] = this._instantiateReactComponent(child, null);
}
},
/**
* Generates a "mount image" for each of the supplied children. In the case
* of `ReactDOMComponent`, a mount image is a string of markup.
Expand All @@ -55,7 +55,7 @@ var ReactChildReconciler = {
return null;
}
var childInstances = {};
traverseAllChildren(nestedChildNodes, instantiateChild, childInstances);
traverseAllChildren(nestedChildNodes, this.instantiateChild.bind(this), childInstances);
return childInstances;
},

Expand Down
16 changes: 8 additions & 8 deletions src/renderers/shared/reconciler/ReactMultiChild.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,21 +191,21 @@ var ReactMultiChild = {
* @lends {ReactMultiChild.prototype}
*/
Mixin: {

_childReconciler: ReactChildReconciler,
_reconcilerInstantiateChildren: function(nestedChildren, transaction, context) {
if (__DEV__) {
if (this._currentElement) {
try {
ReactCurrentOwner.current = this._currentElement._owner;
return ReactChildReconciler.instantiateChildren(
return this._childReconciler.instantiateChildren(
nestedChildren, transaction, context
);
} finally {
ReactCurrentOwner.current = null;
}
}
}
return ReactChildReconciler.instantiateChildren(
return this._childReconciler.instantiateChildren(
nestedChildren, transaction, context
);
},
Expand All @@ -220,13 +220,13 @@ var ReactMultiChild = {
} finally {
ReactCurrentOwner.current = null;
}
return ReactChildReconciler.updateChildren(
return this._childReconciler.updateChildren(
prevChildren, nextChildren, transaction, context
);
}
}
nextChildren = flattenChildren(nextNestedChildrenElements);
return ReactChildReconciler.updateChildren(
return this._childReconciler.updateChildren(
prevChildren, nextChildren, transaction, context
);
},
Expand Down Expand Up @@ -275,7 +275,7 @@ var ReactMultiChild = {
try {
var prevChildren = this._renderedChildren;
// Remove any rendered children.
ReactChildReconciler.unmountChildren(prevChildren);
this._childReconciler.unmountChildren(prevChildren);
// TODO: The setTextContent operation should be enough
for (var name in prevChildren) {
if (prevChildren.hasOwnProperty(name)) {
Expand Down Expand Up @@ -309,7 +309,7 @@ var ReactMultiChild = {
try {
var prevChildren = this._renderedChildren;
// Remove any rendered children.
ReactChildReconciler.unmountChildren(prevChildren);
this._childReconciler.unmountChildren(prevChildren);
for (var name in prevChildren) {
if (prevChildren.hasOwnProperty(name)) {
this._unmountChild(prevChildren[name]);
Expand Down Expand Up @@ -417,7 +417,7 @@ var ReactMultiChild = {
*/
unmountChildren: function() {
var renderedChildren = this._renderedChildren;
ReactChildReconciler.unmountChildren(renderedChildren);
this._childReconciler.unmountChildren(renderedChildren);
this._renderedChildren = null;
},

Expand Down
132 changes: 121 additions & 11 deletions src/test/ReactTestUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ var assign = require('Object.assign');
var emptyObject = require('emptyObject');
var findDOMNode = require('findDOMNode');
var invariant = require('invariant');
var originInstantiateReactComponent = require('instantiateReactComponent');
var ReactMultiChild = require('ReactMultiChild');
var ReactChildReconciler = require('ReactChildReconciler');

var topLevelTypes = EventConstants.topLevelTypes;

Expand Down Expand Up @@ -76,6 +79,7 @@ function findAllInRenderedTreeInternal(inst, test) {
* @lends ReactTestUtils
*/
var ReactTestUtils = {
renderComponentsAsChild: [],
renderIntoDocument: function(instance) {
var div = document.createElement('div');
// None of our tests actually require attaching the container to the
Expand Down Expand Up @@ -355,6 +359,37 @@ var ReactTestUtils = {
SimulateNative: {},
};

function rebuildElementsFromRenderedComponent(component) {
if (!component._currentElement) {
return null;
}

if (typeof component._currentElement === 'string') {
return component._currentElement;
}

var props = assign({}, component._currentElement.props);

if (props.children) {
delete props.children;
}

if (ReactTestUtils.renderComponentsAsChild.indexOf(component._currentElement.type) !== -1) {
return rebuildElementsFromRenderedComponent(component._renderedComponent);
}

var args = [component._currentElement.type, props];

for (var key in component._renderedChildren) {
var childComponent = component._renderedChildren[key];
var element = rebuildElementsFromRenderedComponent(childComponent);

args.push(element);
}

return React.createElement.apply(null, args);
}

/**
* @class ReactShallowRenderer
*/
Expand All @@ -363,11 +398,7 @@ var ReactShallowRenderer = function() {
};

ReactShallowRenderer.prototype.getRenderOutput = function() {
return (
(this._instance && this._instance._renderedComponent &&
this._instance._renderedComponent._renderedOutput)
|| null
);
return rebuildElementsFromRenderedComponent(this._instance._renderedComponent);
};

ReactShallowRenderer.prototype.getMountedInstance = function() {
Expand Down Expand Up @@ -401,17 +432,96 @@ NoopInternalComponent.prototype = {
},
};

var instantiateReactComponent = function(node) {
var instance;

if (node === null ||
node &&
typeof node === 'string' ||
typeof node.type === 'string' ||
typeof node === 'number') {
instance = new ShallowDOMComponentWrapper(node);

instance.construct(node);

return instance;
} else if (node && typeof node.type === 'function') {

if (ReactTestUtils.renderComponentsAsChild.indexOf(node.type) !== -1) {
instance = new ShallowComponentWrapper(node.type);

instance.construct(node);
} else {
instance = new NoopInternalComponent(node);
}

return instance;
}

var component = originInstantiateReactComponent.apply(this, arguments);

return component;
};

var ShallowComponentWrapper = function() { };

assign(
ShallowComponentWrapper.prototype,
ReactCompositeComponent.Mixin, {
_instantiateReactComponent: function(element) {
return new NoopInternalComponent(element);
},
_instantiateReactComponent: instantiateReactComponent,
_replaceNodeWithMarkup: function() {},
_renderValidatedComponent:
ReactCompositeComponent.Mixin
._renderValidatedComponentWithoutOwnerOrContext,
getNativeNode: function() {},
}
);

var ShallowDOMComponentWrapper = function() {};

assign(
ShallowDOMComponentWrapper.prototype,
ShallowComponentWrapper.prototype,
ReactMultiChild.Mixin,
{
_childReconciler: assign(
{},
ReactChildReconciler,
{
_instantiateReactComponent: instantiateReactComponent,
}
),

mountComponent: function(
transaction,
nativeParent,
nativeContainerInfo,
context
) {
if (typeof this._currentElement !== 'object' ||
this._currentElement === null) {
return;
}

var props = this._currentElement.props;

this.mountChildren(
props.children,
transaction,
context
);
},

receiveComponent: function(nextElement, transaction, context) {
if (typeof this._currentElement !== 'object' ||
this._currentElement === null) {

return;
}
this._currentElement = nextElement;
this.updateChildren(nextElement.props.children, transaction, context);
},

unmountComponent: function() {
this.unmountChildren();
},
}
);

Expand Down
52 changes: 52 additions & 0 deletions src/test/__tests__/ReactTestUtils-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,58 @@ describe('ReactTestUtils', function() {

});

it('should not shallowly render custom components by default', function() {
var renderSpy = jasmine.createSpy('ChildComponent.render');

var ChildComponent = React.createClass({
render: renderSpy,
});

var SomeComponent = React.createClass({
render: function() {
return (<div>
<ChildComponent />
</div>);
},
});

var shallowRenderer = ReactTestUtils.createRenderer();
shallowRenderer.render(<SomeComponent />);
var result = shallowRenderer.getRenderOutput();

expect(result).toEqual(<div>
<ChildComponent />
</div>);

expect(renderSpy).not.toHaveBeenCalled();
});

it('can shallowly render custom components which are on white list', function() {
var ChildComponent = React.createClass({
render: function() {
return <div className="child"></div>;
},
});

ReactTestUtils.renderComponentsAsChild.push(ChildComponent);

var SomeComponent = React.createClass({
render: function() {
return (<div>
<ChildComponent />
</div>);
},
});

var shallowRenderer = ReactTestUtils.createRenderer();
shallowRenderer.render(<SomeComponent />);
var result = shallowRenderer.getRenderOutput();

expect(result).toEqual(<div>
<div className="child"></div>
</div>);
});

it('can scryRenderedDOMComponentsWithClass with className contains \\n', function() {
var Wrapper = React.createClass({
render: function() {
Expand Down