@@ -16,7 +16,8 @@ let ReactFeatureFlags;
16
16
let ReactDOM ;
17
17
let FocusWithinResponder ;
18
18
let useFocusWithin ;
19
- let Scheduler ;
19
+ let ReactTestRenderer ;
20
+ let act ;
20
21
21
22
const initializeModules = hasPointerEvents => {
22
23
setPointerEvent ( hasPointerEvents ) ;
@@ -26,7 +27,8 @@ const initializeModules = hasPointerEvents => {
26
27
ReactFeatureFlags . enableScopeAPI = true ;
27
28
React = require ( 'react' ) ;
28
29
ReactDOM = require ( 'react-dom' ) ;
29
- Scheduler = require ( 'scheduler' ) ;
30
+ ReactTestRenderer = require ( 'react-test-renderer' ) ;
31
+ act = ReactTestRenderer . act ;
30
32
31
33
// TODO: This import throws outside of experimental mode. Figure out better
32
34
// strategy for gated imports.
@@ -43,17 +45,22 @@ const table = [[forcePointerEvents], [!forcePointerEvents]];
43
45
44
46
describe . each ( table ) ( 'FocusWithin responder' , hasPointerEvents => {
45
47
let container ;
48
+ let container2 ;
46
49
47
50
beforeEach ( ( ) => {
48
51
initializeModules ( ) ;
49
52
container = document . createElement ( 'div' ) ;
50
53
document . body . appendChild ( container ) ;
54
+ container2 = document . createElement ( 'div' ) ;
55
+ document . body . appendChild ( container2 ) ;
51
56
} ) ;
52
57
53
58
afterEach ( ( ) => {
54
59
ReactDOM . render ( null , container ) ;
55
60
document . body . removeChild ( container ) ;
61
+ document . body . removeChild ( container2 ) ;
56
62
container = null ;
63
+ container2 = null ;
57
64
} ) ;
58
65
59
66
describe ( 'disabled' , ( ) => {
@@ -366,6 +373,40 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
366
373
) ;
367
374
} ) ;
368
375
376
+ // @gate experimental
377
+ it ( 'is called after many elements are unmounted' , ( ) => {
378
+ const buttonRef = React . createRef ( ) ;
379
+ const inputRef = React . createRef ( ) ;
380
+
381
+ const Component = ( { show} ) => {
382
+ const listener = useFocusWithin ( {
383
+ onBeforeBlurWithin,
384
+ onAfterBlurWithin,
385
+ } ) ;
386
+ return (
387
+ < div ref = { ref } DEPRECATED_flareListeners = { listener } >
388
+ { show && < button > Press me!</ button > }
389
+ { show && < button > Press me!</ button > }
390
+ { show && < input ref = { inputRef } /> }
391
+ { show && < button > Press me!</ button > }
392
+ { ! show && < button ref = { buttonRef } > Press me!</ button > }
393
+ { show && < button > Press me!</ button > }
394
+ < button > Press me!</ button >
395
+ < button > Press me!</ button >
396
+ </ div >
397
+ ) ;
398
+ } ;
399
+
400
+ ReactDOM . render ( < Component show = { true } /> , container ) ;
401
+
402
+ inputRef . current . focus ( ) ;
403
+ expect ( onBeforeBlurWithin ) . toHaveBeenCalledTimes ( 0 ) ;
404
+ expect ( onAfterBlurWithin ) . toHaveBeenCalledTimes ( 0 ) ;
405
+ ReactDOM . render ( < Component show = { false } /> , container ) ;
406
+ expect ( onBeforeBlurWithin ) . toHaveBeenCalledTimes ( 1 ) ;
407
+ expect ( onAfterBlurWithin ) . toHaveBeenCalledTimes ( 1 ) ;
408
+ } ) ;
409
+
369
410
// @gate experimental
370
411
it ( 'is called after a nested focused element is unmounted (with scope query)' , ( ) => {
371
412
const TestScope = React . unstable_createScope ( ) ;
@@ -430,12 +471,10 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
430
471
) ;
431
472
} ;
432
473
433
- const container2 = document . createElement ( 'div' ) ;
434
- document . body . appendChild ( container2 ) ;
435
-
436
474
const root = ReactDOM . createRoot ( container2 ) ;
437
- root . render ( < Component /> ) ;
438
- Scheduler . unstable_flushAll ( ) ;
475
+ act ( ( ) => {
476
+ root . render ( < Component /> ) ;
477
+ } ) ;
439
478
jest . runAllTimers ( ) ;
440
479
expect ( container2 . innerHTML ) . toBe ( '<div><input></div>' ) ;
441
480
@@ -447,17 +486,84 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
447
486
expect ( onAfterBlurWithin ) . toHaveBeenCalledTimes ( 0 ) ;
448
487
449
488
suspend = true ;
450
- root . render ( < Component /> ) ;
451
- Scheduler . unstable_flushAll ( ) ;
489
+ act ( ( ) => {
490
+ root . render ( < Component /> ) ;
491
+ } ) ;
452
492
jest . runAllTimers ( ) ;
453
493
expect ( container2 . innerHTML ) . toBe (
454
494
'<div><input style="display: none;">Loading...</div>' ,
455
495
) ;
456
496
expect ( onBeforeBlurWithin ) . toHaveBeenCalledTimes ( 1 ) ;
457
497
expect ( onAfterBlurWithin ) . toHaveBeenCalledTimes ( 1 ) ;
458
498
resolve ( ) ;
499
+ } ) ;
500
+
501
+ // @gate experimental
502
+ it ( 'is called after a focused suspended element is hidden then shown' , ( ) => {
503
+ const Suspense = React . Suspense ;
504
+ let suspend = false ;
505
+ let resolve ;
506
+ const promise = new Promise ( resolvePromise => ( resolve = resolvePromise ) ) ;
507
+ const buttonRef = React . createRef ( ) ;
508
+
509
+ function Child ( ) {
510
+ if ( suspend ) {
511
+ throw promise ;
512
+ } else {
513
+ return < input ref = { innerRef } /> ;
514
+ }
515
+ }
459
516
460
- document . body . removeChild ( container2 ) ;
517
+ const Component = ( { show} ) => {
518
+ const listener = useFocusWithin ( {
519
+ onBeforeBlurWithin,
520
+ onAfterBlurWithin,
521
+ } ) ;
522
+
523
+ return (
524
+ < div ref = { ref } DEPRECATED_flareListeners = { listener } >
525
+ < Suspense fallback = { < button ref = { buttonRef } > Loading...</ button > } >
526
+ < Child />
527
+ </ Suspense >
528
+ </ div >
529
+ ) ;
530
+ } ;
531
+
532
+ const root = ReactDOM . createRoot ( container2 ) ;
533
+
534
+ act ( ( ) => {
535
+ root . render ( < Component /> ) ;
536
+ } ) ;
537
+ jest . runAllTimers ( ) ;
538
+
539
+ expect ( onBeforeBlurWithin ) . toHaveBeenCalledTimes ( 0 ) ;
540
+ expect ( onAfterBlurWithin ) . toHaveBeenCalledTimes ( 0 ) ;
541
+
542
+ suspend = true ;
543
+ act ( ( ) => {
544
+ root . render ( < Component /> ) ;
545
+ } ) ;
546
+ jest . runAllTimers ( ) ;
547
+ expect ( onBeforeBlurWithin ) . toHaveBeenCalledTimes ( 0 ) ;
548
+ expect ( onAfterBlurWithin ) . toHaveBeenCalledTimes ( 0 ) ;
549
+
550
+ act ( ( ) => {
551
+ root . render ( < Component /> ) ;
552
+ } ) ;
553
+ jest . runAllTimers ( ) ;
554
+ expect ( onBeforeBlurWithin ) . toHaveBeenCalledTimes ( 0 ) ;
555
+ expect ( onAfterBlurWithin ) . toHaveBeenCalledTimes ( 0 ) ;
556
+
557
+ buttonRef . current . focus ( ) ;
558
+ suspend = false ;
559
+ act ( ( ) => {
560
+ root . render ( < Component /> ) ;
561
+ } ) ;
562
+ jest . runAllTimers ( ) ;
563
+ expect ( onBeforeBlurWithin ) . toHaveBeenCalledTimes ( 1 ) ;
564
+ expect ( onAfterBlurWithin ) . toHaveBeenCalledTimes ( 1 ) ;
565
+
566
+ resolve ( ) ;
461
567
} ) ;
462
568
} ) ;
463
569
0 commit comments