Skip to content

Commit

Permalink
Test renderer improvements (#7258)
Browse files Browse the repository at this point in the history
Adds `.update(newElement)` and `.unmount()` and makes children reorders and composite type swapping work.

Part of #7148.
(cherry picked from commit caec8d5)
  • Loading branch information
sophiebits authored and zpao committed Jul 13, 2016
1 parent 40cc8fa commit 1c3b4af
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 15 deletions.
60 changes: 45 additions & 15 deletions src/renderers/testing/ReactTestMount.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ var ReactUpdates = require('ReactUpdates');
var emptyObject = require('emptyObject');
var getHostComponentFromComposite = require('getHostComponentFromComposite');
var instantiateReactComponent = require('instantiateReactComponent');
var invariant = require('invariant');

/**
* Temporary (?) hack so that we can store all top-level pending updates on
Expand Down Expand Up @@ -83,6 +84,48 @@ var ReactTestInstance = function(component) {
ReactTestInstance.prototype.getInstance = function() {
return this._component._renderedComponent.getPublicInstance();
};
ReactTestInstance.prototype.update = function(nextElement) {
invariant(
this._component,
"ReactTestRenderer: .update() can't be called after unmount."
);
var nextWrappedElement = new ReactElement(
TopLevelWrapper,
null,
null,
null,
null,
null,
nextElement
);
var component = this._component;
ReactUpdates.batchedUpdates(function() {
var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(true);
transaction.perform(function() {
ReactReconciler.receiveComponent(
component,
nextWrappedElement,
transaction,
emptyObject
);
});
ReactUpdates.ReactReconcileTransaction.release(transaction);
});
};
ReactTestInstance.prototype.unmount = function(nextElement) {
var component = this._component;
ReactUpdates.batchedUpdates(function() {
var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(true);
transaction.perform(function() {
ReactReconciler.unmountComponent(
component,
false
);
});
ReactUpdates.ReactReconcileTransaction.release(transaction);
});
this._component = null;
};
ReactTestInstance.prototype.toJSON = function() {
var inst = getHostComponentFromComposite(this._component);
return inst.toJSON();
Expand All @@ -92,7 +135,7 @@ ReactTestInstance.prototype.toJSON = function() {
* As soon as `ReactMount` is refactored to not rely on the DOM, we can share
* code between the two. For now, we'll hard code the ID logic.
*/
var ReactHostMount = {
var ReactTestMount = {

render: function(
nextElement: ReactElement
Expand All @@ -107,19 +150,6 @@ var ReactHostMount = {
nextElement
);

// var prevComponent = ReactHostMount._instancesByContainerID[containerTag];
// if (prevComponent) {
// var prevWrappedElement = prevComponent._currentElement;
// var prevElement = prevWrappedElement.props;
// if (shouldUpdateReactComponent(prevElement, nextElement)) {
// ReactUpdateQueue.enqueueElementInternal(prevComponent, nextWrappedElement);
// if (callback) {
// ReactUpdateQueue.enqueueCallbackInternal(prevComponent, callback);
// }
// return prevComponent;
// }
// }

var instance = instantiateReactComponent(nextWrappedElement, false);

// The initial render is synchronous but any updates that happen during
Expand All @@ -141,4 +171,4 @@ var ReactHostMount = {

};

module.exports = ReactHostMount;
module.exports = ReactTestMount;
11 changes: 11 additions & 0 deletions src/renderers/testing/ReactTestRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

'use strict';

var ReactComponentEnvironment = require('ReactComponentEnvironment');
var ReactDefaultBatchingStrategy = require('ReactDefaultBatchingStrategy');
var ReactEmptyComponent = require('ReactEmptyComponent');
var ReactMultiChild = require('ReactMultiChild');
Expand Down Expand Up @@ -62,6 +63,11 @@ ReactTestComponent.prototype.receiveComponent = function(
this.updateChildren(nextElement.props.children, transaction, context);
};
ReactTestComponent.prototype.getHostNode = function() {};
ReactTestComponent.prototype.getPublicInstance = function() {
// I can't say this makes a ton of sense but it seems better than throwing.
// Maybe we'll revise later if someone has a good use case.
return null;
};
ReactTestComponent.prototype.unmountComponent = function() {};
ReactTestComponent.prototype.toJSON = function() {
var {children, ...props} = this._currentElement.props;
Expand Down Expand Up @@ -125,6 +131,11 @@ ReactEmptyComponent.injection.injectEmptyComponentFactory(function() {
return new ReactTestEmptyComponent();
});

ReactComponentEnvironment.injection.injectEnvironment({
processChildrenUpdates: function() {},
replaceNodeWithMarkup: function() {},
});

var ReactTestRenderer = {
create: ReactTestMount.render,

Expand Down
87 changes: 87 additions & 0 deletions src/renderers/testing/__tests__/ReactTestRenderer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,91 @@ describe('ReactTestRenderer', function() {
});
});

it('updates types', function() {
var renderer = ReactTestRenderer.create(<div>mouse</div>);
expect(renderer.toJSON()).toEqual({
type: 'div',
props: {},
children: ['mouse'],
});

renderer.update(<span>mice</span>);
expect(renderer.toJSON()).toEqual({
type: 'span',
props: {},
children: ['mice'],
});
});

it('updates children', function() {
var renderer = ReactTestRenderer.create(
<div>
<span key="a">A</span>
<span key="b">B</span>
<span key="c">C</span>
</div>
);
expect(renderer.toJSON()).toEqual({
type: 'div',
props: {},
children: [
{type: 'span', props: {}, children: ['A']},
{type: 'span', props: {}, children: ['B']},
{type: 'span', props: {}, children: ['C']},
],
});

renderer.update(
<div>
<span key="d">D</span>
<span key="c">C</span>
<span key="b">B</span>
</div>
);
expect(renderer.toJSON()).toEqual({
type: 'div',
props: {},
children: [
{type: 'span', props: {}, children: ['D']},
{type: 'span', props: {}, children: ['C']},
{type: 'span', props: {}, children: ['B']},
],
});
});

it('does the full lifecycle', function() {
var log = [];
class Log extends React.Component {
render() {
log.push('render ' + this.props.name);
return <div />;
}
componentDidMount() {
log.push('mount ' + this.props.name);
}
componentWillUnmount() {
log.push('unmount ' + this.props.name);
}
}

var renderer = ReactTestRenderer.create(<Log key="foo" name="Foo" />);
renderer.update(<Log key="bar" name="Bar" />);
renderer.unmount();

expect(log).toEqual([
'render Foo',
'mount Foo',
'unmount Foo',
'render Bar',
'mount Bar',
'unmount Bar',
]);
});

it('gives a ref to native components', function() {
var log = [];
ReactTestRenderer.create(<div ref={(r) => log.push(r)} />);
expect(log).toEqual([null]);
});

});

0 comments on commit 1c3b4af

Please sign in to comment.