Skip to content

Commit

Permalink
Use _hostContainerInfo to track test renderer options (facebook#8261)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
aweary authored and acusti committed Mar 15, 2017
1 parent 5a0c048 commit ce57589
Show file tree
Hide file tree
Showing 8 changed files with 66 additions and 25 deletions.
2 changes: 2 additions & 0 deletions scripts/fiber/tests-passing.txt
Expand Up @@ -1217,6 +1217,8 @@ src/renderers/testing/__tests__/ReactTestRenderer-test.js
* gives a ref to native components
* warns correctly for refs on SFCs
* allows an optional createNodeMock function
* supports unmounting when using refs
* supports updates when using refs
* supports error boundaries

src/shared/utils/__tests__/KeyEscapeUtils-test.js
Expand Down
Expand Up @@ -1205,10 +1205,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';
Expand Down
4 changes: 1 addition & 3 deletions src/renderers/shared/stack/reconciler/ReactOwner.js
Expand Up @@ -15,7 +15,6 @@
var invariant = require('invariant');

import type { ReactInstance } from 'ReactInstanceType';
import type { Transaction } from 'Transaction';

/**
* @param {?object} object
Expand Down Expand Up @@ -74,7 +73,6 @@ var ReactOwner = {
component: ReactInstance,
ref: string,
owner: ReactInstance,
transaction: Transaction,
): void {
invariant(
isValidOwner(owner),
Expand All @@ -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);
},

/**
Expand Down
8 changes: 2 additions & 6 deletions src/renderers/shared/stack/reconciler/ReactReconciler.js
Expand Up @@ -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 = {
Expand Down
9 changes: 3 additions & 6 deletions src/renderers/shared/stack/reconciler/ReactRef.js
Expand Up @@ -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,
);
}
}
Expand All @@ -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);
}
};

Expand Down
12 changes: 8 additions & 4 deletions src/renderers/testing/ReactTestMount.js
Expand Up @@ -49,19 +49,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;
Expand All @@ -79,12 +81,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;
Expand Down
17 changes: 13 additions & 4 deletions src/renderers/testing/ReactTestRenderer.js
Expand Up @@ -23,6 +23,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';
Expand Down Expand Up @@ -53,20 +54,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);
}
Expand All @@ -81,10 +85,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 {
Expand Down
35 changes: 35 additions & 0 deletions src/renderers/testing/__tests__/ReactTestRenderer-test.js
Expand Up @@ -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 {
Expand Down

0 comments on commit ce57589

Please sign in to comment.