@@ -19,20 +19,23 @@ global.setImmediate = cb => cb();
19
19
20
20
let act ;
21
21
let clientExports ;
22
+ let clientModuleError ;
22
23
let webpackMap ;
23
24
let Stream ;
24
25
let React ;
25
26
let ReactDOMClient ;
26
27
let ReactServerDOMWriter ;
27
28
let ReactServerDOMReader ;
28
29
let Suspense ;
30
+ let ErrorBoundary ;
29
31
30
32
describe ( 'ReactFlightDOM' , ( ) => {
31
33
beforeEach ( ( ) => {
32
34
jest . resetModules ( ) ;
33
35
act = require ( 'jest-react' ) . act ;
34
36
const WebpackMock = require ( './utils/WebpackMock' ) ;
35
37
clientExports = WebpackMock . clientExports ;
38
+ clientModuleError = WebpackMock . clientModuleError ;
36
39
webpackMap = WebpackMock . webpackMap ;
37
40
38
41
Stream = require ( 'stream' ) ;
@@ -41,6 +44,22 @@ describe('ReactFlightDOM', () => {
41
44
ReactDOMClient = require ( 'react-dom/client' ) ;
42
45
ReactServerDOMWriter = require ( 'react-server-dom-webpack/writer.node.server' ) ;
43
46
ReactServerDOMReader = require ( 'react-server-dom-webpack' ) ;
47
+
48
+ ErrorBoundary = class extends React . Component {
49
+ state = { hasError : false , error : null } ;
50
+ static getDerivedStateFromError ( error ) {
51
+ return {
52
+ hasError : true ,
53
+ error,
54
+ } ;
55
+ }
56
+ render ( ) {
57
+ if ( this . state . hasError ) {
58
+ return this . props . fallback ( this . state . error ) ;
59
+ }
60
+ return this . props . children ;
61
+ }
62
+ } ;
44
63
} ) ;
45
64
46
65
function getTestStream ( ) {
@@ -319,22 +338,6 @@ describe('ReactFlightDOM', () => {
319
338
320
339
// Client Components
321
340
322
- class ErrorBoundary extends React . Component {
323
- state = { hasError : false , error : null } ;
324
- static getDerivedStateFromError ( error ) {
325
- return {
326
- hasError : true ,
327
- error,
328
- } ;
329
- }
330
- render ( ) {
331
- if ( this . state . hasError ) {
332
- return this . props . fallback ( this . state . error ) ;
333
- }
334
- return this . props . children ;
335
- }
336
- }
337
-
338
341
function MyErrorBoundary ( { children} ) {
339
342
return (
340
343
< ErrorBoundary fallback = { e => < p > { e . message } </ p > } >
@@ -605,22 +608,6 @@ describe('ReactFlightDOM', () => {
605
608
it ( 'should be able to complete after aborting and throw the reason client-side' , async ( ) => {
606
609
const reportedErrors = [ ] ;
607
610
608
- class ErrorBoundary extends React . Component {
609
- state = { hasError : false , error : null } ;
610
- static getDerivedStateFromError ( error ) {
611
- return {
612
- hasError : true ,
613
- error,
614
- } ;
615
- }
616
- render ( ) {
617
- if ( this . state . hasError ) {
618
- return this . props . fallback ( this . state . error ) ;
619
- }
620
- return this . props . children ;
621
- }
622
- }
623
-
624
611
const { writable, readable} = getTestStream ( ) ;
625
612
const { pipe, abort} = ReactServerDOMWriter . renderToPipeableStream (
626
613
< div >
@@ -661,4 +648,159 @@ describe('ReactFlightDOM', () => {
661
648
662
649
expect ( reportedErrors ) . toEqual ( [ 'for reasons' ] ) ;
663
650
} ) ;
651
+
652
+ it ( 'should be able to recover from a direct reference erroring client-side' , async ( ) => {
653
+ const reportedErrors = [ ] ;
654
+
655
+ const ClientComponent = clientExports ( function ( { prop} ) {
656
+ return 'This should never render' ;
657
+ } ) ;
658
+
659
+ const ClientReference = clientModuleError ( new Error ( 'module init error' ) ) ;
660
+
661
+ const { writable, readable} = getTestStream ( ) ;
662
+ const { pipe} = ReactServerDOMWriter . renderToPipeableStream (
663
+ < div >
664
+ < ClientComponent prop = { ClientReference } />
665
+ </ div > ,
666
+ webpackMap ,
667
+ {
668
+ onError ( x ) {
669
+ reportedErrors . push ( x ) ;
670
+ } ,
671
+ } ,
672
+ ) ;
673
+ pipe ( writable ) ;
674
+ const response = ReactServerDOMReader . createFromReadableStream ( readable ) ;
675
+
676
+ const container = document . createElement ( 'div' ) ;
677
+ const root = ReactDOMClient . createRoot ( container ) ;
678
+
679
+ function App ( { res} ) {
680
+ return res . readRoot ( ) ;
681
+ }
682
+
683
+ await act ( async ( ) => {
684
+ root . render (
685
+ < ErrorBoundary fallback = { e => < p > { e . message } </ p > } >
686
+ < Suspense fallback = { < p > (loading)</ p > } >
687
+ < App res = { response } />
688
+ </ Suspense >
689
+ </ ErrorBoundary > ,
690
+ ) ;
691
+ } ) ;
692
+ expect ( container . innerHTML ) . toBe ( '<p>module init error</p>' ) ;
693
+
694
+ expect ( reportedErrors ) . toEqual ( [ ] ) ;
695
+ } ) ;
696
+
697
+ it ( 'should be able to recover from a direct reference erroring client-side async' , async ( ) => {
698
+ const reportedErrors = [ ] ;
699
+
700
+ const ClientComponent = clientExports ( function ( { prop} ) {
701
+ return 'This should never render' ;
702
+ } ) ;
703
+
704
+ let rejectPromise ;
705
+ const ClientReference = await clientExports (
706
+ new Promise ( ( resolve , reject ) => {
707
+ rejectPromise = reject ;
708
+ } ) ,
709
+ ) ;
710
+
711
+ const { writable, readable} = getTestStream ( ) ;
712
+ const { pipe} = ReactServerDOMWriter . renderToPipeableStream (
713
+ < div >
714
+ < ClientComponent prop = { ClientReference } />
715
+ </ div > ,
716
+ webpackMap ,
717
+ {
718
+ onError ( x ) {
719
+ reportedErrors . push ( x ) ;
720
+ } ,
721
+ } ,
722
+ ) ;
723
+ pipe ( writable ) ;
724
+ const response = ReactServerDOMReader . createFromReadableStream ( readable ) ;
725
+
726
+ const container = document . createElement ( 'div' ) ;
727
+ const root = ReactDOMClient . createRoot ( container ) ;
728
+
729
+ function App ( { res} ) {
730
+ return res . readRoot ( ) ;
731
+ }
732
+
733
+ await act ( async ( ) => {
734
+ root . render (
735
+ < ErrorBoundary fallback = { e => < p > { e . message } </ p > } >
736
+ < Suspense fallback = { < p > (loading)</ p > } >
737
+ < App res = { response } />
738
+ </ Suspense >
739
+ </ ErrorBoundary > ,
740
+ ) ;
741
+ } ) ;
742
+
743
+ expect ( container . innerHTML ) . toBe ( '<p>(loading)</p>' ) ;
744
+
745
+ await act ( async ( ) => {
746
+ rejectPromise ( new Error ( 'async module init error' ) ) ;
747
+ } ) ;
748
+
749
+ expect ( container . innerHTML ) . toBe ( '<p>async module init error</p>' ) ;
750
+
751
+ expect ( reportedErrors ) . toEqual ( [ ] ) ;
752
+ } ) ;
753
+
754
+ it ( 'should be able to recover from a direct reference erroring server-side' , async ( ) => {
755
+ const reportedErrors = [ ] ;
756
+
757
+ const ClientComponent = clientExports ( function ( { prop} ) {
758
+ return 'This should never render' ;
759
+ } ) ;
760
+
761
+ // We simulate a bug in the Webpack bundler which causes an error on the server.
762
+ for ( const id in webpackMap ) {
763
+ Object . defineProperty ( webpackMap , id , {
764
+ get : ( ) => {
765
+ throw new Error ( 'bug in the bundler' ) ;
766
+ } ,
767
+ } ) ;
768
+ }
769
+
770
+ const { writable, readable} = getTestStream ( ) ;
771
+ const { pipe} = ReactServerDOMWriter . renderToPipeableStream (
772
+ < div >
773
+ < ClientComponent />
774
+ </ div > ,
775
+ webpackMap ,
776
+ {
777
+ onError ( x ) {
778
+ reportedErrors . push ( x ) ;
779
+ } ,
780
+ } ,
781
+ ) ;
782
+ pipe ( writable ) ;
783
+
784
+ const response = ReactServerDOMReader . createFromReadableStream ( readable ) ;
785
+
786
+ const container = document . createElement ( 'div' ) ;
787
+ const root = ReactDOMClient . createRoot ( container ) ;
788
+
789
+ function App ( { res} ) {
790
+ return res . readRoot ( ) ;
791
+ }
792
+
793
+ await act ( async ( ) => {
794
+ root . render (
795
+ < ErrorBoundary fallback = { e => < p > { e . message } </ p > } >
796
+ < Suspense fallback = { < p > (loading)</ p > } >
797
+ < App res = { response } />
798
+ </ Suspense >
799
+ </ ErrorBoundary > ,
800
+ ) ;
801
+ } ) ;
802
+ expect ( container . innerHTML ) . toBe ( '<p>bug in the bundler</p>' ) ;
803
+
804
+ expect ( reportedErrors ) . toEqual ( [ ] ) ;
805
+ } ) ;
664
806
} ) ;
0 commit comments