'use strict';
let React;
let Suspense;
let Activity;
let ViewTransition;
let ReactNoop;
let waitForAll;
describe('ReactFragment', () => {
let didCatchErrors = [];
let rootCaughtErrors = [];
let SomethingThatErrors;
let CatchingBoundary;
let onCaughtError;
beforeEach(function () {
jest.resetModules();
React = require('react');
Suspense = React.Suspense;
Activity = React.unstable_Activity;
ViewTransition = React.unstable_ViewTransition;
ReactNoop = require('react-noop-renderer');
const InternalTestUtils = require('internal-test-utils');
waitForAll = InternalTestUtils.waitForAll;
didCatchErrors = [];
rootCaughtErrors = [];
onCaughtError = function (error, errorInfo) {
rootCaughtErrors.push(
error.message,
normalizeCodeLocInfo(errorInfo.componentStack),
React.captureOwnerStack
? normalizeCodeLocInfo(React.captureOwnerStack())
: null,
);
};
SomethingThatErrors = () => {
throw new Error('uh oh');
};
CatchingBoundary = class CatchingBoundary extends React.Component {
constructor() {
super();
this.state = {};
}
static getDerivedStateFromError(error) {
return {errored: true};
}
componentDidCatch(err, errInfo) {
didCatchErrors.push(
err.message,
normalizeCodeLocInfo(errInfo.componentStack),
);
}
render() {
if (this.state.errored) {
return null;
}
return this.props.children;
}
};
});
function componentStack(components) {
return components
.map(component => `\n in ${component} (at **)`)
.join('');
}
function normalizeCodeLocInfo(str) {
return (
str &&
str.replace(/\n +(?:at|in) ([\S]+)[^\n]*/g, function (m, name) {
return '\n in ' + name + ' (at **)';
})
);
}
it('retains component and owner stacks when rethrowing an error', async () => {
class RethrowingBoundary extends React.Component {
static getDerivedStateFromError(error) {
throw error;
}
render() {
return this.props.children;
}
}
function Foo() {
return (
<RethrowingBoundary>
<Bar />
</RethrowingBoundary>
);
}
function Bar() {
return <SomethingThatErrors />;
}
ReactNoop.createRoot({
onCaughtError,
}).render(
<CatchingBoundary>
<Foo />
</CatchingBoundary>,
);
await waitForAll([]);
expect(didCatchErrors).toEqual([
'uh oh',
componentStack([
'SomethingThatErrors',
'Bar',
'RethrowingBoundary',
'Foo',
'CatchingBoundary',
]),
]);
expect(rootCaughtErrors).toEqual([
'uh oh',
componentStack([
'SomethingThatErrors',
'Bar',
'RethrowingBoundary',
'Foo',
'CatchingBoundary',
]),
__DEV__ ? componentStack(['Bar', 'Foo']) : null,
]);
});
it('includes built-in for Suspense', async () => {
ReactNoop.createRoot({
onCaughtError,
}).render(
<CatchingBoundary>
<Suspense>
<SomethingThatErrors />
</Suspense>
</CatchingBoundary>,
);
await waitForAll([]);
expect(didCatchErrors).toEqual([
'uh oh',
componentStack(['SomethingThatErrors', 'Suspense', 'CatchingBoundary']),
]);
expect(rootCaughtErrors).toEqual([
'uh oh',
componentStack(['SomethingThatErrors', 'Suspense', 'CatchingBoundary']),
__DEV__ ? componentStack(['SomethingThatErrors']) : null,
]);
});
it('includes built-in for Activity', async () => {
ReactNoop.createRoot({
onCaughtError,
}).render(
<CatchingBoundary>
<Activity>
<SomethingThatErrors />
</Activity>
</CatchingBoundary>,
);
await waitForAll([]);
expect(didCatchErrors).toEqual([
'uh oh',
componentStack(['SomethingThatErrors', 'Activity', 'CatchingBoundary']),
]);
expect(rootCaughtErrors).toEqual([
'uh oh',
componentStack(['SomethingThatErrors', 'Activity', 'CatchingBoundary']),
__DEV__ ? componentStack(['SomethingThatErrors']) : null,
]);
});
it('includes built-in for ViewTransition', async () => {
ReactNoop.createRoot({
onCaughtError,
}).render(
<CatchingBoundary>
<ViewTransition>
<SomethingThatErrors />
</ViewTransition>
</CatchingBoundary>,
);
await waitForAll([]);
expect(didCatchErrors).toEqual([
'uh oh',
componentStack([
'SomethingThatErrors',
'ViewTransition',
'CatchingBoundary',
]),
]);
expect(rootCaughtErrors).toEqual([
'uh oh',
componentStack([
'SomethingThatErrors',
'ViewTransition',
'CatchingBoundary',
]),
__DEV__ ? componentStack(['SomethingThatErrors']) : null,
]);
});
it('includes built-in for Lazy', async () => {
const LazyComponent = React.lazy(() => {
throw new Error('uh oh');
});
ReactNoop.createRoot({
onCaughtError,
}).render(
<CatchingBoundary>
<LazyComponent />
</CatchingBoundary>,
);
await waitForAll([]);
expect(didCatchErrors).toEqual([
'uh oh',
componentStack(['Lazy', 'CatchingBoundary']),
]);
expect(rootCaughtErrors).toEqual([
'uh oh',
componentStack(['Lazy', 'CatchingBoundary']),
__DEV__ ? '' : null,
]);
});
it('includes built-in for SuspenseList', async () => {
const SuspenseList = React.unstable_SuspenseList;
ReactNoop.createRoot({
onCaughtError,
}).render(
<CatchingBoundary>
<SuspenseList revealOrder="independent">
<SomethingThatErrors />
</SuspenseList>
</CatchingBoundary>,
);
await waitForAll([]);
expect(didCatchErrors).toEqual([
'uh oh',
componentStack([
'SomethingThatErrors',
'SuspenseList',
'CatchingBoundary',
]),
]);
expect(rootCaughtErrors).toEqual([
'uh oh',
componentStack([
'SomethingThatErrors',
'SuspenseList',
'CatchingBoundary',
]),
__DEV__ ? componentStack(['SomethingThatErrors']) : null,
]);
});
it('does not include built-in for Fragment', async () => {
ReactNoop.createRoot({
onCaughtError,
}).render(
<CatchingBoundary>
<>
<SomethingThatErrors />
</>
</CatchingBoundary>,
);
await waitForAll([]);
expect(didCatchErrors).toEqual([
'uh oh',
componentStack([
'SomethingThatErrors',
'CatchingBoundary',
]),
]);
expect(rootCaughtErrors).toEqual([
'uh oh',
componentStack([
'SomethingThatErrors',
'CatchingBoundary',
]),
__DEV__ ? componentStack(['SomethingThatErrors']) : null,
]);
});
});