diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index cb16a418..4130edaa 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -7698,8 +7698,8 @@ getJasmineRequireObj().QueueRunner = function(j$) { } QueueRunner.prototype.execute = function() { - this.handleFinalError = error => { - this.onException(error); + this.handleFinalError = (error, event) => { + this.onException(errorOrMsgForGlobalError(error, event)); }; this.globalErrors.pushListener(this.handleFinalError); this.run(0); @@ -7729,10 +7729,8 @@ getJasmineRequireObj().QueueRunner = function(j$) { this.recordError_(iterativeIndex); }; - function handleError(error) { - // TODO probably shouldn't next() right away here. - // That makes debugging async failures much more confusing. - onException(error); + function handleError(error, event) { + onException(errorOrMsgForGlobalError(error, event)); } const cleanup = once(() => { if (timeoutId !== void 0) { @@ -7918,6 +7916,17 @@ getJasmineRequireObj().QueueRunner = function(j$) { }; } + function errorOrMsgForGlobalError(error, event) { + // TODO: In cases where error is a string or undefined, the error message + // that gets sent to reporters will be `${message} thrown`, which could + // be improved to not say "thrown" when the cause wasn't necessarily + // an exception or to provide hints about throwing Errors rather than + // strings. + return ( + error || (event && event.message) || 'Global error event with no message' + ); + } + return QueueRunner; }; diff --git a/spec/core/QueueRunnerSpec.js b/spec/core/QueueRunnerSpec.js index ed743b7f..0e8b2690 100644 --- a/spec/core/QueueRunnerSpec.js +++ b/spec/core/QueueRunnerSpec.js @@ -459,6 +459,32 @@ describe('QueueRunner', function() { expect(nextQueueableFn.fn).toHaveBeenCalled(); }); + it('handles a global error event with a message but no error', function() { + const queueableFn = { + // eslint-disable-next-line no-unused-vars + fn: function(done) { + const currentHandler = globalErrors.pushListener.calls.mostRecent() + .args[0]; + currentHandler(undefined, { message: 'nope' }); + }, + timeout: 1 + }; + const onException = jasmine.createSpy('onException'); + const globalErrors = { + pushListener: jasmine.createSpy('pushListener'), + popListener: jasmine.createSpy('popListener') + }; + const queueRunner = new jasmineUnderTest.QueueRunner({ + queueableFns: [queueableFn], + onException: onException, + globalErrors: globalErrors + }); + + queueRunner.execute(); + + expect(onException).toHaveBeenCalledWith('nope'); + }); + it('handles exceptions thrown while waiting for the stack to clear', function() { const queueableFn = { fn: function(done) { @@ -492,6 +518,40 @@ describe('QueueRunner', function() { clearStack.calls.argsFor(0)[0](); expect(onException).toHaveBeenCalledWith(error); }); + + it('handles a global error event with no error while waiting for the stack to clear', function() { + const queueableFn = { + fn: function(done) { + done(); + } + }; + const errorListeners = []; + const globalErrors = { + pushListener: function(f) { + errorListeners.push(f); + }, + popListener: function() { + errorListeners.pop(); + } + }; + const clearStack = jasmine.createSpy('clearStack'); + const onException = jasmine.createSpy('onException'); + const queueRunner = new jasmineUnderTest.QueueRunner({ + queueableFns: [queueableFn], + globalErrors: globalErrors, + clearStack: clearStack, + onException: onException + }); + + queueRunner.execute(); + jasmine.clock().tick(); + expect(clearStack).toHaveBeenCalled(); + expect(errorListeners.length).toEqual(1); + errorListeners[0](undefined, { message: 'nope' }); + + clearStack.calls.argsFor(0)[0](); + expect(onException).toHaveBeenCalledWith('nope'); + }); }); describe('with a function that returns a promise', function() { diff --git a/src/core/QueueRunner.js b/src/core/QueueRunner.js index 0982c0bd..034233e6 100644 --- a/src/core/QueueRunner.js +++ b/src/core/QueueRunner.js @@ -65,8 +65,8 @@ getJasmineRequireObj().QueueRunner = function(j$) { } QueueRunner.prototype.execute = function() { - this.handleFinalError = error => { - this.onException(error); + this.handleFinalError = (error, event) => { + this.onException(errorOrMsgForGlobalError(error, event)); }; this.globalErrors.pushListener(this.handleFinalError); this.run(0); @@ -96,10 +96,8 @@ getJasmineRequireObj().QueueRunner = function(j$) { this.recordError_(iterativeIndex); }; - function handleError(error) { - // TODO probably shouldn't next() right away here. - // That makes debugging async failures much more confusing. - onException(error); + function handleError(error, event) { + onException(errorOrMsgForGlobalError(error, event)); } const cleanup = once(() => { if (timeoutId !== void 0) { @@ -285,5 +283,16 @@ getJasmineRequireObj().QueueRunner = function(j$) { }; } + function errorOrMsgForGlobalError(error, event) { + // TODO: In cases where error is a string or undefined, the error message + // that gets sent to reporters will be `${message} thrown`, which could + // be improved to not say "thrown" when the cause wasn't necessarily + // an exception or to provide hints about throwing Errors rather than + // strings. + return ( + error || (event && event.message) || 'Global error event with no message' + ); + } + return QueueRunner; };