@@ -14,6 +14,7 @@ let React;
14
14
let ReactDOM ;
15
15
let ReactDOMServer ;
16
16
let ReactTestUtils ;
17
+ let act ;
17
18
18
19
function getTestDocument ( markup ) {
19
20
const doc = document . implementation . createHTMLDocument ( '' ) ;
@@ -33,6 +34,7 @@ describe('ReactTestUtils', () => {
33
34
ReactDOM = require ( 'react-dom' ) ;
34
35
ReactDOMServer = require ( 'react-dom/server' ) ;
35
36
ReactTestUtils = require ( 'react-dom/test-utils' ) ;
37
+ act = ReactTestUtils . act ;
36
38
} ) ;
37
39
38
40
it ( 'Simulate should have locally attached media events' , ( ) => {
@@ -515,4 +517,168 @@ describe('ReactTestUtils', () => {
515
517
ReactTestUtils . renderIntoDocument ( < Component /> ) ;
516
518
expect ( mockArgs . length ) . toEqual ( 0 ) ;
517
519
} ) ;
520
+
521
+ it ( 'can use act to batch effects' , ( ) => {
522
+ function App ( props ) {
523
+ React . useEffect ( props . callback ) ;
524
+ return null ;
525
+ }
526
+ const container = document . createElement ( 'div' ) ;
527
+ document . body . appendChild ( container ) ;
528
+
529
+ try {
530
+ let called = false ;
531
+ act ( ( ) => {
532
+ ReactDOM . render (
533
+ < App
534
+ callback = { ( ) => {
535
+ called = true ;
536
+ } }
537
+ /> ,
538
+ container ,
539
+ ) ;
540
+ } ) ;
541
+
542
+ expect ( called ) . toBe ( true ) ;
543
+ } finally {
544
+ document . body . removeChild ( container ) ;
545
+ }
546
+ } ) ;
547
+
548
+ it ( 'flushes effects on every call' , ( ) => {
549
+ function App ( props ) {
550
+ let [ ctr , setCtr ] = React . useState ( 0 ) ;
551
+ React . useEffect ( ( ) => {
552
+ props . callback ( ctr ) ;
553
+ } ) ;
554
+ return (
555
+ < button id = "button" onClick = { ( ) => setCtr ( x => x + 1 ) } >
556
+ click me!
557
+ </ button >
558
+ ) ;
559
+ }
560
+
561
+ const container = document . createElement ( 'div' ) ;
562
+ document . body . appendChild ( container ) ;
563
+ let calledCtr = 0 ;
564
+ act ( ( ) => {
565
+ ReactDOM . render (
566
+ < App
567
+ callback = { val => {
568
+ calledCtr = val ;
569
+ } }
570
+ /> ,
571
+ container ,
572
+ ) ;
573
+ } ) ;
574
+ const button = document . getElementById ( 'button' ) ;
575
+ function click ( ) {
576
+ button . dispatchEvent ( new MouseEvent ( 'click' , { bubbles : true } ) ) ;
577
+ }
578
+
579
+ act ( ( ) => {
580
+ click ( ) ;
581
+ click ( ) ;
582
+ click ( ) ;
583
+ } ) ;
584
+ expect ( calledCtr ) . toBe ( 3 ) ;
585
+ act ( click ) ;
586
+ expect ( calledCtr ) . toBe ( 4 ) ;
587
+ act ( click ) ;
588
+ expect ( calledCtr ) . toBe ( 5 ) ;
589
+
590
+ document . body . removeChild ( container ) ;
591
+ } ) ;
592
+
593
+ it ( 'can use act to batch effects on updates too' , ( ) => {
594
+ function App ( ) {
595
+ let [ ctr , setCtr ] = React . useState ( 0 ) ;
596
+ return (
597
+ < button id = "button" onClick = { ( ) => setCtr ( x => x + 1 ) } >
598
+ { ctr }
599
+ </ button >
600
+ ) ;
601
+ }
602
+ const container = document . createElement ( 'div' ) ;
603
+ document . body . appendChild ( container ) ;
604
+ let button ;
605
+ act ( ( ) => {
606
+ ReactDOM . render ( < App /> , container ) ;
607
+ } ) ;
608
+ button = document . getElementById ( 'button' ) ;
609
+ expect ( button . innerHTML ) . toBe ( '0' ) ;
610
+ act ( ( ) => {
611
+ button . dispatchEvent ( new MouseEvent ( 'click' , { bubbles : true } ) ) ;
612
+ } ) ;
613
+ expect ( button . innerHTML ) . toBe ( '1' ) ;
614
+ document . body . removeChild ( container ) ;
615
+ } ) ;
616
+
617
+ it ( 'detects setState being called outside of act(...)' , ( ) => {
618
+ let setValueRef = null ;
619
+ function App ( ) {
620
+ let [ value , setValue ] = React . useState ( 0 ) ;
621
+ setValueRef = setValue ;
622
+ return (
623
+ < button id = "button" onClick = { ( ) => setValue ( 2 ) } >
624
+ { value }
625
+ </ button >
626
+ ) ;
627
+ }
628
+ const container = document . createElement ( 'div' ) ;
629
+ document . body . appendChild ( container ) ;
630
+ let button ;
631
+ act ( ( ) => {
632
+ ReactDOM . render ( < App /> , container ) ;
633
+ button = container . querySelector ( '#button' ) ;
634
+ button . dispatchEvent ( new MouseEvent ( 'click' , { bubbles : true } ) ) ;
635
+ } ) ;
636
+ expect ( button . innerHTML ) . toBe ( '2' ) ;
637
+ expect ( ( ) => setValueRef ( 1 ) ) . toWarnDev (
638
+ [ 'An update to App inside a test was not wrapped in act(...).' ] ,
639
+ { withoutStack : 1 } ,
640
+ ) ;
641
+ document . body . removeChild ( container ) ;
642
+ } ) ;
643
+
644
+ it ( 'lets a ticker update' , ( ) => {
645
+ function App ( ) {
646
+ let [ toggle , setToggle ] = React . useState ( 0 ) ;
647
+ React . useEffect ( ( ) => {
648
+ let timeout = setTimeout ( ( ) => {
649
+ setToggle ( 1 ) ;
650
+ } , 200 ) ;
651
+ return ( ) => clearTimeout ( timeout ) ;
652
+ } ) ;
653
+ return toggle ;
654
+ }
655
+ const container = document . createElement ( 'div' ) ;
656
+
657
+ act ( ( ) => {
658
+ act ( ( ) => {
659
+ ReactDOM . render ( < App /> , container ) ;
660
+ } ) ;
661
+ jest . advanceTimersByTime ( 250 ) ;
662
+ } ) ;
663
+
664
+ expect ( container . innerHTML ) . toBe ( '1' ) ;
665
+ } ) ;
666
+
667
+ it ( 'warns if you return a value inside act' , ( ) => {
668
+ expect ( ( ) => act ( ( ) => 123 ) ) . toWarnDev (
669
+ [
670
+ 'The callback passed to ReactTestUtils.act(...) function must not return anything.' ,
671
+ ] ,
672
+ { withoutStack : true } ,
673
+ ) ;
674
+ } ) ;
675
+
676
+ it ( 'warns if you try to await an .act call' , ( ) => {
677
+ expect ( act ( ( ) => { } ) . then ) . toWarnDev (
678
+ [
679
+ 'Do not await the result of calling ReactTestUtils.act(...), it is not a Promise.' ,
680
+ ] ,
681
+ { withoutStack : true } ,
682
+ ) ;
683
+ } ) ;
518
684
} ) ;
0 commit comments