1
1
import { Scope } from '@sentry/browser' ;
2
+ import { Event , Severity } from '@sentry/types' ;
2
3
import { fireEvent , render , screen } from '@testing-library/react' ;
3
4
import * as React from 'react' ;
5
+ import { useState } from 'react' ;
4
6
5
7
import { ErrorBoundary , ErrorBoundaryProps , UNKNOWN_COMPONENT , withErrorBoundary } from '../src/errorboundary' ;
6
8
7
- const mockCaptureException = jest . fn ( ) ;
9
+ const mockCaptureEvent = jest . fn ( ) ;
8
10
const mockShowReportDialog = jest . fn ( ) ;
9
11
const EVENT_ID = 'test-id-123' ;
10
12
11
13
jest . mock ( '@sentry/browser' , ( ) => {
12
14
const actual = jest . requireActual ( '@sentry/browser' ) ;
13
15
return {
14
16
...actual ,
15
- captureException : ( err : any , ctx : any ) => {
16
- mockCaptureException ( err , ctx ) ;
17
+ captureEvent : ( event : Event ) => {
18
+ mockCaptureEvent ( event ) ;
17
19
return EVENT_ID ;
18
20
} ,
19
21
showReportDialog : ( options : any ) => {
@@ -22,6 +24,15 @@ jest.mock('@sentry/browser', () => {
22
24
} ;
23
25
} ) ;
24
26
27
+ function Boo ( { title } : { title : string } ) : JSX . Element {
28
+ throw new Error ( title ) ;
29
+ }
30
+
31
+ function Bam ( ) : JSX . Element {
32
+ const [ title ] = useState ( 'boom' ) ;
33
+ return < Boo title = { title } /> ;
34
+ }
35
+
25
36
const TestApp : React . FC < ErrorBoundaryProps > = ( { children, ...props } ) => {
26
37
const [ isError , setError ] = React . useState ( false ) ;
27
38
return (
@@ -45,10 +56,6 @@ const TestApp: React.FC<ErrorBoundaryProps> = ({ children, ...props }) => {
45
56
) ;
46
57
} ;
47
58
48
- function Bam ( ) : JSX . Element {
49
- throw new Error ( 'boom' ) ;
50
- }
51
-
52
59
describe ( 'withErrorBoundary' , ( ) => {
53
60
it ( 'sets displayName properly' , ( ) => {
54
61
const TestComponent = ( ) => < h1 > Hello World</ h1 > ;
@@ -67,7 +74,7 @@ describe('ErrorBoundary', () => {
67
74
jest . spyOn ( console , 'error' ) . mockImplementation ( ) ;
68
75
69
76
afterEach ( ( ) => {
70
- mockCaptureException . mockClear ( ) ;
77
+ mockCaptureEvent . mockClear ( ) ;
71
78
mockShowReportDialog . mockClear ( ) ;
72
79
} ) ;
73
80
@@ -170,10 +177,15 @@ describe('ErrorBoundary', () => {
170
177
expect ( container . innerHTML ) . toBe ( '<div>Fallback here</div>' ) ;
171
178
172
179
expect ( errorString ) . toBe ( 'Error: boom' ) ;
173
- expect ( compStack ) . toBe ( `
174
- in Bam (created by TestApp)
175
- in ErrorBoundary (created by TestApp)
176
- in TestApp` ) ;
180
+ /*
181
+ at Boo (/path/to/sentry-javascript/packages/react/test/errorboundary.test.tsx:23:20)
182
+ at Bam (/path/to/sentry-javascript/packages/react/test/errorboundary.test.tsx:40:11)
183
+ at ErrorBoundary (/path/to/sentry-javascript/packages/react/src/errorboundary.tsx:2026:39)
184
+ at TestApp (/path/to/sentry-javascript/packages/react/test/errorboundary.test.tsx:22:23)
185
+ */
186
+ expect ( compStack ) . toMatch (
187
+ / \s + ( a t B o o ) \( .* ?\) \s + ( a t B a m ) \( .* ?\) \s + ( a t E r r o r B o u n d a r y ) \( .* ?\) \s + ( a t T e s t A p p ) \( .* ?\) / g,
188
+ ) ;
177
189
expect ( eventIdString ) . toBe ( EVENT_ID ) ;
178
190
} ) ;
179
191
} ) ;
@@ -188,25 +200,60 @@ describe('ErrorBoundary', () => {
188
200
) ;
189
201
190
202
expect ( mockOnError ) . toHaveBeenCalledTimes ( 0 ) ;
191
- expect ( mockCaptureException ) . toHaveBeenCalledTimes ( 0 ) ;
203
+ expect ( mockCaptureEvent ) . toHaveBeenCalledTimes ( 0 ) ;
192
204
193
205
const btn = screen . getByTestId ( 'errorBtn' ) ;
194
206
fireEvent . click ( btn ) ;
195
207
196
208
expect ( mockOnError ) . toHaveBeenCalledTimes ( 1 ) ;
197
209
expect ( mockOnError ) . toHaveBeenCalledWith ( expect . any ( Error ) , expect . any ( String ) , expect . any ( String ) ) ;
198
210
199
- expect ( mockCaptureException ) . toHaveBeenCalledTimes ( 1 ) ;
200
- expect ( mockCaptureException ) . toHaveBeenCalledWith ( expect . any ( Error ) , {
201
- contexts : { react : { componentStack : expect . any ( String ) } } ,
202
- } ) ;
211
+ expect ( mockCaptureEvent ) . toHaveBeenCalledTimes ( 1 ) ;
212
+
213
+ // We do a detailed assert on the stacktrace as a regression test against future
214
+ // react changes (that way we can update the docs if frames change in a major way).
215
+ const event = mockCaptureEvent . mock . calls [ 0 ] [ 0 ] ;
216
+ expect ( event . exception . values ) . toHaveLength ( 2 ) ;
217
+ expect ( event . level ) . toBe ( Severity . Error ) ;
218
+
219
+ expect ( event . exception . values [ 0 ] . type ) . toEqual ( 'React ErrorBoundary Error' ) ;
220
+ expect ( event . exception . values [ 0 ] . stacktrace . frames ) . toEqual ( [
221
+ {
222
+ colno : expect . any ( Number ) ,
223
+ filename : expect . stringContaining ( 'errorboundary.test.tsx' ) ,
224
+ function : 'TestApp' ,
225
+ in_app : true ,
226
+ lineno : expect . any ( Number ) ,
227
+ } ,
228
+ {
229
+ colno : expect . any ( Number ) ,
230
+ filename : expect . stringContaining ( 'errorboundary.tsx' ) ,
231
+ function : 'ErrorBoundary' ,
232
+ in_app : true ,
233
+ lineno : expect . any ( Number ) ,
234
+ } ,
235
+ {
236
+ colno : expect . any ( Number ) ,
237
+ filename : expect . stringContaining ( 'errorboundary.test.tsx' ) ,
238
+ function : 'Bam' ,
239
+ in_app : true ,
240
+ lineno : expect . any ( Number ) ,
241
+ } ,
242
+ {
243
+ colno : expect . any ( Number ) ,
244
+ filename : expect . stringContaining ( 'errorboundary.test.tsx' ) ,
245
+ function : 'Boo' ,
246
+ in_app : true ,
247
+ lineno : expect . any ( Number ) ,
248
+ } ,
249
+ ] ) ;
203
250
} ) ;
204
251
205
252
it ( 'calls `beforeCapture()` when an error occurs' , ( ) => {
206
253
const mockBeforeCapture = jest . fn ( ) ;
207
254
208
255
const testBeforeCapture = ( ...args : any [ ] ) => {
209
- expect ( mockCaptureException ) . toHaveBeenCalledTimes ( 0 ) ;
256
+ expect ( mockCaptureEvent ) . toHaveBeenCalledTimes ( 0 ) ;
210
257
mockBeforeCapture ( ...args ) ;
211
258
} ;
212
259
@@ -217,14 +264,14 @@ describe('ErrorBoundary', () => {
217
264
) ;
218
265
219
266
expect ( mockBeforeCapture ) . toHaveBeenCalledTimes ( 0 ) ;
220
- expect ( mockCaptureException ) . toHaveBeenCalledTimes ( 0 ) ;
267
+ expect ( mockCaptureEvent ) . toHaveBeenCalledTimes ( 0 ) ;
221
268
222
269
const btn = screen . getByTestId ( 'errorBtn' ) ;
223
270
fireEvent . click ( btn ) ;
224
271
225
272
expect ( mockBeforeCapture ) . toHaveBeenCalledTimes ( 1 ) ;
226
273
expect ( mockBeforeCapture ) . toHaveBeenLastCalledWith ( expect . any ( Scope ) , expect . any ( Error ) , expect . any ( String ) ) ;
227
- expect ( mockCaptureException ) . toHaveBeenCalledTimes ( 1 ) ;
274
+ expect ( mockCaptureEvent ) . toHaveBeenCalledTimes ( 1 ) ;
228
275
} ) ;
229
276
230
277
it ( 'shows a Sentry Report Dialog with correct options' , ( ) => {
0 commit comments