1
- import { afterEach , describe , expect , test , vi } from 'vitest' ;
1
+ import { afterEach , describe , expect , it , test , vi } from 'vitest' ;
2
2
3
3
import { setCurrentClient } from '@sentry/browser' ;
4
4
@@ -7,26 +7,29 @@ import type { Operation, Options, ViewModel, Vue } from '../src/types';
7
7
import { generateComponentTrace } from '../src/vendor/components' ;
8
8
9
9
describe ( 'attachErrorHandler' , ( ) => {
10
- describe ( 'attachProps ' , ( ) => {
10
+ describe ( 'attach data to captureException ' , ( ) => {
11
11
afterEach ( ( ) => {
12
12
vi . resetAllMocks ( ) ;
13
+ // we need timers to still call captureException wrapped inside setTimeout after the error throws
14
+ vi . useRealTimers ( ) ;
13
15
} ) ;
14
16
15
17
describe ( "given I don't want to `attachProps`" , ( ) => {
16
18
test ( 'no `propsData` is added to the metadata' , ( ) => {
17
- // arrange
18
19
const t = testHarness ( {
19
- enableErrorHandler : false ,
20
20
enableWarnHandler : false ,
21
21
attachProps : false ,
22
22
vm : null ,
23
+ enableConsole : true ,
23
24
} ) ;
24
25
25
- // act
26
- t . run ( ) ;
26
+ vi . useFakeTimers ( ) ;
27
+ expect ( ( ) => t . run ( ) ) . toThrow ( DummyError ) ;
28
+ vi . runAllTimers ( ) ;
27
29
28
30
// assert
29
31
t . expect . errorToHaveBeenCaptured ( ) . withoutProps ( ) ;
32
+ t . expect . errorToHaveBeenCaptured ( ) . withMechanismMetadata ( { handled : false , type : 'vue-errorHandler' } ) ;
30
33
} ) ;
31
34
} ) ;
32
35
@@ -41,10 +44,13 @@ describe('attachErrorHandler', () => {
41
44
} ) ;
42
45
43
46
// act
44
- t . run ( ) ;
47
+ vi . useFakeTimers ( ) ;
48
+ expect ( ( ) => t . run ( ) ) . toThrow ( DummyError ) ;
49
+ vi . runAllTimers ( ) ;
45
50
46
51
// assert
47
52
t . expect . errorToHaveBeenCaptured ( ) . withoutProps ( ) ;
53
+ t . expect . errorToHaveBeenCaptured ( ) . withMechanismMetadata ( { handled : false , type : 'vue-errorHandler' } ) ;
48
54
} ) ;
49
55
} ) ;
50
56
@@ -58,7 +64,9 @@ describe('attachErrorHandler', () => {
58
64
} ) ;
59
65
60
66
// act
61
- t . run ( ) ;
67
+ vi . useFakeTimers ( ) ;
68
+ expect ( ( ) => t . run ( ) ) . toThrow ( DummyError ) ;
69
+ vi . runAllTimers ( ) ;
62
70
63
71
// assert
64
72
t . expect . errorToHaveBeenCaptured ( ) . withoutProps ( ) ;
@@ -76,7 +84,9 @@ describe('attachErrorHandler', () => {
76
84
} ) ;
77
85
78
86
// act
79
- t . run ( ) ;
87
+ vi . useFakeTimers ( ) ;
88
+ expect ( ( ) => t . run ( ) ) . toThrow ( DummyError ) ;
89
+ vi . runAllTimers ( ) ;
80
90
81
91
// assert
82
92
t . expect . errorToHaveBeenCaptured ( ) . withoutProps ( ) ;
@@ -94,7 +104,9 @@ describe('attachErrorHandler', () => {
94
104
} ) ;
95
105
96
106
// act
97
- t . run ( ) ;
107
+ vi . useFakeTimers ( ) ;
108
+ expect ( ( ) => t . run ( ) ) . toThrow ( DummyError ) ;
109
+ vi . runAllTimers ( ) ;
98
110
99
111
// assert
100
112
t . expect . errorToHaveBeenCaptured ( ) . withProps ( props ) ;
@@ -114,7 +126,9 @@ describe('attachErrorHandler', () => {
114
126
} ) ;
115
127
116
128
// act
117
- t . run ( ) ;
129
+ vi . useFakeTimers ( ) ;
130
+ expect ( ( ) => t . run ( ) ) . toThrow ( DummyError ) ;
131
+ vi . runAllTimers ( ) ;
118
132
119
133
// assert
120
134
t . expect . errorToHaveBeenCaptured ( ) . withProps ( props ) ;
@@ -123,31 +137,22 @@ describe('attachErrorHandler', () => {
123
137
} ) ;
124
138
} ) ;
125
139
} ) ;
126
- } ) ;
127
140
128
- describe ( 'provided errorHandler' , ( ) => {
129
- describe ( 'given I did not provide an `errorHandler`' , ( ) => {
130
- test ( 'it is not called' , ( ) => {
141
+ describe ( 'attach mechanism metadata' , ( ) => {
142
+ it ( 'should mark error as unhandled and capture correct metadata' , ( ) => {
131
143
// arrange
132
- const t = testHarness ( {
133
- enableErrorHandler : false ,
134
- vm : {
135
- $options : {
136
- name : 'stub-vm' ,
137
- } ,
138
- } ,
139
- } ) ;
144
+ const t = testHarness ( { vm : null } ) ;
140
145
141
146
// act
142
- t . run ( ) ;
147
+ vi . useFakeTimers ( ) ;
148
+ expect ( ( ) => t . run ( ) ) . toThrow ( DummyError ) ;
149
+ vi . runAllTimers ( ) ;
143
150
144
151
// assert
145
- t . expect . errorHandlerSpy . not . toHaveBeenCalled ( ) ;
152
+ t . expect . errorToHaveBeenCaptured ( ) . withMechanismMetadata ( { handled : false , type : 'vue-errorHandler' } ) ;
146
153
} ) ;
147
- } ) ;
148
154
149
- describe ( 'given I provided an `errorHandler`' , ( ) => {
150
- test ( 'it is called' , ( ) => {
155
+ it ( 'should mark error as handled and properly delegate to error handler' , ( ) => {
151
156
// arrange
152
157
const vm = {
153
158
$options : {
@@ -156,6 +161,7 @@ describe('attachErrorHandler', () => {
156
161
} ;
157
162
const t = testHarness ( {
158
163
enableErrorHandler : true ,
164
+ enableConsole : true ,
159
165
vm,
160
166
} ) ;
161
167
@@ -164,137 +170,102 @@ describe('attachErrorHandler', () => {
164
170
165
171
// assert
166
172
t . expect . errorHandlerSpy . toHaveBeenCalledWith ( expect . any ( Error ) , vm , 'stub-lifecycle-hook' ) ;
173
+ t . expect . errorToHaveBeenCaptured ( ) . withMechanismMetadata ( { handled : true , type : 'vue-errorHandler' } ) ;
167
174
} ) ;
168
175
} ) ;
169
176
} ) ;
170
177
171
- describe ( 'error logging' , ( ) => {
172
- describe ( 'given I disabled error logging' , ( ) => {
173
- describe ( 'when an error is captured' , ( ) => {
174
- test ( 'it logs nothing' , ( ) => {
175
- // arrange
176
- const vm = {
177
- $options : {
178
- name : 'stub-vm' ,
179
- } ,
180
- } ;
181
- const t = testHarness ( {
182
- enableWarnHandler : false ,
183
- logErrors : false ,
184
- vm,
185
- } ) ;
186
-
187
- // act
188
- t . run ( ) ;
178
+ describe ( 'error re-throwing and logging' , ( ) => {
179
+ afterEach ( ( ) => {
180
+ vi . resetAllMocks ( ) ;
181
+ } ) ;
189
182
190
- // assert
191
- t . expect . consoleErrorSpy . not . toHaveBeenCalled ( ) ;
192
- t . expect . warnHandlerSpy . not . toHaveBeenCalled ( ) ;
183
+ describe ( 'error re-throwing' , ( ) => {
184
+ it ( 'should re-throw error when no error handler exists' , ( ) => {
185
+ const t = testHarness ( {
186
+ enableErrorHandler : false ,
187
+ enableConsole : true ,
188
+ vm : { $options : { name : 'stub-vm' } } ,
193
189
} ) ;
190
+
191
+ expect ( ( ) => t . run ( ) ) . toThrow ( DummyError ) ;
194
192
} ) ;
195
- } ) ;
196
193
197
- describe ( 'given I enabled error logging' , ( ) => {
198
- describe ( 'when I provide a `warnHandler`' , ( ) => {
199
- describe ( 'when a error is captured' , ( ) => {
200
- test . each ( [
201
- [
202
- 'with wm' ,
203
- {
204
- $options : {
205
- name : 'stub-vm' ,
206
- } ,
207
- } ,
208
- generateComponentTrace ( {
209
- $options : {
210
- name : 'stub-vm' ,
211
- } ,
212
- } as ViewModel ) ,
213
- ] ,
214
- [ 'without vm' , null , '' ] ,
215
- ] ) ( 'it calls my `warnHandler` (%s)' , ( _ , vm , expectedTrace ) => {
216
- // arrange
217
- const t = testHarness ( {
218
- vm,
219
- logErrors : true ,
220
- enableWarnHandler : true ,
221
- } ) ;
194
+ it ( 'should call user-defined error handler when provided' , ( ) => {
195
+ const vm = { $options : { name : 'stub-vm' } } ;
196
+ const t = testHarness ( {
197
+ enableErrorHandler : true ,
198
+ enableConsole : true ,
199
+ vm,
200
+ } ) ;
222
201
223
- // act
224
- t . run ( ) ;
202
+ t . run ( ) ;
225
203
226
- // assert
227
- t . expect . consoleErrorSpy . not . toHaveBeenCalled ( ) ;
228
- t . expect . warnHandlerSpy . toHaveBeenCalledWith (
229
- 'Error in stub-lifecycle-hook: "DummyError: just an error"' ,
230
- vm ,
231
- expectedTrace ,
232
- ) ;
233
- } ) ;
234
- } ) ;
204
+ t . expect . errorHandlerSpy . toHaveBeenCalledWith ( expect . any ( Error ) , vm , 'stub-lifecycle-hook' ) ;
235
205
} ) ;
206
+ } ) ;
236
207
237
- describe ( 'when I do not provide a `warnHandler`' , ( ) => {
238
- describe ( "and I don't have a console" , ( ) => {
239
- test ( 'it logs nothing' , ( ) => {
240
- // arrange
241
- const vm = {
242
- $options : {
243
- name : 'stub-vm' ,
244
- } ,
245
- } ;
246
- const t = testHarness ( {
247
- vm,
248
- logErrors : true ,
249
- enableConsole : false ,
250
- } ) ;
208
+ describe ( 'error logging enabled' , ( ) => {
209
+ it ( 'should use console.error when an `errorHandler` is available' , ( ) => {
210
+ const t = testHarness ( {
211
+ vm : null ,
212
+ logErrors : true ,
213
+ enableConsole : true ,
214
+ enableErrorHandler : true ,
215
+ enableWarnHandler : false ,
216
+ } ) ;
251
217
252
- // act
253
- t . run ( ) ;
218
+ t . run ( ) ;
254
219
255
- // assert
256
- t . expect . consoleErrorSpy . not . toHaveBeenCalled ( ) ;
257
- } ) ;
220
+ t . expect . consoleErrorSpy . toHaveBeenCalledWith (
221
+ '[Vue warn]: Error in stub-lifecycle-hook: "DummyError: just an error"' ,
222
+ ) ;
223
+ } ) ;
224
+
225
+ it ( 'should prefer warn handler over console.error when both are available' , ( ) => {
226
+ const vm = { $options : { name : 'stub-vm' } } ;
227
+ const t = testHarness ( {
228
+ vm,
229
+ logErrors : true ,
230
+ enableErrorHandler : true ,
231
+ enableWarnHandler : true ,
232
+ enableConsole : true ,
258
233
} ) ;
259
234
260
- describe ( 'and I silenced logging in Vue' , ( ) => {
261
- test ( 'it logs nothing' , ( ) => {
262
- // arrange
263
- const vm = {
264
- $options : {
265
- name : 'stub-vm' ,
266
- } ,
267
- } ;
268
- const t = testHarness ( {
269
- vm,
270
- logErrors : true ,
271
- silent : true ,
272
- } ) ;
235
+ t . run ( ) ;
273
236
274
- // act
275
- t . run ( ) ;
237
+ t . expect . consoleErrorSpy . not . toHaveBeenCalled ( ) ;
238
+ t . expect . warnHandlerSpy . toHaveBeenCalledWith (
239
+ 'Error in stub-lifecycle-hook: "DummyError: just an error"' ,
240
+ vm ,
241
+ generateComponentTrace ( vm as ViewModel ) ,
242
+ ) ;
243
+ } ) ;
276
244
277
- // assert
278
- t . expect . consoleErrorSpy . not . toHaveBeenCalled ( ) ;
279
- } ) ;
245
+ it ( 'should throw error when no handler is available' , ( ) => {
246
+ const vm = { $options : { name : 'stub-vm' } } ;
247
+ const t = testHarness ( {
248
+ vm,
249
+ logErrors : true ,
250
+ silent : true ,
280
251
} ) ;
281
252
282
- test ( 'it call `console.error`' , ( ) => {
283
- // arrange
284
- const t = testHarness ( {
285
- vm : null ,
286
- logErrors : true ,
287
- enableConsole : true ,
288
- } ) ;
289
-
290
- // act
291
- t . run ( ) ;
253
+ expect ( ( ) => t . run ( ) ) . toThrow ( DummyError ) ;
254
+ } ) ;
292
255
293
- // assert
294
- t . expect . consoleErrorSpy . toHaveBeenCalledWith (
295
- '[Vue warn]: Error in stub-lifecycle-hook: "DummyError: just an error"' ,
296
- ) ;
256
+ it ( 'should fallback to console.error when warn handler is not available' , ( ) => {
257
+ const t = testHarness ( {
258
+ vm : null ,
259
+ logErrors : true ,
260
+ enableConsole : true ,
261
+ enableErrorHandler : true ,
297
262
} ) ;
263
+
264
+ t . run ( ) ;
265
+
266
+ t . expect . consoleErrorSpy . toHaveBeenCalledWith (
267
+ '[Vue warn]: Error in stub-lifecycle-hook: "DummyError: just an error"' ,
268
+ ) ;
298
269
} ) ;
299
270
} ) ;
300
271
} ) ;
@@ -393,6 +364,7 @@ const testHarness = ({
393
364
expect ( captureExceptionSpy ) . toHaveBeenCalledTimes ( 1 ) ;
394
365
const error = captureExceptionSpy . mock . calls [ 0 ] [ 0 ] ;
395
366
const contexts = captureExceptionSpy . mock . calls [ 0 ] [ 1 ] ?. captureContext . contexts ;
367
+ const mechanismMetadata = captureExceptionSpy . mock . calls [ 0 ] [ 1 ] ?. mechanism ;
396
368
397
369
expect ( error ) . toBeInstanceOf ( DummyError ) ;
398
370
@@ -403,6 +375,9 @@ const testHarness = ({
403
375
withoutProps : ( ) => {
404
376
expect ( contexts ) . not . toHaveProperty ( 'vue.propsData' ) ;
405
377
} ,
378
+ withMechanismMetadata : ( mechanism : { handled : boolean ; type : 'vue-errorHandler' } ) => {
379
+ expect ( mechanismMetadata ) . toEqual ( mechanism ) ;
380
+ } ,
406
381
} ;
407
382
} ,
408
383
} ,
0 commit comments