Breaking change: use addEventListener rather than setting window.onerror
* Generally simplifies error handling in browsers * Makes Jasmine's own integration tests easier to debug * Stack traces will be provided for more global errors * ... but less error information will be provided in some browsers if the error comes from a file:// URL (use `npx serve` or similar instead) * Jasmine will no longer override existing onerror handlers in browsers * Setting window.onerror will no longer override Jasmine's global error handling (use jasmine.spyOnGlobalErrors instead)
This commit is contained in:
@@ -1291,20 +1291,14 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
|
||||
if (!options.suppressLoadErrors) {
|
||||
installGlobalErrors();
|
||||
globalErrors.pushListener(function loadtimeErrorHandler(
|
||||
message,
|
||||
filename,
|
||||
lineno,
|
||||
colNo,
|
||||
err
|
||||
) {
|
||||
globalErrors.pushListener(function loadtimeErrorHandler(error, event) {
|
||||
topSuite.result.failedExpectations.push({
|
||||
passed: false,
|
||||
globalErrorType: 'load',
|
||||
message: message,
|
||||
stack: err && err.stack,
|
||||
filename: filename,
|
||||
lineno: lineno
|
||||
message: error ? error.message : event.message,
|
||||
stack: error && error.stack,
|
||||
filename: event && event.filename,
|
||||
lineno: event && event.lineno
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -3978,18 +3972,22 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
|
||||
let overrideHandler = null,
|
||||
onRemoveOverrideHandler = null;
|
||||
|
||||
function onerror(message, source, lineno, colno, error) {
|
||||
function onBrowserError(event) {
|
||||
dispatchBrowserError(event.error, event);
|
||||
}
|
||||
|
||||
function dispatchBrowserError(error, event) {
|
||||
if (overrideHandler) {
|
||||
overrideHandler(error || message);
|
||||
overrideHandler(error);
|
||||
return;
|
||||
}
|
||||
|
||||
const handler = handlers[handlers.length - 1];
|
||||
|
||||
if (handler) {
|
||||
handler.apply(null, Array.prototype.slice.call(arguments, 0));
|
||||
handler(error, event);
|
||||
} else {
|
||||
throw arguments[0];
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4066,8 +4064,7 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
|
||||
this.installOne_('uncaughtException', 'Uncaught exception');
|
||||
this.installOne_('unhandledRejection', 'Unhandled promise rejection');
|
||||
} else {
|
||||
const originalHandler = global.onerror;
|
||||
global.onerror = onerror;
|
||||
global.addEventListener('error', onBrowserError);
|
||||
|
||||
const browserRejectionHandler = function browserRejectionHandler(
|
||||
event
|
||||
@@ -4075,16 +4072,19 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
|
||||
if (j$.isError_(event.reason)) {
|
||||
event.reason.jasmineMessage =
|
||||
'Unhandled promise rejection: ' + event.reason;
|
||||
global.onerror(event.reason);
|
||||
dispatchBrowserError(event.reason, event);
|
||||
} else {
|
||||
global.onerror('Unhandled promise rejection: ' + event.reason);
|
||||
dispatchBrowserError(
|
||||
'Unhandled promise rejection: ' + event.reason,
|
||||
event
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
global.addEventListener('unhandledrejection', browserRejectionHandler);
|
||||
|
||||
this.uninstall = function uninstall() {
|
||||
global.onerror = originalHandler;
|
||||
global.removeEventListener('error', onBrowserError);
|
||||
global.removeEventListener(
|
||||
'unhandledrejection',
|
||||
browserRejectionHandler
|
||||
@@ -4093,6 +4093,13 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
|
||||
}
|
||||
};
|
||||
|
||||
// The listener at the top of the stack will be called with two arguments:
|
||||
// the error and the event. Either of them may be falsy.
|
||||
// The error will normally be provided, but will be falsy in the case of
|
||||
// some browser load-time errors. The event will normally be provided in
|
||||
// browsers but will be falsy in Node.
|
||||
// Listeners that are pushed after spec files have been loaded should be
|
||||
// able to just use the error parameter.
|
||||
this.pushListener = function pushListener(listener) {
|
||||
handlers.push(listener);
|
||||
};
|
||||
@@ -7490,11 +7497,8 @@ getJasmineRequireObj().QueueRunner = function(j$) {
|
||||
}
|
||||
|
||||
QueueRunner.prototype.execute = function() {
|
||||
this.handleFinalError = (message, source, lineno, colno, error) => {
|
||||
// Older browsers would send the error as the first parameter. HTML5
|
||||
// specifies the the five parameters above. The error instance should
|
||||
// be preffered, otherwise the call stack would get lost.
|
||||
this.onException(error || message);
|
||||
this.handleFinalError = error => {
|
||||
this.onException(error);
|
||||
};
|
||||
this.globalErrors.pushListener(this.handleFinalError);
|
||||
this.run(0);
|
||||
@@ -10448,5 +10452,5 @@ getJasmineRequireObj().UserContext = function(j$) {
|
||||
};
|
||||
|
||||
getJasmineRequireObj().version = function() {
|
||||
return '4.3.0';
|
||||
return '5.0.0-dev.0';
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jasmine-core",
|
||||
"license": "MIT",
|
||||
"version": "4.3.0",
|
||||
"version": "5.0.0-dev.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/jasmine/jasmine.git"
|
||||
|
||||
@@ -1,56 +1,42 @@
|
||||
describe('GlobalErrors', function() {
|
||||
it('calls the added handler on error', function() {
|
||||
const fakeGlobal = minimalBrowserGlobal();
|
||||
const fakeGlobal = browserGlobal();
|
||||
const handler = jasmine.createSpy('errorHandler');
|
||||
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
|
||||
|
||||
errors.install();
|
||||
errors.pushListener(handler);
|
||||
|
||||
fakeGlobal.onerror('foo');
|
||||
|
||||
expect(handler).toHaveBeenCalledWith('foo');
|
||||
});
|
||||
|
||||
it('enables external interception of error by overriding global.onerror', function() {
|
||||
const fakeGlobal = minimalBrowserGlobal();
|
||||
const handler = jasmine.createSpy('errorHandler');
|
||||
const hijackHandler = jasmine.createSpy('hijackErrorHandler');
|
||||
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
|
||||
|
||||
errors.install();
|
||||
errors.pushListener(handler);
|
||||
|
||||
fakeGlobal.onerror = hijackHandler;
|
||||
|
||||
fakeGlobal.onerror('foo');
|
||||
|
||||
expect(hijackHandler).toHaveBeenCalledWith('foo');
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls the global error handler with all parameters', function() {
|
||||
const fakeGlobal = minimalBrowserGlobal();
|
||||
const handler = jasmine.createSpy('errorHandler');
|
||||
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
|
||||
const fooError = new Error('foo');
|
||||
|
||||
errors.install();
|
||||
errors.pushListener(handler);
|
||||
|
||||
fakeGlobal.onerror(fooError.message, 'foo.js', 1, 1, fooError);
|
||||
const error = new Error('nope');
|
||||
dispatchErrorEvent(fakeGlobal, { error });
|
||||
|
||||
expect(handler).toHaveBeenCalledWith(
|
||||
fooError.message,
|
||||
'foo.js',
|
||||
1,
|
||||
1,
|
||||
fooError
|
||||
jasmine.is(error),
|
||||
jasmine.objectContaining({ error: jasmine.is(error) })
|
||||
);
|
||||
});
|
||||
|
||||
it('is not affected by overriding global.onerror', function() {
|
||||
const fakeGlobal = browserGlobal();
|
||||
const handler = jasmine.createSpy('errorHandler');
|
||||
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
|
||||
|
||||
errors.install();
|
||||
errors.pushListener(handler);
|
||||
|
||||
fakeGlobal.onerror = () => {};
|
||||
|
||||
const error = new Error('nope');
|
||||
dispatchErrorEvent(fakeGlobal, { error });
|
||||
|
||||
expect(handler).toHaveBeenCalledWith(
|
||||
jasmine.is(error),
|
||||
jasmine.objectContaining({ error: jasmine.is(error) })
|
||||
);
|
||||
});
|
||||
|
||||
it('only calls the most recent handler', function() {
|
||||
const fakeGlobal = minimalBrowserGlobal();
|
||||
const fakeGlobal = browserGlobal();
|
||||
const handler1 = jasmine.createSpy('errorHandler1');
|
||||
const handler2 = jasmine.createSpy('errorHandler2');
|
||||
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
|
||||
@@ -59,14 +45,18 @@ describe('GlobalErrors', function() {
|
||||
errors.pushListener(handler1);
|
||||
errors.pushListener(handler2);
|
||||
|
||||
fakeGlobal.onerror('foo');
|
||||
const error = new Error('nope');
|
||||
dispatchErrorEvent(fakeGlobal, { error });
|
||||
|
||||
expect(handler1).not.toHaveBeenCalled();
|
||||
expect(handler2).toHaveBeenCalledWith('foo');
|
||||
expect(handler2).toHaveBeenCalledWith(
|
||||
jasmine.is(error),
|
||||
jasmine.objectContaining({ error: jasmine.is(error) })
|
||||
);
|
||||
});
|
||||
|
||||
it('calls previous handlers when one is removed', function() {
|
||||
const fakeGlobal = minimalBrowserGlobal();
|
||||
const fakeGlobal = browserGlobal();
|
||||
const handler1 = jasmine.createSpy('errorHandler1');
|
||||
const handler2 = jasmine.createSpy('errorHandler2');
|
||||
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
|
||||
@@ -77,9 +67,13 @@ describe('GlobalErrors', function() {
|
||||
|
||||
errors.popListener(handler2);
|
||||
|
||||
fakeGlobal.onerror('foo');
|
||||
const error = new Error('nope');
|
||||
dispatchErrorEvent(fakeGlobal, { error });
|
||||
|
||||
expect(handler1).toHaveBeenCalledWith('foo');
|
||||
expect(handler1).toHaveBeenCalledWith(
|
||||
jasmine.is(error),
|
||||
jasmine.objectContaining({ error: jasmine.is(error) })
|
||||
);
|
||||
expect(handler2).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -90,34 +84,27 @@ describe('GlobalErrors', function() {
|
||||
}).toThrowError('popListener expects a listener');
|
||||
});
|
||||
|
||||
it('uninstalls itself, putting back a previous callback', function() {
|
||||
const originalCallback = jasmine.createSpy('error');
|
||||
const fakeGlobal = {
|
||||
...minimalBrowserGlobal(),
|
||||
onerror: originalCallback
|
||||
};
|
||||
it('uninstalls itself', function() {
|
||||
const fakeGlobal = browserGlobal();
|
||||
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
|
||||
|
||||
expect(fakeGlobal.onerror).toBe(originalCallback);
|
||||
function unrelatedListener() {}
|
||||
|
||||
errors.install();
|
||||
|
||||
expect(fakeGlobal.onerror).not.toBe(originalCallback);
|
||||
|
||||
fakeGlobal.addEventListener('error', unrelatedListener);
|
||||
errors.uninstall();
|
||||
|
||||
expect(fakeGlobal.onerror).toBe(originalCallback);
|
||||
expect(fakeGlobal.listeners_.error).toEqual([unrelatedListener]);
|
||||
});
|
||||
|
||||
it('rethrows the original error when there is no handler', function() {
|
||||
const fakeGlobal = minimalBrowserGlobal();
|
||||
const fakeGlobal = browserGlobal();
|
||||
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
|
||||
const originalError = new Error('nope');
|
||||
|
||||
errors.install();
|
||||
|
||||
try {
|
||||
fakeGlobal.onerror(originalError);
|
||||
dispatchErrorEvent(fakeGlobal, { error: originalError });
|
||||
} catch (e) {
|
||||
expect(e).toBe(originalError);
|
||||
}
|
||||
@@ -289,128 +276,61 @@ describe('GlobalErrors', function() {
|
||||
|
||||
describe('Reporting unhandled promise rejections in the browser', function() {
|
||||
it('subscribes and unsubscribes from the unhandledrejection event', function() {
|
||||
const fakeGlobal = jasmine.createSpyObj('globalErrors', [
|
||||
'addEventListener',
|
||||
'removeEventListener',
|
||||
'onerror'
|
||||
]),
|
||||
errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
|
||||
const fakeGlobal = browserGlobal();
|
||||
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
|
||||
|
||||
errors.install();
|
||||
expect(fakeGlobal.addEventListener).toHaveBeenCalledWith(
|
||||
'unhandledrejection',
|
||||
expect(fakeGlobal.listeners_.unhandledrejection).toEqual([
|
||||
jasmine.any(Function)
|
||||
);
|
||||
]);
|
||||
|
||||
const addedListener = fakeGlobal.addEventListener.calls.argsFor(0)[1];
|
||||
errors.uninstall();
|
||||
|
||||
expect(fakeGlobal.removeEventListener).toHaveBeenCalledWith(
|
||||
'unhandledrejection',
|
||||
addedListener
|
||||
);
|
||||
expect(fakeGlobal.listeners_.unhandledrejection).toEqual([]);
|
||||
});
|
||||
|
||||
it('reports rejections whose reason is a string', function() {
|
||||
const fakeGlobal = jasmine.createSpyObj('globalErrors', [
|
||||
'addEventListener',
|
||||
'removeEventListener',
|
||||
'onerror'
|
||||
]),
|
||||
handler = jasmine.createSpy('errorHandler'),
|
||||
errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
|
||||
const fakeGlobal = browserGlobal();
|
||||
const handler = jasmine.createSpy('errorHandler');
|
||||
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
|
||||
|
||||
errors.install();
|
||||
errors.pushListener(handler);
|
||||
|
||||
const addedListener = fakeGlobal.addEventListener.calls.argsFor(0)[1];
|
||||
addedListener({ reason: 'nope' });
|
||||
const event = { reason: 'nope' };
|
||||
dispatchUnhandledRejectionEvent(fakeGlobal, event);
|
||||
|
||||
expect(handler).toHaveBeenCalledWith('Unhandled promise rejection: nope');
|
||||
expect(handler).toHaveBeenCalledWith(
|
||||
'Unhandled promise rejection: nope',
|
||||
event
|
||||
);
|
||||
});
|
||||
|
||||
it('reports rejections whose reason is an Error', function() {
|
||||
const fakeGlobal = jasmine.createSpyObj('globalErrors', [
|
||||
'addEventListener',
|
||||
'removeEventListener',
|
||||
'onerror'
|
||||
]),
|
||||
handler = jasmine.createSpy('errorHandler'),
|
||||
errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
|
||||
const fakeGlobal = browserGlobal();
|
||||
const handler = jasmine.createSpy('errorHandler');
|
||||
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
|
||||
|
||||
errors.install();
|
||||
errors.pushListener(handler);
|
||||
|
||||
const addedListener = fakeGlobal.addEventListener.calls.argsFor(0)[1];
|
||||
const reason = new Error('bar');
|
||||
|
||||
addedListener({ reason: reason });
|
||||
const event = { reason };
|
||||
dispatchUnhandledRejectionEvent(fakeGlobal, event);
|
||||
|
||||
expect(handler).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
jasmineMessage: 'Unhandled promise rejection: Error: bar',
|
||||
message: reason.message,
|
||||
stack: reason.stack
|
||||
})
|
||||
}),
|
||||
event
|
||||
);
|
||||
});
|
||||
|
||||
describe('Enabling external interception of reported rejections by overriding global.onerror', function() {
|
||||
it('overriding global.onerror intercepts rejections whose reason is a string', function() {
|
||||
const fakeGlobal = jasmine.createSpyObj('globalErrors', [
|
||||
'addEventListener'
|
||||
]),
|
||||
handler = jasmine.createSpy('errorHandler'),
|
||||
hijackHandler = jasmine.createSpy('hijackErrorHandler'),
|
||||
errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
|
||||
|
||||
errors.install();
|
||||
errors.pushListener(handler);
|
||||
|
||||
fakeGlobal.onerror = hijackHandler;
|
||||
|
||||
const addedListener = fakeGlobal.addEventListener.calls.argsFor(0)[1];
|
||||
addedListener({ reason: 'nope' });
|
||||
|
||||
expect(hijackHandler).toHaveBeenCalledWith(
|
||||
'Unhandled promise rejection: nope'
|
||||
);
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('overriding global.onerror intercepts rejections whose reason is an Error', function() {
|
||||
const fakeGlobal = jasmine.createSpyObj('globalErrors', [
|
||||
'addEventListener'
|
||||
]),
|
||||
handler = jasmine.createSpy('errorHandler'),
|
||||
hijackHandler = jasmine.createSpy('hijackErrorHandler'),
|
||||
errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
|
||||
|
||||
errors.install();
|
||||
errors.pushListener(handler);
|
||||
|
||||
fakeGlobal.onerror = hijackHandler;
|
||||
|
||||
const addedListener = fakeGlobal.addEventListener.calls.argsFor(0)[1];
|
||||
const reason = new Error('bar');
|
||||
|
||||
addedListener({ reason: reason });
|
||||
|
||||
expect(hijackHandler).toHaveBeenCalledWith(
|
||||
jasmine.objectContaining({
|
||||
jasmineMessage: 'Unhandled promise rejection: Error: bar',
|
||||
message: reason.message,
|
||||
stack: reason.stack
|
||||
})
|
||||
);
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#setOverrideListener', function() {
|
||||
it('overrides the existing handlers in browsers until removed', function() {
|
||||
const fakeGlobal = minimalBrowserGlobal();
|
||||
const fakeGlobal = browserGlobal();
|
||||
const handler0 = jasmine.createSpy('handler0');
|
||||
const handler1 = jasmine.createSpy('handler1');
|
||||
const overrideHandler = jasmine.createSpy('overrideHandler');
|
||||
@@ -420,19 +340,18 @@ describe('GlobalErrors', function() {
|
||||
errors.pushListener(handler0);
|
||||
errors.setOverrideListener(overrideHandler, () => {});
|
||||
errors.pushListener(handler1);
|
||||
fakeGlobal.onerror('foo');
|
||||
fakeGlobal.onerror(null, null, null, null, new Error('bar'));
|
||||
dispatchErrorEvent(fakeGlobal, { error: 'foo' });
|
||||
|
||||
expect(overrideHandler).toHaveBeenCalledWith('foo');
|
||||
expect(overrideHandler).toHaveBeenCalledWith(new Error('bar'));
|
||||
expect(handler0).not.toHaveBeenCalled();
|
||||
expect(handler1).not.toHaveBeenCalled();
|
||||
|
||||
errors.removeOverrideListener();
|
||||
|
||||
fakeGlobal.onerror('baz');
|
||||
const event = { error: 'baz' };
|
||||
dispatchErrorEvent(fakeGlobal, event);
|
||||
expect(overrideHandler).not.toHaveBeenCalledWith('baz');
|
||||
expect(handler1).toHaveBeenCalledWith('baz');
|
||||
expect(handler1).toHaveBeenCalledWith('baz', event);
|
||||
});
|
||||
|
||||
it('overrides the existing handlers in Node until removed', function() {
|
||||
@@ -532,7 +451,7 @@ describe('GlobalErrors', function() {
|
||||
});
|
||||
|
||||
it('throws if there is already an override handler', function() {
|
||||
const errors = new jasmineUnderTest.GlobalErrors(minimalBrowserGlobal());
|
||||
const errors = new jasmineUnderTest.GlobalErrors(browserGlobal());
|
||||
|
||||
errors.setOverrideListener(() => {}, () => {});
|
||||
expect(function() {
|
||||
@@ -544,7 +463,7 @@ describe('GlobalErrors', function() {
|
||||
describe('#removeOverrideListener', function() {
|
||||
it("calls the handler's onRemove callback", function() {
|
||||
const onRemove = jasmine.createSpy('onRemove');
|
||||
const errors = new jasmineUnderTest.GlobalErrors(minimalBrowserGlobal());
|
||||
const errors = new jasmineUnderTest.GlobalErrors(browserGlobal());
|
||||
|
||||
errors.setOverrideListener(() => {}, onRemove);
|
||||
errors.removeOverrideListener();
|
||||
@@ -553,17 +472,43 @@ describe('GlobalErrors', function() {
|
||||
});
|
||||
|
||||
it('does not throw if there is no handler', function() {
|
||||
const errors = new jasmineUnderTest.GlobalErrors(minimalBrowserGlobal());
|
||||
const errors = new jasmineUnderTest.GlobalErrors(browserGlobal());
|
||||
|
||||
expect(() => errors.removeOverrideListener()).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
function minimalBrowserGlobal() {
|
||||
function browserGlobal() {
|
||||
return {
|
||||
addEventListener() {},
|
||||
removeEventListener() {},
|
||||
onerror: null
|
||||
listeners_: { error: [], unhandledrejection: [] },
|
||||
addEventListener(eventName, listener) {
|
||||
this.listeners_[eventName].push(listener);
|
||||
},
|
||||
removeEventListener(eventName, listener) {
|
||||
this.listeners_[eventName] = this.listeners_[eventName].filter(
|
||||
l => l !== listener
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function dispatchErrorEvent(global, event) {
|
||||
expect(global.listeners_.error.length)
|
||||
.withContext('number of error listeners')
|
||||
.toBeGreaterThan(0);
|
||||
|
||||
for (const l of global.listeners_.error) {
|
||||
l(event);
|
||||
}
|
||||
}
|
||||
|
||||
function dispatchUnhandledRejectionEvent(global, event) {
|
||||
expect(global.listeners_.unhandledrejection.length)
|
||||
.withContext('number of unhandledrejection listeners')
|
||||
.toBeGreaterThan(0);
|
||||
|
||||
for (const l of global.listeners_.unhandledrejection) {
|
||||
l(event);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -651,7 +651,7 @@ describe('QueueRunner', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('passes the error instance to exception handlers in HTML browsers', function() {
|
||||
it('passes final errors to exception handlers', function() {
|
||||
const error = new Error('fake error'),
|
||||
onExceptionCallback = jasmine.createSpy('on exception callback'),
|
||||
queueRunner = new jasmineUnderTest.QueueRunner({
|
||||
@@ -659,24 +659,11 @@ describe('QueueRunner', function() {
|
||||
});
|
||||
|
||||
queueRunner.execute();
|
||||
queueRunner.handleFinalError(error.message, 'fake.js', 1, 1, error);
|
||||
queueRunner.handleFinalError(error);
|
||||
|
||||
expect(onExceptionCallback).toHaveBeenCalledWith(error);
|
||||
});
|
||||
|
||||
it('passes the first argument to exception handlers for compatibility', function() {
|
||||
const error = new Error('fake error'),
|
||||
onExceptionCallback = jasmine.createSpy('on exception callback'),
|
||||
queueRunner = new jasmineUnderTest.QueueRunner({
|
||||
onException: onExceptionCallback
|
||||
});
|
||||
|
||||
queueRunner.execute();
|
||||
queueRunner.handleFinalError(error.message);
|
||||
|
||||
expect(onExceptionCallback).toHaveBeenCalledWith(error.message);
|
||||
});
|
||||
|
||||
it('calls exception handlers when an exception is thrown in a fn', function() {
|
||||
const queueableFn = {
|
||||
type: 'queueable',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
describe('Env integration', function() {
|
||||
let env;
|
||||
const isBrowser = typeof window !== 'undefined';
|
||||
|
||||
beforeEach(function() {
|
||||
jasmine.getEnv().registerIntegrationMatchers();
|
||||
@@ -455,7 +456,7 @@ describe('Env integration', function() {
|
||||
env.describe('A suite', function() {
|
||||
env.it('fails', function(specDone) {
|
||||
setTimeout(function() {
|
||||
global.onerror('fail');
|
||||
dispatchErrorEvent(global, { error: 'fail' });
|
||||
specDone();
|
||||
});
|
||||
});
|
||||
@@ -509,10 +510,14 @@ describe('Env integration', function() {
|
||||
},
|
||||
specDone: function() {
|
||||
clearStackCallbacks[clearStackCallCount + 1] = function() {
|
||||
global.onerror('fail at the end of the reporter queue');
|
||||
dispatchErrorEvent(global, {
|
||||
error: 'fail at the end of the reporter queue'
|
||||
});
|
||||
};
|
||||
clearStackCallbacks[clearStackCallCount + 2] = function() {
|
||||
global.onerror('fail at the end of the spec queue');
|
||||
dispatchErrorEvent(global, {
|
||||
error: 'fail at the end of the spec queue'
|
||||
});
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -559,7 +564,7 @@ describe('Env integration', function() {
|
||||
specDone();
|
||||
queueMicrotask(function() {
|
||||
queueMicrotask(function() {
|
||||
global.onerror('fail');
|
||||
dispatchErrorEvent(global, { error: 'fail' });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -622,10 +627,14 @@ describe('Env integration', function() {
|
||||
|
||||
if (result.description === 'A nested suite') {
|
||||
clearStackCallbacks[clearStackCallCount + 1] = function() {
|
||||
global.onerror('fail at the end of the reporter queue');
|
||||
dispatchErrorEvent(global, {
|
||||
error: 'fail at the end of the reporter queue'
|
||||
});
|
||||
};
|
||||
clearStackCallbacks[clearStackCallCount + 2] = function() {
|
||||
global.onerror('fail at the end of the suite queue');
|
||||
dispatchErrorEvent(global, {
|
||||
error: 'fail at the end of the suite queue'
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -668,7 +677,7 @@ describe('Env integration', function() {
|
||||
|
||||
env.addReporter({
|
||||
jasmineDone: function() {
|
||||
global.onerror('a very late error');
|
||||
dispatchErrorEvent(global, { error: 'a very late error' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -720,7 +729,7 @@ describe('Env integration', function() {
|
||||
expectedErrors.push(`${msg} thrown`);
|
||||
}
|
||||
|
||||
global.onerror(msg);
|
||||
dispatchErrorEvent(global, { error: msg });
|
||||
realClearStack(fn);
|
||||
});
|
||||
spyOn(console, 'error');
|
||||
@@ -2524,15 +2533,24 @@ describe('Env integration', function() {
|
||||
);
|
||||
});
|
||||
|
||||
await env.execute();
|
||||
await jasmine.spyOnGlobalErrorsAsync(async function(globalErrorSpy) {
|
||||
await env.execute();
|
||||
|
||||
if (isBrowser) {
|
||||
// Verify that there were no unexpected errors
|
||||
expect(globalErrorSpy).toHaveBeenCalledTimes(2);
|
||||
expect(globalErrorSpy).toHaveBeenCalledWith(new Error('suite'));
|
||||
expect(globalErrorSpy).toHaveBeenCalledWith(new Error('spec'));
|
||||
}
|
||||
});
|
||||
|
||||
expect(reporter.suiteDone).toHaveFailedExpectationsForRunnable(
|
||||
'async suite',
|
||||
[/^(((Uncaught )?(exception: )?Error: suite( thrown)?)|(suite thrown))$/]
|
||||
[/Error: suite/]
|
||||
);
|
||||
expect(reporter.specDone).toHaveFailedExpectationsForRunnable(
|
||||
'suite async spec',
|
||||
[/^(((Uncaught )?(exception: )?Error: spec( thrown)?)|(spec thrown))$/]
|
||||
[/Error: spec/]
|
||||
);
|
||||
});
|
||||
|
||||
@@ -2666,14 +2684,14 @@ describe('Env integration', function() {
|
||||
]);
|
||||
|
||||
env.addReporter(reporter);
|
||||
global.onerror(
|
||||
'Uncaught SyntaxError: Unexpected end of input',
|
||||
'borkenSpec.js',
|
||||
42,
|
||||
undefined,
|
||||
{ stack: 'a stack' }
|
||||
);
|
||||
global.onerror('Uncaught Error: ENOCHEESE');
|
||||
dispatchErrorEvent(global, {
|
||||
message: 'Uncaught SyntaxError: Unexpected end of input',
|
||||
error: undefined,
|
||||
filename: 'borkenSpec.js',
|
||||
lineno: 42
|
||||
});
|
||||
const error = new Error('ENOCHEESE');
|
||||
dispatchErrorEvent(global, { error });
|
||||
|
||||
await env.execute();
|
||||
|
||||
@@ -2683,15 +2701,15 @@ describe('Env integration', function() {
|
||||
passed: false,
|
||||
globalErrorType: 'load',
|
||||
message: 'Uncaught SyntaxError: Unexpected end of input',
|
||||
stack: 'a stack',
|
||||
stack: undefined,
|
||||
filename: 'borkenSpec.js',
|
||||
lineno: 42
|
||||
},
|
||||
{
|
||||
passed: false,
|
||||
globalErrorType: 'load',
|
||||
message: 'Uncaught Error: ENOCHEESE',
|
||||
stack: undefined,
|
||||
message: 'ENOCHEESE',
|
||||
stack: error.stack,
|
||||
filename: undefined,
|
||||
lineno: undefined
|
||||
}
|
||||
@@ -2923,7 +2941,7 @@ describe('Env integration', function() {
|
||||
|
||||
env.addReporter(reporter);
|
||||
env.it('passes', function() {});
|
||||
global.onerror('Uncaught Error: ENOCHEESE');
|
||||
dispatchErrorEvent(global, { error: 'ENOCHEESE' });
|
||||
await env.execute();
|
||||
|
||||
expect(reporter.jasmineDone).toHaveBeenCalled();
|
||||
@@ -3702,7 +3720,16 @@ describe('Env integration', function() {
|
||||
|
||||
const reporter = jasmine.createSpyObj('reporter', ['specDone']);
|
||||
env.addReporter(reporter);
|
||||
await env.execute();
|
||||
await jasmine.spyOnGlobalErrorsAsync(async function(globalErrorSpy) {
|
||||
await env.execute();
|
||||
|
||||
if (isBrowser) {
|
||||
// Verify that there were no unexpected errors
|
||||
expect(globalErrorSpy).toHaveBeenCalledTimes(2);
|
||||
expect(globalErrorSpy).toHaveBeenCalledWith(new Error('nope'));
|
||||
expect(globalErrorSpy).toHaveBeenCalledWith(new Error('yep'));
|
||||
}
|
||||
});
|
||||
|
||||
const passingResult = resultForRunable(
|
||||
reporter.specDone,
|
||||
@@ -3749,7 +3776,17 @@ describe('Env integration', function() {
|
||||
'suiteDone'
|
||||
]);
|
||||
env.addReporter(reporter);
|
||||
await env.execute();
|
||||
await jasmine.spyOnGlobalErrorsAsync(async function(globalErrorSpy) {
|
||||
await env.execute();
|
||||
|
||||
if (isBrowser) {
|
||||
// Verify that there were no unexpected errors
|
||||
expect(globalErrorSpy).toHaveBeenCalledTimes(1);
|
||||
expect(globalErrorSpy).toHaveBeenCalledWith(
|
||||
new Error('should fail the spec')
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const suiteResult = resultForRunable(reporter.suiteDone, 'Suite 1');
|
||||
expect(suiteResult.status).toEqual('failed');
|
||||
@@ -3794,7 +3831,17 @@ describe('Env integration', function() {
|
||||
'suiteDone'
|
||||
]);
|
||||
env.addReporter(reporter);
|
||||
await env.execute();
|
||||
await jasmine.spyOnGlobalErrorsAsync(async function(globalErrorSpy) {
|
||||
await env.execute();
|
||||
|
||||
if (isBrowser) {
|
||||
// Verify that there were no unexpected errors
|
||||
expect(globalErrorSpy).toHaveBeenCalledTimes(1);
|
||||
expect(globalErrorSpy).toHaveBeenCalledWith(
|
||||
new Error('should fail the spec')
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
expect(reporter.suiteDone).toHaveFailedExpectationsForRunnable(
|
||||
'Suite 1',
|
||||
@@ -3844,7 +3891,18 @@ describe('Env integration', function() {
|
||||
'suiteDone'
|
||||
]);
|
||||
env.addReporter(reporter);
|
||||
await env.execute();
|
||||
|
||||
await jasmine.spyOnGlobalErrorsAsync(async function(globalErrorSpy) {
|
||||
await env.execute();
|
||||
|
||||
if (isBrowser) {
|
||||
// Verify that there were no unexpected errors
|
||||
expect(globalErrorSpy).toHaveBeenCalledTimes(1);
|
||||
expect(globalErrorSpy).toHaveBeenCalledWith(
|
||||
new Error('should fail the spec')
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const spec1Result = resultForRunable(reporter.specDone, 'Suite 1 a spec');
|
||||
expect(spec1Result.status).toEqual('failed');
|
||||
@@ -3880,7 +3938,17 @@ describe('Env integration', function() {
|
||||
|
||||
const reporter = jasmine.createSpyObj('reporter', ['specDone']);
|
||||
env.addReporter(reporter);
|
||||
await env.execute();
|
||||
await jasmine.spyOnGlobalErrorsAsync(async function(globalErrorSpy) {
|
||||
await env.execute();
|
||||
|
||||
if (isBrowser) {
|
||||
// Verify that there were no unexpected errors
|
||||
expect(globalErrorSpy).toHaveBeenCalledTimes(1);
|
||||
expect(globalErrorSpy).toHaveBeenCalledWith(
|
||||
new Error('should fail the spec')
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const spec1Result = resultForRunable(reporter.specDone, 'spec 1');
|
||||
expect(spec1Result.status).toEqual('failed');
|
||||
@@ -3925,7 +3993,17 @@ describe('Env integration', function() {
|
||||
'suiteDone'
|
||||
]);
|
||||
env.addReporter(reporter);
|
||||
await env.execute();
|
||||
await jasmine.spyOnGlobalErrorsAsync(async function(globalErrorSpy) {
|
||||
await env.execute();
|
||||
|
||||
if (isBrowser) {
|
||||
// Verify that there were no unexpected errors
|
||||
expect(globalErrorSpy).toHaveBeenCalledTimes(1);
|
||||
expect(globalErrorSpy).toHaveBeenCalledWith(
|
||||
new Error('should fail the spec')
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const spec1Result = resultForRunable(reporter.specDone, 'Suite 1 a spec');
|
||||
expect(spec1Result.status).toEqual('failed');
|
||||
@@ -3945,8 +4023,25 @@ describe('Env integration', function() {
|
||||
|
||||
function browserEventMethods() {
|
||||
return {
|
||||
addEventListener() {},
|
||||
removeEventListener() {}
|
||||
listeners_: { error: [], unhandledrejection: [] },
|
||||
addEventListener(eventName, listener) {
|
||||
this.listeners_[eventName].push(listener);
|
||||
},
|
||||
removeEventListener(eventName, listener) {
|
||||
this.listeners_[eventName] = this.listeners_[eventName].filter(
|
||||
l => l !== listener
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function dispatchErrorEvent(global, event) {
|
||||
expect(global.listeners_.error.length)
|
||||
.withContext('number of error listeners')
|
||||
.toBeGreaterThan(0);
|
||||
|
||||
for (const l of global.listeners_.error) {
|
||||
l(event);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -149,20 +149,14 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
|
||||
if (!options.suppressLoadErrors) {
|
||||
installGlobalErrors();
|
||||
globalErrors.pushListener(function loadtimeErrorHandler(
|
||||
message,
|
||||
filename,
|
||||
lineno,
|
||||
colNo,
|
||||
err
|
||||
) {
|
||||
globalErrors.pushListener(function loadtimeErrorHandler(error, event) {
|
||||
topSuite.result.failedExpectations.push({
|
||||
passed: false,
|
||||
globalErrorType: 'load',
|
||||
message: message,
|
||||
stack: err && err.stack,
|
||||
filename: filename,
|
||||
lineno: lineno
|
||||
message: error ? error.message : event.message,
|
||||
stack: error && error.stack,
|
||||
filename: event && event.filename,
|
||||
lineno: event && event.lineno
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,18 +6,22 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
|
||||
let overrideHandler = null,
|
||||
onRemoveOverrideHandler = null;
|
||||
|
||||
function onerror(message, source, lineno, colno, error) {
|
||||
function onBrowserError(event) {
|
||||
dispatchBrowserError(event.error, event);
|
||||
}
|
||||
|
||||
function dispatchBrowserError(error, event) {
|
||||
if (overrideHandler) {
|
||||
overrideHandler(error || message);
|
||||
overrideHandler(error);
|
||||
return;
|
||||
}
|
||||
|
||||
const handler = handlers[handlers.length - 1];
|
||||
|
||||
if (handler) {
|
||||
handler.apply(null, Array.prototype.slice.call(arguments, 0));
|
||||
handler(error, event);
|
||||
} else {
|
||||
throw arguments[0];
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,8 +98,7 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
|
||||
this.installOne_('uncaughtException', 'Uncaught exception');
|
||||
this.installOne_('unhandledRejection', 'Unhandled promise rejection');
|
||||
} else {
|
||||
const originalHandler = global.onerror;
|
||||
global.onerror = onerror;
|
||||
global.addEventListener('error', onBrowserError);
|
||||
|
||||
const browserRejectionHandler = function browserRejectionHandler(
|
||||
event
|
||||
@@ -103,16 +106,19 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
|
||||
if (j$.isError_(event.reason)) {
|
||||
event.reason.jasmineMessage =
|
||||
'Unhandled promise rejection: ' + event.reason;
|
||||
global.onerror(event.reason);
|
||||
dispatchBrowserError(event.reason, event);
|
||||
} else {
|
||||
global.onerror('Unhandled promise rejection: ' + event.reason);
|
||||
dispatchBrowserError(
|
||||
'Unhandled promise rejection: ' + event.reason,
|
||||
event
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
global.addEventListener('unhandledrejection', browserRejectionHandler);
|
||||
|
||||
this.uninstall = function uninstall() {
|
||||
global.onerror = originalHandler;
|
||||
global.removeEventListener('error', onBrowserError);
|
||||
global.removeEventListener(
|
||||
'unhandledrejection',
|
||||
browserRejectionHandler
|
||||
@@ -121,6 +127,13 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
|
||||
}
|
||||
};
|
||||
|
||||
// The listener at the top of the stack will be called with two arguments:
|
||||
// the error and the event. Either of them may be falsy.
|
||||
// The error will normally be provided, but will be falsy in the case of
|
||||
// some browser load-time errors. The event will normally be provided in
|
||||
// browsers but will be falsy in Node.
|
||||
// Listeners that are pushed after spec files have been loaded should be
|
||||
// able to just use the error parameter.
|
||||
this.pushListener = function pushListener(listener) {
|
||||
handlers.push(listener);
|
||||
};
|
||||
|
||||
@@ -66,11 +66,8 @@ getJasmineRequireObj().QueueRunner = function(j$) {
|
||||
}
|
||||
|
||||
QueueRunner.prototype.execute = function() {
|
||||
this.handleFinalError = (message, source, lineno, colno, error) => {
|
||||
// Older browsers would send the error as the first parameter. HTML5
|
||||
// specifies the the five parameters above. The error instance should
|
||||
// be preffered, otherwise the call stack would get lost.
|
||||
this.onException(error || message);
|
||||
this.handleFinalError = error => {
|
||||
this.onException(error);
|
||||
};
|
||||
this.globalErrors.pushListener(this.handleFinalError);
|
||||
this.run(0);
|
||||
|
||||
Reference in New Issue
Block a user