[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
This commit is contained in:
Dustan Kasten
2017-01-11 11:19:32 -08:00
committed by Dan Abramov
parent 837835d7f3
commit 2da35fcae8
24 changed files with 630 additions and 174 deletions
+1 -1
View File
@@ -33,7 +33,7 @@
"babel-plugin-transform-react-jsx-source": "^6.8.0",
"babel-preset-react": "^6.5.0",
"babel-traverse": "^6.9.0",
"babylon": "6.8.0",
"babylon": "6.15.0",
"browserify": "^13.0.0",
"bundle-collapser": "^1.1.1",
"coffee-script": "^1.8.0",
+3
View File
@@ -0,0 +1,3 @@
'use strict';
module.exports = require('./lib/ReactTestRendererFiber');
+1 -1
View File
@@ -1,3 +1,3 @@
'use strict';
module.exports = require('./lib/ReactTestRenderer');
module.exports = require('./lib/ReactTestRendererStack');
+4
View File
@@ -1675,6 +1675,7 @@ src/renderers/testing/__tests__/ReactTestRenderer-test.js
* renders a simple component
* renders a top-level empty component
* exposes a type flag
* can render a composite component
* renders some basics with an update
* exposes the instance
* updates types
@@ -1687,6 +1688,9 @@ src/renderers/testing/__tests__/ReactTestRenderer-test.js
* supports unmounting inner instances
* supports updates when using refs
* supports error boundaries
* can update text nodes
* can update text nodes when rendered as root
* can render and update root fragments
src/shared/utils/__tests__/KeyEscapeUtils-test.js
* should properly escape and wrap user defined keys
+4
View File
@@ -464,6 +464,10 @@ const ARTRenderer = ReactFiberReconciler({
return false;
},
getPublicInstance(instance) {
return instance;
},
insertBefore(parentInstance, child, beforeChild) {
invariant(
child !== beforeChild,
+4
View File
@@ -137,6 +137,10 @@ var DOMRenderer = ReactFiberReconciler({
return getChildNamespace(parentNamespace, type);
},
getPublicInstance(instance) {
return instance;
},
prepareForCommit() : void {
eventsEnabled = ReactBrowserEventEmitter.isEnabled();
selectionInformation = ReactInputSelection.getSelectionInformation();
+4
View File
@@ -232,6 +232,10 @@ const NativeRenderer = ReactFiberReconciler({
return emptyObject;
},
getPublicInstance(instance) {
return instance;
},
insertBefore(
parentInstance : Instance | Container,
child : Instance | TextInstance,
+4
View File
@@ -54,6 +54,10 @@ var NoopRenderer = ReactFiberReconciler({
return emptyObject;
},
getPublicInstance(instance) {
return instance;
},
createInstance(type : string, props : Props) : Instance {
const inst = {
id: instanceCounter++,
+1 -1
View File
@@ -102,7 +102,7 @@ export type Fiber = {
// The ref last used to attach this node.
// I'll avoid adding an owner field for prod and model that as functions.
ref: null | (((handle : ?Object) => void) & { _stringRef: ?string }),
ref: null | (((handle : mixed) => void) & { _stringRef: ?string }),
// Input is the data coming into process this fiber. Arguments. Props.
pendingProps: any, // This type will be more specific once we overload the tag.
@@ -71,8 +71,8 @@ if (__DEV__) {
var warnedAboutStatelessRefs = {};
}
module.exports = function<T, P, I, TI, C, CX>(
config : HostConfig<T, P, I, TI, C, CX>,
module.exports = function<T, P, I, TI, PI, C, CX>(
config : HostConfig<T, P, I, TI, PI, C, CX>,
hostContext : HostContext<C, CX>,
scheduleUpdate : (fiber : Fiber, priorityLevel : PriorityLevel) => void,
getPriorityContext : () => PriorityLevel,
@@ -34,8 +34,8 @@ var {
ContentReset,
} = require('ReactTypeOfSideEffect');
module.exports = function<T, P, I, TI, C, CX>(
config : HostConfig<T, P, I, TI, C, CX>,
module.exports = function<T, P, I, TI, PI, C, CX>(
config : HostConfig<T, P, I, TI, PI, C, CX>,
hostContext : HostContext<C, CX>,
captureError : (failedFiber : Fiber, error: Error) => ?Fiber
) {
@@ -48,6 +48,7 @@ module.exports = function<T, P, I, TI, C, CX>(
appendChild,
insertBefore,
removeChild,
getPublicInstance,
} = config;
const {
@@ -462,7 +463,7 @@ module.exports = function<T, P, I, TI, C, CX>(
}
const ref = finishedWork.ref;
if (ref) {
const instance = finishedWork.stateNode;
const instance = getPublicInstance(finishedWork.stateNode);
ref(instance);
}
}
@@ -46,8 +46,8 @@ if (__DEV__) {
var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber');
}
module.exports = function<T, P, I, TI, C, CX>(
config : HostConfig<T, P, I, TI, C, CX>,
module.exports = function<T, P, I, TI, PI, C, CX>(
config : HostConfig<T, P, I, TI, PI, C, CX>,
hostContext : HostContext<C, CX>,
) {
const {
@@ -34,8 +34,8 @@ export type HostContext<C, CX> = {
resetHostContainer() : void,
};
module.exports = function<T, P, I, TI, C, CX>(
config : HostConfig<T, P, I, TI, C, CX>
module.exports = function<T, P, I, TI, PI, C, CX>(
config : HostConfig<T, P, I, TI, PI, C, CX>
) : HostContext<C, CX> {
const {
getChildHostContext,
@@ -43,10 +43,11 @@ export type Deadline = {
type OpaqueNode = Fiber;
export type HostConfig<T, P, I, TI, C, CX> = {
export type HostConfig<T, P, I, TI, PI, C, CX> = {
getRootHostContext(rootContainerInstance : C) : CX,
getChildHostContext(parentHostContext : CX, type : T) : CX,
getPublicInstance(instance : I | TI) : PI,
createInstance(type : T, props : P, rootContainerInstance : C, hostContext : CX, internalInstanceHandle : OpaqueNode) : I,
appendInitialChild(parentInstance : I, child : I | TI) : void,
@@ -101,7 +102,7 @@ getContextForSubtree._injectFiber(function(fiber : Fiber) {
parentContext;
});
module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C, CX>) : Reconciler<C, I, TI> {
module.exports = function<T, P, I, TI, PI, C, CX>(config : HostConfig<T, P, I, TI, PI, C, CX>) : Reconciler<C, I, TI> {
var {
scheduleUpdate,
@@ -74,7 +74,7 @@ if (__DEV__) {
var timeHeuristicForUnitOfWork = 1;
module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C, CX>) {
module.exports = function<T, P, I, TI, PI, C, CX>(config : HostConfig<T, P, I, TI, PI, C, CX>) {
const hostContext = ReactFiberHostContext(config);
const { popHostContainer, popHostContext, resetHostContainer } = hostContext;
const { beginWork, beginFailedWork } = ReactFiberBeginWork(
+5 -144
View File
@@ -1,5 +1,5 @@
/**
* Copyright 2013-present, Facebook, Inc.
* Copyright (c) 2013-present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@@ -7,151 +7,12 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ReactTestRenderer
* @preventMunge
* @flow
*/
'use strict';
var ReactComponentEnvironment = require('ReactComponentEnvironment');
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 ReactTestTextComponent = require('ReactTestTextComponent');
var ReactTestEmptyComponent = require('ReactTestEmptyComponent');
var invariant = require('invariant');
const ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
import type { ReactElement } from 'ReactElementType';
import type { ReactInstance } from 'ReactInstanceType';
type ReactTestRendererJSON = {
type: string,
props: { [propName: string]: string },
children: null | Array<string | ReactTestRendererJSON>,
$$typeof?: any
}
/**
* 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;
}
class ReactTestComponent {
_currentElement: ReactElement;
_renderedChildren: null | Object;
_topLevelWrapper: null | ReactInstance;
_hostContainerInfo: null | Object;
constructor(element: ReactElement) {
this._currentElement = element;
this._renderedChildren = null;
this._topLevelWrapper = null;
this._hostContainerInfo = null;
}
mountComponent(
transaction: ReactTestReconcileTransaction,
nativeParent: null | ReactTestComponent,
hostContainerInfo: Object,
context: Object,
) {
var element = this._currentElement;
this._hostContainerInfo = hostContainerInfo;
// $FlowFixMe https://github.com/facebook/flow/issues/1805
this.mountChildren(element.props.children, transaction, context);
}
receiveComponent(
nextElement: ReactElement,
transaction: ReactTestReconcileTransaction,
context: Object,
) {
this._currentElement = nextElement;
// $FlowFixMe https://github.com/facebook/flow/issues/1805
this.updateChildren(nextElement.props.children, transaction, context);
}
getPublicInstance(): Object {
var element = this._currentElement;
var hostContainerInfo = this._hostContainerInfo;
invariant(
hostContainerInfo,
'hostContainerInfo should be populated before ' +
'getPublicInstance is called.'
);
return hostContainerInfo.createNodeMock(element);
}
toJSON(): ReactTestRendererJSON {
// not using `children`, but I don't want to rewrite without destructuring
// eslint-disable-next-line no-unused-vars
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);
}
}
var object: ReactTestRendererJSON = {
type: this._currentElement.type,
props: props,
children: childrenJSON.length ? childrenJSON : null,
};
Object.defineProperty(object, '$$typeof', {
value: Symbol.for('react.test.json'),
});
return object;
}
getHostNode(): void {}
unmountComponent(safely, skipLifecycle): void {
// $FlowFixMe https://github.com/facebook/flow/issues/1805
this.unmountChildren(safely, skipLifecycle);
}
}
Object.assign(ReactTestComponent.prototype, ReactMultiChild);
// =============================================================================
ReactUpdates.injection.injectReconcileTransaction(
ReactTestReconcileTransaction
);
ReactUpdates.injection.injectBatchingStrategy(ReactDefaultBatchingStrategy);
ReactHostComponent.injection.injectGenericComponentClass(ReactTestComponent);
ReactHostComponent.injection.injectTextComponentClass(ReactTestTextComponent);
ReactEmptyComponent.injection.injectEmptyComponentFactory(function() {
return new ReactTestEmptyComponent();
});
ReactComponentEnvironment.injection.injectEnvironment({
processChildrenUpdates: function() {},
replaceNodeWithMarkup: function() {},
});
var ReactTestRenderer = {
create: ReactTestMount.render,
/* eslint-disable camelcase */
unstable_batchedUpdates: ReactUpdates.batchedUpdates,
/* eslint-enable camelcase */
};
module.exports = ReactTestRenderer;
module.exports = ReactDOMFeatureFlags.useFiber
? require('ReactTestRendererFiber')
: require('ReactTestRendererStack');
@@ -0,0 +1,293 @@
/**
* 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 ReactTestRendererFiber
* @preventMunge
* @flow
*/
'use strict';
var ReactFiberReconciler = require('ReactFiberReconciler');
var ReactGenericBatching = require('ReactGenericBatching');
var emptyObject = require('emptyObject');
import type { TestRendererOptions } from 'ReactTestMount';
type ReactTestRendererJSON = {|
type : string,
props : {[propName: string] : string },
children : null | Array<ReactTestRendererNode>,
$$typeof ?: Symbol, // Optional because we add it with defineProperty().
|};
type ReactTestRendererNode = ReactTestRendererJSON | string;
type Container = {|
children : Array<Instance | TextInstance>,
createNodeMock : Function,
tag : 'CONTAINER',
|};
type Props = Object;
type Instance = {|
type : string,
props : Object,
children : Array<Instance | TextInstance>,
rootContainerInstance : Container,
tag : 'INSTANCE',
|};
type TextInstance = {|
text : string,
tag : 'TEXT',
|};
var TestRenderer = ReactFiberReconciler({
getRootHostContext() {
return emptyObject;
},
getChildHostContext() {
return emptyObject;
},
prepareForCommit() : void {
// noop
},
resetAfterCommit() : void {
// noop
},
createInstance(
type : string,
props : Props,
rootContainerInstance : Container,
hostContext : Object,
internalInstanceHandle : Object,
) : Instance {
return {
type,
props,
children: [],
rootContainerInstance,
tag: 'INSTANCE',
};
},
appendInitialChild(parentInstance : Instance, child : Instance | TextInstance) : void {
const index = parentInstance.children.indexOf(child);
if (index !== -1) {
parentInstance.children.splice(index, 1);
}
parentInstance.children.push(child);
},
finalizeInitialChildren(
testElement : Instance,
type : string,
props : Props,
rootContainerInstance : Container,
) : boolean {
return false;
},
prepareUpdate(
testElement : Instance,
type : string,
oldProps : Props,
newProps : Props,
hostContext : Object,
) : boolean {
return true;
},
commitUpdate(
instance : Instance,
type : string,
oldProps : Props,
newProps : Props,
rootContainerInstance : Container,
internalInstanceHandle : Object,
) : void {
instance.type = type;
instance.props = newProps;
},
commitMount(
instance : Instance,
type : string,
newProps : Props,
rootContainerInstance : Object,
internalInstanceHandle : Object
) : void {
// noop
},
shouldSetTextContent(props : Props) : boolean {
return false;
},
resetTextContent(testElement : Instance) : void {
// noop
},
createTextInstance(
text : string,
rootContainerInstance : Container,
hostContext : Object,
internalInstanceHandle : Object
) : TextInstance {
return {
text,
tag: 'TEXT',
};
},
commitTextUpdate(textInstance : TextInstance, oldText : string, newText : string) : void {
textInstance.text = newText;
},
appendChild(parentInstance : Instance | Container, child : Instance | TextInstance) : void {
const index = parentInstance.children.indexOf(child);
if (index !== -1) {
parentInstance.children.splice(index, 1);
}
parentInstance.children.push(child);
},
insertBefore(
parentInstance : Instance | Container,
child : Instance | TextInstance,
beforeChild : Instance | TextInstance
) : void {
const index = parentInstance.children.indexOf(child);
if (index !== -1) {
parentInstance.children.splice(index, 1);
}
const beforeIndex = parentInstance.children.indexOf(beforeChild);
parentInstance.children.splice(beforeIndex, 0, child);
},
removeChild(parentInstance : Instance | Container, child : Instance | TextInstance) : void {
const index = parentInstance.children.indexOf(child);
parentInstance.children.splice(index, 1);
},
scheduleAnimationCallback(fn : Function) : void {
setTimeout(fn);
},
scheduleDeferredCallback(fn : Function) : void {
setTimeout(fn, 0, {timeRemaining: Infinity});
},
useSyncScheduling: true,
getPublicInstance(inst) {
switch (inst.tag) {
case 'INSTANCE':
const createNodeMock = inst.rootContainerInstance.createNodeMock;
return createNodeMock({
type: inst.type,
props: inst.props,
});
default:
return inst;
}
},
});
var defaultTestOptions = {
createNodeMock: function() {
return null;
},
};
function toJSON(inst : Instance | TextInstance) : ReactTestRendererNode {
switch (inst.tag) {
case 'TEXT':
return inst.text;
case 'INSTANCE':
/* eslint-disable no-unused-vars */
// We don't include the `children` prop in JSON.
// Instead, we will include the actual rendered children.
const {children, ...props} = inst.props;
/* eslint-enable */
let renderedChildren = null;
if (inst.children && inst.children.length) {
renderedChildren = inst.children.map(toJSON);
}
const json : ReactTestRendererJSON = {
type: inst.type,
props: props,
children: renderedChildren,
};
Object.defineProperty(json, '$$typeof', {value: Symbol.for('react.test.json')});
return json;
default:
throw new Error(`Unexpected node type in toJSON: ${inst.tag}`);
}
}
var ReactTestFiberRenderer = {
create(element : ReactElement<any>, options : TestRendererOptions) {
var createNodeMock = defaultTestOptions.createNodeMock;
if (options && typeof options.createNodeMock === 'function') {
createNodeMock = options.createNodeMock;
}
var container = {
children: [],
createNodeMock,
tag: 'CONTAINER',
};
var root = TestRenderer.createContainer(container);
TestRenderer.updateContainer(element, root, null, null);
return {
toJSON() {
if (root == null || container == null) {
return null;
}
if (container.children.length === 0) {
return null;
}
if (container.children.length === 1) {
return toJSON(container.children[0]);
}
return container.children.map(toJSON);
},
update(newElement : ReactElement<any>) {
if (root == null) {
return;
}
TestRenderer.updateContainer(newElement, root, null, null);
},
unmount() {
if (root == null) {
return;
}
TestRenderer.updateContainer(null, root, null);
container = null;
root = null;
},
getInstance() {
if (root == null) {
return null;
}
return TestRenderer.getPublicRootInstance(root);
},
};
},
/* eslint-disable camelcase */
unstable_batchedUpdates: ReactGenericBatching.batchedUpdates,
/* eslint-enable camelcase */
};
module.exports = ReactTestFiberRenderer;
@@ -13,6 +13,7 @@
var React = require('React');
var ReactTestRenderer = require('ReactTestRenderer');
var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
describe('ReactTestRenderer', () => {
function normalizeCodeLocInfo(str) {
@@ -55,6 +56,31 @@ describe('ReactTestRenderer', () => {
}
});
it('can render a composite component', () => {
class Component extends React.Component {
render() {
return (
<div className="purple">
<Child />
</div>
);
}
}
var Child = () => {
return <moo />;
};
var renderer = ReactTestRenderer.create(<Component />);
expect(renderer.toJSON()).toEqual({
type: 'div',
props: { className: 'purple' },
children: [
{ type: 'moo', props: {}, children: null },
],
});
});
it('renders some basics with an update', () => {
var renders = 0;
@@ -92,7 +118,7 @@ describe('ReactTestRenderer', () => {
type: 'div',
props: { className: 'purple' },
children: [
7,
ReactDOMFeatureFlags.useFiber ? '7' : 7,
{ type: 'moo', props: {}, children: null },
],
});
@@ -124,8 +150,8 @@ describe('ReactTestRenderer', () => {
mouse.handleMoose();
expect(renderer.toJSON()).toEqual({
type: 'div',
props: {},
children: ['moose'],
props: {},
});
});
@@ -366,8 +392,7 @@ describe('ReactTestRenderer', () => {
{createNodeMock}
);
inst.update(<Foo useDiv={false} />);
// It's called with 'div' twice (mounting and unmounting)
expect(log).toEqual(['div', 'div', 'span']);
expect(log).toEqual(['div', 'span']);
});
it('supports error boundaries', () => {
@@ -420,12 +445,107 @@ describe('ReactTestRenderer', () => {
props: {},
children: ['Happy Birthday!'],
});
expect(log).toEqual([
'Boundary render',
'Angry render',
'Boundary render',
'Boundary componentDidMount',
]);
if (ReactDOMFeatureFlags.useFiber) {
expect(log).toEqual([
'Boundary render',
'Angry render',
'Boundary componentDidMount',
'Boundary render',
]);
} else {
expect(log).toEqual([
'Boundary render',
'Angry render',
'Boundary render',
'Boundary componentDidMount',
]);
}
});
it('can update text nodes', () => {
class Component extends React.Component {
render() {
return (
<div>
{this.props.children}
</div>
);
}
}
var renderer = ReactTestRenderer.create(<Component>Hi</Component>);
expect(renderer.toJSON()).toEqual({
type: 'div',
children: ['Hi'],
props: {},
});
renderer.update(<Component>{['Hi', 'Bye']}</Component>);
expect(renderer.toJSON()).toEqual({
type: 'div',
children: ['Hi', 'Bye'],
props: {},
});
renderer.update(<Component>Bye</Component>);
expect(renderer.toJSON()).toEqual({
type: 'div',
children: ['Bye'],
props: {},
});
renderer.update(<Component>{42}</Component>);
expect(renderer.toJSON()).toEqual({
type: 'div',
children: [
ReactDOMFeatureFlags.useFiber ? '42' : 42,
],
props: {},
});
renderer.update(<Component><div /></Component>);
expect(renderer.toJSON()).toEqual({
type: 'div',
children: [{
type: 'div',
children: null,
props: {},
}],
props: {},
});
});
if (ReactDOMFeatureFlags.useFiber) {
it('can update text nodes when rendered as root', () => {
var renderer = ReactTestRenderer.create(['Hello', 'world']);
expect(renderer.toJSON()).toEqual(['Hello', 'world']);
renderer.update(42);
expect(renderer.toJSON()).toEqual('42');
renderer.update([42, 'world']);
expect(renderer.toJSON()).toEqual(['42', 'world']);
});
it('can render and update root fragments', () => {
var Component = (props) => props.children;
var renderer = ReactTestRenderer.create([
<Component>Hi</Component>,
<Component>Bye</Component>,
]);
expect(renderer.toJSON()).toEqual(['Hi', 'Bye']);
renderer.update(<div />);
expect(renderer.toJSON()).toEqual({
type: 'div',
children: null,
props: {},
});
renderer.update([<div>goodbye</div>, 'world']);
expect(renderer.toJSON()).toEqual([
{
type: 'div',
children: [
'goodbye',
],
props: {},
},
'world',
]);
});
}
});
@@ -0,0 +1,159 @@
/**
* 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 ReactTestRendererStack
* @preventMunge
* @flow
*/
'use strict';
var ReactComponentEnvironment = require('ReactComponentEnvironment');
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 ReactTestTextComponent = require('ReactTestTextComponent');
var ReactTestEmptyComponent = require('ReactTestEmptyComponent');
var invariant = require('invariant');
import type { ReactElement } from 'ReactElementType';
import type { ReactInstance } from 'ReactInstanceType';
type ReactTestRendererJSON = {
type: string,
props: { [propName: string]: string },
children: null | Array<string | ReactTestRendererJSON>,
$$typeof?: any
}
/**
* 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 UNSET = {};
class ReactTestComponent {
_currentElement: ReactElement;
_renderedChildren: null | Object;
_topLevelWrapper: null | ReactInstance;
_hostContainerInfo: null | Object;
_nodeMock: Object;
constructor(element: ReactElement) {
this._currentElement = element;
this._renderedChildren = null;
this._topLevelWrapper = null;
this._hostContainerInfo = null;
this._nodeMock = UNSET;
}
mountComponent(
transaction: ReactTestReconcileTransaction,
nativeParent: null | ReactTestComponent,
hostContainerInfo: Object,
context: Object,
) {
var element = this._currentElement;
this._hostContainerInfo = hostContainerInfo;
this._nodeMock = hostContainerInfo.createNodeMock(element);
// $FlowFixMe https://github.com/facebook/flow/issues/1805
this.mountChildren(element.props.children, transaction, context);
}
receiveComponent(
nextElement: ReactElement,
transaction: ReactTestReconcileTransaction,
context: Object,
) {
this._currentElement = nextElement;
// $FlowFixMe https://github.com/facebook/flow/issues/1805
this.updateChildren(nextElement.props.children, transaction, context);
}
getPublicInstance(): Object {
invariant(
this._nodeMock !== UNSET,
'getPublicInstance should not be called before component is mounted.'
);
return this._nodeMock;
}
toJSON(): ReactTestRendererJSON {
// not using `children`, but I don't want to rewrite without destructuring
// eslint-disable-next-line no-unused-vars
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);
}
}
var object: ReactTestRendererJSON = {
type: this._currentElement.type,
props: props,
children: childrenJSON.length ? childrenJSON : null,
};
Object.defineProperty(object, '$$typeof', {
value: Symbol.for('react.test.json'),
});
return object;
}
getHostNode(): void {}
unmountComponent(safely, skipLifecycle): void {
// $FlowFixMe https://github.com/facebook/flow/issues/1805
this.unmountChildren(safely, skipLifecycle);
}
}
Object.assign(ReactTestComponent.prototype, ReactMultiChild);
// =============================================================================
ReactUpdates.injection.injectReconcileTransaction(
ReactTestReconcileTransaction
);
ReactUpdates.injection.injectBatchingStrategy(ReactDefaultBatchingStrategy);
ReactHostComponent.injection.injectGenericComponentClass(ReactTestComponent);
ReactHostComponent.injection.injectTextComponentClass(ReactTestTextComponent);
ReactEmptyComponent.injection.injectEmptyComponentFactory(function() {
return new ReactTestEmptyComponent();
});
ReactComponentEnvironment.injection.injectEnvironment({
processChildrenUpdates: function() {},
replaceNodeWithMarkup: function() {},
});
var ReactTestRenderer = {
create: ReactTestMount.render,
/* eslint-disable camelcase */
unstable_batchedUpdates: ReactUpdates.batchedUpdates,
/* eslint-enable camelcase */
};
module.exports = ReactTestRenderer;
+3 -5
View File
@@ -859,11 +859,9 @@ babel@^5.4.7:
slash "^1.0.0"
source-map "^0.5.0"
babylon@6.8.0:
version "6.8.0"
resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.8.0.tgz#c1057f7bf703620dc04ddb69cd59ade961b87cb0"
dependencies:
babel-runtime "^6.0.0"
babylon@6.15.0:
version "6.15.0"
resolved "http://registry.yarnpkg.com/babylon/-/babylon-6.15.0.tgz#ba65cfa1a80e1759b0e89fb562e27dccae70348e"
babylon@^5.8.38:
version "5.8.38"