@@ -30,6 +30,31 @@ import { MemoryRouter, Route } from 'react-router-dom';
3030use ( sinonChai ) ;
3131use ( chaiAsPromised ) ;
3232
33+ class TestErrorBoundary extends React . Component <
34+ { children : React . ReactNode } ,
35+ { error : Error | null }
36+ > {
37+ constructor ( props : { children : React . ReactNode } ) {
38+ super ( props ) ;
39+ this . state = { error : null } ;
40+ }
41+
42+ static getDerivedStateFromError ( error : Error ) : { error : Error } {
43+ return { error } ;
44+ }
45+
46+ componentDidCatch ( error : Error ) : void {
47+ // We can also log here if needed
48+ }
49+
50+ render ( ) : React . ReactNode {
51+ if ( this . state . error ) {
52+ return null ;
53+ }
54+ return this . props . children ;
55+ }
56+ }
57+
3358describe ( 'FirebaseCrashlytics' , ( ) => {
3459 let getCrashlyticsStub : sinon . SinonStub ;
3560 let recordErrorStub : sinon . SinonStub ;
@@ -82,18 +107,21 @@ describe('FirebaseCrashlytics', () => {
82107 throw new Error ( 'render error' ) ;
83108 } ;
84109
85- it ( 'captures render errors in routes' , ( ) => {
110+ it ( 'captures render errors in routes and re-throws them ' , ( ) => {
86111 // Stub console.error to avoid React error logging in test output
87112 const consoleErrorStub = stub ( console , 'error' ) ;
88113
89- render (
90- < MemoryRouter >
91- < CrashlyticsRoutes firebaseApp = { fakeApp } >
92- < Route path = "/" element = { < ThrowingComponent /> } />
93- </ CrashlyticsRoutes >
94- </ MemoryRouter >
114+ const { container } = render (
115+ < TestErrorBoundary >
116+ < MemoryRouter >
117+ < CrashlyticsRoutes firebaseApp = { fakeApp } >
118+ < Route path = "/" element = { < ThrowingComponent /> } />
119+ </ CrashlyticsRoutes >
120+ </ MemoryRouter >
121+ </ TestErrorBoundary >
95122 ) ;
96123
124+ // Verify the error was recorded
97125 expect ( getCrashlyticsStub ) . to . have . been . calledWith ( fakeApp ) ;
98126 expect ( recordErrorStub ) . to . have . been . calledWith (
99127 fakeCrashlytics ,
@@ -103,18 +131,24 @@ describe('FirebaseCrashlytics', () => {
103131 sinon . match ( { route : '/' } )
104132 ) ;
105133
134+ // Verify the error was caught by our TestErrorBoundary (meaning it was re-thrown)
135+ // Since TestErrorBoundary returns null on error, container should be empty
136+ expect ( container . firstChild ) . to . be . null ;
137+
106138 consoleErrorStub . restore ( ) ;
107139 } ) ;
108140
109- it ( 'captures parameterized route pattern' , ( ) => {
141+ it ( 'captures parameterized route pattern and re-throws ' , ( ) => {
110142 const consoleErrorStub = stub ( console , 'error' ) ;
111143
112- render (
113- < MemoryRouter initialEntries = { [ '/users/123' ] } >
114- < CrashlyticsRoutes firebaseApp = { fakeApp } >
115- < Route path = "/users/:id" element = { < ThrowingComponent /> } />
116- </ CrashlyticsRoutes >
117- </ MemoryRouter >
144+ const { container } = render (
145+ < TestErrorBoundary >
146+ < MemoryRouter initialEntries = { [ '/users/123' ] } >
147+ < CrashlyticsRoutes firebaseApp = { fakeApp } >
148+ < Route path = "/users/:id" element = { < ThrowingComponent /> } />
149+ </ CrashlyticsRoutes >
150+ </ MemoryRouter >
151+ </ TestErrorBoundary >
118152 ) ;
119153
120154 expect ( getCrashlyticsStub ) . to . have . been . calledWith ( fakeApp ) ;
@@ -126,6 +160,9 @@ describe('FirebaseCrashlytics', () => {
126160 sinon . match ( { route : '/users/:id' } )
127161 ) ;
128162
163+ // Verify re-throw
164+ expect ( container . firstChild ) . to . be . null ;
165+
129166 consoleErrorStub . restore ( ) ;
130167 } ) ;
131168 } ) ;
0 commit comments