Use _hostContainerInfo to track test renderer options (#8261)

* Use _hostContainerInfo to track test renderer options

The transaction is not available when unmounting or updating the
instance, so we track it using _hostContainerInfo

* Throw if hostContainerInfo is not populated in getPublicInstance

* Linting fixes

* Remove transaction from ref lifecycle code path

We don't need to pass the transaction around anymore since we store the
test options on _hostContainerInfo instead

* Remove unused argument

(cherry picked from commit e43aaab254)
This commit is contained in:
Brandon Dail
2016-11-16 12:08:28 +00:00
committed by Paul O’Shannessy
parent 28e51005dc
commit 7df71a2525
7 changed files with 64 additions and 25 deletions
@@ -1100,10 +1100,10 @@ var ReactCompositeComponent = {
* @final
* @private
*/
attachRef: function(ref, component, transaction) {
attachRef: function(ref, component) {
var inst = this.getPublicInstance();
invariant(inst != null, 'Stateless function components cannot have refs.');
var publicComponentInstance = component.getPublicInstance(transaction);
var publicComponentInstance = component.getPublicInstance();
if (__DEV__) {
var componentName = component && component.getName ?
component.getName() : 'a component';
@@ -15,7 +15,6 @@
var invariant = require('invariant');
import type { ReactInstance } from 'ReactInstanceType';
import type { Transaction } from 'Transaction';
/**
* @param {?object} object
@@ -74,7 +73,6 @@ var ReactOwner = {
component: ReactInstance,
ref: string,
owner: ReactInstance,
transaction: Transaction,
): void {
invariant(
isValidOwner(owner),
@@ -83,7 +81,7 @@ var ReactOwner = {
'`render` method, or you have multiple copies of React loaded ' +
'(details: https://fb.me/react-refs-must-have-owner).'
);
owner.attachRef(ref, component, transaction);
owner.attachRef(ref, component);
},
/**
@@ -20,12 +20,8 @@ var warning = require('warning');
* Helper to call ReactRef.attachRefs with this composite component, split out
* to avoid allocations in the transaction mount-ready queue.
*/
function attachRefs(transaction) {
ReactRef.attachRefs(
this,
this._currentElement,
transaction,
);
function attachRefs() {
ReactRef.attachRefs(this, this._currentElement);
}
var ReactReconciler = {
@@ -16,20 +16,18 @@ var ReactOwner = require('ReactOwner');
import type { ReactInstance } from 'ReactInstanceType';
import type { ReactElement } from 'ReactElementType';
import type { Transaction } from 'Transaction';
var ReactRef = {};
function attachRef(ref, component, owner, transaction) {
function attachRef(ref, component, owner) {
if (typeof ref === 'function') {
ref(component.getPublicInstance(transaction));
ref(component.getPublicInstance());
} else {
// Legacy ref
ReactOwner.addComponentAsRefTo(
component,
ref,
owner,
transaction,
);
}
}
@@ -46,14 +44,13 @@ function detachRef(ref, component, owner) {
ReactRef.attachRefs = function(
instance: ReactInstance,
element: ReactElement | string | number | null | false,
transaction: Transaction,
): void {
if (element === null || typeof element !== 'object') {
return;
}
var ref = element.ref;
if (ref != null) {
attachRef(ref, instance, element._owner, transaction);
attachRef(ref, instance, element._owner);
}
};
+8 -4
View File
@@ -51,19 +51,21 @@ TopLevelWrapper.isReactTopLevelWrapper = true;
* Mounts this component and inserts it into the DOM.
*
* @param {ReactComponent} componentInstance The instance to mount.
* @param {number} rootID ID of the root node.
* @param {number} containerTag container element to mount into.
* @param {ReactReconcileTransaction} transaction
* @param {Object} hostParent
* @param {Object} hostContainerInfo
*/
function mountComponentIntoNode(
componentInstance,
transaction,
hostParent,
hostContainerInfo
) {
var image = ReactReconciler.mountComponent(
componentInstance,
transaction,
null,
null,
hostContainerInfo,
emptyObject
);
componentInstance._renderedComponent._topLevelWrapper = componentInstance;
@@ -81,12 +83,14 @@ function batchedMountComponentIntoNode(
componentInstance,
options,
) {
var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(options);
var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(true);
var image = transaction.perform(
mountComponentIntoNode,
null,
componentInstance,
transaction,
null,
options
);
ReactUpdates.ReactReconcileTransaction.release(transaction);
return image;
+13 -4
View File
@@ -22,6 +22,7 @@ var ReactTestReconcileTransaction = require('ReactTestReconcileTransaction');
var ReactUpdates = require('ReactUpdates');
var ReactTestTextComponent = require('ReactTestTextComponent');
var ReactTestEmptyComponent = require('ReactTestEmptyComponent');
var invariant = require('invariant');
import type { ReactElement } from 'ReactElementType';
import type { ReactInstance } from 'ReactInstanceType';
@@ -52,20 +53,23 @@ class ReactTestComponent {
_currentElement: ReactElement;
_renderedChildren: null | Object;
_topLevelWrapper: null | ReactInstance;
_hostContainerInfo: null | Object;
constructor(element: ReactElement) {
this._currentElement = element;
this._renderedChildren = null;
this._topLevelWrapper = null;
this._hostContainerInfo = null;
}
mountComponent(
transaction: ReactTestReconcileTransaction,
nativeParent: null | ReactTestComponent,
nativeContainerInfo: ?null,
hostContainerInfo: Object,
context: Object,
) {
var element = this._currentElement;
this._hostContainerInfo = hostContainerInfo;
// $FlowFixMe https://github.com/facebook/flow/issues/1805
this.mountChildren(element.props.children, transaction, context);
}
@@ -80,10 +84,15 @@ class ReactTestComponent {
this.updateChildren(nextElement.props.children, transaction, context);
}
getPublicInstance(transaction: ReactTestReconcileTransaction): Object {
getPublicInstance(): Object {
var element = this._currentElement;
var options = transaction.getTestOptions();
return options.createNodeMock(element);
var hostContainerInfo = this._hostContainerInfo;
invariant(
hostContainerInfo,
'hostContainerInfo should be populated before ' +
'getPublicInstance is called.'
);
return hostContainerInfo.createNodeMock(element);
}
toJSON(): ReactTestRendererJSON {
@@ -306,6 +306,41 @@ describe('ReactTestRenderer', () => {
]);
});
it('supports unmounting when using refs', () => {
class Foo extends React.Component {
render() {
return <div ref="foo" />;
}
}
const inst = ReactTestRenderer.create(
<Foo />,
{createNodeMock: () => 'foo'}
);
expect(() => inst.unmount()).not.toThrow();
});
it('supports updates when using refs', () => {
const log = [];
const createNodeMock = element => {
log.push(element.type);
return element.type;
};
class Foo extends React.Component {
render() {
return this.props.useDiv
? <div ref="foo" />
: <span ref="foo" />;
}
}
const inst = ReactTestRenderer.create(
<Foo useDiv={true} />,
{createNodeMock}
);
inst.update(<Foo useDiv={false} />);
// It's called with 'div' twice (mounting and unmounting)
expect(log).toEqual(['div', 'div', 'span']);
});
it('supports error boundaries', () => {
var log = [];
class Angry extends React.Component {