mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Test renderer improvements (#7258)
Adds `.update(newElement)` and `.unmount()` and makes children reorders and composite type swapping work. Part of #7148.
This commit is contained in:
@@ -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
|
||||
@@ -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();
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -141,4 +171,4 @@ var ReactHostMount = {
|
||||
|
||||
};
|
||||
|
||||
module.exports = ReactHostMount;
|
||||
module.exports = ReactTestMount;
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
var ReactComponentEnvironment = require('ReactComponentEnvironment');
|
||||
var ReactDefaultBatchingStrategy = require('ReactDefaultBatchingStrategy');
|
||||
var ReactEmptyComponent = require('ReactEmptyComponent');
|
||||
var ReactMultiChild = require('ReactMultiChild');
|
||||
@@ -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;
|
||||
@@ -125,6 +131,11 @@ ReactEmptyComponent.injection.injectEmptyComponentFactory(function() {
|
||||
return new ReactTestEmptyComponent();
|
||||
});
|
||||
|
||||
ReactComponentEnvironment.injection.injectEnvironment({
|
||||
processChildrenUpdates: function() {},
|
||||
replaceNodeWithMarkup: function() {},
|
||||
});
|
||||
|
||||
var ReactTestRenderer = {
|
||||
create: ReactTestMount.render,
|
||||
|
||||
|
||||
@@ -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]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user