Skip to content

Commit 90007f7

Browse files
sophiebitszpao
authored andcommitted
Add rudimentary test renderer (facebook#6944)
(cherry picked from commit 50982ce)
1 parent 39ddfdd commit 90007f7

File tree

4 files changed

+488
-0
lines changed

4 files changed

+488
-0
lines changed
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*
9+
* @providesModule ReactTestMount
10+
* @flow
11+
*/
12+
'use strict';
13+
14+
var ReactElement = require('ReactElement');
15+
var ReactInstrumentation = require('ReactInstrumentation');
16+
var ReactReconciler = require('ReactReconciler');
17+
var ReactUpdates = require('ReactUpdates');
18+
19+
var emptyObject = require('emptyObject');
20+
var getHostComponentFromComposite = require('getHostComponentFromComposite');
21+
var instantiateReactComponent = require('instantiateReactComponent');
22+
23+
/**
24+
* Temporary (?) hack so that we can store all top-level pending updates on
25+
* composites instead of having to worry about different types of components
26+
* here.
27+
*/
28+
var TopLevelWrapper = function() {};
29+
TopLevelWrapper.prototype.isReactComponent = {};
30+
if (__DEV__) {
31+
TopLevelWrapper.displayName = 'TopLevelWrapper';
32+
}
33+
TopLevelWrapper.prototype.render = function() {
34+
// this.props is actually a ReactElement
35+
return this.props;
36+
};
37+
38+
/**
39+
* Mounts this component and inserts it into the DOM.
40+
*
41+
* @param {ReactComponent} componentInstance The instance to mount.
42+
* @param {number} rootID ID of the root node.
43+
* @param {number} containerTag container element to mount into.
44+
* @param {ReactReconcileTransaction} transaction
45+
*/
46+
function mountComponentIntoNode(
47+
componentInstance,
48+
transaction) {
49+
var image = ReactReconciler.mountComponent(
50+
componentInstance,
51+
transaction,
52+
null,
53+
null,
54+
emptyObject
55+
);
56+
componentInstance._renderedComponent._topLevelWrapper = componentInstance;
57+
return image;
58+
}
59+
60+
/**
61+
* Batched mount.
62+
*
63+
* @param {ReactComponent} componentInstance The instance to mount.
64+
* @param {number} rootID ID of the root node.
65+
* @param {number} containerTag container element to mount into.
66+
*/
67+
function batchedMountComponentIntoNode(
68+
componentInstance) {
69+
var transaction = ReactUpdates.ReactReconcileTransaction.getPooled();
70+
var image = transaction.perform(
71+
mountComponentIntoNode,
72+
null,
73+
componentInstance,
74+
transaction
75+
);
76+
ReactUpdates.ReactReconcileTransaction.release(transaction);
77+
return image;
78+
}
79+
80+
var ReactTestInstance = function(component) {
81+
this._component = component;
82+
};
83+
ReactTestInstance.prototype.getInstance = function() {
84+
return this._component._renderedComponent.getPublicInstance();
85+
};
86+
ReactTestInstance.prototype.toJSON = function() {
87+
var inst = getHostComponentFromComposite(this._component);
88+
return inst.toJSON();
89+
};
90+
91+
/**
92+
* As soon as `ReactMount` is refactored to not rely on the DOM, we can share
93+
* code between the two. For now, we'll hard code the ID logic.
94+
*/
95+
var ReactHostMount = {
96+
97+
render: function(
98+
nextElement: ReactElement
99+
): ?ReactComponent {
100+
var nextWrappedElement = new ReactElement(
101+
TopLevelWrapper,
102+
null,
103+
null,
104+
null,
105+
null,
106+
null,
107+
nextElement
108+
);
109+
110+
// var prevComponent = ReactHostMount._instancesByContainerID[containerTag];
111+
// if (prevComponent) {
112+
// var prevWrappedElement = prevComponent._currentElement;
113+
// var prevElement = prevWrappedElement.props;
114+
// if (shouldUpdateReactComponent(prevElement, nextElement)) {
115+
// ReactUpdateQueue.enqueueElementInternal(prevComponent, nextWrappedElement);
116+
// if (callback) {
117+
// ReactUpdateQueue.enqueueCallbackInternal(prevComponent, callback);
118+
// }
119+
// return prevComponent;
120+
// }
121+
// }
122+
123+
var instance = instantiateReactComponent(nextWrappedElement);
124+
125+
if (__DEV__) {
126+
// Mute future events from the top level wrapper.
127+
// It is an implementation detail that devtools should not know about.
128+
instance._debugID = 0;
129+
130+
if (__DEV__) {
131+
ReactInstrumentation.debugTool.onBeginFlush();
132+
}
133+
}
134+
135+
// The initial render is synchronous but any updates that happen during
136+
// rendering, in componentWillMount or componentDidMount, will be batched
137+
// according to the current batching strategy.
138+
139+
ReactUpdates.batchedUpdates(
140+
batchedMountComponentIntoNode,
141+
instance
142+
);
143+
if (__DEV__) {
144+
// The instance here is TopLevelWrapper so we report mount for its child.
145+
ReactInstrumentation.debugTool.onMountRootComponent(
146+
instance._renderedComponent._debugID
147+
);
148+
ReactInstrumentation.debugTool.onEndFlush();
149+
}
150+
return new ReactTestInstance(instance);
151+
},
152+
153+
};
154+
155+
module.exports = ReactHostMount;
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*
9+
* @providesModule ReactTestReconcileTransaction
10+
* @flow
11+
*/
12+
'use strict';
13+
14+
var CallbackQueue = require('CallbackQueue');
15+
var PooledClass = require('PooledClass');
16+
var Transaction = require('Transaction');
17+
18+
/**
19+
* Provides a `CallbackQueue` queue for collecting `onDOMReady` callbacks during
20+
* the performing of the transaction.
21+
*/
22+
var ON_DOM_READY_QUEUEING = {
23+
/**
24+
* Initializes the internal `onDOMReady` queue.
25+
*/
26+
initialize: function() {
27+
this.reactMountReady.reset();
28+
},
29+
30+
/**
31+
* After DOM is flushed, invoke all registered `onDOMReady` callbacks.
32+
*/
33+
close: function() {
34+
this.reactMountReady.notifyAll();
35+
},
36+
};
37+
38+
/**
39+
* Executed within the scope of the `Transaction` instance. Consider these as
40+
* being member methods, but with an implied ordering while being isolated from
41+
* each other.
42+
*/
43+
var TRANSACTION_WRAPPERS = [ON_DOM_READY_QUEUEING];
44+
45+
/**
46+
* Currently:
47+
* - The order that these are listed in the transaction is critical:
48+
* - Suppresses events.
49+
* - Restores selection range.
50+
*
51+
* Future:
52+
* - Restore document/overflow scroll positions that were unintentionally
53+
* modified via DOM insertions above the top viewport boundary.
54+
* - Implement/integrate with customized constraint based layout system and keep
55+
* track of which dimensions must be remeasured.
56+
*
57+
* @class ReactTestReconcileTransaction
58+
*/
59+
function ReactTestReconcileTransaction() {
60+
this.reinitializeTransaction();
61+
this.reactMountReady = CallbackQueue.getPooled(null);
62+
}
63+
64+
var Mixin = {
65+
/**
66+
* @see Transaction
67+
* @abstract
68+
* @final
69+
* @return {array<object>} List of operation wrap procedures.
70+
* TODO: convert to array<TransactionWrapper>
71+
*/
72+
getTransactionWrappers: function() {
73+
return TRANSACTION_WRAPPERS;
74+
},
75+
76+
/**
77+
* @return {object} The queue to collect `onDOMReady` callbacks with.
78+
* TODO: convert to ReactMountReady
79+
*/
80+
getReactMountReady: function() {
81+
return this.reactMountReady;
82+
},
83+
84+
/**
85+
* `PooledClass` looks for this, and will invoke this before allowing this
86+
* instance to be reused.
87+
*/
88+
destructor: function() {
89+
CallbackQueue.release(this.reactMountReady);
90+
this.reactMountReady = null;
91+
},
92+
};
93+
94+
Object.assign(
95+
ReactTestReconcileTransaction.prototype,
96+
Transaction.Mixin,
97+
ReactTestReconcileTransaction,
98+
Mixin
99+
);
100+
101+
PooledClass.addPoolingTo(ReactTestReconcileTransaction);
102+
103+
module.exports = ReactTestReconcileTransaction;
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/**
2+
* Copyright 2013-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*
9+
* @providesModule ReactTestRenderer
10+
*/
11+
12+
'use strict';
13+
14+
var ReactDefaultBatchingStrategy = require('ReactDefaultBatchingStrategy');
15+
var ReactEmptyComponent = require('ReactEmptyComponent');
16+
var ReactMultiChild = require('ReactMultiChild');
17+
var ReactHostComponent = require('ReactHostComponent');
18+
var ReactTestMount = require('ReactTestMount');
19+
var ReactTestReconcileTransaction = require('ReactTestReconcileTransaction');
20+
var ReactUpdates = require('ReactUpdates');
21+
22+
var renderSubtreeIntoContainer = require('renderSubtreeIntoContainer');
23+
24+
/**
25+
* Drill down (through composites and empty components) until we get a native or
26+
* native text component.
27+
*
28+
* This is pretty polymorphic but unavoidable with the current structure we have
29+
* for `_renderedChildren`.
30+
*/
31+
function getRenderedHostOrTextFromComponent(component) {
32+
var rendered;
33+
while ((rendered = component._renderedComponent)) {
34+
component = rendered;
35+
}
36+
return component;
37+
}
38+
39+
40+
// =============================================================================
41+
42+
var ReactTestComponent = function(element) {
43+
this._currentElement = element;
44+
this._renderedChildren = null;
45+
this._topLevelWrapper = null;
46+
};
47+
ReactTestComponent.prototype.mountComponent = function(
48+
transaction,
49+
nativeParent,
50+
nativeContainerInfo,
51+
context
52+
) {
53+
var element = this._currentElement;
54+
this.mountChildren(element.props.children, transaction, context);
55+
};
56+
ReactTestComponent.prototype.receiveComponent = function(
57+
nextElement,
58+
transaction,
59+
context
60+
) {
61+
this._currentElement = nextElement;
62+
this.updateChildren(nextElement.props.children, transaction, context);
63+
};
64+
ReactTestComponent.prototype.getHostNode = function() {};
65+
ReactTestComponent.prototype.unmountComponent = function() {};
66+
ReactTestComponent.prototype.toJSON = function() {
67+
var {children, ...props} = this._currentElement.props;
68+
var childrenJSON = [];
69+
for (var key in this._renderedChildren) {
70+
var inst = this._renderedChildren[key];
71+
inst = getRenderedHostOrTextFromComponent(inst);
72+
var json = inst.toJSON();
73+
if (json !== undefined) {
74+
childrenJSON.push(json);
75+
}
76+
}
77+
return {
78+
type: this._currentElement.type,
79+
props: props,
80+
children: childrenJSON.length ? childrenJSON : null,
81+
};
82+
};
83+
Object.assign(ReactTestComponent.prototype, ReactMultiChild.Mixin);
84+
85+
// =============================================================================
86+
87+
var ReactTestTextComponent = function(element) {
88+
this._currentElement = element;
89+
};
90+
ReactTestTextComponent.prototype.mountComponent = function() {};
91+
ReactTestTextComponent.prototype.receiveComponent = function(nextElement) {
92+
this._currentElement = nextElement;
93+
};
94+
ReactTestTextComponent.prototype.getHostNode = function() {};
95+
ReactTestTextComponent.prototype.unmountComponent = function() {};
96+
ReactTestTextComponent.prototype.toJSON = function() {
97+
return this._currentElement;
98+
};
99+
100+
// =============================================================================
101+
102+
var ReactTestEmptyComponent = function(element) {
103+
this._currentElement = null;
104+
};
105+
ReactTestEmptyComponent.prototype.mountComponent = function() {};
106+
ReactTestEmptyComponent.prototype.receiveComponent = function() {};
107+
ReactTestEmptyComponent.prototype.getHostNode = function() {};
108+
ReactTestEmptyComponent.prototype.unmountComponent = function() {};
109+
ReactTestEmptyComponent.prototype.toJSON = function() {};
110+
111+
// =============================================================================
112+
113+
ReactUpdates.injection.injectReconcileTransaction(
114+
ReactTestReconcileTransaction
115+
);
116+
ReactUpdates.injection.injectBatchingStrategy(ReactDefaultBatchingStrategy);
117+
118+
ReactHostComponent.injection.injectGenericComponentClass(ReactTestComponent);
119+
ReactHostComponent.injection.injectTextComponentClass(ReactTestTextComponent);
120+
ReactEmptyComponent.injection.injectEmptyComponentFactory(function() {
121+
return new ReactTestEmptyComponent();
122+
});
123+
124+
var ReactTestRenderer = {
125+
create: ReactTestMount.render,
126+
127+
/* eslint-disable camelcase */
128+
unstable_batchedUpdates: ReactUpdates.batchedUpdates,
129+
unstable_renderSubtreeIntoContainer: renderSubtreeIntoContainer,
130+
/* eslint-enable camelcase */
131+
};
132+
133+
module.exports = ReactTestRenderer;

0 commit comments

Comments
 (0)