Skip to content

Commit 5d39e4f

Browse files
committed
Devtools: add feature to trigger an error boundary
1 parent e16d61c commit 5d39e4f

24 files changed

+567
-5
lines changed

packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCache-test.js.snap

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,7 @@ Object {
719719
0,
720720
1,
721721
0,
722+
0,
722723
4,
723724
2,
724725
12000,
@@ -729,6 +730,7 @@ Object {
729730
2,
730731
2,
731732
3,
733+
0,
732734
4,
733735
4,
734736
0,
@@ -739,6 +741,7 @@ Object {
739741
2,
740742
2,
741743
4,
744+
0,
742745
4,
743746
5,
744747
1000,
@@ -749,6 +752,7 @@ Object {
749752
2,
750753
2,
751754
0,
755+
0,
752756
4,
753757
6,
754758
1000,
@@ -772,6 +776,7 @@ Object {
772776
2,
773777
1,
774778
2,
779+
0,
775780
4,
776781
7,
777782
2000,
@@ -1178,6 +1183,7 @@ Object {
11781183
0,
11791184
1,
11801185
0,
1186+
0,
11811187
4,
11821188
2,
11831189
11000,
@@ -1188,6 +1194,7 @@ Object {
11881194
2,
11891195
2,
11901196
3,
1197+
0,
11911198
4,
11921199
4,
11931200
0,
@@ -1198,6 +1205,7 @@ Object {
11981205
2,
11991206
2,
12001207
0,
1208+
0,
12011209
4,
12021210
5,
12031211
1000,
@@ -1221,6 +1229,7 @@ Object {
12211229
2,
12221230
1,
12231231
2,
1232+
0,
12241233
4,
12251234
6,
12261235
1000,
@@ -1256,6 +1265,7 @@ Object {
12561265
2,
12571266
1,
12581267
2,
1268+
0,
12591269
4,
12601270
7,
12611271
2000,
@@ -1455,6 +1465,7 @@ Object {
14551465
2,
14561466
1,
14571467
2,
1468+
0,
14581469
4,
14591470
12,
14601471
2000,
@@ -1647,6 +1658,7 @@ Object {
16471658
0,
16481659
1,
16491660
0,
1661+
0,
16501662
4,
16511663
14,
16521664
11000,
@@ -1657,6 +1669,7 @@ Object {
16571669
14,
16581670
2,
16591671
3,
1672+
0,
16601673
4,
16611674
16,
16621675
0,
@@ -1667,6 +1680,7 @@ Object {
16671680
14,
16681681
2,
16691682
0,
1683+
0,
16701684
4,
16711685
17,
16721686
1000,
@@ -2036,6 +2050,7 @@ Object {
20362050
2,
20372051
1,
20382052
2,
2053+
0,
20392054
4,
20402055
12,
20412056
2000,
@@ -2273,6 +2288,7 @@ Object {
22732288
0,
22742289
1,
22752290
0,
2291+
0,
22762292
4,
22772293
14,
22782294
11000,
@@ -2283,6 +2299,7 @@ Object {
22832299
14,
22842300
2,
22852301
3,
2302+
0,
22862303
4,
22872304
16,
22882305
0,
@@ -2293,6 +2310,7 @@ Object {
22932310
14,
22942311
2,
22952312
0,
2313+
0,
22962314
4,
22972315
17,
22982316
1000,
@@ -2472,6 +2490,7 @@ Object {
24722490
0,
24732491
1,
24742492
0,
2493+
0,
24752494
4,
24762495
2,
24772496
0,
@@ -2968,6 +2987,7 @@ Object {
29682987
0,
29692988
1,
29702989
0,
2990+
0,
29712991
4,
29722992
2,
29732993
0,
@@ -2978,6 +2998,7 @@ Object {
29782998
0,
29792999
2,
29803000
0,
3001+
0,
29813002
4,
29823003
3,
29833004
0,
@@ -4176,6 +4197,7 @@ Object {
41764197
0,
41774198
1,
41784199
0,
4200+
0,
41794201
4,
41804202
2,
41814203
0,
@@ -4186,6 +4208,7 @@ Object {
41864208
2,
41874209
2,
41884210
0,
4211+
0,
41894212
4,
41904213
3,
41914214
0,
@@ -4196,6 +4219,7 @@ Object {
41964219
2,
41974220
3,
41984221
0,
4222+
0,
41994223
4,
42004224
4,
42014225
0,
@@ -4206,6 +4230,7 @@ Object {
42064230
4,
42074231
4,
42084232
0,
4233+
0,
42094234
4,
42104235
5,
42114236
0,
@@ -4216,6 +4241,7 @@ Object {
42164241
2,
42174242
5,
42184243
0,
4244+
0,
42194245
4,
42204246
6,
42214247
0,
@@ -4226,6 +4252,7 @@ Object {
42264252
6,
42274253
4,
42284254
0,
4255+
0,
42294256
4,
42304257
7,
42314258
0,

packages/react-devtools-shared/src/__tests__/inspectedElement-test.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2380,4 +2380,62 @@ describe('InspectedElement', () => {
23802380
`);
23812381
});
23822382
});
2383+
2384+
describe('error boundary', () => {
2385+
it('can toggle error', async () => {
2386+
class ErrorBoundary extends React.Component<any> {
2387+
state = {hasError: false};
2388+
static getDerivedStateFromError(error) {
2389+
return {hasError: true};
2390+
}
2391+
render() {
2392+
const {hasError} = this.state;
2393+
return hasError ? 'has-error' : this.props.children;
2394+
}
2395+
}
2396+
const Example = () => 'example';
2397+
2398+
await utils.actAsync(() =>
2399+
ReactDOM.render(
2400+
<ErrorBoundary>
2401+
<Example />
2402+
</ErrorBoundary>,
2403+
document.createElement('div'),
2404+
),
2405+
);
2406+
2407+
const errorBoundaryID = ((store.getElementIDAtIndex(0): any): number);
2408+
const toggleError = async forceError => {
2409+
await withErrorsOrWarningsIgnored(['ErrorBoundary'], async () => {
2410+
await utils.actAsync(() => {
2411+
bridge.send('overrideError', {
2412+
id: errorBoundaryID,
2413+
rendererID: store.getRendererIDForElement(errorBoundaryID),
2414+
forceError,
2415+
});
2416+
});
2417+
});
2418+
2419+
TestUtilsAct(() => {
2420+
jest.runOnlyPendingTimers();
2421+
});
2422+
};
2423+
2424+
let inspectedElement = await inspectElementAtIndex(1);
2425+
expect(inspectedElement.canToggleError).toBe(true);
2426+
expect(inspectedElement.isErrored).toBe(false);
2427+
2428+
await toggleError(true);
2429+
2430+
inspectedElement = await inspectElementAtIndex(0);
2431+
expect(inspectedElement.canToggleError).toBe(true);
2432+
expect(inspectedElement.isErrored).toBe(true);
2433+
2434+
await toggleError(false);
2435+
2436+
inspectedElement = await inspectElementAtIndex(0);
2437+
expect(inspectedElement.canToggleError).toBe(true);
2438+
expect(inspectedElement.isErrored).toBe(false);
2439+
});
2440+
});
23832441
});

packages/react-devtools-shared/src/__tests__/inspectedElementSerializer.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export function test(maybeInspectedElement) {
1414
hasOwnProperty('canEditFunctionProps') &&
1515
hasOwnProperty('canEditHooks') &&
1616
hasOwnProperty('canToggleSuspense') &&
17+
hasOwnProperty('canToggleError') &&
1718
hasOwnProperty('canViewSource')
1819
);
1920
}

packages/react-devtools-shared/src/__tests__/store-test.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -976,6 +976,41 @@ describe('Store', () => {
976976
`);
977977
});
978978

979+
it('should detect if element is an error boundary', async () => {
980+
class ErrorBoundary extends React.Component {
981+
state = {hasError: false};
982+
static getDerivedStateFromError(error) {
983+
return {hasError: true};
984+
}
985+
render() {
986+
return this.props.children;
987+
}
988+
}
989+
class ClassComponent extends React.Component {
990+
render() {
991+
return null;
992+
}
993+
}
994+
const FunctionalComponent = () => null;
995+
996+
const App = () => (
997+
<React.Fragment>
998+
<ErrorBoundary>
999+
<ClassComponent />
1000+
<FunctionalComponent />
1001+
</ErrorBoundary>
1002+
</React.Fragment>
1003+
);
1004+
1005+
const container = document.createElement('div');
1006+
1007+
act(() => ReactDOM.render(<App />, container));
1008+
1009+
expect(store.getElementAtIndex(1).isErrorBoundary).toBe(true);
1010+
expect(store.getElementAtIndex(2).isErrorBoundary).toBe(false);
1011+
expect(store.getElementAtIndex(3).isErrorBoundary).toBe(false);
1012+
});
1013+
9791014
describe('Lazy', () => {
9801015
async function fakeImport(result) {
9811016
return {default: result};

packages/react-devtools-shared/src/backend/agent.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,12 @@ type OverrideValueAtPathParams = {|
122122
value: any,
123123
|};
124124

125+
type OverrideErrorParams = {|
126+
id: number,
127+
rendererID: number,
128+
forceError: boolean,
129+
|};
130+
125131
type OverrideSuspenseParams = {|
126132
id: number,
127133
rendererID: number,
@@ -183,6 +189,7 @@ export default class Agent extends EventEmitter<{|
183189
bridge.addListener('getOwnersList', this.getOwnersList);
184190
bridge.addListener('inspectElement', this.inspectElement);
185191
bridge.addListener('logElementToConsole', this.logElementToConsole);
192+
bridge.addListener('overrideError', this.overrideError);
186193
bridge.addListener('overrideSuspense', this.overrideSuspense);
187194
bridge.addListener('overrideValueAtPath', this.overrideValueAtPath);
188195
bridge.addListener('reloadAndProfile', this.reloadAndProfile);
@@ -381,6 +388,15 @@ export default class Agent extends EventEmitter<{|
381388
}
382389
};
383390

391+
overrideError = ({id, rendererID, forceError}: OverrideErrorParams) => {
392+
const renderer = this._rendererInterfaces[rendererID];
393+
if (renderer == null) {
394+
console.warn(`Invalid renderer id "${rendererID}" for element "${id}"`);
395+
} else {
396+
renderer.overrideError(id, forceError);
397+
}
398+
};
399+
384400
overrideSuspense = ({
385401
id,
386402
rendererID,

0 commit comments

Comments
 (0)