Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@
".*": "./scripts/jest/preprocessor.js"
},
"setupFiles": [
"./scripts/jest/setup.js",
"./scripts/jest/environment.js"
],
"setupTestFrameworkScriptFile": "./scripts/jest/test-framework-setup.js",
Expand Down
5 changes: 5 additions & 0 deletions packages/react-reconciler/src/ReactFiberErrorLogger.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ export function logCapturedError(capturedError: CapturedError): void {
}

const error = (capturedError.error: any);
const suppressLogging = error && error.suppressReactErrorLogging;
if (suppressLogging) {
return;
}

if (__DEV__) {
const {
componentName,
Expand Down
5 changes: 4 additions & 1 deletion packages/react-reconciler/src/ReactFiberScheduler.js
Original file line number Diff line number Diff line change
Expand Up @@ -1004,7 +1004,10 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
} catch (e) {
// Prevent cycle if logCapturedError() throws.
// A cycle may still occur if logCapturedError renders a component that throws.
console.error(e);
const suppressLogging = e && e.suppressReactErrorLogging;
if (!suppressLogging) {
console.error(e);
}
}

// If we're in the commit phase, defer scheduling an update on the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ describe('ReactIncrementalErrorHandling', () => {
ReactNoop = require('react-noop-renderer');
});

afterEach(() => {
jest.unmock('../ReactFiberErrorLogger');
});

