Files
react/src/renderers/shared/utils/__tests__/ReactErrorUtils-test.js
T
Andrew Clark 4fcc25a229 Support throwing null (#10213)
* Support throwing null

In JavaScript, you can throw values of any type, not just errors. That
includes null. We currently rely on null checks to determine if a user-
provided function has thrown. This refactors our error handling code to
keep track of an explicit boolean flag instead.

* Add DOM fixture test case for break on exception behavior

* preventDefault error events during feature test

We call invokeGuardedCallbackDev at startup as part of a feature test.
But we don't want those errors to log to the console.

* Add throwing null test case

* Use ReactFeatureFlags instead of ReactDOMFeatureFlags

React ART uses this, too.

* Non-errors in error logger

If a non-error is thrown, we'll coerce the value to a string and use
that as the message.
2017-07-21 15:34:41 -07:00

204 lines
5.5 KiB
JavaScript

/**
* Copyright 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @emails react-core
*/
'use strict';
var ReactErrorUtils;
describe('ReactErrorUtils', () => {
beforeEach(() => {
// TODO: can we express this test with only public API?
ReactErrorUtils = require('ReactErrorUtils');
});
// Run tests in both DEV and production
describe(
'invokeGuardedCallback (development)',
invokeGuardedCallbackTests.bind(null, 'development'),
);
describe('invokeGuardedCallback (production)', () => {
let oldProcess;
beforeEach(() => {
__DEV__ = false;
oldProcess = process;
global.process = {
env: Object.assign({}, process.env, {NODE_ENV: 'production'}),
};
jest.resetModules();
ReactErrorUtils = require('ReactErrorUtils');
});
afterEach(() => {
__DEV__ = true;
global.process = oldProcess;
});
invokeGuardedCallbackTests('production');
});
function invokeGuardedCallbackTests(environment) {
it(`it should rethrow caught errors (${environment})`, () => {
var err = new Error('foo');
var callback = function() {
throw err;
};
ReactErrorUtils.invokeGuardedCallbackAndCatchFirstError(
'foo',
callback,
null,
);
expect(ReactErrorUtils.hasCaughtError()).toBe(false);
expect(() => ReactErrorUtils.rethrowCaughtError()).toThrow(err);
});
it(`should call the callback the passed arguments (${environment})`, () => {
var callback = jest.fn();
ReactErrorUtils.invokeGuardedCallback(
'foo',
callback,
null,
'arg1',
'arg2',
);
expect(callback).toBeCalledWith('arg1', 'arg2');
});
it(`should call the callback with the provided context (${environment})`, () => {
var context = {didCall: false};
ReactErrorUtils.invokeGuardedCallback(
'foo',
function() {
this.didCall = true;
},
context,
);
expect(context.didCall).toBe(true);
});
it(`should catch errors (${environment})`, () => {
const error = new Error();
const returnValue = ReactErrorUtils.invokeGuardedCallback(
'foo',
function() {
throw error;
},
null,
'arg1',
'arg2',
);
expect(returnValue).toBe(undefined);
expect(ReactErrorUtils.hasCaughtError()).toBe(true);
expect(ReactErrorUtils.clearCaughtError()).toBe(error);
});
it(`should return false from clearCaughtError if no error was thrown (${environment})`, () => {
var callback = jest.fn();
ReactErrorUtils.invokeGuardedCallback('foo', callback, null);
expect(ReactErrorUtils.hasCaughtError()).toBe(false);
expect(ReactErrorUtils.clearCaughtError).toThrow('no error');
});
it(`can nest with same debug name (${environment})`, () => {
const err1 = new Error();
let err2;
const err3 = new Error();
let err4;
ReactErrorUtils.invokeGuardedCallback(
'foo',
function() {
ReactErrorUtils.invokeGuardedCallback(
'foo',
function() {
throw err1;
},
null,
);
err2 = ReactErrorUtils.clearCaughtError();
throw err3;
},
null,
);
err4 = ReactErrorUtils.clearCaughtError();
expect(err2).toBe(err1);
expect(err4).toBe(err3);
});
it(`does not return nested errors (${environment})`, () => {
const err1 = new Error();
let err2;
ReactErrorUtils.invokeGuardedCallback(
'foo',
function() {
ReactErrorUtils.invokeGuardedCallback(
'foo',
function() {
throw err1;
},
null,
);
err2 = ReactErrorUtils.clearCaughtError();
},
null,
);
// Returns null because inner error was already captured
expect(ReactErrorUtils.hasCaughtError()).toBe(false);
expect(err2).toBe(err1);
});
if (environment === 'production') {
// jsdom doesn't handle this properly, but Chrome and Firefox should. Test
// this with a fixture.
it('catches null values', () => {
ReactErrorUtils.invokeGuardedCallback(
null,
function() {
throw null;
},
null,
);
expect(ReactErrorUtils.hasCaughtError()).toBe(true);
expect(ReactErrorUtils.clearCaughtError()).toBe(null);
});
}
it(`can be shimmed (${environment})`, () => {
const ops = [];
// Override the original invokeGuardedCallback
ReactErrorUtils.injection.injectErrorUtils({
invokeGuardedCallback(name, func, context, a) {
ops.push(a);
try {
func.call(context, a);
} catch (error) {
this._hasCaughtError = true;
this._caughtError = error;
}
},
});
var err = new Error('foo');
var callback = function() {
throw err;
};
ReactErrorUtils.invokeGuardedCallbackAndCatchFirstError(
'foo',
callback,
null,
'somearg',
);
expect(() => ReactErrorUtils.rethrowCaughtError()).toThrow(err);
expect(ops).toEqual(['somearg']);
});
}
});