diff --git a/packages/browser/src/eventbuilder.ts b/packages/browser/src/eventbuilder.ts index 4db58b9b7b43..8084cd63b9d1 100644 --- a/packages/browser/src/eventbuilder.ts +++ b/packages/browser/src/eventbuilder.ts @@ -22,7 +22,8 @@ export function exceptionFromError(stackParser: StackParser, ex: Error): Excepti const frames = parseStackFrames(stackParser, ex); const exception: Exception = { - type: ex && ex.name, + // This is necessary in order to get the name of user-defined errors which subclass `Error` + type: ex && ex.constructor.name, value: extractMessage(ex), }; diff --git a/packages/browser/test/unit/eventbuilder.test.ts b/packages/browser/test/unit/eventbuilder.test.ts index ac9b564e99e0..524e683a444a 100644 --- a/packages/browser/test/unit/eventbuilder.test.ts +++ b/packages/browser/test/unit/eventbuilder.test.ts @@ -1,7 +1,7 @@ import type { Client } from '@sentry/types'; import { defaultStackParser } from '../../src'; -import { eventFromPlainObject } from '../../src/eventbuilder'; +import { eventFromPlainObject, exceptionFromError } from '../../src/eventbuilder'; jest.mock('@sentry/core', () => { const original = jest.requireActual('@sentry/core'); @@ -62,3 +62,25 @@ describe('eventFromPlainObject', () => { }); }); }); + +describe('exceptionFromError ', () => { + it('correctly reads error type and value from built-in `Error` subclass', () => { + const exceptionJSON = exceptionFromError(() => [], new TypeError("Expected type 'ChewToy', got type 'Shoe'")); + + expect(exceptionJSON).toEqual({ + type: 'TypeError', + value: "Expected type 'ChewToy', got type 'Shoe'", + }); + }); + + it('correctly reads error type and value from user-defined `Error` subclass', () => { + class DidNotFetch extends Error {} + + const exceptionJSON = exceptionFromError(() => [], new DidNotFetch("Failed to fetch requested object: 'ball'")); + + expect(exceptionJSON).toEqual({ + type: 'DidNotFetch', + value: "Failed to fetch requested object: 'ball'", + }); + }); +}); diff --git a/packages/browser/test/unit/helper/error-object.ts b/packages/browser/test/unit/helper/error-object.ts new file mode 100644 index 000000000000..1716a82f8717 --- /dev/null +++ b/packages/browser/test/unit/helper/error-object.ts @@ -0,0 +1,6 @@ +// Convert an Error-like object into something TS recognizes as an `Error`, getting around read-only properties +export function makeMockError(obj: { [key: string]: any; message: string; name: string; stack?: string }): Error { + const anyObj = obj as any; + anyObj.constructor = { name: obj.name }; + return anyObj as Error; +} diff --git a/packages/browser/test/unit/tracekit/chromium.test.ts b/packages/browser/test/unit/tracekit/chromium.test.ts index 56b5844711e7..906e773f3885 100644 --- a/packages/browser/test/unit/tracekit/chromium.test.ts +++ b/packages/browser/test/unit/tracekit/chromium.test.ts @@ -1,10 +1,11 @@ import { exceptionFromError } from '../../../src/eventbuilder'; import { defaultStackParser as parser } from '../../../src/stack-parsers'; +import { makeMockError } from '../helper/error-object'; describe('Tracekit - Chrome Tests', () => { it('should parse Chrome error with no location', () => { const NO_LOCATION = { message: 'foo', name: 'bar', stack: 'error\n at Array.forEach (native)' }; - const ex = exceptionFromError(parser, NO_LOCATION); + const ex = exceptionFromError(parser, makeMockError(NO_LOCATION)); expect(ex).toEqual({ value: 'foo', @@ -26,7 +27,7 @@ describe('Tracekit - Chrome Tests', () => { ' at http://path/to/file.js:24:4', }; - const ex = exceptionFromError(parser, CHROME_15); + const ex = exceptionFromError(parser, makeMockError(CHROME_15)); expect(ex).toEqual({ value: "Object # has no method 'undef'", @@ -53,7 +54,7 @@ describe('Tracekit - Chrome Tests', () => { ' at I.e.fn.(anonymous function) [as index] (http://localhost:8080/file.js:10:3651)', }; - const ex = exceptionFromError(parser, CHROME_36); + const ex = exceptionFromError(parser, makeMockError(CHROME_36)); expect(ex).toEqual({ value: 'Default error', @@ -99,7 +100,7 @@ describe('Tracekit - Chrome Tests', () => { ' at TESTTESTTEST.proxiedMethod(webpack:///./~/react-proxy/modules/createPrototypeProxy.js?:44:30)', }; - const ex = exceptionFromError(parser, CHROME_XX_WEBPACK); + const ex = exceptionFromError(parser, makeMockError(CHROME_XX_WEBPACK)); expect(ex).toEqual({ value: "Cannot read property 'error' of undefined", @@ -152,7 +153,7 @@ describe('Tracekit - Chrome Tests', () => { 'at http://localhost:8080/file.js:31:13\n', }; - const ex = exceptionFromError(parser, CHROME_48_EVAL); + const ex = exceptionFromError(parser, makeMockError(CHROME_48_EVAL)); expect(ex).toEqual({ value: 'message string', @@ -184,7 +185,7 @@ describe('Tracekit - Chrome Tests', () => { ' at n.handle (blob:http%3A//localhost%3A8080/abfc40e9-4742-44ed-9dcd-af8f99a29379:7:2863)', }; - const ex = exceptionFromError(parser, CHROME_48_BLOB); + const ex = exceptionFromError(parser, makeMockError(CHROME_48_BLOB)); expect(ex).toEqual({ value: 'Error: test', @@ -247,7 +248,7 @@ describe('Tracekit - Chrome Tests', () => { at examplescheme://examplehost/cd351f7250857e22ceaa.worker.js:70179:15`, }; - const ex = exceptionFromError(parser, CHROMIUM_EMBEDDED_FRAMEWORK_CUSTOM_SCHEME); + const ex = exceptionFromError(parser, makeMockError(CHROMIUM_EMBEDDED_FRAMEWORK_CUSTOM_SCHEME)); expect(ex).toEqual({ value: 'message string', @@ -277,7 +278,7 @@ describe('Tracekit - Chrome Tests', () => { at http://localhost:5000/test:24:7`, }; - const ex = exceptionFromError(parser, CHROME73_NATIVE_CODE_EXCEPTION); + const ex = exceptionFromError(parser, makeMockError(CHROME73_NATIVE_CODE_EXCEPTION)); expect(ex).toEqual({ value: 'test', @@ -310,7 +311,7 @@ describe('Tracekit - Chrome Tests', () => { at http://localhost:5000/:50:19`, }; - const ex = exceptionFromError(parser, CHROME73_EVAL_EXCEPTION); + const ex = exceptionFromError(parser, makeMockError(CHROME73_EVAL_EXCEPTION)); expect(ex).toEqual({ value: 'bad', @@ -342,7 +343,7 @@ describe('Tracekit - Chrome Tests', () => { at test (http://localhost:5000/:33:23)`, }; - const ex = exceptionFromError(parser, CHROME_109_ASYNC_URL); + const ex = exceptionFromError(parser, makeMockError(CHROME_109_ASYNC_URL)); expect(ex).toEqual({ value: 'bad', @@ -368,7 +369,7 @@ describe('Tracekit - Chrome Tests', () => { at Global code (http://localhost:5000/test:24:7)`, }; - const ex = exceptionFromError(parser, EDGE44_NATIVE_CODE_EXCEPTION); + const ex = exceptionFromError(parser, makeMockError(EDGE44_NATIVE_CODE_EXCEPTION)); expect(ex).toEqual({ value: 'test', @@ -401,7 +402,7 @@ describe('Tracekit - Chrome Tests', () => { at Anonymous function (http://localhost:5000/:50:8)`, }; - const ex = exceptionFromError(parser, EDGE44_EVAL_EXCEPTION); + const ex = exceptionFromError(parser, makeMockError(EDGE44_EVAL_EXCEPTION)); expect(ex).toEqual({ value: 'aha', @@ -437,7 +438,7 @@ describe('Tracekit - Chrome Tests', () => { at TESTTESTTEST.someMethod (C:\\Users\\user\\path\\to\\file.js:295:108)`, }; - const ex = exceptionFromError(parser, CHROME_ELECTRON_RENDERER); + const ex = exceptionFromError(parser, makeMockError(CHROME_ELECTRON_RENDERER)); expect(ex).toEqual({ value: "Cannot read property 'error' of undefined", @@ -469,7 +470,7 @@ describe('Tracekit - Chrome Tests', () => { at commitLayoutEffects (react-dom.development.js?f8c1:23426:1)`, }; - const ex = exceptionFromError(parser, EXCEPTION); + const ex = exceptionFromError(parser, makeMockError(EXCEPTION)); expect(ex).toEqual({ value: 'aha', @@ -535,7 +536,7 @@ describe('Tracekit - Chrome Tests', () => { at Array.reduce()`, }; - const ex = exceptionFromError(parser, EXCEPTION); + const ex = exceptionFromError(parser, makeMockError(EXCEPTION)); expect(ex).toEqual({ value: 'aha', @@ -582,7 +583,7 @@ describe('Tracekit - Chrome Tests', () => { at more (http://localhost:5000/(some)/(thing)/index.html:25:7)`, }; - const ex = exceptionFromError(parser, CHROME_BRACES_URL); + const ex = exceptionFromError(parser, makeMockError(CHROME_BRACES_URL)); expect(ex).toEqual({ value: 'bad', @@ -620,7 +621,7 @@ describe('Tracekit - Chrome Tests', () => { at http://localhost:5000/:50:19`, }; - const ex = exceptionFromError(parser, LONG_FRAME); + const ex = exceptionFromError(parser, makeMockError(LONG_FRAME)); expect(ex).toEqual({ value: 'bad', diff --git a/packages/browser/test/unit/tracekit/firefox.test.ts b/packages/browser/test/unit/tracekit/firefox.test.ts index f75dd7ccf010..30db06fa0a8b 100644 --- a/packages/browser/test/unit/tracekit/firefox.test.ts +++ b/packages/browser/test/unit/tracekit/firefox.test.ts @@ -1,5 +1,6 @@ import { exceptionFromError } from '../../../src/eventbuilder'; import { defaultStackParser as parser } from '../../../src/stack-parsers'; +import { makeMockError } from '../helper/error-object'; describe('Tracekit - Firefox Tests', () => { it('should parse Firefox 3 error', () => { @@ -19,7 +20,7 @@ describe('Tracekit - Firefox Tests', () => { '', }; - const ex = exceptionFromError(parser, FIREFOX_3); + const ex = exceptionFromError(parser, makeMockError(FIREFOX_3)); expect(ex).toEqual({ value: 'this.undef is not a function', @@ -55,7 +56,7 @@ describe('Tracekit - Firefox Tests', () => { '', }; - const ex = exceptionFromError(parser, FIREFOX_7); + const ex = exceptionFromError(parser, makeMockError(FIREFOX_7)); expect(ex).toEqual({ value: 'bar', @@ -87,7 +88,7 @@ describe('Tracekit - Firefox Tests', () => { lineNumber: 48, }; - const ex = exceptionFromError(parser, FIREFOX_14); + const ex = exceptionFromError(parser, makeMockError(FIREFOX_14)); expect(ex).toEqual({ value: 'x is null', @@ -116,7 +117,7 @@ describe('Tracekit - Firefox Tests', () => { columnNumber: 12, }; - const ex = exceptionFromError(parser, FIREFOX_31); + const ex = exceptionFromError(parser, makeMockError(FIREFOX_31)); expect(ex).toEqual({ value: 'Default error', @@ -151,7 +152,7 @@ describe('Tracekit - Firefox Tests', () => { result: 2147500037, }; - const ex = exceptionFromError(parser, FIREFOX_44_NS_EXCEPTION); + const ex = exceptionFromError(parser, makeMockError(FIREFOX_44_NS_EXCEPTION)); expect(ex).toEqual({ value: 'No error message', @@ -186,7 +187,7 @@ describe('Tracekit - Firefox Tests', () => { name: 'TypeError', }; - const ex = exceptionFromError(parser, FIREFOX_50_RESOURCE_URL); + const ex = exceptionFromError(parser, makeMockError(FIREFOX_50_RESOURCE_URL)); expect(ex).toEqual({ value: 'this.props.raw[this.state.dataSource].rows is undefined', @@ -234,7 +235,7 @@ describe('Tracekit - Firefox Tests', () => { '@http://localhost:8080/file.js:33:9', }; - const ex = exceptionFromError(parser, FIREFOX_43_EVAL); + const ex = exceptionFromError(parser, makeMockError(FIREFOX_43_EVAL)); expect(ex).toEqual({ value: 'message string', @@ -260,7 +261,7 @@ describe('Tracekit - Firefox Tests', () => { @http://localhost:5000/test:24:7`, }; - const stacktrace = exceptionFromError(parser, FIREFOX66_NATIVE_CODE_EXCEPTION); + const stacktrace = exceptionFromError(parser, makeMockError(FIREFOX66_NATIVE_CODE_EXCEPTION)); expect(stacktrace).toEqual({ value: 'test', @@ -290,7 +291,7 @@ describe('Tracekit - Firefox Tests', () => { @http://localhost:5000/:50:19`, }; - const stacktrace = exceptionFromError(parser, FIREFOX66_EVAL_EXCEPTION); + const stacktrace = exceptionFromError(parser, makeMockError(FIREFOX66_EVAL_EXCEPTION)); expect(stacktrace).toEqual({ value: 'aha', @@ -324,7 +325,7 @@ describe('Tracekit - Firefox Tests', () => { name: 'TypeError', }; - const stacktrace = exceptionFromError(parser, FIREFOX_FILE_IN_IDENTIFIER); + const stacktrace = exceptionFromError(parser, makeMockError(FIREFOX_FILE_IN_IDENTIFIER)); expect(stacktrace).toEqual({ stacktrace: { diff --git a/packages/browser/test/unit/tracekit/ie.test.ts b/packages/browser/test/unit/tracekit/ie.test.ts index 544542b0dcaf..445f6aad587c 100644 --- a/packages/browser/test/unit/tracekit/ie.test.ts +++ b/packages/browser/test/unit/tracekit/ie.test.ts @@ -1,5 +1,6 @@ import { exceptionFromError } from '../../../src/eventbuilder'; import { defaultStackParser as parser } from '../../../src/stack-parsers'; +import { makeMockError } from '../helper/error-object'; describe('Tracekit - IE Tests', () => { it('should parse IE 10 error', () => { @@ -15,7 +16,7 @@ describe('Tracekit - IE Tests', () => { number: -2146823281, }; - const ex = exceptionFromError(parser, IE_10); + const ex = exceptionFromError(parser, makeMockError(IE_10)); // TODO: func should be normalized expect(ex).toEqual({ @@ -44,7 +45,7 @@ describe('Tracekit - IE Tests', () => { number: -2146823281, }; - const ex = exceptionFromError(parser, IE_11); + const ex = exceptionFromError(parser, makeMockError(IE_11)); // TODO: func should be normalized expect(ex).toEqual({ @@ -73,7 +74,7 @@ describe('Tracekit - IE Tests', () => { number: -2146823279, }; - const ex = exceptionFromError(parser, IE_11_EVAL); + const ex = exceptionFromError(parser, makeMockError(IE_11_EVAL)); expect(ex).toEqual({ value: "'getExceptionProps' is undefined", diff --git a/packages/browser/test/unit/tracekit/misc.test.ts b/packages/browser/test/unit/tracekit/misc.test.ts index b092c8d10723..31dcd63f964b 100644 --- a/packages/browser/test/unit/tracekit/misc.test.ts +++ b/packages/browser/test/unit/tracekit/misc.test.ts @@ -1,5 +1,6 @@ import { exceptionFromError } from '../../../src/eventbuilder'; import { defaultStackParser as parser } from '../../../src/stack-parsers'; +import { makeMockError } from '../helper/error-object'; describe('Tracekit - Misc Tests', () => { it('should parse PhantomJS 1.19 error', () => { @@ -12,7 +13,7 @@ describe('Tracekit - Misc Tests', () => { ' at foo (http://path/to/file.js:4283)\n' + ' at http://path/to/file.js:4287', }; - const ex = exceptionFromError(parser, PHANTOMJS_1_19); + const ex = exceptionFromError(parser, makeMockError(PHANTOMJS_1_19)); expect(ex).toEqual({ value: 'bar', @@ -47,7 +48,7 @@ describe('Tracekit - Misc Tests', () => { ' at playTimer.current(./app/components/replays/replayContext.tsx:397:62)\n' + ' at sentryWrapped(../node_modules/@sentry/browser/esm/helpers.js:90:17)', }; - const ex = exceptionFromError(parser, SECURITY_ERROR); + const ex = exceptionFromError(parser, makeMockError(SECURITY_ERROR)); expect(ex).toEqual({ type: 'SecurityError', diff --git a/packages/browser/test/unit/tracekit/opera.test.ts b/packages/browser/test/unit/tracekit/opera.test.ts index e86855dc172a..f6b4ec6a2d0c 100644 --- a/packages/browser/test/unit/tracekit/opera.test.ts +++ b/packages/browser/test/unit/tracekit/opera.test.ts @@ -2,6 +2,7 @@ import { createStackParser } from '@sentry/utils'; import { exceptionFromError } from '../../../src/eventbuilder'; import { defaultStackParser, opera10StackLineParser, opera11StackLineParser } from '../../../src/stack-parsers'; +import { makeMockError } from '../helper/error-object'; const operaParser = createStackParser(opera10StackLineParser, opera11StackLineParser); const chromiumParser = defaultStackParser; @@ -30,7 +31,7 @@ describe('Tracekit - Opera Tests', () => { '', }; - const ex = exceptionFromError(operaParser, OPERA_10); + const ex = exceptionFromError(operaParser, makeMockError(OPERA_10)); expect(ex).toEqual({ value: 'Statement on line 42: Type mismatch (usually non-object value supplied where object required)', @@ -76,7 +77,7 @@ describe('Tracekit - Opera Tests', () => { ' foo();', }; - const ex = exceptionFromError(operaParser, OPERA_11); + const ex = exceptionFromError(operaParser, makeMockError(OPERA_11)); expect(ex).toEqual({ value: "'this.undef' is not a function", @@ -113,7 +114,7 @@ describe('Tracekit - Opera Tests', () => { ' dumpException3();', }; - const ex = exceptionFromError(operaParser, OPERA_12); + const ex = exceptionFromError(operaParser, makeMockError(OPERA_12)); expect(ex).toEqual({ value: "Cannot convert 'x' to object", @@ -157,7 +158,7 @@ describe('Tracekit - Opera Tests', () => { ' at bar (http://path/to/file.js:108:168)', }; - const ex = exceptionFromError(chromiumParser, OPERA_25); + const ex = exceptionFromError(chromiumParser, makeMockError(OPERA_25)); expect(ex).toEqual({ value: "Cannot read property 'undef' of null", diff --git a/packages/browser/test/unit/tracekit/react-native.test.ts b/packages/browser/test/unit/tracekit/react-native.test.ts index 9a74e46007b1..89dc026e223d 100644 --- a/packages/browser/test/unit/tracekit/react-native.test.ts +++ b/packages/browser/test/unit/tracekit/react-native.test.ts @@ -1,5 +1,6 @@ import { exceptionFromError } from '../../../src/eventbuilder'; import { defaultStackParser as parser } from '../../../src/stack-parsers'; +import { makeMockError } from '../helper/error-object'; describe('Tracekit - React Native Tests', () => { it('should parse exceptions for react-native-v8', () => { @@ -15,7 +16,7 @@ describe('Tracekit - React Native Tests', () => { at Object.y(index.android.bundle:93:571) at P(index.android.bundle:93:714)`, }; - const stacktrace = exceptionFromError(parser, REACT_NATIVE_V8_EXCEPTION); + const stacktrace = exceptionFromError(parser, makeMockError(REACT_NATIVE_V8_EXCEPTION)); expect(stacktrace).toEqual({ value: 'Manually triggered crash to test Sentry reporting', @@ -62,7 +63,7 @@ describe('Tracekit - React Native Tests', () => { p@/data/user/0/com.sentrytest/files/.expo-internal/bundle-613EDD44F3305B9D75D4679663900F2BCDDDC326F247CA3202A3A4219FD412D3:96:385 forEach@[native code]`, }; - const stacktrace = exceptionFromError(parser, REACT_NATIVE_EXPO_EXCEPTION); + const stacktrace = exceptionFromError(parser, makeMockError(REACT_NATIVE_EXPO_EXCEPTION)); expect(stacktrace).toEqual({ value: 'Test Error Expo', @@ -123,7 +124,7 @@ describe('Tracekit - React Native Tests', () => { 'at this(/home/username/sample-workspace/sampleapp.collect.react/node_modules/react-native/Libraries/Renderer/src/renderers/native/ReactNativeBaseComponent.js:74:41)\n', }; - const ex = exceptionFromError(parser, ANDROID_REACT_NATIVE); + const ex = exceptionFromError(parser, makeMockError(ANDROID_REACT_NATIVE)); expect(ex).toEqual({ value: 'Error: test', @@ -242,7 +243,7 @@ describe('Tracekit - React Native Tests', () => { '[native code]', }; - const ex = exceptionFromError(parser, ANDROID_REACT_NATIVE_PROD); + const ex = exceptionFromError(parser, makeMockError(ANDROID_REACT_NATIVE_PROD)); expect(ex).toEqual({ value: 'Error: test', @@ -353,7 +354,7 @@ describe('Tracekit - React Native Tests', () => { 'at value (address at index.android.bundle:1:32776)\n' + 'at value (address at index.android.bundle:1:31561)', }; - const ex = exceptionFromError(parser, ANDROID_REACT_NATIVE_HERMES); + const ex = exceptionFromError(parser, makeMockError(ANDROID_REACT_NATIVE_HERMES)); expect(ex).toEqual({ value: 'Error: lets throw!', diff --git a/packages/browser/test/unit/tracekit/react.test.ts b/packages/browser/test/unit/tracekit/react.test.ts index d949a4dee0eb..7a26cd49b073 100644 --- a/packages/browser/test/unit/tracekit/react.test.ts +++ b/packages/browser/test/unit/tracekit/react.test.ts @@ -1,5 +1,6 @@ import { exceptionFromError } from '../../../src/eventbuilder'; import { defaultStackParser as parser } from '../../../src/stack-parsers'; +import { makeMockError } from '../helper/error-object'; describe('Tracekit - React Tests', () => { it('should correctly parse Invariant Violation errors and use framesToPop to drop info message', () => { @@ -15,7 +16,7 @@ describe('Tracekit - React Tests', () => { at f (http://localhost:5000/:1:980)`, }; - const ex = exceptionFromError(parser, REACT_INVARIANT_VIOLATION_EXCEPTION); + const ex = exceptionFromError(parser, makeMockError(REACT_INVARIANT_VIOLATION_EXCEPTION)); expect(ex).toEqual({ value: @@ -62,7 +63,7 @@ describe('Tracekit - React Tests', () => { at f (http://localhost:5000/:1:980)`, }; - const ex = exceptionFromError(parser, REACT_PRODUCTION_ERROR); + const ex = exceptionFromError(parser, makeMockError(REACT_PRODUCTION_ERROR)); expect(ex).toEqual({ value: @@ -110,7 +111,7 @@ describe('Tracekit - React Tests', () => { at f (http://localhost:5000/:1:980)`, }; - const ex = exceptionFromError(parser, REACT_PRODUCTION_ERROR); + const ex = exceptionFromError(parser, makeMockError(REACT_PRODUCTION_ERROR)); expect(ex).toEqual({ value: diff --git a/packages/browser/test/unit/tracekit/safari.test.ts b/packages/browser/test/unit/tracekit/safari.test.ts index 657ffc7daecc..ad2526db35c5 100644 --- a/packages/browser/test/unit/tracekit/safari.test.ts +++ b/packages/browser/test/unit/tracekit/safari.test.ts @@ -1,5 +1,6 @@ import { exceptionFromError } from '../../../src/eventbuilder'; import { defaultStackParser as parser } from '../../../src/stack-parsers'; +import { makeMockError } from '../helper/error-object'; describe('Tracekit - Safari Tests', () => { it('should parse Safari 6 error', () => { @@ -15,7 +16,7 @@ describe('Tracekit - Safari Tests', () => { sourceURL: 'http://path/to/file.js', }; - const stackFrames = exceptionFromError(parser, SAFARI_6); + const stackFrames = exceptionFromError(parser, makeMockError(SAFARI_6)); expect(stackFrames).toEqual({ value: "'null' is not an object (evaluating 'x.undef')", @@ -41,7 +42,7 @@ describe('Tracekit - Safari Tests', () => { sourceURL: 'http://path/to/file.js', }; - const stackFrames = exceptionFromError(parser, SAFARI_7); + const stackFrames = exceptionFromError(parser, makeMockError(SAFARI_7)); expect(stackFrames).toEqual({ value: "'null' is not an object (evaluating 'x.undef')", @@ -67,7 +68,7 @@ describe('Tracekit - Safari Tests', () => { sourceURL: 'http://path/to/file.js', }; - const stackFrames = exceptionFromError(parser, SAFARI_8); + const stackFrames = exceptionFromError(parser, makeMockError(SAFARI_8)); expect(stackFrames).toEqual({ value: "null is not an object (evaluating 'x.undef')", @@ -97,7 +98,7 @@ describe('Tracekit - Safari Tests', () => { column: 18, }; - const stackFrames = exceptionFromError(parser, SAFARI_8_EVAL); + const stackFrames = exceptionFromError(parser, makeMockError(SAFARI_8_EVAL)); expect(stackFrames).toEqual({ value: "Can't find variable: getExceptionProps", @@ -122,7 +123,7 @@ describe('Tracekit - Safari Tests', () => { at safari-extension:(//3284871F-A480-4FFC-8BC4-3F362C752446/2665fee0/topee-content.js:3313:26)`, }; - const ex = exceptionFromError(parser, SAFARI_EXTENSION_EXCEPTION); + const ex = exceptionFromError(parser, makeMockError(SAFARI_EXTENSION_EXCEPTION)); expect(ex).toEqual({ value: 'wat', @@ -156,7 +157,7 @@ describe('Tracekit - Safari Tests', () => { safari-extension://com.grammarly.safari.extension.ext2-W8F64X92K3/ee7759dd/Grammarly.js:2:1588410 promiseReactionJob@[native code]`, }; - const ex = exceptionFromError(parser, SAFARI_EXTENSION_EXCEPTION); + const ex = exceptionFromError(parser, makeMockError(SAFARI_EXTENSION_EXCEPTION)); expect(ex).toEqual({ value: "undefined is not an object (evaluating 'e.groups.includes')", @@ -192,7 +193,7 @@ describe('Tracekit - Safari Tests', () => { at safari-web-extension:(//3284871F-A480-4FFC-8BC4-3F362C752446/2665fee0/topee-content.js:3313:26)`, }; - const ex = exceptionFromError(parser, SAFARI_WEB_EXTENSION_EXCEPTION); + const ex = exceptionFromError(parser, makeMockError(SAFARI_WEB_EXTENSION_EXCEPTION)); expect(ex).toEqual({ value: 'wat', @@ -226,7 +227,7 @@ describe('Tracekit - Safari Tests', () => { safari-web-extension://46434E60-F5BD-48A4-80C8-A422C5D16897/scripts/content-script.js:29:56027 promiseReactionJob@[native code]`, }; - const ex = exceptionFromError(parser, SAFARI_EXTENSION_EXCEPTION); + const ex = exceptionFromError(parser, makeMockError(SAFARI_EXTENSION_EXCEPTION)); expect(ex).toEqual({ value: "undefined is not an object (evaluating 'e.groups.includes')", @@ -264,7 +265,7 @@ describe('Tracekit - Safari Tests', () => { global code@http://localhost:5000/test:24:10`, }; - const ex = exceptionFromError(parser, SAFARI12_NATIVE_CODE_EXCEPTION); + const ex = exceptionFromError(parser, makeMockError(SAFARI12_NATIVE_CODE_EXCEPTION)); expect(ex).toEqual({ value: 'test', @@ -298,7 +299,7 @@ describe('Tracekit - Safari Tests', () => { http://localhost:5000/:50:29`, }; - const ex = exceptionFromError(parser, SAFARI12_EVAL_EXCEPTION); + const ex = exceptionFromError(parser, makeMockError(SAFARI12_EVAL_EXCEPTION)); expect(ex).toEqual({ value: 'aha', diff --git a/packages/node/src/eventbuilder.ts b/packages/node/src/eventbuilder.ts index f2bb1443a40f..2092baf99bec 100644 --- a/packages/node/src/eventbuilder.ts +++ b/packages/node/src/eventbuilder.ts @@ -30,7 +30,8 @@ export function parseStackFrames(stackParser: StackParser, error: Error): StackF */ export function exceptionFromError(stackParser: StackParser, error: Error): Exception { const exception: Exception = { - type: error.name || error.constructor.name, + // This is necessary in order to get the name of user-defined errors which subclass `Error` + type: error.constructor.name, value: error.message, }; diff --git a/packages/node/test/eventbuilders.test.ts b/packages/node/test/eventbuilders.test.ts index 46dfc02a3c33..1cea28bf522e 100644 --- a/packages/node/test/eventbuilders.test.ts +++ b/packages/node/test/eventbuilders.test.ts @@ -1,7 +1,7 @@ import type { Client } from '@sentry/types'; import { defaultStackParser, Scope } from '../src'; -import { eventFromUnknownInput } from '../src/eventbuilder'; +import { eventFromUnknownInput, exceptionFromError } from '../src/eventbuilder'; const testScope = new Scope(); @@ -74,3 +74,25 @@ describe('eventFromUnknownInput', () => { }); }); }); + +describe('exceptionFromError ', () => { + it('correctly reads error type and value from built-in `Error` subclass', () => { + const exceptionJSON = exceptionFromError(() => [], new TypeError("Expected type 'ChewToy', got type 'Shoe'")); + + expect(exceptionJSON).toEqual({ + type: 'TypeError', + value: "Expected type 'ChewToy', got type 'Shoe'", + }); + }); + + it('correctly reads error type and value from user-defined `Error` subclass', () => { + class DidNotFetch extends Error {} + + const exceptionJSON = exceptionFromError(() => [], new DidNotFetch("Failed to fetch requested object: 'ball'")); + + expect(exceptionJSON).toEqual({ + type: 'DidNotFetch', + value: "Failed to fetch requested object: 'ball'", + }); + }); +}); diff --git a/packages/react/src/errorboundary.tsx b/packages/react/src/errorboundary.tsx index 9a01ae110a64..c108ee4b4454 100644 --- a/packages/react/src/errorboundary.tsx +++ b/packages/react/src/errorboundary.tsx @@ -125,7 +125,12 @@ class ErrorBoundary extends React.Component { const error = mockCaptureException.mock.calls[0][0]; const cause = error.cause; expect(cause.stack).toEqual(mockCaptureException.mock.calls[0][1].contexts.react.componentStack); - expect(cause.name).toContain('React ErrorBoundary'); + expect(cause.constructor.name).toContain('React ErrorBoundary'); expect(cause.message).toEqual(error.message); }); @@ -339,7 +339,7 @@ describe('ErrorBoundary', () => { const firstError = secondError.cause; const cause = firstError.cause; expect(cause.stack).toEqual(mockCaptureException.mock.calls[0][1].contexts.react.componentStack); - expect(cause.name).toContain('React ErrorBoundary'); + expect(cause.constructor.name).toContain('React ErrorBoundary'); expect(cause.message).toEqual(thirdError.message); }); @@ -379,7 +379,7 @@ describe('ErrorBoundary', () => { const cause = error.cause; // We need to make sure that recursive error.cause does not cause infinite loop expect(cause.stack).not.toEqual(mockCaptureException.mock.calls[0][1].contexts.react.componentStack); - expect(cause.name).not.toContain('React ErrorBoundary'); + expect(cause.constructor.name).not.toContain('React ErrorBoundary'); }); it('calls `beforeCapture()` when an error occurs', () => {