14
14
'use strict' ;
15
15
16
16
var ReactFiberReconciler = require ( 'ReactFiberReconciler' ) ;
17
+ var ReactFiberTreeReflection = require ( 'ReactFiberTreeReflection' ) ;
17
18
var ReactGenericBatching = require ( 'ReactGenericBatching' ) ;
19
+ var ReactTestRendererFeatureFlags = require ( 'ReactTestRendererFeatureFlags' ) ;
18
20
var emptyObject = require ( 'fbjs/lib/emptyObject' ) ;
19
21
var ReactTypeOfWork = require ( 'ReactTypeOfWork' ) ;
20
22
var invariant = require ( 'fbjs/lib/invariant' ) ;
21
23
var {
24
+ Fragment,
22
25
FunctionalComponent,
23
26
ClassComponent,
24
27
HostComponent,
@@ -61,8 +64,29 @@ type TextInstance = {|
61
64
tag : 'TEXT' ,
62
65
| } ;
63
66
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
+
64
75
const UPDATE_SIGNAL = { } ;
65
76
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
+
66
90
function appendChild (
67
91
parentInstance : Instance | Container ,
68
92
child : Instance | TextInstance ,
@@ -225,18 +249,7 @@ var TestRenderer = ReactFiberReconciler({
225
249
226
250
useSyncScheduling : true ,
227
251
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 ,
240
253
} ) ;
241
254
242
255
var defaultTestOptions = {
@@ -325,6 +338,219 @@ function toTree(node: ?Fiber) {
325
338
}
326
339
}
327
340
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
+
328
554
var ReactTestRendererFiber = {
329
555
create ( element : ReactElement < any > , options : TestRendererOptions ) {
330
556
var createNodeMock = defaultTestOptions . createNodeMock ;
@@ -336,11 +562,22 @@ var ReactTestRendererFiber = {
336
562
createNodeMock,
337
563
tag : 'CONTAINER' ,
338
564
} ;
339
- var root : ? FiberRoot = TestRenderer . createContainer ( container ) ;
565
+ var root : FiberRoot | null = TestRenderer . createContainer ( container ) ;
340
566
invariant ( root != null , 'something went wrong' ) ;
341
567
TestRenderer . updateContainer ( element , root , null , null ) ;
342
568
343
569
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
+ } ,
344
581
toJSON ( ) {
345
582
if ( root == null || root . current == null || container == null ) {
346
583
return null ;
0 commit comments