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,
@@ -61,8 +64,29 @@ type TextInstance = {|
6164 tag : 'TEXT' ,
6265| } ;
6366
67+ type FindOptions = $Shape < {
68+ // performs a "greedy" search: if a matching node is found, will continue
69+ // to search within the matching node's children. (default: true)
70+ deep : boolean ,
71+ } > ;
72+
73+ export type Predicate = ( node : ReactTestInstance ) => ?boolean ;
74+
6475const UPDATE_SIGNAL = { } ;
6576
77+ function getPublicInstance ( inst : Instance | TextInstance ) : * {
78+ switch ( inst . tag ) {
79+ case 'INSTANCE' :
80+ const createNodeMock = inst . rootContainerInstance . createNodeMock ;
81+ return createNodeMock ( {
82+ type : inst . type ,
83+ props : inst . props ,
84+ } ) ;
85+ default:
86+ return inst ;
87+ }
88+ }
89+
6690function appendChild (
6791 parentInstance : Instance | Container ,
6892 child : Instance | TextInstance ,
@@ -225,18 +249,7 @@ var TestRenderer = ReactFiberReconciler({
225249
226250 useSyncScheduling : true ,
227251
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- } ,
252+ getPublicInstance ,
240253} ) ;
241254
242255var defaultTestOptions = {
@@ -325,6 +338,219 @@ function toTree(node: ?Fiber) {
325338 }
326339}
327340
341+ const fiberToWrapper = new WeakMap ( ) ;
342+ function wrapFiber ( fiber : Fiber ) : ReactTestInstance {
343+ let wrapper = fiberToWrapper . get ( fiber ) ;
344+ if ( wrapper === undefined && fiber . alternate !== null ) {
345+ wrapper = fiberToWrapper . get ( fiber . alternate ) ;
346+ }
347+ if ( wrapper === undefined ) {
348+ wrapper = new ReactTestInstance ( fiber ) ;
349+ fiberToWrapper . set ( fiber , wrapper ) ;
350+ }
351+ return wrapper ;
352+ }
353+
354+ const validWrapperTypes = new Set ( [
355+ FunctionalComponent ,
356+ ClassComponent ,
357+ HostComponent ,
358+ ] ) ;
359+
360+ class ReactTestInstance {
361+ _fiber : Fiber ;
362+
363+ _currentFiber ( ) : Fiber {
364+ // Throws if this component has been unmounted.
365+ const fiber = ReactFiberTreeReflection . findCurrentFiberUsingSlowPath (
366+ this . _fiber ,
367+ ) ;
368+ invariant (
369+ fiber !== null ,
370+ "Can't read from currently-mounting component. This error is likely " +
371+ 'caused by a bug in React. Please file an issue.' ,
372+ ) ;
373+ return fiber ;
374+ }
375+
376+ constructor ( fiber : Fiber ) {
377+ invariant (
378+ validWrapperTypes . has ( fiber . tag ) ,
379+ 'Unexpected object passed to ReactTestInstance constructor (tag: %s). ' +
380+ 'This is probably a bug in React.' ,
381+ fiber . tag ,
382+ ) ;
383+ this . _fiber = fiber ;
384+ }
385+
386+ get instance ( ) {
387+ if ( this . _fiber . tag === HostComponent ) {
388+ return getPublicInstance ( this . _fiber . stateNode ) ;
389+ } else {
390+ return this . _fiber . stateNode ;
391+ }
392+ }
393+
394+ get type ( ) {
395+ return this . _fiber . type ;
396+ }
397+
398+ get props ( ) : Object {
399+ return this . _currentFiber ( ) . memoizedProps ;
400+ }
401+
402+ get parent ( ) : ?ReactTestInstance {
403+ const parent = this . _fiber . return ;
404+ return parent === null || parent . tag === HostRoot
405+ ? null
406+ : wrapFiber ( parent ) ;
407+ }
408+
409+ get children ( ) : Array < ReactTestInstance | string > {
410+ const children = [ ] ;
411+ const startingNode = this . _currentFiber ( ) ;
412+ let node : Fiber = startingNode ;
413+ if ( node . child === null ) {
414+ return children ;
415+ }
416+ node . child . return = node ;
417+ node = node . child ;
418+ outer: while ( true ) {
419+ let descend = false ;
420+ switch ( node . tag ) {
421+ case FunctionalComponent :
422+ case ClassComponent :
423+ case HostComponent :
424+ children . push ( wrapFiber ( node ) ) ;
425+ break ;
426+ case HostText :
427+ children . push ( '' + node . memoizedProps ) ;
428+ break ;
429+ case Fragment :
430+ descend = true ;
431+ break ;
432+ default :
433+ invariant (
434+ false ,
435+ 'Unsupported component type %s in test renderer. ' +
436+ 'This is probably a bug in React.' ,
437+ node . tag ,
438+ ) ;
439+ }
440+ if ( descend && node . child !== null ) {
441+ node . child . return = node ;
442+ node = node . child ;
443+ continue ;
444+ }
445+ while ( node . sibling === null ) {
446+ if ( node . return === startingNode ) {
447+ break outer;
448+ }
449+ node = ( node . return : any ) ;
450+ }
451+ ( node . sibling : any ) . return = node . return ;
452+ node = ( node . sibling : any ) ;
453+ }
454+ return children ;
455+ }
456+
457+ // Custom search functions
458+ find ( predicate : Predicate ) : ReactTestInstance {
459+ return expectOne (
460+ this . findAll ( predicate , { deep : false } ) ,
461+ `matching custom predicate: ${ predicate . toString ( ) } ` ,
462+ ) ;
463+ }
464+
465+ findByType ( type : any ) : ReactTestInstance {
466+ return expectOne (
467+ this . findAllByType ( type , { deep : false } ) ,
468+ `with node type: "${ type . displayName || type . name } "` ,
469+ ) ;
470+ }
471+
472+ findByProps ( props : Object ) : ReactTestInstance {
473+ return expectOne (
474+ this . findAllByProps ( props , { deep : false } ) ,
475+ `with props: ${ JSON . stringify ( props ) } ` ,
476+ ) ;
477+ }
478+
479+ findAll (
480+ predicate : Predicate ,
481+ options : ?FindOptions = null ,
482+ ) : Array < ReactTestInstance > {
483+ return findAll ( this , predicate , options ) ;
484+ }
485+
486+ findAllByType (
487+ type : any ,
488+ options : ?FindOptions = null ,
489+ ) : Array < ReactTestInstance > {
490+ return findAll ( this , node => node . type === type , options ) ;
491+ }
492+
493+ findAllByProps (
494+ props : Object ,
495+ options : ?FindOptions = null ,
496+ ) : Array < ReactTestInstance > {
497+ return findAll (
498+ this ,
499+ node => node . props && propsMatch ( node . props , props ) ,
500+ options ,
501+ ) ;
502+ }
503+ }
504+
505+ function findAll (
506+ root : ReactTestInstance ,
507+ predicate : Predicate ,
508+ options : ?FindOptions ,
509+ ) : Array < ReactTestInstance > {
510+ const deep = options ? options . deep : true ;
511+ const results = [ ] ;
512+
513+ if ( predicate ( root ) ) {
514+ results . push ( root ) ;
515+ if ( ! deep ) {
516+ return results ;
517+ }
518+ }
519+
520+ for ( const child of root . children ) {
521+ if ( typeof child === 'string' ) {
522+ continue ;
523+ }
524+ results . push ( ...findAll ( child , predicate , options ) ) ;
525+ }
526+
527+ return results ;
528+ }
529+
530+ function expectOne (
531+ all : Array < ReactTestInstance > ,
532+ message : string ,
533+ ) : ReactTestInstance {
534+ if ( all . length === 1 ) {
535+ return all [ 0 ] ;
536+ }
537+
538+ const prefix = all . length === 0
539+ ? 'No instances found '
540+ : `Expected 1 but found ${ all . length } instances ` ;
541+
542+ throw new Error ( prefix + message ) ;
543+ }
544+
545+ function propsMatch ( props : Object , filter : Object ) : boolean {
546+ for ( const key in filter ) {
547+ if ( props [ key ] !== filter [ key ] ) {
548+ return false ;
549+ }
550+ }
551+ return true ;
552+ }
553+
328554var ReactTestRendererFiber = {
329555 create ( element : ReactElement < any > , options : TestRendererOptions ) {
330556 var createNodeMock = defaultTestOptions . createNodeMock ;
@@ -336,11 +562,22 @@ var ReactTestRendererFiber = {
336562 createNodeMock,
337563 tag : 'CONTAINER' ,
338564 } ;
339- var root : ? FiberRoot = TestRenderer . createContainer ( container ) ;
565+ var root : FiberRoot | null = TestRenderer . createContainer ( container ) ;
340566 invariant ( root != null , 'something went wrong' ) ;
341567 TestRenderer . updateContainer ( element , root , null , null ) ;
342568
343569 return {
570+ get root ( ) {
571+ if ( ! ReactTestRendererFeatureFlags . enableTraversal ) {
572+ throw new Error (
573+ 'Test renderer traversal is experimental and not enabled' ,
574+ ) ;
575+ }
576+ if ( root === null || root . current . child === null ) {
577+ throw new Error ( "Can't access .root on unmounted test renderer" ) ;
578+ }
579+ return wrapFiber ( root . current . child ) ;
580+ } ,
344581 toJSON ( ) {
345582 if ( root == null || root . current == null || container == null ) {
346583 return null ;
0 commit comments