-
Notifications
You must be signed in to change notification settings - Fork 47.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add rudimentary test renderer (#6944)
(cherry picked from commit 50982ce)
- Loading branch information
1 parent
39ddfdd
commit 90007f7
Showing
4 changed files
with
488 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
/** | ||
* Copyright (c) 2015-present, Facebook, Inc. | ||
* All rights reserved. | ||
* | ||
* This source code is licensed under the BSD-style license found in the | ||
* LICENSE file in the root directory of this source tree. An additional grant | ||
* of patent rights can be found in the PATENTS file in the same directory. | ||
* | ||
* @providesModule ReactTestMount | ||
* @flow | ||
*/ | ||
'use strict'; | ||
|
||
var ReactElement = require('ReactElement'); | ||
var ReactInstrumentation = require('ReactInstrumentation'); | ||
var ReactReconciler = require('ReactReconciler'); | ||
var ReactUpdates = require('ReactUpdates'); | ||
|
||
var emptyObject = require('emptyObject'); | ||
var getHostComponentFromComposite = require('getHostComponentFromComposite'); | ||
var instantiateReactComponent = require('instantiateReactComponent'); | ||
|
||
/** | ||
* Temporary (?) hack so that we can store all top-level pending updates on | ||
* composites instead of having to worry about different types of components | ||
* here. | ||
*/ | ||
var TopLevelWrapper = function() {}; | ||
TopLevelWrapper.prototype.isReactComponent = {}; | ||
if (__DEV__) { | ||
TopLevelWrapper.displayName = 'TopLevelWrapper'; | ||
} | ||
TopLevelWrapper.prototype.render = function() { | ||
// this.props is actually a ReactElement | ||
return this.props; | ||
}; | ||
|
||
/** | ||
* 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 | ||
*/ | ||
function mountComponentIntoNode( | ||
componentInstance, | ||
transaction) { | ||
var image = ReactReconciler.mountComponent( | ||
componentInstance, | ||
transaction, | ||
null, | ||
null, | ||
emptyObject | ||
); | ||
componentInstance._renderedComponent._topLevelWrapper = componentInstance; | ||
return image; | ||
} | ||
|
||
/** | ||
* Batched mount. | ||
* | ||
* @param {ReactComponent} componentInstance The instance to mount. | ||
* @param {number} rootID ID of the root node. | ||
* @param {number} containerTag container element to mount into. | ||
*/ | ||
function batchedMountComponentIntoNode( | ||
componentInstance) { | ||
var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(); | ||
var image = transaction.perform( | ||
mountComponentIntoNode, | ||
null, | ||
componentInstance, | ||
transaction | ||
); | ||
ReactUpdates.ReactReconcileTransaction.release(transaction); | ||
return image; | ||
} | ||
|
||
var ReactTestInstance = function(component) { | ||
this._component = component; | ||
}; | ||
ReactTestInstance.prototype.getInstance = function() { | ||
return this._component._renderedComponent.getPublicInstance(); | ||
}; | ||
ReactTestInstance.prototype.toJSON = function() { | ||
var inst = getHostComponentFromComposite(this._component); | ||
return inst.toJSON(); | ||
}; | ||
|
||
/** | ||
* As soon as `ReactMount` is refactored to not rely on the DOM, we can share | ||
* code between the two. For now, we'll hard code the ID logic. | ||
*/ | ||
var ReactHostMount = { | ||
|
||
render: function( | ||
nextElement: ReactElement | ||
): ?ReactComponent { | ||
var nextWrappedElement = new ReactElement( | ||
TopLevelWrapper, | ||
null, | ||
null, | ||
null, | ||
null, | ||
null, | ||
nextElement | ||
); | ||
|
||
// var prevComponent = ReactHostMount._instancesByContainerID[containerTag]; | ||
// if (prevComponent) { | ||
// var prevWrappedElement = prevComponent._currentElement; | ||
// var prevElement = prevWrappedElement.props; | ||
// if (shouldUpdateReactComponent(prevElement, nextElement)) { | ||
// ReactUpdateQueue.enqueueElementInternal(prevComponent, nextWrappedElement); | ||
// if (callback) { | ||
// ReactUpdateQueue.enqueueCallbackInternal(prevComponent, callback); | ||
// } | ||
// return prevComponent; | ||
// } | ||
// } | ||
|
||
var instance = instantiateReactComponent(nextWrappedElement); | ||
|
||
if (__DEV__) { | ||
// Mute future events from the top level wrapper. | ||
// It is an implementation detail that devtools should not know about. | ||
instance._debugID = 0; | ||
|
||
if (__DEV__) { | ||
ReactInstrumentation.debugTool.onBeginFlush(); | ||
} | ||
} | ||
|
||
// The initial render is synchronous but any updates that happen during | ||
// rendering, in componentWillMount or componentDidMount, will be batched | ||
// according to the current batching strategy. | ||
|
||
ReactUpdates.batchedUpdates( | ||
batchedMountComponentIntoNode, | ||
instance | ||
); | ||
if (__DEV__) { | ||
// The instance here is TopLevelWrapper so we report mount for its child. | ||
ReactInstrumentation.debugTool.onMountRootComponent( | ||
instance._renderedComponent._debugID | ||
); | ||
ReactInstrumentation.debugTool.onEndFlush(); | ||
} | ||
return new ReactTestInstance(instance); | ||
}, | ||
|
||
}; | ||
|
||
module.exports = ReactHostMount; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
/** | ||
* Copyright (c) 2015-present, Facebook, Inc. | ||
* All rights reserved. | ||
* | ||
* This source code is licensed under the BSD-style license found in the | ||
* LICENSE file in the root directory of this source tree. An additional grant | ||
* of patent rights can be found in the PATENTS file in the same directory. | ||
* | ||
* @providesModule ReactTestReconcileTransaction | ||
* @flow | ||
*/ | ||
'use strict'; | ||
|
||
var CallbackQueue = require('CallbackQueue'); | ||
var PooledClass = require('PooledClass'); | ||
var Transaction = require('Transaction'); | ||
|
||
/** | ||
* Provides a `CallbackQueue` queue for collecting `onDOMReady` callbacks during | ||
* the performing of the transaction. | ||
*/ | ||
var ON_DOM_READY_QUEUEING = { | ||
/** | ||
* Initializes the internal `onDOMReady` queue. | ||
*/ | ||
initialize: function() { | ||
this.reactMountReady.reset(); | ||
}, | ||
|
||
/** | ||
* After DOM is flushed, invoke all registered `onDOMReady` callbacks. | ||
*/ | ||
close: function() { | ||
this.reactMountReady.notifyAll(); | ||
}, | ||
}; | ||
|
||
/** | ||
* Executed within the scope of the `Transaction` instance. Consider these as | ||
* being member methods, but with an implied ordering while being isolated from | ||
* each other. | ||
*/ | ||
var TRANSACTION_WRAPPERS = [ON_DOM_READY_QUEUEING]; | ||
|
||
/** | ||
* Currently: | ||
* - The order that these are listed in the transaction is critical: | ||
* - Suppresses events. | ||
* - Restores selection range. | ||
* | ||
* Future: | ||
* - Restore document/overflow scroll positions that were unintentionally | ||
* modified via DOM insertions above the top viewport boundary. | ||
* - Implement/integrate with customized constraint based layout system and keep | ||
* track of which dimensions must be remeasured. | ||
* | ||
* @class ReactTestReconcileTransaction | ||
*/ | ||
function ReactTestReconcileTransaction() { | ||
this.reinitializeTransaction(); | ||
this.reactMountReady = CallbackQueue.getPooled(null); | ||
} | ||
|
||
var Mixin = { | ||
/** | ||
* @see Transaction | ||
* @abstract | ||
* @final | ||
* @return {array<object>} List of operation wrap procedures. | ||
* TODO: convert to array<TransactionWrapper> | ||
*/ | ||
getTransactionWrappers: function() { | ||
return TRANSACTION_WRAPPERS; | ||
}, | ||
|
||
/** | ||
* @return {object} The queue to collect `onDOMReady` callbacks with. | ||
* TODO: convert to ReactMountReady | ||
*/ | ||
getReactMountReady: function() { | ||
return this.reactMountReady; | ||
}, | ||
|
||
/** | ||
* `PooledClass` looks for this, and will invoke this before allowing this | ||
* instance to be reused. | ||
*/ | ||
destructor: function() { | ||
CallbackQueue.release(this.reactMountReady); | ||
this.reactMountReady = null; | ||
}, | ||
}; | ||
|
||
Object.assign( | ||
ReactTestReconcileTransaction.prototype, | ||
Transaction.Mixin, | ||
ReactTestReconcileTransaction, | ||
Mixin | ||
); | ||
|
||
PooledClass.addPoolingTo(ReactTestReconcileTransaction); | ||
|
||
module.exports = ReactTestReconcileTransaction; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
/** | ||
* Copyright 2013-present, Facebook, Inc. | ||
* All rights reserved. | ||
* | ||
* This source code is licensed under the BSD-style license found in the | ||
* LICENSE file in the root directory of this source tree. An additional grant | ||
* of patent rights can be found in the PATENTS file in the same directory. | ||
* | ||
* @providesModule ReactTestRenderer | ||
*/ | ||
|
||
'use strict'; | ||
|
||
var ReactDefaultBatchingStrategy = require('ReactDefaultBatchingStrategy'); | ||
var ReactEmptyComponent = require('ReactEmptyComponent'); | ||
var ReactMultiChild = require('ReactMultiChild'); | ||
var ReactHostComponent = require('ReactHostComponent'); | ||
var ReactTestMount = require('ReactTestMount'); | ||
var ReactTestReconcileTransaction = require('ReactTestReconcileTransaction'); | ||
var ReactUpdates = require('ReactUpdates'); | ||
|
||
var renderSubtreeIntoContainer = require('renderSubtreeIntoContainer'); | ||
|
||
/** | ||
* Drill down (through composites and empty components) until we get a native or | ||
* native text component. | ||
* | ||
* This is pretty polymorphic but unavoidable with the current structure we have | ||
* for `_renderedChildren`. | ||
*/ | ||
function getRenderedHostOrTextFromComponent(component) { | ||
var rendered; | ||
while ((rendered = component._renderedComponent)) { | ||
component = rendered; | ||
} | ||
return component; | ||
} | ||
|
||
|
||
// ============================================================================= | ||
|
||
var ReactTestComponent = function(element) { | ||
this._currentElement = element; | ||
this._renderedChildren = null; | ||
this._topLevelWrapper = null; | ||
}; | ||
ReactTestComponent.prototype.mountComponent = function( | ||
transaction, | ||
nativeParent, | ||
nativeContainerInfo, | ||
context | ||
) { | ||
var element = this._currentElement; | ||
this.mountChildren(element.props.children, transaction, context); | ||
}; | ||
ReactTestComponent.prototype.receiveComponent = function( | ||
nextElement, | ||
transaction, | ||
context | ||
) { | ||
this._currentElement = nextElement; | ||
this.updateChildren(nextElement.props.children, transaction, context); | ||
}; | ||
ReactTestComponent.prototype.getHostNode = function() {}; | ||
ReactTestComponent.prototype.unmountComponent = function() {}; | ||
ReactTestComponent.prototype.toJSON = function() { | ||
var {children, ...props} = this._currentElement.props; | ||
var childrenJSON = []; | ||
for (var key in this._renderedChildren) { | ||
var inst = this._renderedChildren[key]; | ||
inst = getRenderedHostOrTextFromComponent(inst); | ||
var json = inst.toJSON(); | ||
if (json !== undefined) { | ||
childrenJSON.push(json); | ||
} | ||
} | ||
return { | ||
type: this._currentElement.type, | ||
props: props, | ||
children: childrenJSON.length ? childrenJSON : null, | ||
}; | ||
}; | ||
Object.assign(ReactTestComponent.prototype, ReactMultiChild.Mixin); | ||
|
||
// ============================================================================= | ||
|
||
var ReactTestTextComponent = function(element) { | ||
this._currentElement = element; | ||
}; | ||
ReactTestTextComponent.prototype.mountComponent = function() {}; | ||
ReactTestTextComponent.prototype.receiveComponent = function(nextElement) { | ||
this._currentElement = nextElement; | ||
}; | ||
ReactTestTextComponent.prototype.getHostNode = function() {}; | ||
ReactTestTextComponent.prototype.unmountComponent = function() {}; | ||
ReactTestTextComponent.prototype.toJSON = function() { | ||
return this._currentElement; | ||
}; | ||
|
||
// ============================================================================= | ||
|
||
var ReactTestEmptyComponent = function(element) { | ||
this._currentElement = null; | ||
}; | ||
ReactTestEmptyComponent.prototype.mountComponent = function() {}; | ||
ReactTestEmptyComponent.prototype.receiveComponent = function() {}; | ||
ReactTestEmptyComponent.prototype.getHostNode = function() {}; | ||
ReactTestEmptyComponent.prototype.unmountComponent = function() {}; | ||
ReactTestEmptyComponent.prototype.toJSON = function() {}; | ||
|
||
// ============================================================================= | ||
|
||
ReactUpdates.injection.injectReconcileTransaction( | ||
ReactTestReconcileTransaction | ||
); | ||
ReactUpdates.injection.injectBatchingStrategy(ReactDefaultBatchingStrategy); | ||
|
||
ReactHostComponent.injection.injectGenericComponentClass(ReactTestComponent); | ||
ReactHostComponent.injection.injectTextComponentClass(ReactTestTextComponent); | ||
ReactEmptyComponent.injection.injectEmptyComponentFactory(function() { | ||
return new ReactTestEmptyComponent(); | ||
}); | ||
|
||
var ReactTestRenderer = { | ||
create: ReactTestMount.render, | ||
|
||
/* eslint-disable camelcase */ | ||
unstable_batchedUpdates: ReactUpdates.batchedUpdates, | ||
unstable_renderSubtreeIntoContainer: renderSubtreeIntoContainer, | ||
/* eslint-enable camelcase */ | ||
}; | ||
|
||
module.exports = ReactTestRenderer; |
Oops, something went wrong.