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
Brandon Dail committed
Nov 11, 2016 at 08:35 UTC
e43aaab2547870bc80c0b778604c5ee55b1d87f0
8 files changed
+66
-25
scripts/fiber/tests-passing.txt
+2
@@ -1217,6 +1217,8 @@ src/renderers/testing/__tests__/ReactTestRenderer-test.js
1217
* gives a ref to native components
1218
* warns correctly for refs on SFCs
1219
* allows an optional createNodeMock function
1220
+* supports unmounting when using refs
1221
+* supports updates when using refs
1222
* supports error boundaries
1223
1224
src/shared/utils/__tests__/KeyEscapeUtils-test.js
src/renderers/shared/stack/reconciler/ReactCompositeComponent.js
+2
-2
@@ -1205,10 +1205,10 @@ var ReactCompositeComponent = {
1205
* @final
1206
* @private
1207
*/
1208
- attachRef: function(ref, component, transaction) {
1208
+ attachRef: function(ref, component) {
1209
var inst = this.getPublicInstance();
1210
invariant(inst != null, 'Stateless function components cannot have refs.');
1211
- var publicComponentInstance = component.getPublicInstance(transaction);
1211
+ var publicComponentInstance = component.getPublicInstance();
1212
if (__DEV__) {
1213
var componentName = component && component.getName ?
1214
component.getName() : 'a component';
src/renderers/shared/stack/reconciler/ReactOwner.js
+1
-3
@@ -15,7 +15,6 @@
15
var invariant = require('invariant');
16
17
import type { ReactInstance } from 'ReactInstanceType';
18
-import type { Transaction } from 'Transaction';
18
19
/**
20
* @param {?object} object
@@ -74,7 +73,6 @@ var ReactOwner = {
73
component: ReactInstance,
74
ref: string,
75
owner: ReactInstance,
77
- transaction: Transaction,
76
): void {
77
invariant(
78
isValidOwner(owner),
@@ -83,7 +81,7 @@ var ReactOwner = {
81
'`render` method, or you have multiple copies of React loaded ' +
82
'(details: https://fb.me/react-refs-must-have-owner).'
83
);
86
- owner.attachRef(ref, component, transaction);
84
+ owner.attachRef(ref, component);
85
},
86
87
/**
src/renderers/shared/stack/reconciler/ReactReconciler.js
+2
-6
@@ -20,12 +20,8 @@ var warning = require('warning');
20
* Helper to call ReactRef.attachRefs with this composite component, split out
21
* to avoid allocations in the transaction mount-ready queue.
22
*/
23
-function attachRefs(transaction) {
24
- ReactRef.attachRefs(
25
- this,
26
- this._currentElement,
27
- transaction,
28
- );
23
+function attachRefs() {
24
+ ReactRef.attachRefs(this, this._currentElement);
25
}
26
27
var ReactReconciler = {
src/renderers/shared/stack/reconciler/ReactRef.js
+3
-6
@@ -16,20 +16,18 @@ var ReactOwner = require('ReactOwner');
16
17
import type { ReactInstance } from 'ReactInstanceType';
18
import type { ReactElement } from 'ReactElementType';
19
-import type { Transaction } from 'Transaction';
19
20
var ReactRef = {};
21
23
-function attachRef(ref, component, owner, transaction) {
22
+function attachRef(ref, component, owner) {
23
if (typeof ref === 'function') {
25
- ref(component.getPublicInstance(transaction));
24
+ ref(component.getPublicInstance());
25
} else {
26
// Legacy ref
27
ReactOwner.addComponentAsRefTo(
28
component,
29
ref,
30
owner,
32
- transaction,
31
);
32
}
33
}
@@ -46,14 +44,13 @@ function detachRef(ref, component, owner) {
44
ReactRef.attachRefs = function(
45
instance: ReactInstance,
46
element: ReactElement | string | number | null | false,
49
- transaction: Transaction,
47
): void {
48
if (element === null || typeof element !== 'object') {
49
return;
50
}
51
var ref = element.ref;
52
if (ref != null) {
56
- attachRef(ref, instance, element._owner, transaction);
53
+ attachRef(ref, instance, element._owner);
54
}
55
};
56
src/renderers/testing/ReactTestMount.js
+8
-4
@@ -49,19 +49,21 @@ TopLevelWrapper.isReactTopLevelWrapper = true;
49
* Mounts this component and inserts it into the DOM.
50
*
51
* @param {ReactComponent} componentInstance The instance to mount.
52
- * @param {number} rootID ID of the root node.
53
- * @param {number} containerTag container element to mount into.
52
* @param {ReactReconcileTransaction} transaction
53
+ * @param {Object} hostParent
54
+ * @param {Object} hostContainerInfo
55
*/
56
function mountComponentIntoNode(
57
componentInstance,
58
transaction,
59
+ hostParent,
60
+ hostContainerInfo
61
) {
62
var image = ReactReconciler.mountComponent(
63
componentInstance,
64
transaction,
65
null,
64
- null,
66
+ hostContainerInfo,
67
emptyObject
68
);
69
componentInstance._renderedComponent._topLevelWrapper = componentInstance;
@@ -79,12 +81,14 @@ function batchedMountComponentIntoNode(
81
componentInstance,
82
options,
83
) {
82
- var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(options);
84
+ var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(true);
85
var image = transaction.perform(
86
mountComponentIntoNode,
87
null,
88
componentInstance,
89
transaction,
90
+ null,
91
+ options
92
);
93
ReactUpdates.ReactReconcileTransaction.release(transaction);
94
return image;
src/renderers/testing/ReactTestRenderer.js
+13
-4
@@ -23,6 +23,7 @@ var ReactTestReconcileTransaction = require('ReactTestReconcileTransaction');
23
var ReactUpdates = require('ReactUpdates');
24
var ReactTestTextComponent = require('ReactTestTextComponent');
25
var ReactTestEmptyComponent = require('ReactTestEmptyComponent');
26
+var invariant = require('invariant');
27
28
import type { ReactElement } from 'ReactElementType';
29
import type { ReactInstance } from 'ReactInstanceType';
@@ -53,20 +54,23 @@ class ReactTestComponent {
54
_currentElement: ReactElement;
55
_renderedChildren: null | Object;
56
_topLevelWrapper: null | ReactInstance;
57
+ _hostContainerInfo: null | Object;
58
59
constructor(element: ReactElement) {
60
this._currentElement = element;
61
this._renderedChildren = null;
62
this._topLevelWrapper = null;
63
+ this._hostContainerInfo = null;
64
}
65
66
mountComponent(
67
transaction: ReactTestReconcileTransaction,
68
nativeParent: null | ReactTestComponent,
66
- nativeContainerInfo: ?null,
69
+ hostContainerInfo: Object,
70
context: Object,
71
) {
72
var element = this._currentElement;
73
+ this._hostContainerInfo = hostContainerInfo;
74
// $FlowFixMe https://github.com/facebook/flow/issues/1805
75
this.mountChildren(element.props.children, transaction, context);
76
}
@@ -81,10 +85,15 @@ class ReactTestComponent {
85
this.updateChildren(nextElement.props.children, transaction, context);
86
}
87
84
- getPublicInstance(transaction: ReactTestReconcileTransaction): Object {
88
+ getPublicInstance(): Object {
89
var element = this._currentElement;
86
- var options = transaction.getTestOptions();
87
- return options.createNodeMock(element);
90
+ var hostContainerInfo = this._hostContainerInfo;
91
+ invariant(
92
+ hostContainerInfo,
93
+ 'hostContainerInfo should be populated before ' +
94
+ 'getPublicInstance is called.'
95
+ );
96
+ return hostContainerInfo.createNodeMock(element);
97
}
98
99
toJSON(): ReactTestRendererJSON {
src/renderers/testing/__tests__/ReactTestRenderer-test.js
+35
@@ -306,6 +306,41 @@ describe('ReactTestRenderer', () => {
306
]);
307
});
308
309
+ it('supports unmounting when using refs', () => {
310
+ class Foo extends React.Component {
311
+ render() {
312
+ return <div ref="foo" />;
313
+ }
314
+ }
315
+ const inst = ReactTestRenderer.create(
316
+ <Foo />,
317
+ {createNodeMock: () => 'foo'}
318
+ );
319
+ expect(() => inst.unmount()).not.toThrow();
320
+ });
321
+
322
+ it('supports updates when using refs', () => {
323
+ const log = [];
324
+ const createNodeMock = element => {
325
+ log.push(element.type);
326
+ return element.type;
327
+ };
328
+ class Foo extends React.Component {
329
+ render() {
330
+ return this.props.useDiv
331
+ ? <div ref="foo" />
332
+ : <span ref="foo" />;
333
+ }
334
+ }
335
+ const inst = ReactTestRenderer.create(
336
+ <Foo useDiv={true} />,
337
+ {createNodeMock}
338
+ );
339
+ inst.update(<Foo useDiv={false} />);
340
+ // It's called with 'div' twice (mounting and unmounting)
341
+ expect(log).toEqual(['div', 'div', 'span']);
342
+ });
343
+
344
it('supports error boundaries', () => {
345
var log = [];
346
class Angry extends React.Component {