-
Notifications
You must be signed in to change notification settings - Fork 47.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add rudimentary test renderer #6944
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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; |
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; |
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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could you help me understand why this is returning the component when it’s looking at the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, if A renders B renders div, I want the div and that div doesn't have _renderedComponent. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🙌 that’s the scenario I was thinking. Thank you. |
||
} | ||
|
||
|
||
// ============================================================================= | ||
|
||
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
extra jsdoc blocks (
rootId
andcontainerTag
)