Jasmine 3.0 dropped support for the last browser that didn't support the standard event handler methods (IE 9).
570 lines
18 KiB
JavaScript
570 lines
18 KiB
JavaScript
describe('GlobalErrors', function() {
|
|
it('calls the added handler on error', function() {
|
|
const fakeGlobal = minimalBrowserGlobal();
|
|
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);
|
|
|
|
expect(handler).toHaveBeenCalledWith(
|
|
fooError.message,
|
|
'foo.js',
|
|
1,
|
|
1,
|
|
fooError
|
|
);
|
|
});
|
|
|
|
it('only calls the most recent handler', function() {
|
|
const fakeGlobal = minimalBrowserGlobal();
|
|
const handler1 = jasmine.createSpy('errorHandler1');
|
|
const handler2 = jasmine.createSpy('errorHandler2');
|
|
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
|
|
|
|
errors.install();
|
|
errors.pushListener(handler1);
|
|
errors.pushListener(handler2);
|
|
|
|
fakeGlobal.onerror('foo');
|
|
|
|
expect(handler1).not.toHaveBeenCalled();
|
|
expect(handler2).toHaveBeenCalledWith('foo');
|
|
});
|
|
|
|
it('calls previous handlers when one is removed', function() {
|
|
const fakeGlobal = minimalBrowserGlobal();
|
|
const handler1 = jasmine.createSpy('errorHandler1');
|
|
const handler2 = jasmine.createSpy('errorHandler2');
|
|
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
|
|
|
|
errors.install();
|
|
errors.pushListener(handler1);
|
|
errors.pushListener(handler2);
|
|
|
|
errors.popListener(handler2);
|
|
|
|
fakeGlobal.onerror('foo');
|
|
|
|
expect(handler1).toHaveBeenCalledWith('foo');
|
|
expect(handler2).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('throws when no listener is passed to #popListener', function() {
|
|
const errors = new jasmineUnderTest.GlobalErrors({});
|
|
expect(function() {
|
|
errors.popListener();
|
|
}).toThrowError('popListener expects a listener');
|
|
});
|
|
|
|
it('uninstalls itself, putting back a previous callback', function() {
|
|
const originalCallback = jasmine.createSpy('error');
|
|
const fakeGlobal = {
|
|
...minimalBrowserGlobal(),
|
|
onerror: originalCallback
|
|
};
|
|
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
|
|
|
|
expect(fakeGlobal.onerror).toBe(originalCallback);
|
|
|
|
errors.install();
|
|
|
|
expect(fakeGlobal.onerror).not.toBe(originalCallback);
|
|
|
|
errors.uninstall();
|
|
|
|
expect(fakeGlobal.onerror).toBe(originalCallback);
|
|
});
|
|
|
|
it('rethrows the original error when there is no handler', function() {
|
|
const fakeGlobal = minimalBrowserGlobal();
|
|
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
|
|
const originalError = new Error('nope');
|
|
|
|
errors.install();
|
|
|
|
try {
|
|
fakeGlobal.onerror(originalError);
|
|
} catch (e) {
|
|
expect(e).toBe(originalError);
|
|
}
|
|
|
|
errors.uninstall();
|
|
});
|
|
|
|
it('reports uncaught exceptions in node.js', function() {
|
|
const fakeGlobal = {
|
|
process: {
|
|
on: jasmine.createSpy('process.on'),
|
|
removeListener: jasmine.createSpy('process.removeListener'),
|
|
listeners: jasmine
|
|
.createSpy('process.listeners')
|
|
.and.returnValue(['foo']),
|
|
removeAllListeners: jasmine.createSpy('process.removeAllListeners')
|
|
}
|
|
},
|
|
handler = jasmine.createSpy('errorHandler'),
|
|
errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
|
|
|
|
errors.install();
|
|
expect(fakeGlobal.process.on).toHaveBeenCalledWith(
|
|
'uncaughtException',
|
|
jasmine.any(Function)
|
|
);
|
|
expect(fakeGlobal.process.listeners).toHaveBeenCalledWith(
|
|
'uncaughtException'
|
|
);
|
|
expect(fakeGlobal.process.removeAllListeners).toHaveBeenCalledWith(
|
|
'uncaughtException'
|
|
);
|
|
|
|
errors.pushListener(handler);
|
|
|
|
const addedListener = fakeGlobal.process.on.calls.argsFor(0)[1];
|
|
addedListener(new Error('bar'));
|
|
|
|
expect(handler).toHaveBeenCalledWith(new Error('bar'));
|
|
expect(handler.calls.argsFor(0)[0].jasmineMessage).toBe(
|
|
'Uncaught exception: Error: bar'
|
|
);
|
|
|
|
errors.uninstall();
|
|
|
|
expect(fakeGlobal.process.removeListener).toHaveBeenCalledWith(
|
|
'uncaughtException',
|
|
addedListener
|
|
);
|
|
expect(fakeGlobal.process.on).toHaveBeenCalledWith(
|
|
'uncaughtException',
|
|
'foo'
|
|
);
|
|
});
|
|
|
|
describe('Reporting unhandled promise rejections in node.js', function() {
|
|
it('reports rejections with `Error` reasons', function() {
|
|
const fakeGlobal = {
|
|
process: {
|
|
on: jasmine.createSpy('process.on'),
|
|
removeListener: jasmine.createSpy('process.removeListener'),
|
|
listeners: jasmine
|
|
.createSpy('process.listeners')
|
|
.and.returnValue(['foo']),
|
|
removeAllListeners: jasmine.createSpy('process.removeAllListeners')
|
|
}
|
|
},
|
|
handler = jasmine.createSpy('errorHandler'),
|
|
errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
|
|
|
|
errors.install();
|
|
expect(fakeGlobal.process.on).toHaveBeenCalledWith(
|
|
'unhandledRejection',
|
|
jasmine.any(Function)
|
|
);
|
|
expect(fakeGlobal.process.listeners).toHaveBeenCalledWith(
|
|
'unhandledRejection'
|
|
);
|
|
expect(fakeGlobal.process.removeAllListeners).toHaveBeenCalledWith(
|
|
'unhandledRejection'
|
|
);
|
|
|
|
errors.pushListener(handler);
|
|
|
|
const addedListener = fakeGlobal.process.on.calls.argsFor(1)[1];
|
|
addedListener(new Error('bar'));
|
|
|
|
expect(handler).toHaveBeenCalledWith(new Error('bar'));
|
|
expect(handler.calls.argsFor(0)[0].jasmineMessage).toBe(
|
|
'Unhandled promise rejection: Error: bar'
|
|
);
|
|
|
|
errors.uninstall();
|
|
|
|
expect(fakeGlobal.process.removeListener).toHaveBeenCalledWith(
|
|
'unhandledRejection',
|
|
addedListener
|
|
);
|
|
expect(fakeGlobal.process.on).toHaveBeenCalledWith(
|
|
'unhandledRejection',
|
|
'foo'
|
|
);
|
|
});
|
|
|
|
it('reports rejections with non-`Error` reasons', function() {
|
|
const fakeGlobal = {
|
|
process: {
|
|
on: jasmine.createSpy('process.on'),
|
|
removeListener: function() {},
|
|
listeners: function() {
|
|
return [];
|
|
},
|
|
removeAllListeners: function() {}
|
|
}
|
|
},
|
|
handler = jasmine.createSpy('errorHandler'),
|
|
errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
|
|
|
|
errors.install();
|
|
errors.pushListener(handler);
|
|
|
|
expect(fakeGlobal.process.on.calls.argsFor(1)[0]).toEqual(
|
|
'unhandledRejection'
|
|
);
|
|
const addedListener = fakeGlobal.process.on.calls.argsFor(1)[1];
|
|
addedListener(17);
|
|
|
|
expect(handler).toHaveBeenCalledWith(
|
|
new Error(
|
|
'Unhandled promise rejection: 17\n' +
|
|
'(Tip: to get a useful stack trace, use ' +
|
|
'Promise.reject(new Error(...)) instead of Promise.reject(...).)'
|
|
)
|
|
);
|
|
});
|
|
|
|
it('reports rejections with no reason provided', function() {
|
|
const fakeGlobal = {
|
|
process: {
|
|
on: jasmine.createSpy('process.on'),
|
|
removeListener: function() {},
|
|
listeners: function() {
|
|
return [];
|
|
},
|
|
removeAllListeners: function() {}
|
|
}
|
|
},
|
|
handler = jasmine.createSpy('errorHandler'),
|
|
errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
|
|
|
|
errors.install();
|
|
errors.pushListener(handler);
|
|
|
|
expect(fakeGlobal.process.on.calls.argsFor(1)[0]).toEqual(
|
|
'unhandledRejection'
|
|
);
|
|
const addedListener = fakeGlobal.process.on.calls.argsFor(1)[1];
|
|
addedListener(undefined);
|
|
|
|
expect(handler).toHaveBeenCalledWith(
|
|
new Error(
|
|
'Unhandled promise rejection with no error or message\n' +
|
|
'(Tip: to get a useful stack trace, use ' +
|
|
'Promise.reject(new Error(...)) instead of Promise.reject().)'
|
|
)
|
|
);
|
|
});
|
|
});
|
|
|
|
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);
|
|
|
|
errors.install();
|
|
expect(fakeGlobal.addEventListener).toHaveBeenCalledWith(
|
|
'unhandledrejection',
|
|
jasmine.any(Function)
|
|
);
|
|
|
|
const addedListener = fakeGlobal.addEventListener.calls.argsFor(0)[1];
|
|
errors.uninstall();
|
|
|
|
expect(fakeGlobal.removeEventListener).toHaveBeenCalledWith(
|
|
'unhandledrejection',
|
|
addedListener
|
|
);
|
|
});
|
|
|
|
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);
|
|
|
|
errors.install();
|
|
errors.pushListener(handler);
|
|
|
|
const addedListener = fakeGlobal.addEventListener.calls.argsFor(0)[1];
|
|
addedListener({ reason: 'nope' });
|
|
|
|
expect(handler).toHaveBeenCalledWith('Unhandled promise rejection: nope');
|
|
});
|
|
|
|
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);
|
|
|
|
errors.install();
|
|
errors.pushListener(handler);
|
|
|
|
const addedListener = fakeGlobal.addEventListener.calls.argsFor(0)[1];
|
|
const reason = new Error('bar');
|
|
|
|
addedListener({ reason: reason });
|
|
|
|
expect(handler).toHaveBeenCalledWith(
|
|
jasmine.objectContaining({
|
|
jasmineMessage: 'Unhandled promise rejection: Error: bar',
|
|
message: reason.message,
|
|
stack: reason.stack
|
|
})
|
|
);
|
|
});
|
|
|
|
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 handler0 = jasmine.createSpy('handler0');
|
|
const handler1 = jasmine.createSpy('handler1');
|
|
const overrideHandler = jasmine.createSpy('overrideHandler');
|
|
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
|
|
|
|
errors.install();
|
|
errors.pushListener(handler0);
|
|
errors.setOverrideListener(overrideHandler, () => {});
|
|
errors.pushListener(handler1);
|
|
fakeGlobal.onerror('foo');
|
|
fakeGlobal.onerror(null, null, null, null, new Error('bar'));
|
|
|
|
expect(overrideHandler).toHaveBeenCalledWith('foo');
|
|
expect(overrideHandler).toHaveBeenCalledWith(new Error('bar'));
|
|
expect(handler0).not.toHaveBeenCalled();
|
|
expect(handler1).not.toHaveBeenCalled();
|
|
|
|
errors.removeOverrideListener();
|
|
|
|
fakeGlobal.onerror('baz');
|
|
expect(overrideHandler).not.toHaveBeenCalledWith('baz');
|
|
expect(handler1).toHaveBeenCalledWith('baz');
|
|
});
|
|
|
|
it('overrides the existing handlers in Node until removed', function() {
|
|
const globalEventListeners = {};
|
|
const fakeGlobal = {
|
|
process: {
|
|
on: (name, listener) => (globalEventListeners[name] = listener),
|
|
removeListener: () => {},
|
|
listeners: name => globalEventListeners[name],
|
|
removeAllListeners: name => (globalEventListeners[name] = [])
|
|
}
|
|
};
|
|
const handler0 = jasmine.createSpy('handler0');
|
|
const handler1 = jasmine.createSpy('handler1');
|
|
const overrideHandler = jasmine.createSpy('overrideHandler');
|
|
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
|
|
|
|
errors.install();
|
|
errors.pushListener(handler0);
|
|
errors.setOverrideListener(overrideHandler);
|
|
errors.pushListener(handler1);
|
|
|
|
globalEventListeners['uncaughtException'](new Error('foo'));
|
|
|
|
expect(overrideHandler).toHaveBeenCalledWith(new Error('foo'));
|
|
expect(handler0).not.toHaveBeenCalled();
|
|
expect(handler1).not.toHaveBeenCalled();
|
|
|
|
errors.removeOverrideListener();
|
|
|
|
globalEventListeners['uncaughtException'](new Error('bar'));
|
|
expect(overrideHandler).not.toHaveBeenCalledWith(new Error('bar'));
|
|
expect(handler1).toHaveBeenCalledWith(new Error('bar'));
|
|
});
|
|
|
|
it('handles unhandled promise rejections in browsers', function() {
|
|
const globalEventListeners = {};
|
|
const fakeGlobal = {
|
|
addEventListener(name, listener) {
|
|
globalEventListeners[name] = listener;
|
|
},
|
|
removeEventListener() {}
|
|
};
|
|
const handler = jasmine.createSpy('handler');
|
|
const overrideHandler = jasmine.createSpy('overrideHandler');
|
|
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
|
|
|
|
errors.install();
|
|
errors.pushListener(handler);
|
|
errors.setOverrideListener(overrideHandler, () => {});
|
|
|
|
const reason = new Error('bar');
|
|
|
|
globalEventListeners['unhandledrejection']({ reason: reason });
|
|
|
|
expect(overrideHandler).toHaveBeenCalledWith(
|
|
jasmine.objectContaining({
|
|
jasmineMessage: 'Unhandled promise rejection: Error: bar',
|
|
message: reason.message,
|
|
stack: reason.stack
|
|
})
|
|
);
|
|
expect(handler).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('handles unhandled promise rejections in Node', function() {
|
|
const globalEventListeners = {};
|
|
const fakeGlobal = {
|
|
process: {
|
|
on(name, listener) {
|
|
globalEventListeners[name] = listener;
|
|
},
|
|
removeListener() {},
|
|
listeners(name) {
|
|
return globalEventListeners[name];
|
|
},
|
|
removeAllListeners(name) {
|
|
globalEventListeners[name] = null;
|
|
}
|
|
}
|
|
};
|
|
const handler0 = jasmine.createSpy('handler0');
|
|
const handler1 = jasmine.createSpy('handler1');
|
|
const overrideHandler = jasmine.createSpy('overrideHandler');
|
|
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
|
|
|
|
errors.install();
|
|
errors.pushListener(handler0);
|
|
errors.setOverrideListener(overrideHandler, () => {});
|
|
errors.pushListener(handler1);
|
|
|
|
globalEventListeners['unhandledRejection'](new Error('nope'));
|
|
|
|
expect(overrideHandler).toHaveBeenCalledWith(new Error('nope'));
|
|
expect(handler0).not.toHaveBeenCalled();
|
|
expect(handler1).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('throws if there is already an override handler', function() {
|
|
const errors = new jasmineUnderTest.GlobalErrors(minimalBrowserGlobal());
|
|
|
|
errors.setOverrideListener(() => {}, () => {});
|
|
expect(function() {
|
|
errors.setOverrideListener(() => {}, () => {});
|
|
}).toThrowError("Can't set more than one override listener at a time");
|
|
});
|
|
});
|
|
|
|
describe('#removeOverrideListener', function() {
|
|
it("calls the handler's onRemove callback", function() {
|
|
const onRemove = jasmine.createSpy('onRemove');
|
|
const errors = new jasmineUnderTest.GlobalErrors(minimalBrowserGlobal());
|
|
|
|
errors.setOverrideListener(() => {}, onRemove);
|
|
errors.removeOverrideListener();
|
|
|
|
expect(onRemove).toHaveBeenCalledWith();
|
|
});
|
|
|
|
it('does not throw if there is no handler', function() {
|
|
const errors = new jasmineUnderTest.GlobalErrors(minimalBrowserGlobal());
|
|
|
|
expect(() => errors.removeOverrideListener()).not.toThrow();
|
|
});
|
|
});
|
|
|
|
function minimalBrowserGlobal() {
|
|
return {
|
|
addEventListener() {},
|
|
removeEventListener() {},
|
|
onerror: null
|
|
};
|
|
}
|
|
});
|