@@ -35,6 +35,9 @@ describe('InspectedElement', () => {
35 let legacyRender ;
35 let legacyRender ;
36 let testRendererInstance ;
36 let testRendererInstance ;
37
37
38+ let ErrorBoundary ;
39+ let errorBoundaryInstance ;
40+
38 beforeEach ( ( ) => {
41 beforeEach ( ( ) => {
39 utils = require ( './utils' ) ;
42 utils = require ( './utils' ) ;
40 utils . beforeEachProfiling ( ) ;
43 utils . beforeEachProfiling ( ) ;
@@ -69,6 +72,23 @@ describe('InspectedElement', () => {
69 testRendererInstance = TestRenderer . create ( null , {
72 testRendererInstance = TestRenderer . create ( null , {
70 unstable_isConcurrent : true ,
73 unstable_isConcurrent : true ,
71 } ) ;
74 } ) ;
75+
76+ errorBoundaryInstance = null ;
77+
78+ ErrorBoundary = class extends React . Component {
79+ state = { error : null } ;
80+ componentDidCatch ( error ) {
81+ this . setState ( { error} ) ;
82+ }
83+ render ( ) {
84+ errorBoundaryInstance = this ;
85+
86+ if ( this . state . error ) {
87+ return null ;
88+ }
89+ return this . props . children ;
90+ }
91+ } ;
72 } ) ;
92 } ) ;
73
93
74 afterEach ( ( ) => {
94 afterEach ( ( ) => {
@@ -109,7 +129,11 @@ describe('InspectedElement', () => {
109
129
110 function noop ( ) { }
130 function noop ( ) { }
111
131
112- async function inspectElementAtIndex ( index , useCustomHook = noop ) {
132+ async function inspectElementAtIndex (
133+ index ,
134+ useCustomHook = noop ,
135+ shouldThrow = false ,
136+ ) {
113 let didFinish = false ;
137 let didFinish = false ;
114 let inspectedElement = null ;
138 let inspectedElement = null ;
115
139
@@ -124,17 +148,21 @@ describe('InspectedElement', () => {
124
148
125 await utils . actAsync ( ( ) => {
149 await utils . actAsync ( ( ) => {
126 testRendererInstance . update (
150 testRendererInstance . update (
151+ < ErrorBoundary >
127 < Contexts
152 < Contexts
128 defaultSelectedElementID = { id }
153 defaultSelectedElementID = { id }
129 defaultSelectedElementIndex = { index } >
154 defaultSelectedElementIndex = { index } >
130 < React . Suspense fallback = { null } >
155 < React . Suspense fallback = { null } >
131 < Suspender id = { id } index = { index } />
156 < Suspender id = { id } index = { index } />
132 </ React . Suspense >
157 </ React . Suspense >
133- </ Contexts > ,
158+ </ Contexts >
159+ </ ErrorBoundary > ,
134 ) ;
160 ) ;
135 } , false ) ;
161 } , false ) ;
136
162
163+ if ( ! shouldThrow ) {
137 expect ( didFinish ) . toBe ( true ) ;
164 expect ( didFinish ) . toBe ( true ) ;
165+ }
138
166
139 return inspectedElement ;
167 return inspectedElement ;
140 }
168 }
@@ -2069,6 +2097,37 @@ describe('InspectedElement', () => {
2069 expect ( inspectedElement . rootType ) . toMatchInlineSnapshot ( `"createRoot()"` ) ;
2097 expect ( inspectedElement . rootType ) . toMatchInlineSnapshot ( `"createRoot()"` ) ;
2070 } ) ;
2098 } ) ;
2071
2099
2100+ it ( 'should gracefully surface backend errors on the frontend rather than timing out' , async ( ) => {
2101+ spyOn ( console , 'error' ) ;
2102+
2103+ let shouldThrow = false ;
2104+
2105+ const Example = ( ) => {
2106+ const [ count ] = React . useState ( 0 ) ;
2107+
2108+ if ( shouldThrow ) {
2109+ throw Error ( 'Expected' ) ;
2110+ } else {
2111+ return count ;
2112+ }
2113+ } ;
2114+
2115+ await utils . actAsync ( ( ) => {
2116+ const container = document . createElement ( 'div' ) ;
2117+ ReactDOM . createRoot ( container ) . render ( < Example /> ) ;
2118+ } , false ) ;
2119+
2120+ shouldThrow = true ;
2121+
2122+ const value = await inspectElementAtIndex ( 0 , noop , true ) ;
2123+
2124+ expect ( value ) . toBe ( null ) ;
2125+
2126+ const error = errorBoundaryInstance . state . error ;
2127+ expect ( error . message ) . toBe ( 'Expected' ) ;
2128+ expect ( error . stack ) . toContain ( 'inspectHooksOfFiber' ) ;
2129+ } ) ;
2130+
2072 describe ( '$r' , ( ) => {
2131 describe ( '$r' , ( ) => {
2073 it ( 'should support function components' , async ( ) => {
2132 it ( 'should support function components' , async ( ) => {
2074 const Example = ( ) => {
2133 const Example = ( ) => {
@@ -2656,7 +2715,7 @@ describe('InspectedElement', () => {
2656
2715
2657 describe ( 'error boundary' , ( ) => {
2716 describe ( 'error boundary' , ( ) => {
2658 it ( 'can toggle error' , async ( ) => {
2717 it ( 'can toggle error' , async ( ) => {
2659- class ErrorBoundary extends React . Component < any > {
2718+ class LocalErrorBoundary extends React . Component < any > {
2660 state = { hasError : false } ;
2719 state = { hasError : false } ;
2661 static getDerivedStateFromError ( error ) {
2720 static getDerivedStateFromError ( error ) {
2662 return { hasError : true } ;
2721 return { hasError : true } ;
@@ -2666,13 +2725,14 @@ describe('InspectedElement', () => {
2666 return hasError ? 'has-error' : this . props . children ;
2725 return hasError ? 'has-error' : this . props . children ;
2667 }
2726 }
2668 }
2727 }
2728+
2669 const Example = ( ) = > 'example' ;
2729 const Example = ( ) = > 'example' ;
2670
2730
2671 await utils . actAsync ( ( ) =>
2731 await utils . actAsync ( ( ) =>
2672 legacyRender (
2732 legacyRender (
2673- < ErrorBoundary >
2733+ < LocalErrorBoundary >
2674 < Example />
2734 < Example />
2675- </ ErrorBoundary > ,
2735+ </ LocalErrorBoundary > ,
2676 document . createElement ( 'div' ) ,
2736 document . createElement ( 'div' ) ,
2677 ) ,
2737 ) ,
2678 ) ;
2738 ) ;
0 commit comments