Files
react/src/renderers/testing/stack/ReactTestMount.js
T
Dustan KastenandDan Abramov 2da35fcae8 [Fiber] Implement test renderer (#8628)
* ReactTestRenderer move current impl to stack dir

* ReactTestRenderer on fiber: commence!

* ReactTestRenderer: most non-ref/non-public-instance tests are passing

* Move ReactTestFiberComponent functions from Renderer to Component file

* test renderer: get rid of private root containers and root Maps

* TestRenderer: switch impl based on ReactDOMFeatureFlag.useFiber

* ReactTestRenderer: inline component creation

* ReactTestRenderer: return to pristine original glory (+ Fiber for error order difference)

* TestRendererFiber: use a simple class as TestComponentInstances

* Add `getPublicInstance` to support TestRenderer `createNodeMock`

* Rename files to end. Update for `mountContainer->createContainer` change

* test renderer return same object to prevent unnecessary context pushing/popping

* Fiber HostConfig add getPublicInstance. This should be the identity fn everywhere except the test renderer

* appease flow

* Initial cleanup from sleepy work

* unstable_batchedUpdates

* Stack test renderer: cache nodeMock to not call on unmount

* add public instance type parameter to the reconciler

* test renderer: set _nodeMock when mounted

* More cleanup

* Add test cases for root fragments and (maybe?) root text nodes

* Fix the npm package build

Explicitly require the Stack version by default.
Add a separate entry point for Fiber.

We don't add fiber.js to the package yet since it's considered internal until React 16.

* Relax the ref type from Object to mixed

This seems like the most straightforward way to support getPublicInstance for test renderer.

* Remove accidental newline

* test renderer: unify TestComponent and TestContainer, handle root updates

* Remove string/number serialization attempts since Fiber ensures all textInstances are strings

* Return full fragments in toJSON

* Test Renderer remove TestComponent instances for simple objects

* Update babylon for exact object type syntax

* Use $$typeof because clarity > punching ducks.

* Minor Flow annotation tweaks

* Tweak style, types, and naming

* Fix typo
2017-01-11 11:19:32 -08:00

180 lines
4.9 KiB
JavaScript

/**
* 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 React = require('React');
var ReactReconciler = require('ReactReconciler');
var ReactUpdates = require('ReactUpdates');
var emptyObject = require('emptyObject');
var getHostComponentFromComposite = require('getHostComponentFromComposite');
var instantiateReactComponent = require('instantiateReactComponent');
var invariant = require('invariant');
export type TestRendererOptions = {
createNodeMock: (element: ReactElement<any>) => any,
};
var defaultTestOptions = {
createNodeMock: function() {
return null;
},
};
/**
* 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() {
return this.props.child;
};
TopLevelWrapper.isReactTopLevelWrapper = true;
/**
* Mounts this component and inserts it into the DOM.
*
* @param {ReactComponent} componentInstance The instance to mount.
* @param {ReactReconcileTransaction} transaction
* @param {Object} hostParent
* @param {Object} hostContainerInfo
*/
function mountComponentIntoNode(
componentInstance,
transaction,
hostParent,
hostContainerInfo
) {
var image = ReactReconciler.mountComponent(
componentInstance,
transaction,
null,
hostContainerInfo,
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,
options,
) {
var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(true);
var image = transaction.perform(
mountComponentIntoNode,
null,
componentInstance,
transaction,
null,
options
);
ReactUpdates.ReactReconcileTransaction.release(transaction);
return image;
}
var ReactTestInstance = function(component) {
this._component = component;
};
ReactTestInstance.prototype.getInstance = function() {
return this._component._renderedComponent.getPublicInstance();
};
ReactTestInstance.prototype.update = function(nextElement) {
invariant(
this._component,
"ReactTestRenderer: .update() can't be called after unmount."
);
var nextWrappedElement = React.createElement(
TopLevelWrapper,
{ child: nextElement }
);
var component = this._component;
ReactUpdates.batchedUpdates(function() {
var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(true);
transaction.perform(function() {
ReactReconciler.receiveComponent(
component,
nextWrappedElement,
transaction,
emptyObject
);
});
ReactUpdates.ReactReconcileTransaction.release(transaction);
});
};
ReactTestInstance.prototype.unmount = function(nextElement) {
var component = this._component;
ReactUpdates.batchedUpdates(function() {
var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(true);
transaction.perform(function() {
ReactReconciler.unmountComponent(
component,
false, /* safely */
false /* skipLifecycle */
);
});
ReactUpdates.ReactReconcileTransaction.release(transaction);
});
this._component = null;
};
ReactTestInstance.prototype.toJSON = function() {
var inst = getHostComponentFromComposite(this._component);
if (inst === null) {
return null;
}
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 ReactTestMount = {
render: function(
nextElement: ReactElement<any>,
options?: TestRendererOptions,
): ReactTestInstance {
var nextWrappedElement = React.createElement(
TopLevelWrapper,
{child: nextElement},
);
var instance = instantiateReactComponent(nextWrappedElement, false);
// 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,
Object.assign({}, defaultTestOptions, options),
);
return new ReactTestInstance(instance);
},
};
module.exports = ReactTestMount;