diff --git a/scripts/fiber/tests-passing.txt b/scripts/fiber/tests-passing.txt index 4af7f5496914f..438bec74ba75c 100644 --- a/scripts/fiber/tests-passing.txt +++ b/scripts/fiber/tests-passing.txt @@ -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 diff --git a/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js b/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js index 8e4e03072e960..d7ea65be32a3c 100644 --- a/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js +++ b/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js @@ -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'; diff --git a/src/renderers/shared/stack/reconciler/ReactOwner.js b/src/renderers/shared/stack/reconciler/ReactOwner.js index a804513d73583..c634dfb384432 100644 --- a/src/renderers/shared/stack/reconciler/ReactOwner.js +++ b/src/renderers/shared/stack/reconciler/ReactOwner.js @@ -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); }, /** diff --git a/src/renderers/shared/stack/reconciler/ReactReconciler.js b/src/renderers/shared/stack/reconciler/ReactReconciler.js index f55bf339ee7aa..14a4191710d44 100644 --- a/src/renderers/shared/stack/reconciler/ReactReconciler.js +++ b/src/renderers/shared/stack/reconciler/ReactReconciler.js @@ -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 = { diff --git a/src/renderers/shared/stack/reconciler/ReactRef.js b/src/renderers/shared/stack/reconciler/ReactRef.js index 12e04e81c02ca..a7350cc6a8a73 100644 --- a/src/renderers/shared/stack/reconciler/ReactRef.js +++ b/src/renderers/shared/stack/reconciler/ReactRef.js @@ -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); } }; diff --git a/src/renderers/testing/ReactTestMount.js b/src/renderers/testing/ReactTestMount.js index d1dcdfeb41865..65dd8c4d0a209 100644 --- a/src/renderers/testing/ReactTestMount.js +++ b/src/renderers/testing/ReactTestMount.js @@ -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; @@ -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; diff --git a/src/renderers/testing/ReactTestRenderer.js b/src/renderers/testing/ReactTestRenderer.js index 47184fea0c1bf..840e045d7b391 100644 --- a/src/renderers/testing/ReactTestRenderer.js +++ b/src/renderers/testing/ReactTestRenderer.js @@ -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'; @@ -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); } @@ -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 { diff --git a/src/renderers/testing/__tests__/ReactTestRenderer-test.js b/src/renderers/testing/__tests__/ReactTestRenderer-test.js index 7ca6c201396ce..d939fa86bc2c7 100644 --- a/src/renderers/testing/__tests__/ReactTestRenderer-test.js +++ b/src/renderers/testing/__tests__/ReactTestRenderer-test.js @@ -306,6 +306,41 @@ describe('ReactTestRenderer', () => { ]); }); + it('supports unmounting when using refs', () => { + class Foo extends React.Component { + render() { + return
; + } + } + const inst = ReactTestRenderer.create( + , + {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 + ?
+ : ; + } + } + const inst = ReactTestRenderer.create( + , + {createNodeMock} + ); + inst.update(); + // 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 {