Add ReactFiberErrorDialog from React + tests (#25671)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/25671 Moves the RN-specific `ReactFiberErrorDialog` implementation from React to RN for easier iteration. Also adds new unit tests. This current change is additive, so we're compatible with the current React renderer which still uses `ExceptionsManager` and not the file added here. After the corresponding React update we can remove `ExceptionsManager` from the RN private interface entirely. Reviewed By: cpojer Differential Revision: D16278938 fbshipit-source-id: 0c2c0c3e65e524e079730ae3b0cc23e0c0bdc5fd
This commit is contained in:
Родитель
bcc482e655
Коммит
a9cab21010
|
@ -0,0 +1,58 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
* @flow strict-local
|
||||
*/
|
||||
|
||||
export type CapturedError = {
|
||||
+componentName: ?string,
|
||||
+componentStack: string,
|
||||
+error: mixed,
|
||||
+errorBoundary: ?{},
|
||||
+errorBoundaryFound: boolean,
|
||||
+errorBoundaryName: string | null,
|
||||
+willRetry: boolean,
|
||||
};
|
||||
|
||||
import {handleException} from './ExceptionsManager';
|
||||
|
||||
/**
|
||||
* Intercept lifecycle errors and ensure they are shown with the correct stack
|
||||
* trace within the native redbox component.
|
||||
*/
|
||||
export function showErrorDialog(capturedError: CapturedError): boolean {
|
||||
const {componentStack, error} = capturedError;
|
||||
|
||||
let errorToHandle: Error;
|
||||
|
||||
// Typically Errors are thrown but eg strings or null can be thrown as well.
|
||||
if (error instanceof Error) {
|
||||
const {message, name} = error;
|
||||
|
||||
const summary = message ? `${name}: ${message}` : name;
|
||||
|
||||
errorToHandle = error;
|
||||
|
||||
try {
|
||||
errorToHandle.message = `${summary}\n\nThis error is located at:${componentStack}`;
|
||||
} catch (e) {}
|
||||
} else if (typeof error === 'string') {
|
||||
errorToHandle = new Error(
|
||||
`${error}\n\nThis error is located at:${componentStack}`,
|
||||
);
|
||||
} else {
|
||||
errorToHandle = new Error(`Unspecified error at:${componentStack}`);
|
||||
}
|
||||
|
||||
handleException(errorToHandle, false);
|
||||
|
||||
// Return false here to prevent ReactFiberErrorLogger default behavior of
|
||||
// logging error details to console.error. Calls to console.error are
|
||||
// automatically routed to the native redbox controller, which we've already
|
||||
// done above by calling ExceptionsManager.
|
||||
return false;
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
* @emails oncall+react_native
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const capturedErrorDefaults = {
|
||||
componentName: 'A',
|
||||
componentStack: '\n in A\n in B\n in C',
|
||||
errorBoundary: null,
|
||||
errorBoundaryFound: false,
|
||||
errorBoundaryName: null,
|
||||
willRetry: false,
|
||||
};
|
||||
|
||||
describe('ReactFiberErrorDialog', () => {
|
||||
let ReactFiberErrorDialog, ExceptionsManager;
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
jest.mock('../ExceptionsManager', () => {
|
||||
return {
|
||||
handleException: jest.fn(),
|
||||
};
|
||||
});
|
||||
ReactFiberErrorDialog = require('../ReactFiberErrorDialog');
|
||||
ExceptionsManager = require('../ExceptionsManager');
|
||||
});
|
||||
|
||||
describe('showErrorDialog', () => {
|
||||
test('forwards error instance to handleException', () => {
|
||||
const error = new ReferenceError('Some error happened');
|
||||
error.someCustomProp = 42;
|
||||
// Copy all the data we care about before any possible mutation.
|
||||
const {name, stack, message, someCustomProp} = error;
|
||||
|
||||
const logToConsole = ReactFiberErrorDialog.showErrorDialog({
|
||||
...capturedErrorDefaults,
|
||||
error,
|
||||
});
|
||||
|
||||
expect(ExceptionsManager.handleException.mock.calls.length).toBe(1);
|
||||
const errorArg = ExceptionsManager.handleException.mock.calls[0][0];
|
||||
const isFatalArg = ExceptionsManager.handleException.mock.calls[0][1];
|
||||
// We intentionally don't test whether errorArg === error, because this
|
||||
// implementation detail might change. Instead, we test that they are
|
||||
// functionally equivalent.
|
||||
expect(errorArg).toBeInstanceOf(ReferenceError);
|
||||
expect(errorArg).toHaveProperty('name', name);
|
||||
expect(errorArg).toHaveProperty('stack', stack);
|
||||
expect(errorArg).toHaveProperty('someCustomProp', someCustomProp);
|
||||
expect(errorArg).toHaveProperty(
|
||||
'message',
|
||||
'ReferenceError: ' +
|
||||
message +
|
||||
'\n\n' +
|
||||
'This error is located at:' +
|
||||
capturedErrorDefaults.componentStack,
|
||||
);
|
||||
expect(isFatalArg).toBe(false);
|
||||
expect(logToConsole).toBe(false);
|
||||
});
|
||||
|
||||
test('wraps string in an Error and sends to handleException', () => {
|
||||
const message = 'Some error happened';
|
||||
|
||||
const logToConsole = ReactFiberErrorDialog.showErrorDialog({
|
||||
...capturedErrorDefaults,
|
||||
error: message,
|
||||
});
|
||||
|
||||
expect(ExceptionsManager.handleException.mock.calls.length).toBe(1);
|
||||
const errorArg = ExceptionsManager.handleException.mock.calls[0][0];
|
||||
const isFatalArg = ExceptionsManager.handleException.mock.calls[0][1];
|
||||
expect(errorArg).toBeInstanceOf(Error);
|
||||
expect(errorArg).toHaveProperty(
|
||||
'message',
|
||||
message +
|
||||
'\n\n' +
|
||||
'This error is located at:' +
|
||||
capturedErrorDefaults.componentStack,
|
||||
);
|
||||
expect(isFatalArg).toBe(false);
|
||||
expect(logToConsole).toBe(false);
|
||||
});
|
||||
|
||||
test('reports "Unspecified error" if error is null', () => {
|
||||
const logToConsole = ReactFiberErrorDialog.showErrorDialog({
|
||||
...capturedErrorDefaults,
|
||||
error: null,
|
||||
});
|
||||
|
||||
expect(ExceptionsManager.handleException.mock.calls.length).toBe(1);
|
||||
const errorArg = ExceptionsManager.handleException.mock.calls[0][0];
|
||||
const isFatalArg = ExceptionsManager.handleException.mock.calls[0][1];
|
||||
expect(errorArg).toBeInstanceOf(Error);
|
||||
expect(errorArg).toHaveProperty(
|
||||
'message',
|
||||
'Unspecified error at:' + capturedErrorDefaults.componentStack,
|
||||
);
|
||||
expect(isFatalArg).toBe(false);
|
||||
expect(logToConsole).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -40,4 +40,7 @@ module.exports = {
|
|||
get flattenStyle() {
|
||||
return require('../StyleSheet/flattenStyle');
|
||||
},
|
||||
get ReactFiberErrorDialog() {
|
||||
return require('../Core/ReactFiberErrorDialog');
|
||||
},
|
||||
};
|
||||
|
|
Загрузка…
Ссылка в новой задаче