@@ -19,20 +19,23 @@ global.setImmediate = cb => cb();
1919
2020let act ;
2121let clientExports ;
22+ let clientModuleError ;
2223let webpackMap ;
2324let Stream ;
2425let React ;
2526let ReactDOMClient ;
2627let ReactServerDOMWriter ;
2728let ReactServerDOMReader ;
2829let Suspense ;
30+ let ErrorBoundary ;
2931
3032describe ( 'ReactFlightDOM' , ( ) => {
3133 beforeEach ( ( ) => {
3234 jest . resetModules ( ) ;
3335 act = require ( 'jest-react' ) . act ;
3436 const WebpackMock = require ( './utils/WebpackMock' ) ;
3537 clientExports = WebpackMock . clientExports ;
38+ clientModuleError = WebpackMock . clientModuleError ;
3639 webpackMap = WebpackMock . webpackMap ;
3740
3841 Stream = require ( 'stream' ) ;
@@ -41,6 +44,22 @@ describe('ReactFlightDOM', () => {
4144 ReactDOMClient = require ( 'react-dom/client' ) ;
4245 ReactServerDOMWriter = require ( 'react-server-dom-webpack/writer.node.server' ) ;
4346 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+ } ;
4463 } ) ;
4564
4665 function getTestStream ( ) {
@@ -319,22 +338,6 @@ describe('ReactFlightDOM', () => {
319338
320339 // Client Components
321340
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-
338341 function MyErrorBoundary ( { children} ) {
339342 return (
340343 < ErrorBoundary fallback = { e => < p > { e . message } </ p > } >
@@ -605,22 +608,6 @@ describe('ReactFlightDOM', () => {
605608 it ( 'should be able to complete after aborting and throw the reason client-side' , async ( ) => {
606609 const reportedErrors = [ ] ;
607610
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-
624611 const { writable, readable} = getTestStream ( ) ;
625612 const { pipe, abort} = ReactServerDOMWriter . renderToPipeableStream (
626613 < div >
@@ -661,4 +648,159 @@ describe('ReactFlightDOM', () => {
661648
662649 expect ( reportedErrors ) . toEqual ( [ 'for reasons' ] ) ;
663650 } ) ;
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+ } ) ;
664806} ) ;
0 commit comments