@@ -594,6 +594,49 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
594
594
j$ . debugLog = function ( msg ) {
595
595
j$ . getEnv ( ) . debugLog ( msg ) ;
596
596
} ;
597
+
598
+ /**
599
+ * Replaces Jasmine's global error handling with a spy. This prevents Jasmine
600
+ * from treating uncaught exceptions and unhandled promise rejections
601
+ * as spec failures and allows them to be inspected using the spy's
602
+ * {@link Spy#calls|calls property} and related matchers such as
603
+ * {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}.
604
+ *
605
+ * After installing the spy, spyOnGlobalErrorsAsync immediately calls its
606
+ * argument, which must be an async or promise-returning function. The spy
607
+ * will be passed as the first argument to that callback. Normal error
608
+ * handling will be restored when the promise returned from the callback is
609
+ * settled.
610
+ *
611
+ * Note: The JavaScript runtime may deliver uncaught error events and unhandled
612
+ * rejection events asynchronously, especially in browsers. If the event
613
+ * occurs after the promise returned from the callback is settled, it won't
614
+ * be routed to the spy even if the underlying error occurred previously.
615
+ * It's up to you to ensure that the returned promise isn't resolved until
616
+ * all of the error/rejection events that you want to handle have occurred.
617
+ *
618
+ * You must await the return value of spyOnGlobalErrorsAsync.
619
+ * @name jasmine.spyOnGlobalErrorsAsync
620
+ * @function
621
+ * @async
622
+ * @param {AsyncFunction } fn - A function to run, during which the global error spy will be effective
623
+ * @example
624
+ * it('demonstrates global error spies', async function() {
625
+ * await jasmine.spyOnGlobalErrorsAsync(async function(globalErrorSpy) {
626
+ * setTimeout(function() {
627
+ * throw new Error('the expected error');
628
+ * });
629
+ * await new Promise(function(resolve) {
630
+ * setTimeout(resolve);
631
+ * });
632
+ * const expected = new Error('the expected error');
633
+ * expect(globalErrorSpy).toHaveBeenCalledWith(expected);
634
+ * });
635
+ * });
636
+ */
637
+ j$ . spyOnGlobalErrorsAsync = async function ( fn ) {
638
+ await jasmine . getEnv ( ) . spyOnGlobalErrorsAsync ( fn ) ;
639
+ } ;
597
640
} ;
598
641
599
642
getJasmineRequireObj ( ) . util = function ( j$ ) {
@@ -764,13 +807,19 @@ getJasmineRequireObj().Spec = function(j$) {
764
807
765
808
Spec . prototype . addExpectationResult = function ( passed , data , isError ) {
766
809
const expectationResult = j$ . buildExpectationResult ( data ) ;
810
+
767
811
if ( passed ) {
768
812
this . result . passedExpectations . push ( expectationResult ) ;
769
813
} else {
770
814
if ( this . reportedDone ) {
771
815
this . onLateError ( expectationResult ) ;
772
816
} else {
773
817
this . result . failedExpectations . push ( expectationResult ) ;
818
+
819
+ // TODO: refactor so that we don't need to override cached status
820
+ if ( this . result . status ) {
821
+ this . result . status = 'failed' ;
822
+ }
774
823
}
775
824
776
825
if ( this . throwOnExpectationFailure && ! isError ) {
@@ -1117,9 +1166,23 @@ getJasmineRequireObj().Env = function(j$) {
1117
1166
new j$ . MockDate ( global )
1118
1167
) ;
1119
1168
1120
- const runableResources = new j$ . RunableResources ( function ( ) {
1121
- const r = runner . currentRunable ( ) ;
1122
- return r ? r . id : null ;
1169
+ const globalErrors = new j$ . GlobalErrors ( ) ;
1170
+ const installGlobalErrors = ( function ( ) {
1171
+ let installed = false ;
1172
+ return function ( ) {
1173
+ if ( ! installed ) {
1174
+ globalErrors . install ( ) ;
1175
+ installed = true ;
1176
+ }
1177
+ } ;
1178
+ } ) ( ) ;
1179
+
1180
+ const runableResources = new j$ . RunableResources ( {
1181
+ getCurrentRunableId : function ( ) {
1182
+ const r = runner . currentRunable ( ) ;
1183
+ return r ? r . id : null ;
1184
+ } ,
1185
+ globalErrors
1123
1186
} ) ;
1124
1187
1125
1188
let reporter ;
@@ -1226,20 +1289,9 @@ getJasmineRequireObj().Env = function(j$) {
1226
1289
verboseDeprecations : false
1227
1290
} ;
1228
1291
1229
- let globalErrors = null ;
1230
-
1231
- function installGlobalErrors ( ) {
1232
- if ( globalErrors ) {
1233
- return ;
1234
- }
1235
-
1236
- globalErrors = new j$ . GlobalErrors ( ) ;
1237
- globalErrors . install ( ) ;
1238
- }
1239
-
1240
1292
if ( ! options . suppressLoadErrors ) {
1241
1293
installGlobalErrors ( ) ;
1242
- globalErrors . pushListener ( function (
1294
+ globalErrors . pushListener ( function loadtimeErrorHandler (
1243
1295
message ,
1244
1296
filename ,
1245
1297
lineno ,
@@ -1712,6 +1764,47 @@ getJasmineRequireObj().Env = function(j$) {
1712
1764
) ;
1713
1765
} ;
1714
1766
1767
+ this . spyOnGlobalErrorsAsync = async function ( fn ) {
1768
+ const spy = this . createSpy ( 'global error handler' ) ;
1769
+ const associatedRunable = runner . currentRunable ( ) ;
1770
+ let cleanedUp = false ;
1771
+
1772
+ globalErrors . setOverrideListener ( spy , ( ) => {
1773
+ if ( ! cleanedUp ) {
1774
+ const message =
1775
+ 'Global error spy was not uninstalled. (Did you ' +
1776
+ 'forget to await the return value of spyOnGlobalErrorsAsync?)' ;
1777
+ associatedRunable . addExpectationResult ( false , {
1778
+ matcherName : '' ,
1779
+ passed : false ,
1780
+ expected : '' ,
1781
+ actual : '' ,
1782
+ message,
1783
+ error : null
1784
+ } ) ;
1785
+ }
1786
+
1787
+ cleanedUp = true ;
1788
+ } ) ;
1789
+
1790
+ try {
1791
+ const maybePromise = fn ( spy ) ;
1792
+
1793
+ if ( ! j$ . isPromiseLike ( maybePromise ) ) {
1794
+ throw new Error (
1795
+ 'The callback to spyOnGlobalErrorsAsync must be an async or promise-returning function'
1796
+ ) ;
1797
+ }
1798
+
1799
+ await maybePromise ;
1800
+ } finally {
1801
+ if ( ! cleanedUp ) {
1802
+ cleanedUp = true ;
1803
+ globalErrors . removeOverrideListener ( ) ;
1804
+ }
1805
+ }
1806
+ } ;
1807
+
1715
1808
function ensureIsNotNested ( method ) {
1716
1809
const runable = runner . currentRunable ( ) ;
1717
1810
if ( runable !== null && runable !== undefined ) {
@@ -3853,18 +3946,26 @@ getJasmineRequireObj().formatErrorMsg = function() {
3853
3946
3854
3947
getJasmineRequireObj ( ) . GlobalErrors = function ( j$ ) {
3855
3948
function GlobalErrors ( global ) {
3856
- const handlers = [ ] ;
3857
3949
global = global || j$ . getGlobal ( ) ;
3858
3950
3859
- const onerror = function onerror ( ) {
3951
+ const handlers = [ ] ;
3952
+ let overrideHandler = null ,
3953
+ onRemoveOverrideHandler = null ;
3954
+
3955
+ function onerror ( message , source , lineno , colno , error ) {
3956
+ if ( overrideHandler ) {
3957
+ overrideHandler ( error || message ) ;
3958
+ return ;
3959
+ }
3960
+
3860
3961
const handler = handlers [ handlers . length - 1 ] ;
3861
3962
3862
3963
if ( handler ) {
3863
3964
handler . apply ( null , Array . prototype . slice . call ( arguments , 0 ) ) ;
3864
3965
} else {
3865
3966
throw arguments [ 0 ] ;
3866
3967
}
3867
- } ;
3968
+ }
3868
3969
3869
3970
this . originalHandlers = { } ;
3870
3971
this . jasmineHandlers = { } ;
@@ -3895,6 +3996,11 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
3895
3996
3896
3997
const handler = handlers [ handlers . length - 1 ] ;
3897
3998
3999
+ if ( overrideHandler ) {
4000
+ overrideHandler ( error ) ;
4001
+ return ;
4002
+ }
4003
+
3898
4004
if ( handler ) {
3899
4005
handler ( error ) ;
3900
4006
} else {
@@ -3979,6 +4085,24 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
3979
4085
3980
4086
handlers . pop ( ) ;
3981
4087
} ;
4088
+
4089
+ this . setOverrideListener = function ( listener , onRemove ) {
4090
+ if ( overrideHandler ) {
4091
+ throw new Error ( "Can't set more than one override listener at a time" ) ;
4092
+ }
4093
+
4094
+ overrideHandler = listener ;
4095
+ onRemoveOverrideHandler = onRemove ;
4096
+ } ;
4097
+
4098
+ this . removeOverrideListener = function ( ) {
4099
+ if ( onRemoveOverrideHandler ) {
4100
+ onRemoveOverrideHandler ( ) ;
4101
+ }
4102
+
4103
+ overrideHandler = null ;
4104
+ onRemoveOverrideHandler = null ;
4105
+ } ;
3982
4106
}
3983
4107
3984
4108
return GlobalErrors ;
@@ -8083,9 +8207,10 @@ getJasmineRequireObj().interface = function(jasmine, env) {
8083
8207
8084
8208
getJasmineRequireObj ( ) . RunableResources = function ( j$ ) {
8085
8209
class RunableResources {
8086
- constructor ( getCurrentRunableId ) {
8210
+ constructor ( options ) {
8087
8211
this . byRunableId_ = { } ;
8088
- this . getCurrentRunableId_ = getCurrentRunableId ;
8212
+ this . getCurrentRunableId_ = options . getCurrentRunableId ;
8213
+ this . globalErrors_ = options . globalErrors ;
8089
8214
8090
8215
this . spyFactory = new j$ . SpyFactory (
8091
8216
( ) => {
@@ -8136,6 +8261,7 @@ getJasmineRequireObj().RunableResources = function(j$) {
8136
8261
}
8137
8262
8138
8263
clearForRunable ( runableId ) {
8264
+ this . globalErrors_ . removeOverrideListener ( ) ;
8139
8265
this . spyRegistry . clearSpies ( ) ;
8140
8266
delete this . byRunableId_ [ runableId ] ;
8141
8267
}
@@ -9597,6 +9723,11 @@ getJasmineRequireObj().Suite = function(j$) {
9597
9723
this . onLateError ( expectationResult ) ;
9598
9724
} else {
9599
9725
this . result . failedExpectations . push ( expectationResult ) ;
9726
+
9727
+ // TODO: refactor so that we don't need to override cached status
9728
+ if ( this . result . status ) {
9729
+ this . result . status = 'failed' ;
9730
+ }
9600
9731
}
9601
9732
9602
9733
if ( this . throwOnExpectationFailure ) {
0 commit comments