Skip to content

Commit

Permalink
[Fiber] Implement test renderer (#8628)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
iamdustan authored and gaearon committed Jan 11, 2017
1 parent 837835d commit 2da35fc
Show file tree
Hide file tree
Showing 24 changed files with 630 additions and 174 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 3 additions & 0 deletions packages/react-test-renderer/fiber.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
'use strict';

module.exports = require('./lib/ReactTestRendererFiber');
2 changes: 1 addition & 1 deletion packages/react-test-renderer/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
'use strict';

module.exports = require('./lib/ReactTestRenderer');
module.exports = require('./lib/ReactTestRendererStack');
4 changes: 4 additions & 0 deletions scripts/fiber/tests-passing.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
4 changes: 4 additions & 0 deletions src/renderers/art/ReactARTFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,10 @@ const ARTRenderer = ReactFiberReconciler({
return false;
},

getPublicInstance(instance) {
return instance;
},

insertBefore(parentInstance, child, beforeChild) {
invariant(
child !== beforeChild,
Expand Down
4 changes: 4 additions & 0 deletions src/renderers/dom/fiber/ReactDOMFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,10 @@ var DOMRenderer = ReactFiberReconciler({
return getChildNamespace(parentNamespace, type);
},

getPublicInstance(instance) {
return instance;
},

prepareForCommit() : void {
eventsEnabled = ReactBrowserEventEmitter.isEnabled();
selectionInformation = ReactInputSelection.getSelectionInformation();
Expand Down
4 changes: 4 additions & 0 deletions src/renderers/native/ReactNativeFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,10 @@ const NativeRenderer = ReactFiberReconciler({
return emptyObject;
},

getPublicInstance(instance) {
return instance;
},

insertBefore(
parentInstance : Instance | Container,
child : Instance | TextInstance,
Expand Down
4 changes: 4 additions & 0 deletions src/renderers/noop/ReactNoop.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ var NoopRenderer = ReactFiberReconciler({
return emptyObject;
},

getPublicInstance(instance) {
return instance;
},

createInstance(type : string, props : Props) : Instance {
const inst = {
id: instanceCounter++,
Expand Down
2 changes: 1 addition & 1 deletion src/renderers/shared/fiber/ReactFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions src/renderers/shared/fiber/ReactFiberBeginWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
7 changes: 4 additions & 3 deletions src/renderers/shared/fiber/ReactFiberCommitWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
) {
Expand All @@ -48,6 +48,7 @@ module.exports = function<T, P, I, TI, C, CX>(
appendChild,
insertBefore,
removeChild,
getPublicInstance,
} = config;

const {
Expand Down Expand Up @@ -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);
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/renderers/shared/fiber/ReactFiberCompleteWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions src/renderers/shared/fiber/ReactFiberHostContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
5 changes: 3 additions & 2 deletions src/renderers/shared/fiber/ReactFiberReconciler.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion src/renderers/shared/fiber/ReactFiberScheduler.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
149 changes: 5 additions & 144 deletions src/renderers/testing/ReactTestRenderer.js
Original file line number Diff line number Diff line change
@@ -1,157 +1,18 @@
/**
* 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
* 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
* @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');
Loading

0 comments on commit 2da35fc

Please sign in to comment.