Skip to content

Commit 448829f

Browse files
committed
fix(react): Correctly give eventID to dialog
1 parent 598c5bb commit 448829f

File tree

2 files changed

+73
-14
lines changed

2 files changed

+73
-14
lines changed

packages/react/src/errorboundary.tsx

+25-3
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,31 @@ export type FallbackRender = (fallback: {
1111
}) => React.ReactNode;
1212

1313
export type ErrorBoundaryProps = {
14+
/** If a Sentry report dialog should be rendered on error */
1415
showDialog?: boolean;
16+
/**
17+
* Options to be passed into the Sentry report dialog.
18+
* No-op if {@link showDialog} is false.
19+
*/
1520
dialogOptions?: Sentry.ReportDialogOptions;
16-
// tslint:disable-next-line: no-null-undefined-union
21+
// tslint:disable no-null-undefined-union
22+
/**
23+
* A fallback component that gets rendered when the error boundary encounters an error.
24+
*
25+
* Can either provide a React Component, or a function that returns React Component as
26+
* a valid fallback prop. If a function is provided, the function will be called with
27+
* the error, the component stack, and an function that resets the error boundary on error.
28+
*
29+
*/
1730
fallback?: React.ReactNode | FallbackRender;
31+
// tslint:enable no-null-undefined-union
32+
/** Called with the error boundary encounters an error */
1833
onError?(error: Error, componentStack: string): void;
34+
/** Called on componentDidMount() */
1935
onMount?(): void;
36+
/** Called if resetError() is called from the fallback render props function */
2037
onReset?(error: Error | null, componentStack: string | null): void;
38+
/** Called on componentWillUnmount() */
2139
onUnmount?(error: Error | null, componentStack: string | null): void;
2240
};
2341

@@ -31,17 +49,21 @@ const INITIAL_STATE = {
3149
error: null,
3250
};
3351

52+
/**
53+
* A ErrorBoundary component that logs errors to Sentry.
54+
* Requires React >= 16
55+
*/
3456
class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
3557
public state: ErrorBoundaryState = INITIAL_STATE;
3658

