Skip to content

Commit

Permalink
Add createComponentMock option to test renderer
Browse files Browse the repository at this point in the history
  • Loading branch information
lelandrichardson committed Mar 25, 2017
1 parent cb58522 commit e35416f
Show file tree
Hide file tree
Showing 6 changed files with 331 additions and 5 deletions.
2 changes: 2 additions & 0 deletions scripts/fiber/tests-passing.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1791,6 +1791,8 @@ src/renderers/testing/__tests__/ReactTestRenderer-test.js
* toTree() renders complicated trees of composites and hosts
* can update text nodes when rendered as root
* can render and update root fragments
* allows createComponentMock option to be passed in, simulating shallow
* createComponentMock can mock out specific components

src/shared/utils/__tests__/KeyEscapeUtils-test.js
* should properly escape and wrap user defined keys
Expand Down
2 changes: 2 additions & 0 deletions src/renderers/shared/fiber/ReactFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export type Fiber = {
_debugSource?: Source | null,
_debugOwner?: Fiber | ReactInstance | null, // Stack compatible
_debugIsCurrentlyTiming?: boolean,
_unmockedType?: any,

// These first fields are conceptually members of an Instance. This used to
// be split into a separate type and intersected with the other Fiber fields,
Expand Down Expand Up @@ -222,6 +223,7 @@ var createFiber = function(tag: TypeOfWork, key: null | string): Fiber {
fiber._debugSource = null;
fiber._debugOwner = null;
fiber._debugIsCurrentlyTiming = false;
fiber._unmockedType = null;
if (typeof Object.preventExtensions === 'function') {
Object.preventExtensions(fiber);
}
Expand Down
17 changes: 17 additions & 0 deletions src/renderers/shared/fiber/ReactFiberBeginWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,11 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
memoizeState,
);

if (__DEV__) {
var hasMockingBehavior = typeof config.mockComponent === 'function';
var mockComponent = config.mockComponent || (() => {});
}

function markChildAsProgressed(current, workInProgress, priorityLevel) {
// We now have clones. Let's store them as the currently progressed work.
workInProgress.progressedChild = workInProgress.child;
Expand Down Expand Up @@ -794,6 +799,18 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
workInProgress.child = workInProgress.progressedChild;
}

if (__DEV__) {
if (hasMockingBehavior) {
switch (workInProgress.tag) {
case IndeterminateComponent:
case FunctionalComponent:
case ClassComponent:
mockComponent(workInProgress, hostContext.getRootHostContainer());
break;
}
}
}

switch (workInProgress.tag) {
case IndeterminateComponent:
return mountIndeterminateComponent(
Expand Down
2 changes: 2 additions & 0 deletions src/renderers/shared/fiber/ReactFiberReconciler.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ export type HostConfig<T, P, I, TI, PI, C, CX, PL> = {
resetAfterCommit(): void,

useSyncScheduling?: boolean,

mockComponent?: Function,
};

export type Reconciler<C, I, TI> = {
Expand Down
86 changes: 81 additions & 5 deletions src/renderers/testing/ReactTestRendererFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ var emptyObject = require('fbjs/lib/emptyObject');
var ReactTypeOfWork = require('ReactTypeOfWork');
var invariant = require('fbjs/lib/invariant');
var {
IndeterminateComponent,
FunctionalComponent,
ClassComponent,
HostComponent,
Fragment,
HostText,
HostRoot,
} = ReactTypeOfWork;
Expand All @@ -41,6 +43,7 @@ type ReactTestRendererNode = ReactTestRendererJSON | string;
type Container = {|
children: Array<Instance | TextInstance>,
createNodeMock: Function,
createComponentMock: Function,
tag: 'CONTAINER',
|};

Expand Down Expand Up @@ -217,6 +220,29 @@ var TestRenderer = ReactFiberReconciler({
setTimeout(fn, 0, {timeRemaining: Infinity});
},

mockComponent(component: Fiber, rootContainer: Container) {
invariant(
component._unmockedType === null,
'Trying to mock an already mocked component',
);
const mockedFn = rootContainer.createComponentMock({
type: component.type,
props: component.pendingProps,
});
invariant(
typeof mockedFn === 'function',
'createComponentMock() must return a function. Found %s instead.',
typeof mockedFn,
);
if (mockedFn !== component.type) {
component._unmockedType = component.type;
component.type = mockedFn;
// force the fiber to be indeterminate so that users can mock a class component
// into a functional component and vice versa
component.tag = IndeterminateComponent;
}
},

useSyncScheduling: true,

getPublicInstance(inst) {
Expand All @@ -237,6 +263,9 @@ var defaultTestOptions = {
createNodeMock: function() {
return null;
},
createComponentMock: function(component: {type: Function, props: any}) {
return component.type;
},
};

function toJSON(inst: Instance | TextInstance): ReactTestRendererNode {
Expand Down Expand Up @@ -277,6 +306,46 @@ function nodeAndSiblingsArray(nodeWithSibling: ?Fiber) {
return array;
}

function childrenToTree(node) {
if (!node) {
return null;
}
const children = nodeAndSiblingsArray(node);
if (children.length === 0) {
return null;
} else if (children.length === 1) {
return toTree(children[0]);
} else {
return flatten(children.map(toTree));
}
}

function flatten(arr) {
const result = [];
const stack = [{i: 0, array: arr}];
while (stack.length) {
let n = stack.pop();
while (n.i < n.array.length) {
const el = n.array[n.i];
n.i += 1;
if (Array.isArray(el)) {
stack.push(n);
stack.push({i: 0, array: el});
break;
}
result.push(el);
}
}
return result;
}

function publicType(node: Fiber) {
if (node._unmockedType !== null) {
return node._unmockedType;
}
return node.type;
}

function toTree(node: ?Fiber) {
if (node == null) {
return null;
Expand All @@ -287,26 +356,28 @@ function toTree(node: ?Fiber) {
case ClassComponent:
return {
nodeType: 'component',
type: node.type,
type: publicType(node),
props: {...node.memoizedProps},
instance: node.stateNode,
rendered: toTree(node.child),
rendered: childrenToTree(node.child),
};
case Fragment: // 10
return childrenToTree(node.child);
case FunctionalComponent: // 1
return {
nodeType: 'component',
type: node.type,
type: publicType(node),
props: {...node.memoizedProps},
instance: null,
rendered: toTree(node.child),
rendered: childrenToTree(node.child),
};
case HostComponent: // 5
return {
nodeType: 'host',
type: node.type,
props: {...node.memoizedProps},
instance: null, // TODO: use createNodeMock here somehow?
rendered: nodeAndSiblingsArray(node.child).map(toTree),
rendered: flatten(nodeAndSiblingsArray(node.child).map(toTree)),
};
case HostText: // 6
return node.stateNode.text;
Expand All @@ -325,9 +396,14 @@ var ReactTestFiberRenderer = {
if (options && typeof options.createNodeMock === 'function') {
createNodeMock = options.createNodeMock;
}
var createComponentMock = defaultTestOptions.createComponentMock;
if (options && typeof options.createComponentMock === 'function') {
createComponentMock = options.createComponentMock;
}
var container = {
children: [],
createNodeMock,
createComponentMock,
tag: 'CONTAINER',
};
var root: ?FiberRoot = TestRenderer.createContainer(container);
Expand Down
Loading

0 comments on commit e35416f

Please sign in to comment.