366
}
367
});
368
369
+ it('does not show a fallback if mismatch is after suspending', async () => {
370
+ // We can't use the toErrorDev helper here because this is async.
371
+ const originalConsoleError = console.error;
372
+ const mockError = jest.fn();
373
+ console.error = (...args) => {
374
+ mockError(...args.map(normalizeCodeLocInfo));
375
+ };
376
+ let client = false;
377
+ let suspend = false;
378
+ let resolve;
379
+ const promise = new Promise(resolvePromise => {
380
+ resolve = () => {
381
+ suspend = false;
382
+ resolvePromise();
383
+ };
384
+ });
385
+ function Child() {
386
+ if (suspend) {
387
+ Scheduler.log('Suspend');
388
+ throw promise;
389
+ } else {
390
+ Scheduler.log('Hello');
391
+ return 'Hello';
392
+ }
393
+ }
394
+ function Component({shouldMismatch}) {
395
+ Scheduler.log('Component');
396
+ if (shouldMismatch && client) {
397
+ return <article>Mismatch</article>;
398
+ }
399
+ return <div>Component</div>;
400
+ }
401
+ function Fallback() {
402
+ Scheduler.log('Fallback');
403
+ return 'Loading...';
404
+ }
405
+ function App() {
406
+ return (
407
+ <Suspense fallback={<Fallback />}>
408
+ <Child />
409
+ <Component shouldMismatch={true} />
410
+ </Suspense>
411
+ );
412
+ }
413
+ try {
414
+ const finalHTML = ReactDOMServer.renderToString(<App />);
415
+ const container = document.createElement('section');
416
+ container.innerHTML = finalHTML;
417
+ assertLog(['Hello', 'Component']);
418
+
419
+ expect(container.innerHTML).toBe(
420
+ '<!--$-->Hello<div>Component</div><!--/$-->',
421
+ );
422
+
423
+ suspend = true;
424
+ client = true;
425
+
426
+ ReactDOMClient.hydrateRoot(container, <App />, {
427
+ onRecoverableError(error) {
428
+ Scheduler.log(error.message);
429
+ },
430
+ });
431
+ await waitForAll(['Suspend']);
432
+ jest.runAllTimers();
433
+
434
+ // !! Unchanged, continue showing server content while suspended.
435
+ expect(container.innerHTML).toBe(
436
+ '<!--$-->Hello<div>Component</div><!--/$-->',
437
+ );
438
+
439
+ suspend = false;
440
+ resolve();
441
+ await promise;
442
+ await waitForAll([
443
+ // first pass, mismatches at end
444
+ 'Hello',
445
+ 'Component',
446
+ 'Hello',
447
+ 'Component',
448
+ 'Hydration failed because the initial UI does not match what was rendered on the server.',
449
+ 'There was an error while hydrating this Suspense boundary. Switched to client rendering.',
450
+ ]);
451
+ jest.runAllTimers();
452
+
453
+ // Client rendered - suspense comment nodes removed.
454
+ expect(container.innerHTML).toBe('Hello<article>Mismatch</article>');
455
+ if (__DEV__) {
456
+ const secondToLastCall =
457
+ mockError.mock.calls[mockError.mock.calls.length - 2];
458
+ expect(secondToLastCall).toEqual([
459
+ 'Warning: Expected server HTML to contain a matching <%s> in <%s>.%s',
460
+ 'article',
461
+ 'section',
462
+ '\n' +
463
+ ' in article (at **)\n' +
464
+ ' in Component (at **)\n' +
465
+ ' in Suspense (at **)\n' +
466
+ ' in App (at **)',
467
+ ]);
468
+ }
469
+ } finally {
470
+ console.error = originalConsoleError;
471
+ }
472
+ });
473
+
474
+ it('does not show a fallback if mismatch is child of suspended component', async () => {
475
+ // We can't use the toErrorDev helper here because this is async.
476
+ const originalConsoleError = console.error;
477
+ const mockError = jest.fn();
478
+ console.error = (...args) => {
479
+ mockError(...args.map(normalizeCodeLocInfo));
480
+ };
481
+ let client = false;
482
+ let suspend = false;
483
+ let resolve;
484
+ const promise = new Promise(resolvePromise => {
485
+ resolve = () => {
486
+ suspend = false;
487
+ resolvePromise();
488
+ };
489
+ });
490
+ function Child({children}) {
491
+ if (suspend) {
492
+ Scheduler.log('Suspend');
493
+ throw promise;
494
+ } else {
495
+ Scheduler.log('Hello');
496
+ return <div>{children}</div>;
497
+ }
498
+ }
499
+ function Component({shouldMismatch}) {
500
+ Scheduler.log('Component');
501
+ if (shouldMismatch && client) {
502
+ return <article>Mismatch</article>;
503
+ }
504
+ return <div>Component</div>;
505
+ }
506
+ function Fallback() {
507
+ Scheduler.log('Fallback');
508
+ return 'Loading...';
509
+ }
510
+ function App() {
511
+ return (
512
+ <Suspense fallback={<Fallback />}>
513
+ <Child>
514
+ <Component shouldMismatch={true} />
515
+ </Child>
516
+ </Suspense>
517
+ );
518
+ }
519
+ try {
520
+ const finalHTML = ReactDOMServer.renderToString(<App />);
521
+ const container = document.createElement('section');
522
+ container.innerHTML = finalHTML;
523
+ assertLog(['Hello', 'Component']);
524
+
525
+ expect(container.innerHTML).toBe(
526
+ '<!--$--><div><div>Component</div></div><!--/$-->',
527
+ );
528
+
529
+ suspend = true;
530
+ client = true;
531
+
532
+ ReactDOMClient.hydrateRoot(container, <App />, {
533
+ onRecoverableError(error) {
534
+ Scheduler.log(error.message);
535
+ },
536
+ });
537
+ await waitForAll(['Suspend']);
538
+ jest.runAllTimers();
539
+
540
+ // !! Unchanged, continue showing server content while suspended.
541
+ expect(container.innerHTML).toBe(
542
+ '<!--$--><div><div>Component</div></div><!--/$-->',
543
+ );
544
+
545
+ suspend = false;
546
+ resolve();
547
+ await promise;
548
+ await waitForAll([
549
+ // first pass, mismatches at end
550
+ 'Hello',
551
+ 'Component',
552
+ 'Hello',
553
+ 'Component',
554
+ 'Hydration failed because the initial UI does not match what was rendered on the server.',
555
+ 'There was an error while hydrating this Suspense boundary. Switched to client rendering.',
556
+ ]);
557
+ jest.runAllTimers();
558
+
559
+ // Client rendered - suspense comment nodes removed
560
+ expect(container.innerHTML).toBe(
561
+ '<div><article>Mismatch</article></div>',
562
+ );
563
+ if (__DEV__) {
564
+ const secondToLastCall =
565
+ mockError.mock.calls[mockError.mock.calls.length - 2];
566
+ expect(secondToLastCall).toEqual([
567
+ 'Warning: Expected server HTML to contain a matching <%s> in <%s>.%s',
568
+ 'article',
569
+ 'div',
570
+ '\n' +
571
+ ' in article (at **)\n' +
572
+ ' in Component (at **)\n' +
573
+ ' in div (at **)\n' +
574
+ ' in Child (at **)\n' +
575
+ ' in Suspense (at **)\n' +
576
+ ' in App (at **)',
577
+ ]);
578
+ }
579
+ } finally {
580
+ console.error = originalConsoleError;
581
+ }
582
+ });
583
+
584
+ it('does not show a fallback if mismatch is parent and first child suspends', async () => {
585
+ // We can't use the toErrorDev helper here because this is async.
586
+ const originalConsoleError = console.error;
587
+ const mockError = jest.fn();
588
+ console.error = (...args) => {
589
+ mockError(...args.map(normalizeCodeLocInfo));
590
+ };
591
+ let client = false;
592
+ let suspend = false;
593
+ let resolve;
594
+ const promise = new Promise(resolvePromise => {
595
+ resolve = () => {
596
+ suspend = false;
597
+ resolvePromise();
598
+ };
599
+ });
600
+ function Child({children}) {
601
+ if (suspend) {
602
+ Scheduler.log('Suspend');
603
+ throw promise;
604
+ } else {
605
+ Scheduler.log('Hello');
606
+ return <div>{children}</div>;
607
+ }
608
+ }
609
+ function Component({shouldMismatch, children}) {
610
+ Scheduler.log('Component');
611
+ if (shouldMismatch && client) {
612
+ return (
613
+ <div>
614
+ {children}
615
+ <article>Mismatch</article>
616
+ </div>
617
+ );
618
+ }
619
+ return (
620
+ <div>
621
+ {children}
622
+ <div>Component</div>
623
+ </div>
624
+ );
625
+ }
626
+ function Fallback() {
627
+ Scheduler.log('Fallback');
628
+ return 'Loading...';
629
+ }
630
+ function App() {
631
+ return (
632
+ <Suspense fallback={<Fallback />}>
633
+ <Component shouldMismatch={true}>
634
+ <Child />
635
+ </Component>
636
+ </Suspense>
637
+ );
638
+ }
639
+ try {
640
+ const finalHTML = ReactDOMServer.renderToString(<App />);
641
+ const container = document.createElement('section');
642
+ container.innerHTML = finalHTML;
643
+ assertLog(['Component', 'Hello']);
644
+
645
+ expect(container.innerHTML).toBe(
646
+ '<!--$--><div><div></div><div>Component</div></div><!--/$-->',
647
+ );
648
+
649
+ suspend = true;
650
+ client = true;
651
+
652
+ ReactDOMClient.hydrateRoot(container, <App />, {
653
+ onRecoverableError(error) {
654
+ Scheduler.log(error.message);
655
+ },
656
+ });
657
+ await waitForAll(['Component', 'Suspend']);
658
+ jest.runAllTimers();
659
+
660
+ // !! Unchanged, continue showing server content while suspended.
661
+ expect(container.innerHTML).toBe(
662
+ '<!--$--><div><div></div><div>Component</div></div><!--/$-->',
663
+ );
664
+
665
+ suspend = false;
666
+ resolve();
667
+ await promise;
668
+ await waitForAll([
669
+ // first pass, mismatches at end
670
+ 'Component',
671
+ 'Hello',
672
+ 'Component',
673
+ 'Hello',
674
+ 'Hydration failed because the initial UI does not match what was rendered on the server.',
675
+ 'There was an error while hydrating this Suspense boundary. Switched to client rendering.',
676
+ ]);
677
+ jest.runAllTimers();
678
+
679
+ // Client rendered - suspense comment nodes removed
680
+ expect(container.innerHTML).toBe(
681
+ '<div><div></div><article>Mismatch</article></div>',
682
+ );
683
+ if (__DEV__) {
684
+ const secondToLastCall =
685
+ mockError.mock.calls[mockError.mock.calls.length - 2];
686
+ expect(secondToLastCall).toEqual([
687
+ 'Warning: Expected server HTML to contain a matching <%s> in <%s>.%s',
688
+ 'article',
689
+ 'div',
690
+ '\n' +
691
+ ' in article (at **)\n' +
692
+ ' in div (at **)\n' +
693
+ ' in Component (at **)\n' +
694
+ ' in Suspense (at **)\n' +
695
+ ' in App (at **)',
696
+ ]);
697
+ }
698
+ } finally {
699
+ console.error = originalConsoleError;
700
+ }
701
+ });
702
+
703
+ it('does show a fallback if mismatch is parent and second child suspends', async () => {
704
+ // We can't use the toErrorDev helper here because this is async.
705
+ const originalConsoleError = console.error;
706
+ const mockError = jest.fn();
707
+ console.error = (...args) => {
708
+ mockError(...args.map(normalizeCodeLocInfo));
709
+ };
710
+ let client = false;
711
+ let suspend = false;
712
+ let resolve;
713
+ const promise = new Promise(resolvePromise => {
714
+ resolve = () => {
715
+ suspend = false;
716
+ resolvePromise();
717
+ };
718
+ });
719
+ function Child({children}) {
720
+ if (suspend) {
721
+ Scheduler.log('Suspend');
722
+ throw promise;
723
+ } else {
724
+ Scheduler.log('Hello');
725
+ return <div>{children}</div>;
726
+ }
727
+ }
728
+ function Component({shouldMismatch, children}) {
729
+ Scheduler.log('Component');
730
+ if (shouldMismatch && client) {
731
+ return (
732
+ <div>
733
+ <article>Mismatch</article>
734
+ {children}
735
+ </div>
736
+ );
737
+ }
738
+ return (
739
+ <div>
740
+ <div>Component</div>
741
+ {children}
742
+ </div>
743
+ );
744
+ }
745
+ function Fallback() {
746
+ Scheduler.log('Fallback');
747
+ return 'Loading...';
748
+ }
749
+ function App() {
750
+ return (
751
+ <Suspense fallback={<Fallback />}>
752
+ <Component shouldMismatch={true}>
753
+ <Child />
754
+ </Component>
755
+ </Suspense>
756
+ );
757
+ }
758
+ try {
759
+ const finalHTML = ReactDOMServer.renderToString(<App />);
760
+ const container = document.createElement('section');
761
+ container.innerHTML = finalHTML;
762
+ assertLog(['Component', 'Hello']);
763
+
764
+ expect(container.innerHTML).toBe(
765
+ '<!--$--><div><div>Component</div><div></div></div><!--/$-->',
766
+ );
767
+
768
+ suspend = true;
769
+ client = true;
770
+
771
+ ReactDOMClient.hydrateRoot(container, <App />, {
772
+ onRecoverableError(error) {
773
+ Scheduler.log(error.message);
774
+ },
775
+ });
776
+ await waitForAll([
777
+ 'Component',
778
+ 'Component',
779
+ 'Suspend',
780
+ 'Fallback',
781
+ 'Hydration failed because the initial UI does not match what was rendered on the server.',
782
+ 'There was an error while hydrating this Suspense boundary. Switched to client rendering.',
783
+ ]);
784
+ jest.runAllTimers();
785
+
786
+ // !! Client switches to suspense fallback.
787
+ expect(container.innerHTML).toBe('Loading...');
788
+
789
+ suspend = false;
790
+ resolve();
791
+ await promise;
792
+ await waitForAll(['Component', 'Hello']);
793
+ jest.runAllTimers();
794
+
795
+ // Client rendered - suspense comment nodes removed
796
+ expect(container.innerHTML).toBe(
797
+ '<div><article>Mismatch</article><div></div></div>',
798
+ );
799
+ if (__DEV__) {
800
+ const secondToLastCall =
801
+ mockError.mock.calls[mockError.mock.calls.length - 2];
802
+ expect(secondToLastCall).toEqual([
803
+ 'Warning: Expected server HTML to contain a matching <%s> in <%s>.%s',
804
+ 'article',
805
+ 'div',
806
+ '\n' +
807
+ ' in article (at **)\n' +
808
+ ' in div (at **)\n' +
809
+ ' in Component (at **)\n' +
810
+ ' in Suspense (at **)\n' +
811
+ ' in App (at **)',
812
+ ]);
813
+ }
814
+ } finally {
815
+ console.error = originalConsoleError;
816
+ }
817
+ });
818
+
819
+ it('does show a fallback if mismatch is in parent element only', async () => {
820
+ // We can't use the toErrorDev helper here because this is async.
821
+ const originalConsoleError = console.error;
822
+ const mockError = jest.fn();
823
+ console.error = (...args) => {
824
+ mockError(...args.map(normalizeCodeLocInfo));
825
+ };
826
+ let client = false;
827
+ let suspend = false;
828
+ let resolve;
829
+ const promise = new Promise(resolvePromise => {
830
+ resolve = () => {
831
+ suspend = false;
832
+ resolvePromise();
833
+ };
834
+ });
835
+ function Child({children}) {
836
+ if (suspend) {
837
+ Scheduler.log('Suspend');
838
+ throw promise;
839
+ } else {
840
+ Scheduler.log('Hello');
841
+ return <div>{children}</div>;
842
+ }
843
+ }
844
+ function Component({shouldMismatch, children}) {
845
+ Scheduler.log('Component');
846
+ if (shouldMismatch && client) {
847
+ return <article>{children}</article>;
848
+ }
849
+ return <div>{children}</div>;
850
+ }
851
+ function Fallback() {
852
+ Scheduler.log('Fallback');
853
+ return 'Loading...';
854
+ }
855
+ function App() {
856
+ return (
857
+ <Suspense fallback={<Fallback />}>
858
+ <Component shouldMismatch={true}>
859
+ <Child />
860
+ </Component>
861
+ </Suspense>
862
+ );
863
+ }
864
+ try {
865
+ const finalHTML = ReactDOMServer.renderToString(<App />);
866
+ const container = document.createElement('section');
867
+ container.innerHTML = finalHTML;
868
+ assertLog(['Component', 'Hello']);
869
+
870
+ expect(container.innerHTML).toBe(
871
+ '<!--$--><div><div></div></div><!--/$-->',
872
+ );
873
+
874
+ suspend = true;
875
+ client = true;
876
+
877
+ ReactDOMClient.hydrateRoot(container, <App />, {
878
+ onRecoverableError(error) {
879
+ Scheduler.log(error.message);
880
+ },
881
+ });
882
+ await waitForAll([
883
+ 'Component',
884
+ 'Component',
885
+ 'Suspend',
886
+ 'Fallback',
887
+ 'Hydration failed because the initial UI does not match what was rendered on the server.',
888
+ 'There was an error while hydrating this Suspense boundary. Switched to client rendering.',
889
+ ]);
890
+ jest.runAllTimers();
891
+
892
+ // !! Client switches to suspense fallback.
893
+ expect(container.innerHTML).toBe('Loading...');
894
+
895
+ suspend = false;
896
+ resolve();
897
+ await promise;
898
+ await waitForAll(['Component', 'Hello']);
899
+ jest.runAllTimers();
900
+
901
+ // Client rendered - suspense comment nodes removed
902
+ expect(container.innerHTML).toBe('<article><div></div></article>');
903
+ if (__DEV__) {
904
+ const secondToLastCall =
905
+ mockError.mock.calls[mockError.mock.calls.length - 2];
906
+ expect(secondToLastCall).toEqual([
907
+ 'Warning: Expected server HTML to contain a matching <%s> in <%s>.%s',
908
+ 'article',
909
+ 'section',
910
+ '\n' +
911
+ ' in article (at **)\n' +
912
+ ' in Component (at **)\n' +
913
+ ' in Suspense (at **)\n' +
914
+ ' in App (at **)',
915
+ ]);
916
+ }
917
+ } finally {
918
+ console.error = originalConsoleError;
919
+ }
920
+ });
921
+
922
+ it('does show a fallback if mismatch is before suspending', async () => {
923
+ // We can't use the toErrorDev helper here because this is async.
924
+ const originalConsoleError = console.error;
925
+ const mockError = jest.fn();
926
+ console.error = (...args) => {
927
+ mockError(...args.map(normalizeCodeLocInfo));
928
+ };
929
+ let client = false;
930
+ let suspend = false;
931
+ let resolve;
932
+ const promise = new Promise(resolvePromise => {
933
+ resolve = () => {
934
+ suspend = false;
935
+ resolvePromise();
936
+ };
937
+ });
938
+ function Child() {
939
+ if (suspend) {
940
+ Scheduler.log('Suspend');
941
+ throw promise;
942
+ } else {
943
+ Scheduler.log('Hello');
944
+ return 'Hello';
945
+ }
946
+ }
947
+ function Component({shouldMismatch}) {
948
+ Scheduler.log('Component');
949
+ if (shouldMismatch && client) {
950
+ return <article>Mismatch</article>;
951
+ }
952
+ return <div>Component</div>;
953
+ }
954
+ function Fallback() {
955
+ Scheduler.log('Fallback');
956
+ return 'Loading...';
957
+ }
958
+ function App() {
959
+ return (
960
+ <Suspense fallback={<Fallback />}>
961
+ <Component shouldMismatch={true} />
962
+ <Child />
963
+ </Suspense>
964
+ );
965
+ }
966
+ try {
967
+ const finalHTML = ReactDOMServer.renderToString(<App />);
968
+ const container = document.createElement('section');
969
+ container.innerHTML = finalHTML;
970
+ assertLog(['Component', 'Hello']);
971
+
972
+ expect(container.innerHTML).toBe(
973
+ '<!--$--><div>Component</div>Hello<!--/$-->',
974
+ );
975
+
976
+ suspend = true;
977
+ client = true;
978
+
979
+ ReactDOMClient.hydrateRoot(container, <App />, {
980
+ onRecoverableError(error) {
981
+ Scheduler.log(error.message);
982
+ },
983
+ });
984
+ await waitForAll([
985
+ 'Component',
986
+ 'Component',
987
+ 'Suspend',
988
+ 'Fallback',
989
+ 'Hydration failed because the initial UI does not match what was rendered on the server.',
990
+ 'There was an error while hydrating this Suspense boundary. Switched to client rendering.',
991
+ ]);
992
+ jest.runAllTimers();
993
+
994
+ // !! Client switches to suspense fallback.
995
+ expect(container.innerHTML).toBe('Loading...');
996
+
997
+ suspend = false;
998
+ resolve();
999
+ await promise;
1000
+ await waitForAll([
1001
+ // first pass, mismatches at end
1002
+ 'Component',
1003
+ 'Hello',
1004
+ ]);
1005
+ jest.runAllTimers();
1006
+
1007
+ // Client rendered - suspense comment nodes removed
1008
+ expect(container.innerHTML).toBe('<article>Mismatch</article>Hello');
1009
+ if (__DEV__) {
1010
+ const secondToLastCall =
1011
+ mockError.mock.calls[mockError.mock.calls.length - 2];
1012
+ expect(secondToLastCall).toEqual([
1013
+ 'Warning: Expected server HTML to contain a matching <%s> in <%s>.%s',
1014
+ 'article',
1015
+ 'section',
1016
+ '\n' +
1017
+ ' in article (at **)\n' +
1018
+ ' in Component (at **)\n' +
1019
+ ' in Suspense (at **)\n' +
1020
+ ' in App (at **)',
1021
+ ]);
1022
+ }
1023
+ } finally {
1024
+ console.error = originalConsoleError;
1025
+ }
1026
+ });
1027
+
1028
+ it('does show a fallback if mismatch is before suspending in a child', async () => {
1029
+ // We can't use the toErrorDev helper here because this is async.
1030
+ const originalConsoleError = console.error;
1031
+ const mockError = jest.fn();
1032
+ console.error = (...args) => {
1033
+ mockError(...args.map(normalizeCodeLocInfo));
1034
+ };
1035
+ let client = false;
1036
+ let suspend = false;
1037
+ let resolve;
1038
+ const promise = new Promise(resolvePromise => {
1039
+ resolve = () => {
1040
+ suspend = false;
1041
+ resolvePromise();
1042
+ };
1043
+ });
1044
+ function Child() {
1045
+ if (suspend) {
1046
+ Scheduler.log('Suspend');
1047
+ throw promise;
1048
+ } else {
1049
+ Scheduler.log('Hello');
1050
+ return 'Hello';
1051
+ }
1052
+ }
1053
+ function Component({shouldMismatch}) {
1054
+ Scheduler.log('Component');
1055
+ if (shouldMismatch && client) {
1056
+ return <article>Mismatch</article>;
1057
+ }
1058
+ return <div>Component</div>;
1059
+ }
1060
+ function Fallback() {
1061
+ Scheduler.log('Fallback');
1062
+ return 'Loading...';
1063
+ }
1064
+ function App() {
1065
+ return (
1066
+ <Suspense fallback={<Fallback />}>
1067
+ <Component shouldMismatch={true} />
1068
+ <div>
1069
+ <Child />
1070
+ </div>
1071
+ </Suspense>
1072
+ );
1073
+ }
1074
+ try {
1075
+ const finalHTML = ReactDOMServer.renderToString(<App />);
1076
+ const container = document.createElement('section');
1077
+ container.innerHTML = finalHTML;
1078
+ assertLog(['Component', 'Hello']);
1079
+
1080
+ expect(container.innerHTML).toBe(
1081
+ '<!--$--><div>Component</div><div>Hello</div><!--/$-->',
1082
+ );
1083
+
1084
+ suspend = true;
1085
+ client = true;
1086
+
1087
+ ReactDOMClient.hydrateRoot(container, <App />, {
1088
+ onRecoverableError(error) {
1089
+ Scheduler.log(error.message);
1090
+ },
1091
+ });
1092
+ await waitForAll([
1093
+ 'Component',
1094
+ 'Component',
1095
+ 'Suspend',
1096
+ 'Fallback',
1097
+ 'Hydration failed because the initial UI does not match what was rendered on the server.',
1098
+ 'There was an error while hydrating this Suspense boundary. Switched to client rendering.',
1099
+ ]);
1100
+ jest.runAllTimers();
1101
+
1102
+ // !! Client switches to suspense fallback.
1103
+ expect(container.innerHTML).toBe('Loading...');
1104
+
1105
+ suspend = false;
1106
+ resolve();
1107
+ await promise;
1108
+ await waitForAll([
1109
+ // first pass, mismatches at end
1110
+ 'Component',
1111
+ 'Hello',
1112
+ ]);
1113
+ jest.runAllTimers();
1114
+
1115
+ // Client rendered - suspense comment nodes removed.
1116
+ expect(container.innerHTML).toBe(
1117
+ '<article>Mismatch</article><div>Hello</div>',
1118
+ );
1119
+ if (__DEV__) {
1120
+ const secondToLastCall =
1121
+ mockError.mock.calls[mockError.mock.calls.length - 2];
1122
+ expect(secondToLastCall).toEqual([
1123
+ 'Warning: Expected server HTML to contain a matching <%s> in <%s>.%s',
1124
+ 'article',
1125
+ 'section',
1126
+ '\n' +
1127
+ ' in article (at **)\n' +
1128
+ ' in Component (at **)\n' +
1129
+ ' in Suspense (at **)\n' +
1130
+ ' in App (at **)',
1131
+ ]);
1132
+ }
1133
+ } finally {
1134
+ console.error = originalConsoleError;
1135
+ }
1136
+ });
1137
+
1138
it('calls the hydration callbacks after hydration or deletion', async () => {
1139
let suspend = false;
1140
let resolve;