@@ -19,9 +19,11 @@ var emptyObject = require('fbjs/lib/emptyObject');
1919var ReactTypeOfWork = require ( 'ReactTypeOfWork' ) ;
2020var invariant = require ( 'fbjs/lib/invariant' ) ;
2121var {
22+ IndeterminateComponent,
2223 FunctionalComponent,
2324 ClassComponent,
2425 HostComponent,
26+ Fragment,
2527 HostText,
2628 HostRoot,
2729} = ReactTypeOfWork ;
@@ -41,6 +43,7 @@ type ReactTestRendererNode = ReactTestRendererJSON | string;
4143type 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
242271function 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+
280349function 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