1414'use strict' ;
1515
1616var ReactFiberReconciler = require ( 'ReactFiberReconciler' ) ;
17+ var ReactFiberTreeReflection = require ( 'ReactFiberTreeReflection' ) ;
1718var ReactGenericBatching = require ( 'ReactGenericBatching' ) ;
19+ var ReactTestRendererFeatureFlags = require ( 'ReactTestRendererFeatureFlags' ) ;
1820var emptyObject = require ( 'fbjs/lib/emptyObject' ) ;
1921var ReactTypeOfWork = require ( 'ReactTypeOfWork' ) ;
2022var invariant = require ( 'fbjs/lib/invariant' ) ;
2123var {
24+ Fragment,
2225 FunctionalComponent,
2326 ClassComponent,
2427 HostComponent,
@@ -63,6 +66,19 @@ type TextInstance = {|
6366
6467const UPDATE_SIGNAL = { } ;
6568
69+ function getPublicInstance ( inst : Instance | TextInstance ) : * {
70+ switch ( inst . tag ) {
71+ case 'INSTANCE' :
72+ const createNodeMock = inst . rootContainerInstance . createNodeMock ;
73+ return createNodeMock ( {
74+ type : inst . type ,
75+ props : inst . props ,
76+ } ) ;
77+ default:
78+ return inst ;
79+ }
80+ }
81+
6682function appendChild (
6783 parentInstance : Instance | Container ,
6884 child : Instance | TextInstance ,
@@ -225,18 +241,7 @@ var TestRenderer = ReactFiberReconciler({
225241
226242 useSyncScheduling : true ,
227243
228- getPublicInstance ( inst : Instance | TextInstance ) : * {
229- switch ( inst . tag ) {
230- case 'INSTANCE' :
231- const createNodeMock = inst . rootContainerInstance . createNodeMock ;
232- return createNodeMock ( {
233- type : inst . type ,
234- props : inst . props ,
235- } ) ;
236- default:
237- return inst ;
238- }
239- } ,
244+ getPublicInstance ,
240245} ) ;
241246
242247var defaultTestOptions = {
@@ -325,6 +330,193 @@ function toTree(node: ?Fiber) {
325330 }
326331}
327332
333+ const fiberToWrapper = new WeakMap ( ) ;
334+ function wrapFiber ( fiber : Fiber ) : ReactTestInstance {
335+ let wrapper = fiberToWrapper . get ( fiber ) ;
336+ if ( wrapper === undefined && fiber . alternate !== null ) {
337+ wrapper = fiberToWrapper . get ( fiber . alternate ) ;
338+ }
339+ if ( wrapper === undefined ) {
340+ wrapper = new ReactTestInstance ( fiber ) ;
341+ fiberToWrapper . set ( fiber , wrapper ) ;
342+ }
343+ return wrapper ;
344+ }
345+
346+ const validWrapperTypes = new Set ( [
347+ FunctionalComponent ,
348+ ClassComponent ,
349+ HostComponent ,
350+ ] ) ;
351+
352+ class ReactTestInstance {
353+ _fiber : Fiber ;
354+
355+ _currentFiber ( ) : Fiber {
356+ const mounted = ReactFiberTreeReflection . isFiberMounted ( this . _fiber ) ;
357+ invariant ( mounted , 'Can\'t read properties from unmounted component' ) ;
358+ return ReactFiberTreeReflection . findCurrentFiberUsingSlowPath ( this . _fiber ) ;
359+ }
360+
361+ constructor ( fiber : Fiber ) {
362+ invariant (
363+ validWrapperTypes . has ( fiber . tag ) ,
364+ 'Unexpected object passed to ReactTestInstance constructor (tag: %s). ' +
365+ 'This is probably a bug in React.' ,
366+ fiber . tag
367+ ) ;
368+ this . _fiber = fiber ;
369+ }
370+
371+ get instance ( ) {
372+ if ( this . _fiber . tag === HostComponent ) {
373+ return getPublicInstance ( this . _fiber . stateNode ) ;
374+ } else {
375+ return this . _fiber . stateNode ;
376+ }
377+ }
378+
379+ get type ( ) {
380+ return this . _fiber . type ;
381+ }
382+
383+ get props ( ) : Object {
384+ return this . _currentFiber ( ) . memoizedProps ;
385+ }
386+
387+ get parent ( ) : Object {
388+ const parent = this . _fiber . return ;
389+ return parent . tag === HostRoot ? null : wrapFiber ( parent ) ;
390+ }
391+
392+ get children ( ) : Array < ReactTestInstance | string > {
393+ const children = [ ] ;
394+ const startingNode = this . _currentFiber ( ) ;
395+ if ( startingNode . child === null ) {
396+ return children ;
397+ }
398+ let node = startingNode ;
399+ node . child . return = node ;
400+ node = node . child ;
401+ while ( true ) {
402+ let descend = false ;
403+ switch ( node . tag ) {
404+ case FunctionalComponent :
405+ case ClassComponent :
406+ case HostComponent :
407+ children . push ( wrapFiber ( node ) ) ;
408+ break ;
409+ case HostText :
410+ children . push ( '' + node . memoizedProps ) ;
411+ break ;
412+ case Fragment :
413+ descend = true ;
414+ break ;
415+ default :
416+ invariant (
417+ false ,
418+ 'Unsupported component type %s in test renderer. ' +
419+ 'This is probably a bug in React.' ,
420+ node . tag
421+ ) ;
422+ }
423+ if ( descend && node . child !== null ) {
424+ node . child . return = node ;
425+ node = node . child ;
426+ continue ;
427+ }
428+ while ( node . sibling === null ) {
429+ if ( node . return === startingNode ) {
430+ return children ;
431+ }
432+ node = node . return ;
433+ }
434+ node . sibling . return = node . return ;
435+ node = node . sibling ;
436+ }
437+ }
438+
439+ // Custom search functions
440+ find ( predicate : Predicate ) : ReactTestInstance {
441+ return expectOne (
442+ this . findAll ( predicate , { deep : false } ) ,
443+ `matching custom predicate: ${ predicate . toString ( ) } `
444+ ) ;
445+ }
446+
447+ findByType ( type : any ) : ReactTestInstance {
448+ return expectOne (
449+ this . findAllByType ( type , { deep : false } ) ,
450+ `with node type: "${ type . displayName || type . name } "`
451+ ) ;
452+ }
453+
454+ findByProps ( props : Object ) : ReactTestInstance {
455+ return expectOne (
456+ this . findAllByProps ( props , { deep : false } ) ,
457+ `with props: ${ JSON . stringify ( props ) } `
458+ ) ;
459+ }
460+
461+ findAll ( predicate : Predicate , options : ?FindOptions = null ) : Array < ReactTestInstance > {
462+ return findAll ( this , predicate , options ) ;
463+ }
464+
465+ findAllByType ( type : any , options : ?FindOptions = null ) : Array < ReactTestInstance > {
466+ return findAll ( this , node => node . type === type , options ) ;
467+ }
468+
469+ findAllByProps ( props : Object , options : ?FindOptions = null ) : Array < ReactTestInstance > {
470+ return findAll ( this , node => node . props && propsMatch ( node . props , props ) , options ) ;
471+ }
472+ }
473+
474+ function findAll (
475+ root : ReactTestInstance ,
476+ predicate : Predicate ,
477+ options : ?FindOptions ,
478+ ) : Array < ReactTestInstance > {
479+ const deep = options ? options . deep : true ;
480+ const results = [ ] ;
481+
482+ if ( predicate ( root ) ) {
483+ results . push ( root ) ;
484+ if ( ! deep ) {
485+ return results ;
486+ }
487+ }
488+
489+ for ( const child of root . children ) {
490+ if ( typeof child === 'string' ) {
491+ continue ;
492+ }
493+ results . push ( ...findAll ( child , predicate , options ) ) ;
494+ }
495+
496+ return results ;
497+ }
498+
499+ function expectOne ( all : Array < ReactTestInstance > , message : string ) : ReactTestInstance {
500+ if ( all . length === 1 ) {
501+ return all [ 0 ] ;
502+ }
503+
504+ const prefix = all . length === 0
505+ ? 'No instances found '
506+ : `Expected 1 but found ${ all . length } instances ` ;
507+
508+ throw new Error ( prefix + message ) ;
509+ }
510+
511+ function propsMatch ( props : Object , filter : Object ) : boolean {
512+ for ( const key in filter ) {
513+ if ( props [ key ] !== filter [ key ] ) {
514+ return false ;
515+ }
516+ }
517+ return true ;
518+ }
519+
328520var ReactTestRendererFiber = {
329521 create ( element : ReactElement < any > , options : TestRendererOptions ) {
330522 var createNodeMock = defaultTestOptions . createNodeMock ;
@@ -341,6 +533,17 @@ var ReactTestRendererFiber = {
341533 TestRenderer . updateContainer ( element , root , null , null ) ;
342534
343535 return {
536+ get root ( ) {
537+ if ( ! ReactTestRendererFeatureFlags . enableTraversal ) {
538+ throw new Error (
539+ 'Test renderer traversal is experimental and not enabled'
540+ ) ;
541+ }
542+ if ( root === null ) {
543+ throw new Error ( 'Can\'t access .root on unmounted test renderer' ) ;
544+ }
545+ return wrapFiber ( root . current . child ) ;
546+ } ,
344547 toJSON ( ) {
345548 if ( root == null || root . current == null || container == null ) {
346549 return null ;
0 commit comments