3759
public componentDidCatch(error: Error, { componentStack }: React.ErrorInfo): void {
38-
Sentry.captureException(error, { contexts: { react: { componentStack } } });
60+
const eventId = Sentry.captureException(error, { contexts: { react: { componentStack } } });
3961
const { onError, showDialog, dialogOptions } = this.props;
4062
if (onError) {
4163
onError(error, componentStack);
4264
}
4365
if (showDialog) {
44-
Sentry.showReportDialog(dialogOptions);
66+
Sentry.showReportDialog({ ...dialogOptions, eventId });
4567
}
4668

4769
// componentDidCatch is used over getDerivedStateFromError

packages/react/test/errorboundary.test.tsx

+48-11
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
import { fireEvent, render, screen } from '@testing-library/react';
22
import * as React from 'react';
33

4-
import { ErrorBoundary, ErrorBoundaryProps } from '../src/errorboundary';
4+
import { ErrorBoundary, ErrorBoundaryProps, UNKNOWN_COMPONENT, withErrorBoundary } from '../src/errorboundary';
55

66
const mockCaptureException = jest.fn();
77
const mockShowReportDialog = jest.fn();
8+
const EVENT_ID = 'test-id-123';
89

910
jest.mock('@sentry/browser', () => ({
1011
captureException: (err: any, ctx: any) => {
1112
mockCaptureException(err, ctx);
13+
return EVENT_ID;
1214
},
1315
showReportDialog: (options: any) => {
1416
mockShowReportDialog(options);
@@ -18,7 +20,15 @@ jest.mock('@sentry/browser', () => ({
1820
const TestApp: React.FC<ErrorBoundaryProps> = ({ children, ...props }) => {
1921
const [isError, setError] = React.useState(false);
2022
return (
21-
<ErrorBoundary {...props}>
23+
<ErrorBoundary
24+
{...props}
25+
onReset={(err: Error, stack: string) => {
26+
setError(false);
27+
if (props.onReset) {
28+
props.onReset(err, stack);
29+
}
30+
}}
31+
>
2232
{isError ? <Bam /> : children}
2333
<button
2434
data-testid="errorBtn"
@@ -34,6 +44,20 @@ function Bam(): JSX.Element {
3444
throw new Error('boom');
3545
}
3646

47+
describe('withErrorBoundary', () => {
48+
it('sets displayName properly', () => {
49+
const TestComponent = () => <h1>Hello World</h1>;
50+
51+
const Component = withErrorBoundary(TestComponent, { fallback: <h1>fallback</h1> });
52+
expect(Component.displayName).toBe('errorBoundary(TestComponent)');
53+
});
54+
55+
it('defaults to an unknown displayName', () => {
56+
const Component = withErrorBoundary(() => <h1>Hello World</h1>, { fallback: <h1>fallback</h1> });
57+
expect(Component.displayName).toBe(`errorBoundary(${UNKNOWN_COMPONENT})`);
58+
});
59+
});
60+
3761
describe('ErrorBoundary', () => {
3862
jest.spyOn(console, 'error').mockImplementation();
3963

@@ -44,7 +68,6 @@ describe('ErrorBoundary', () => {
4468

4569
it('renders null if not given a valid `fallback` prop', () => {
4670
const { container } = render(
47-
// @ts-ignore
4871
<ErrorBoundary fallback={new Error('true')}>
4972
<Bam />
5073
</ErrorBoundary>,
@@ -55,7 +78,6 @@ describe('ErrorBoundary', () => {
5578

5679
it('renders a fallback on error', () => {
5780
const { container } = render(
58-
// @ts-ignore
5981
<ErrorBoundary fallback={<h1>Error Component</h1>}>
6082
<Bam />
6183
</ErrorBoundary>,
@@ -186,12 +208,30 @@ describe('ErrorBoundary', () => {
186208
fireEvent.click(btn);
187209

188210
expect(mockShowReportDialog).toHaveBeenCalledTimes(1);
189-
expect(mockShowReportDialog).toHaveBeenCalledWith(options);
211+
expect(mockShowReportDialog).toHaveBeenCalledWith({ ...options, eventId: EVENT_ID });
190212
});
191213

192-
it('resets to initial state when reset', () => {
193-
const mockOnReset = jest.fn();
214+
it('resets to initial state when reset', async () => {
194215
const { container } = render(
216+
<TestApp fallback={({ resetError }) => <button data-testid="reset" onClick={resetError} />}>
217+
<h1>children</h1>
218+
</TestApp>,
219+
);
220+
221+
expect(container.innerHTML).toContain('<h1>children</h1>');
222+
const btn = screen.getByTestId('errorBtn');
223+
fireEvent.click(btn);
224+
expect(container.innerHTML).toContain('<button data-testid="reset">');
225+
226+
const reset = screen.getByTestId('reset');
227+
fireEvent.click(reset);
228+
229+
expect(container.innerHTML).toContain('<h1>children</h1>');
230+
});
231+
232+
it('calls `onReset()` when reset', () => {
233+
const mockOnReset = jest.fn();
234+
render(
195235
<TestApp
196236
onReset={mockOnReset}
197237
fallback={({ resetError }) => <button data-testid="reset" onClick={resetError} />}
@@ -200,17 +240,14 @@ describe('ErrorBoundary', () => {
200240
</TestApp>,
201241
);
202242

203-
expect(container.innerHTML).toContain('<h1>children</h1>');
204243
expect(mockOnReset).toHaveBeenCalledTimes(0);
205-
206244
const btn = screen.getByTestId('errorBtn');
207245
fireEvent.click(btn);
208-
209-
expect(container.innerHTML).toContain('<button data-testid="reset">');
210246
expect(mockOnReset).toHaveBeenCalledTimes(0);
211247

212248
const reset = screen.getByTestId('reset');
213249
fireEvent.click(reset);
250+
214251
expect(mockOnReset).toHaveBeenCalledTimes(1);
215252
expect(mockOnReset).toHaveBeenCalledWith(expect.any(Error), expect.any(String));
216253
});

0 commit comments

Comments
 (0)