Skip to content

Commit ba99f9e

Browse files
Add createComponentMock option to test renderer
1 parent cb58522 commit ba99f9e

File tree

6 files changed

+331
-5
lines changed

6 files changed

+331
-5
lines changed

scripts/fiber/tests-passing.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1791,6 +1791,8 @@ src/renderers/testing/__tests__/ReactTestRenderer-test.js
17911791
* toTree() renders complicated trees of composites and hosts
17921792
* can update text nodes when rendered as root
17931793
* can render and update root fragments
1794+
* allows createComponentMock option to be passed in, simulating shallow
1795+
* createComponentMock can mock out specific components
17941796

17951797
src/shared/utils/__tests__/KeyEscapeUtils-test.js
17961798
* should properly escape and wrap user defined keys

src/renderers/shared/fiber/ReactFiber.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export type Fiber = {
6161
_debugSource?: Source | null,
6262
_debugOwner?: Fiber | ReactInstance | null, // Stack compatible
6363
_debugIsCurrentlyTiming?: boolean,
64+
_unmockedType: any,
6465

6566
// These first fields are conceptually members of an Instance. This used to
6667
// be split into a separate type and intersected with the other Fiber fields,
@@ -222,6 +223,7 @@ var createFiber = function(tag: TypeOfWork, key: null | string): Fiber {
222223
fiber._debugSource = null;
223224
fiber._debugOwner = null;
224225
fiber._debugIsCurrentlyTiming = false;
226+
fiber._unmockedType = null;
225227
if (typeof Object.preventExtensions === 'function') {
226228
Object.preventExtensions(fiber);
227229
}

src/renderers/shared/fiber/ReactFiberBeginWork.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,11 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
102102
memoizeState,
103103
);
104104

105+
if (__DEV__) {
106+
var hasMockingBehavior = typeof config.mockComponent === 'function';
107+
var mockComponent = config.mockComponent || (() => {});
108+
}
109+
105110
function markChildAsProgressed(current, workInProgress, priorityLevel) {
106111
// We now have clones. Let's store them as the currently progressed work.
107112
workInProgress.progressedChild = workInProgress.child;
@@ -794,6 +799,18 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
794799
workInProgress.child = workInProgress.progressedChild;
795800
}
796801

802+
if (__DEV__) {
803+
if (hasMockingBehavior) {
804+
switch (workInProgress.tag) {
805+
case IndeterminateComponent:
806+
case FunctionalComponent:
807+
case ClassComponent:
808+
mockComponent(workInProgress, hostContext.getRootHostContainer());
809+
break;
810+
}
811+
}
812+
}
813+
797814
switch (workInProgress.tag) {
798815
case IndeterminateComponent:
799816
return mountIndeterminateComponent(

src/renderers/shared/fiber/ReactFiberReconciler.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ export type HostConfig<T, P, I, TI, PI, C, CX, PL> = {
115115
resetAfterCommit(): void,
116116

117117
useSyncScheduling?: boolean,
118+
119+
mockComponent?: Function,
118120
};
119121

120122
export type Reconciler<C, I, TI> = {

src/renderers/testing/ReactTestRendererFiber.js

Lines changed: 81 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@ var emptyObject = require('fbjs/lib/emptyObject');
1919
var ReactTypeOfWork = require('ReactTypeOfWork');
2020
var invariant = require('fbjs/lib/invariant');
2121
var {
22+
IndeterminateComponent,
2223
FunctionalComponent,
2324
ClassComponent,
2425
HostComponent,
26+
Fragment,
2527
HostText,
2628
HostRoot,
2729
} = ReactTypeOfWork;
@@ -41,6 +43,7 @@ type ReactTestRendererNode = ReactTestRendererJSON | string;
4143
type Container = {|
4244
children: Array<Instance | TextInstance>,
4345
createNodeMock: Function,
46+
createComponentMock: Function,
4447
tag: 'CONTAINER',
4548
|};
4649

@@ -217,6 +220,29 @@ var TestRenderer = ReactFiberReconciler({
217220
setTimeout(fn, 0, {timeRemaining: Infinity});
218221
},
219222

223+
mockComponent(component: Fiber, rootContainer: Container) {
224+
invariant(
225+
component._unmockedType === null,
226+
'Trying to mock an already mocked component',
227+
);
228+
const mockedFn = rootContainer.createComponentMock({
229+
type: component.type,
230+
props: component.pendingProps,
231+
});
232+
invariant(
233+
typeof mockedFn === 'function',
234+
'createComponentMock() must return a function. Found %s instead.',
235+
typeof mockedFn,
236+
);
237+
if (mockedFn !== component.type) {
238+
component._unmockedType = component.type;
239+
component.type = mockedFn;
240+
// force the fiber to be indeterminate so that users can mock a class component
241+
// into a functional component and vice versa
242+
component.tag = IndeterminateComponent;
243+
}
244+
},
245+
220246
useSyncScheduling: true,
221247

222248
getPublicInstance(inst) {
@@ -237,6 +263,9 @@ var defaultTestOptions = {
237263
createNodeMock: function() {
238264
return null;
239265
},
266+
createComponentMock: function(component: {type: Function, props: any}) {
267+
return component.type;
268+
},
240269
};
241270

242271
function toJSON(inst: Instance | TextInstance): ReactTestRendererNode {
@@ -277,6 +306,46 @@ function nodeAndSiblingsArray(nodeWithSibling: ?Fiber) {
277306
return array;
278307
}
279308

309+
function childrenToTree(node) {
310+
if (!node) {
311+
return null;
312+
}
313+
const children = nodeAndSiblingsArray(node);
314+
if (children.length === 0) {
315+
return null;
316+
} else if (children.length === 1) {
317+
return toTree(children[0]);
318+
} else {
319+
return flatten(children.map(toTree));
320+
}
321+
}
322+
323+
function flatten(arr) {
324+
const result = [];
325+
const stack = [{i: 0, array: arr}];
326+
while (stack.length) {
327+
let n = stack.pop();
328+
while (n.i < n.array.length) {
329+
const el = n.array[n.i];
330+
n.i += 1;
331+
if (Array.isArray(el)) {
332+
stack.push(n);
333+
stack.push({i: 0, array: el});
334+
break;
335+
}
336+
result.push(el);
337+
}
338+
}
339+
return result;
340+
}
341+
342+
function publicType(node: Fiber) {
343+
if (node._unmockedType !== null) {
344+
return node._unmockedType;
345+
}
346+
return node.type;
347+
}
348+
280349
function toTree(node: ?Fiber) {
281350
if (node == null) {
282351
return null;
@@ -287,26 +356,28 @@ function toTree(node: ?Fiber) {
287356
case ClassComponent:
288357
return {
289358
nodeType: 'component',
290-
type: node.type,
359+
type: publicType(node),
291360
props: {...node.memoizedProps},
292361
instance: node.stateNode,
293-
rendered: toTree(node.child),
362+
rendered: childrenToTree(node.child),
294363
};
364+
case Fragment: // 10
365+
return childrenToTree(node.child);
295366
case FunctionalComponent: // 1
296367
return {
297368
nodeType: 'component',
298-
type: node.type,
369+
type: publicType(node),
299370
props: {...node.memoizedProps},
300371
instance: null,
301-
rendered: toTree(node.child),
372+
rendered: childrenToTree(node.child),
302373
};
303374
case HostComponent: // 5
304375
return {
305376
nodeType: 'host',
306377
type: node.type,
307378
props: {...node.memoizedProps},
308379
instance: null, // TODO: use createNodeMock here somehow?
309-
rendered: nodeAndSiblingsArray(node.child).map(toTree),
380+
rendered: flatten(nodeAndSiblingsArray(node.child).map(toTree)),
310381
};
311382
case HostText: // 6
312383
return node.stateNode.text;
@@ -325,9 +396,14 @@ var ReactTestFiberRenderer = {
325396
if (options && typeof options.createNodeMock === 'function') {
326397
createNodeMock = options.createNodeMock;
327398
}
399+
var createComponentMock = defaultTestOptions.createComponentMock;
400+
if (options && typeof options.createComponentMock === 'function') {
401+
createComponentMock = options.createComponentMock;
402+
}
328403
var container = {
329404
children: [],
330405
createNodeMock,
406+
createComponentMock,
331407
tag: 'CONTAINER',
332408
};
333409
var root: ?FiberRoot = TestRenderer.createContainer(container);

0 commit comments

Comments
 (0)