function div(...children) {
children = children.map(c => (typeof c === 'string' ? {text: c} : c));
return {type: 'div', children, prop: undefined};
Expand Down Expand Up @@ -952,49 +956,22 @@ describe('ReactIncrementalErrorHandling', () => {
});

describe('ReactFiberErrorLogger', () => {
function initReactFiberErrorLoggerMock(mock) {
jest.resetModules();
if (mock) {
jest.mock('../ReactFiberErrorLogger');
} else {
jest.unmock('../ReactFiberErrorLogger');
}
React = require('react');
ReactNoop = require('react-noop-renderer');
}

beforeEach(() => {
// Assert that we're mocking this file by default.
// If this ever changes, we'll need to update this test suite anyway.
expect(
require('../ReactFiberErrorLogger').logCapturedError._isMockFunction,
).toBe(true);
});

afterEach(() => {
// Restore the default (we verified it was being mocked in beforeEach).
jest.mock('../ReactFiberErrorLogger');
});

function normalizeCodeLocInfo(str) {
return str && str.replace(/\(at .+?:\d+\)/g, '(at **)');
}

it('should log errors that occur during the begin phase', () => {
initReactFiberErrorLoggerMock(false);
// Intentionally spy in production too.
// TODO: It is confusing how these tests correctly "see" console.error()s
// from exceptions becase they called initReactFiberErrorLoggerMock(false)
// and thus unmocked the error logger. It is globally mocked in all other
// tests. We did this to silence warnings from intentionally caught errors
// in tests. But perhaps we should instead make a pass through all tests,
// intentionally assert console.error() is always called for uncaught
// exceptions, and then remove the global mock of ReactFiberErrorLogger.
spyOn(console, 'error');

class ErrorThrowingComponent extends React.Component {
componentWillMount() {
throw Error('componentWillMount error');
const error = new Error('componentWillMount error');
// Note: it's `true` on the Error prototype our test environment.
// That lets us avoid asserting on warnings for each expected error.
// Here we intentionally shadow it to test logging, like in real apps.
error.suppressReactErrorLogging = undefined;
throw error;
}
render() {
return <div />;
Expand Down Expand Up @@ -1030,19 +1007,17 @@ describe('ReactIncrementalErrorHandling', () => {
});

it('should log errors that occur during the commit phase', () => {
initReactFiberErrorLoggerMock(false);
// TODO: It is confusing how these tests correctly "see" console.error()s
// from exceptions becase they called initReactFiberErrorLoggerMock(false)
// and thus unmocked the error logger. It is globally mocked in all other
// tests. We did this to silence warnings from intentionally caught errors
// in tests. But perhaps we should instead make a pass through all tests,
// intentionally assert console.error() is always called for uncaught
// exceptions, and then remove the global mock of ReactFiberErrorLogger.
// Intentionally spy in production too.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

spyOn(console, 'error');

class ErrorThrowingComponent extends React.Component {
componentDidMount() {
throw Error('componentDidMount error');
const error = new Error('componentDidMount error');
// Note: it's `true` on the Error prototype our test environment.
// That lets us avoid asserting on warnings for each expected error.
// Here we intentionally shadow it to test logging, like in real apps.
error.suppressReactErrorLogging = undefined;
throw error;
}
render() {
return <div />;
Expand Down Expand Up @@ -1078,18 +1053,16 @@ describe('ReactIncrementalErrorHandling', () => {
});

it('should ignore errors thrown in log method to prevent cycle', () => {
initReactFiberErrorLoggerMock(true);
jest.resetModules();
jest.mock('../ReactFiberErrorLogger');
React = require('react');
ReactNoop = require('react-noop-renderer');
// Intentionally spy in production too.
// Note: we're seeing console.error() even though ReactFiberErrorLogger is
// mocked because we're currently testing the console.error() call inside
// ReactFiberScheduler (for cycles). It doesn't get affected by mocking.
// TODO: mocking is confusing and we should simplify this and remove
// special cases.
spyOn(console, 'error');

class ErrorThrowingComponent extends React.Component {
render() {
throw Error('render error');
throw new Error('render error');
}
}

Expand All @@ -1099,7 +1072,12 @@ describe('ReactIncrementalErrorHandling', () => {
ReactFiberErrorLogger.logCapturedError.mockImplementation(
capturedError => {
logCapturedErrorCalls.push(capturedError);
throw Error('logCapturedError error');
const error = new Error('logCapturedError error');
// Note: it's `true` on the Error prototype our test environment.
// That lets us avoid asserting on warnings for each expected error.
// Here we intentionally shadow it to test logging, like in real apps.
error.suppressReactErrorLogging = undefined;
throw error;
},
);

Expand All @@ -1124,14 +1102,7 @@ describe('ReactIncrementalErrorHandling', () => {
});

it('should relay info about error boundary and retry attempts if applicable', () => {
initReactFiberErrorLoggerMock(false);
// TODO: It is confusing how these tests correctly "see" console.error()s
// from exceptions becase they called initReactFiberErrorLoggerMock(false)
// and thus unmocked the error logger. It is globally mocked in all other
// tests. We did this to silence warnings from intentionally caught errors
// in tests. But perhaps we should instead make a pass through all tests,
// intentionally assert console.error() is always called for uncaught
// exceptions, and then remove the global mock of ReactFiberErrorLogger.
// Intentionally spy in production too.
spyOn(console, 'error');

class ParentComponent extends React.Component {
Expand All @@ -1155,7 +1126,12 @@ describe('ReactIncrementalErrorHandling', () => {

class ErrorThrowingComponent extends React.Component {
componentDidMount() {
throw Error('componentDidMount error');
const error = new Error('componentDidMount error');
// Note: it's `true` on the Error prototype our test environment.
// That lets us avoid asserting on warnings for each expected error.
// Here we intentionally shadow it to test logging, like in real apps.
error.suppressReactErrorLogging = undefined;
throw error;
}
render() {
renderAttempts++;
Expand Down
9 changes: 9 additions & 0 deletions scripts/jest/environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,12 @@ global.requestIdleCallback = function(callback) {
global.cancelIdleCallback = function(callbackID) {
clearTimeout(callbackID);
};

// By default React console.error()'s any errors, caught or uncaught.
// However it is annoying to assert that a warning fired each time
// we assert that there is an exception in our tests. This lets us
// opt out of extra console error reporting for most tests except
// for the few that specifically test the logging by shadowing this
// property. In real apps, it would usually not be defined at all.
Error.prototype.suppressReactErrorLogging = true;
DOMException.prototype.suppressReactErrorLogging = true;
12 changes: 0 additions & 12 deletions scripts/jest/setup.js

This file was deleted.