11@TestOn ('browser' )
22library ;
33
4+ import 'dart:async' ;
45import 'dart:js_interop' ;
56import 'dart:js_interop_unsafe' ;
67
@@ -36,6 +37,94 @@ void main() {
3637 expect (firstResult, cachedResult);
3738 expect (secondResult, cachedResult);
3839 });
40+
41+ test ('sets the JS SDK name for native JS errors' , () async {
42+ final sut = await fixture.getSut ();
43+ sut.init ({'dsn' : fakeDsn});
44+
45+ const expectedMessage = 'native js captureException error' ;
46+ final client = _getClient ();
47+ if (client == null ) {
48+ throw StateError ('Sentry client is not registered' );
49+ }
50+
51+ final interceptedEvent = Completer <Map <dynamic , dynamic >>();
52+ void completeFailure (String message) {
53+ if (! interceptedEvent.isCompleted) {
54+ interceptedEvent.completeError (StateError (message));
55+ }
56+ }
57+
58+ final JSFunction beforeSendEventCallback = ((JSAny ? event, JSAny ? _) {
59+ if (interceptedEvent.isCompleted) {
60+ return ;
61+ }
62+ if (event == null ) {
63+ completeFailure ('beforeSendEvent callback received a null event.' );
64+ return ;
65+ }
66+ if (! event.isA <JSObject >()) {
67+ completeFailure (
68+ 'beforeSendEvent callback received a non-JSObject event.' );
69+ return ;
70+ }
71+
72+ final eventMap = (event as JSObject ).dartify () as Map <dynamic , dynamic >;
73+ final exception = eventMap['exception' ];
74+ if (exception is ! Map ) {
75+ completeFailure (
76+ "beforeSendEvent event is missing an 'exception' map." );
77+ return ;
78+ }
79+
80+ final values = exception['values' ];
81+ if (values is ! List || values.isEmpty) {
82+ completeFailure (
83+ "beforeSendEvent event exception map is missing a non-empty 'values' list." );
84+ return ;
85+ }
86+
87+ final firstValue = values.first;
88+ if (firstValue is ! Map ) {
89+ completeFailure (
90+ "beforeSendEvent event exception values first item is not a map." );
91+ return ;
92+ }
93+
94+ final value = firstValue['value' ];
95+ if (value == null ) {
96+ completeFailure (
97+ "beforeSendEvent event is missing exception 'value'." );
98+ return ;
99+ }
100+
101+ if (! value.toString ().contains (expectedMessage)) {
102+ completeFailure (
103+ "beforeSendEvent exception value does not contain '$expectedMessage '. Actual: '$value '" ,
104+ );
105+ return ;
106+ }
107+
108+ interceptedEvent.complete (eventMap);
109+ }).toJS;
110+ client.on ('beforeSendEvent' .toJS, beforeSendEventCallback);
111+
112+ final sentry = _globalThis['Sentry' ] as JSObject ? ;
113+ final jsError =
114+ _globalThis.callMethod ('Error' .toJS, expectedMessage.toJS);
115+ sentry! .callMethod ('captureException' .toJS, jsError);
116+
117+ final processedEvent =
118+ await interceptedEvent.future.timeout (const Duration (seconds: 5 ));
119+ final sdk = processedEvent['sdk' ] as Map <dynamic , dynamic >? ;
120+ final exception = processedEvent['exception' ] as Map <dynamic , dynamic >? ;
121+ final values = exception? ['values' ] as List <dynamic >? ;
122+ final firstValue = values? .first as Map <dynamic , dynamic >? ;
123+
124+ expect (sdk, isNotNull);
125+ expect (sdk! ['name' ], jsSdkName);
126+ expect (firstValue? ['value' ], contains (expectedMessage));
127+ });
39128 });
40129}
41130
@@ -51,3 +140,16 @@ class Fixture {
51140
52141@JS ('globalThis' )
53142external JSObject get _globalThis;
143+
144+ @JS ('Sentry.getClient' )
145+ external SentryJsClient ? _getClient ();
146+
147+ @JS ()
148+ @staticInterop
149+ class SentryJsClient {
150+ external factory SentryJsClient ();
151+ }
152+
153+ extension _SentryJsClientExtension on SentryJsClient {
154+ external void on (JSString event, JSFunction callback);
155+ }
0 commit comments