211 lines
5.3 KiB
JavaScript
211 lines
5.3 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @emails react-core
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
let ReactErrorUtils;
|
|
|
|
describe('ReactErrorUtils', () => {
|
|
beforeEach(() => {
|
|
// TODO: can we express this test with only public API?
|
|
ReactErrorUtils = require('shared/ReactErrorUtils');
|
|
});
|
|
|
|
it(`it should rethrow caught errors`, () => {
|
|
const err = new Error('foo');
|
|
const 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`, () => {
|
|
const callback = jest.fn();
|
|
ReactErrorUtils.invokeGuardedCallback(
|
|
'foo',
|
|
callback,
|
|
null,
|
|
'arg1',
|
|
'arg2',
|
|
);
|
|
expect(callback).toBeCalledWith('arg1', 'arg2');
|
|
});
|
|
|
|
it(`should call the callback with the provided context`, () => {
|
|
const context = {didCall: false};
|
|
ReactErrorUtils.invokeGuardedCallback(
|
|
'foo',
|
|
function () {
|
|
this.didCall = true;
|
|
},
|
|
context,
|
|
);
|
|
expect(context.didCall).toBe(true);
|
|
});
|
|
|
|
it(`should catch errors`, () => {
|
|
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`, () => {
|
|
const callback = jest.fn();
|
|
ReactErrorUtils.invokeGuardedCallback('foo', callback, null);
|
|
expect(ReactErrorUtils.hasCaughtError()).toBe(false);
|
|
expect(ReactErrorUtils.clearCaughtError).toThrow('no error was captured');
|
|
});
|
|
|
|
it(`can nest with same debug name`, () => {
|
|
const err1 = new Error();
|
|
let err2;
|
|
const err3 = new Error();
|
|
ReactErrorUtils.invokeGuardedCallback(
|
|
'foo',
|
|
function () {
|
|
ReactErrorUtils.invokeGuardedCallback(
|
|
'foo',
|
|
function () {
|
|
throw err1;
|
|
},
|
|
null,
|
|
);
|
|
err2 = ReactErrorUtils.clearCaughtError();
|
|
throw err3;
|
|
},
|
|
null,
|
|
);
|
|
const err4 = ReactErrorUtils.clearCaughtError();
|
|
|
|
expect(err2).toBe(err1);
|
|
expect(err4).toBe(err3);
|
|
});
|
|
|
|
it(`handles nested errors`, () => {
|
|
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);
|
|
});
|
|
|
|
it('handles nested errors in separate renderers', () => {
|
|
const ReactErrorUtils1 = require('shared/ReactErrorUtils');
|
|
jest.resetModules();
|
|
const ReactErrorUtils2 = require('shared/ReactErrorUtils');
|
|
expect(ReactErrorUtils1).not.toEqual(ReactErrorUtils2);
|
|
|
|
const ops = [];
|
|
|
|
ReactErrorUtils1.invokeGuardedCallback(
|
|
null,
|
|
() => {
|
|
ReactErrorUtils2.invokeGuardedCallback(
|
|
null,
|
|
() => {
|
|
throw new Error('nested error');
|
|
},
|
|
null,
|
|
);
|
|
// ReactErrorUtils2 should catch the error
|
|
ops.push(ReactErrorUtils2.hasCaughtError());
|
|
ops.push(ReactErrorUtils2.clearCaughtError().message);
|
|
},
|
|
null,
|
|
);
|
|
|
|
// ReactErrorUtils1 should not catch the error
|
|
ops.push(ReactErrorUtils1.hasCaughtError());
|
|
|
|
expect(ops).toEqual([true, 'nested error', false]);
|
|
});
|
|
|
|
if (!__DEV__) {
|
|
// 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; // eslint-disable-line no-throw-literal
|
|
},
|
|
null,
|
|
);
|
|
expect(ReactErrorUtils.hasCaughtError()).toBe(true);
|
|
expect(ReactErrorUtils.clearCaughtError()).toBe(null);
|
|
});
|
|
}
|
|
|
|
it(`can be shimmed`, () => {
|
|
const ops = [];
|
|
jest.resetModules();
|
|
jest.mock(
|
|
'shared/invokeGuardedCallbackImpl',
|
|
() =>
|
|
function invokeGuardedCallback(name, func, context, a) {
|
|
ops.push(a);
|
|
try {
|
|
func.call(context, a);
|
|
} catch (error) {
|
|
this.onError(error);
|
|
}
|
|
},
|
|
);
|
|
ReactErrorUtils = require('shared/ReactErrorUtils');
|
|
|
|
try {
|
|
const err = new Error('foo');
|
|
const callback = function () {
|
|
throw err;
|
|
};
|
|
ReactErrorUtils.invokeGuardedCallbackAndCatchFirstError(
|
|
'foo',
|
|
callback,
|
|
null,
|
|
'somearg',
|
|
);
|
|
expect(() => ReactErrorUtils.rethrowCaughtError()).toThrow(err);
|
|
expect(ops).toEqual(['somearg']);
|
|
} finally {
|
|
jest.unmock('shared/invokeGuardedCallbackImpl');
|
|
}
|
|
});
|
|
});
|