Skip to content

Commit

Permalink
feat(v8/react): Add a handled prop to ErrorBoundary (#14978)
Browse files Browse the repository at this point in the history
backport of #14560

---------

Co-authored-by: Henry Huck <henryhuck@hotmail.fr>
  • Loading branch information
Lms24 and HHK1 authored Jan 10, 2025
1 parent 5182853 commit fda1aee
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 42 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott

Work in this release was contributed by @HHK1. Thank you for your contribution!

## 8.48.0

### Deprecations
Expand Down
9 changes: 8 additions & 1 deletion packages/react/src/errorboundary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ export type ErrorBoundaryProps = {
*
*/
fallback?: React.ReactElement | FallbackRender | undefined;
/**
* If set to `true` or `false`, the error `handled` property will be set to the given value.
* If unset, the default behaviour is to rely on the presence of the `fallback` prop to determine
* if the error was handled or not.
*/
handled?: boolean | undefined;
/** Called when the error boundary encounters an error */
onError?: ((error: unknown, componentStack: string | undefined, eventId: string) => void) | undefined;
/** Called on componentDidMount() */
Expand Down Expand Up @@ -107,7 +113,8 @@ class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundarySta
beforeCapture(scope, error, passedInComponentStack);
}

const eventId = captureReactException(error, errorInfo, { mechanism: { handled: !!this.props.fallback } });
const handled = this.props.handled != null ? this.props.handled : !!this.props.fallback;
const eventId = captureReactException(error, errorInfo, { mechanism: { handled } });

if (onError) {
onError(error, passedInComponentStack, eventId);
Expand Down
82 changes: 41 additions & 41 deletions packages/react/test/errorboundary.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { fireEvent, render, screen } from '@testing-library/react';
import * as React from 'react';
import { useState } from 'react';

import type { ErrorBoundaryProps } from '../src/errorboundary';
import type { ErrorBoundaryProps, FallbackRender } from '../src/errorboundary';
import { ErrorBoundary, UNKNOWN_COMPONENT, withErrorBoundary } from '../src/errorboundary';

const mockCaptureException = jest.fn();
Expand Down Expand Up @@ -537,47 +537,47 @@ describe('ErrorBoundary', () => {
expect(mockOnReset).toHaveBeenCalledTimes(1);
expect(mockOnReset).toHaveBeenCalledWith(expect.any(Error), expect.any(String), expect.any(String));
});
it.each`
fallback | handled | expected
${true} | ${undefined} | ${true}
${false} | ${undefined} | ${false}
${true} | ${false} | ${false}
${true} | ${true} | ${true}
${false} | ${true} | ${true}
${false} | ${false} | ${false}
`(
'sets `handled: $expected` when `handled` is $handled and `fallback` is $fallback',
async ({
fallback,
handled,
expected,
}: {
fallback: boolean;
handled: boolean | undefined;
expected: boolean;
}) => {
const fallbackComponent: FallbackRender | undefined = fallback
? ({ resetError }) => <button data-testid="reset" onClick={resetError} />
: undefined;
render(
<TestApp handled={handled} fallback={fallbackComponent}>
<h1>children</h1>
</TestApp>,
);

it('sets `handled: true` when a fallback is provided', async () => {
render(
<TestApp fallback={({ resetError }) => <button data-testid="reset" onClick={resetError} />}>
<h1>children</h1>
</TestApp>,
);

expect(mockCaptureException).toHaveBeenCalledTimes(0);

const btn = screen.getByTestId('errorBtn');
fireEvent.click(btn);

expect(mockCaptureException).toHaveBeenCalledTimes(1);
expect(mockCaptureException).toHaveBeenLastCalledWith(expect.any(Object), {
captureContext: {
contexts: { react: { componentStack: expect.any(String) } },
},
mechanism: { handled: true },
});
});

it('sets `handled: false` when no fallback is provided', async () => {
render(
<TestApp>
<h1>children</h1>
</TestApp>,
);

expect(mockCaptureException).toHaveBeenCalledTimes(0);

const btn = screen.getByTestId('errorBtn');
fireEvent.click(btn);
expect(mockCaptureException).toHaveBeenCalledTimes(0);

expect(mockCaptureException).toHaveBeenCalledTimes(1);
expect(mockCaptureException).toHaveBeenLastCalledWith(expect.any(Object), {
captureContext: {
contexts: { react: { componentStack: expect.any(String) } },
},
mechanism: { handled: false },
});
});
const btn = screen.getByTestId('errorBtn');
fireEvent.click(btn);

expect(mockCaptureException).toHaveBeenCalledTimes(1);
expect(mockCaptureException).toHaveBeenLastCalledWith(expect.any(Object), {
captureContext: {
contexts: { react: { componentStack: expect.any(String) } },
},
mechanism: { handled: expected },
});
},
);
});
});

0 comments on commit fda1aee

Please sign in to comment.