Merge branch 'main' into internal-async

This commit is contained in:
Steve Gravrock
2022-08-22 10:46:03 -07:00
11 changed files with 442 additions and 273 deletions
+4 -21
View File
@@ -2,17 +2,12 @@
[![Build Status](https://circleci.com/gh/jasmine/jasmine.svg?style=shield)](https://circleci.com/gh/jasmine/jasmine) [![Build Status](https://circleci.com/gh/jasmine/jasmine.svg?style=shield)](https://circleci.com/gh/jasmine/jasmine)
[![Open Source Helpers](https://www.codetriage.com/jasmine/jasmine/badges/users.svg)](https://www.codetriage.com/jasmine/jasmine) [![Open Source Helpers](https://www.codetriage.com/jasmine/jasmine/badges/users.svg)](https://www.codetriage.com/jasmine/jasmine)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fjasmine%2Fjasmine.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fjasmine%2Fjasmine?ref=badge_shield)
# A JavaScript Testing Framework # A JavaScript Testing Framework
Jasmine is a Behavior Driven Development testing framework for JavaScript. It does not rely on browsers, DOM, or any JavaScript framework. Thus it's suited for websites, [Node.js](http://nodejs.org) projects, or anywhere that JavaScript can run. Jasmine is a Behavior Driven Development testing framework for JavaScript. It does not rely on browsers, DOM, or any JavaScript framework. Thus it's suited for websites, [Node.js](http://nodejs.org) projects, or anywhere that JavaScript can run.
Documentation & guides live here: [http://jasmine.github.io](http://jasmine.github.io/) Upgrading from Jasmine 3.x? Check out the [upgrade guide](https://jasmine.github.io/tutorials/upgrading_to_Jasmine_4.0).
For a quick start guide of Jasmine, see the beginning of [http://jasmine.github.io/edge/introduction.html](http://jasmine.github.io/edge/introduction.html).
Upgrading from Jasmine 3.x? Check out the 4.0 release notes for a list of
what's new (including breaking changes). You can also read the [upgrade guide](https://jasmine.github.io/tutorials/upgrading_to_Jasmine_4.0).
## Contributing ## Contributing
@@ -28,7 +23,7 @@ for details.
See the [documentation site](https://jasmine.github.io/pages/docs_home.html), See the [documentation site](https://jasmine.github.io/pages/docs_home.html),
particularly the [Your First Suite tutorial](https://jasmine.github.io/tutorials/your_first_suite) particularly the [Your First Suite tutorial](https://jasmine.github.io/tutorials/your_first_suite)
for information on writing specs. for information on writing specs, and [the FAQ](https://jasmine.github.io/pages/faq.html).
## Supported environments ## Supported environments
@@ -47,15 +42,7 @@ For evergreen browsers, each version of Jasmine is tested against the version of
at the time of release. Other browsers, as well as older & newer versions of some supported browsers, are likely to work. at the time of release. Other browsers, as well as older & newer versions of some supported browsers, are likely to work.
However, Jasmine isn't tested against them and they aren't actively supported. However, Jasmine isn't tested against them and they aren't actively supported.
See the [release notes](https://github.com/jasmine/jasmine/tree/main/release_notes) To find out what environments work with a particular Jasmine release, see the [release notes](https://github.com/jasmine/jasmine/tree/main/release_notes).
for the supported environments for each Jasmine release.
## Support
* Search past discussions: [http://groups.google.com/group/jasmine-js](http://groups.google.com/group/jasmine-js).
* Send an email to the list: [jasmine-js@googlegroups.com](mailto:jasmine-js@googlegroups.com).
* View the project backlog at Pivotal Tracker: [http://www.pivotaltracker.com/projects/10606](http://www.pivotaltracker.com/projects/10606).
* Follow us on Twitter: [@JasmineBDD](http://twitter.com/JasmineBDD).
## Maintainers ## Maintainers
@@ -71,8 +58,4 @@ for the supported environments for each Jasmine release.
* [Christian Williams](mailto:antixian666@gmail.com) * [Christian Williams](mailto:antixian666@gmail.com)
* Sheel Choksi * Sheel Choksi
Copyright (c) 2008-2022 Jasmine Maintainers. This software is licensed under the MIT License. Copyright (c) 2008-2022 Jasmine Maintainers. This software is licensed under the [MIT License](https://github.com/jasmine/jasmine/blob/main/MIT.LICENSE).
## License
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fjasmine%2Fjasmine.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fjasmine%2Fjasmine?ref=badge_large)
+7 -7
View File
@@ -35,11 +35,10 @@ When ready to release - specs are all green and the stories are done:
### Commit and push core changes ### Commit and push core changes
1. Run the browser tests using `scripts/run-all-browsers`. 1. Commit release notes and version changes (jasmine.js, package.json)
2. Commit release notes and version changes (jasmine.js, package.json) 2. Push
3. Push 3. Tag the release and push the tag.
4. Tag the release and push the tag. 4. Wait for Circle CI to go green
5. Wait for Circle CI to go green
### Build standalone distribution ### Build standalone distribution
@@ -53,7 +52,9 @@ When ready to release - specs are all green and the stories are done:
### Release the docs ### Release the docs
Probably only need to do this when releasing a minor version, and not a patch version. Probably only need to do this when releasing a minor version, and not a patch
version. See [the README file in the docs repo](https://github.com/jasmine/jasmine.github.io/blob/master/README.md)
for instructions.
1. `rake update_edge_jasmine` 1. `rake update_edge_jasmine`
1. `npm run jsdoc` 1. `npm run jsdoc`
@@ -68,7 +69,6 @@ Probably only need to do this when releasing a minor version, and not a patch ve
1. In `package.json`, update both the package version and the jasmine-core dependency version 1. In `package.json`, update both the package version and the jasmine-core dependency version
1. Commit and push. 1. Commit and push.
1. Wait for Circle CI to go green again. 1. Wait for Circle CI to go green again.
1. Run the tests on Windows locally.
1. `grunt release `. (Note: This will publish the package by running `npm publish`.) 1. `grunt release `. (Note: This will publish the package by running `npm publish`.)
### Finally ### Finally
+55 -36
View File
@@ -2789,8 +2789,32 @@ getJasmineRequireObj().CallTracker = function(j$) {
getJasmineRequireObj().clearStack = function(j$) { getJasmineRequireObj().clearStack = function(j$) {
const maxInlineCallCount = 10; const maxInlineCallCount = 10;
function messageChannelImpl(global, setTimeout) { function browserQueueMicrotaskImpl(global) {
const channel = new global.MessageChannel(); const { setTimeout, queueMicrotask } = global;
let currentCallCount = 0;
return function clearStack(fn) {
currentCallCount++;
if (currentCallCount < maxInlineCallCount) {
queueMicrotask(fn);
} else {
currentCallCount = 0;
setTimeout(fn);
}
};
}
function nodeQueueMicrotaskImpl(global) {
const { queueMicrotask } = global;
return function(fn) {
queueMicrotask(fn);
};
}
function messageChannelImpl(global) {
const { MessageChannel, setTimeout } = global;
const channel = new MessageChannel();
let head = {}; let head = {};
let tail = head; let tail = head;
@@ -2801,7 +2825,7 @@ getJasmineRequireObj().clearStack = function(j$) {
delete head.task; delete head.task;
if (taskRunning) { if (taskRunning) {
global.setTimeout(task, 0); setTimeout(task, 0);
} else { } else {
try { try {
taskRunning = true; taskRunning = true;
@@ -2827,29 +2851,31 @@ getJasmineRequireObj().clearStack = function(j$) {
} }
function getClearStack(global) { function getClearStack(global) {
let currentCallCount = 0; const NODE_JS =
const realSetTimeout = global.setTimeout; global.process &&
const setTimeoutImpl = function clearStack(fn) { global.process.versions &&
Function.prototype.apply.apply(realSetTimeout, [global, [fn, 0]]); typeof global.process.versions.node === 'string';
};
if (j$.isFunction_(global.setImmediate)) { const SAFARI =
const realSetImmediate = global.setImmediate; global.navigator &&
return function(fn) { /^((?!chrome|android).)*safari/i.test(global.navigator.userAgent);
currentCallCount++;
if (currentCallCount < maxInlineCallCount) { if (NODE_JS) {
realSetImmediate(fn); // Unlike browsers, Node doesn't require us to do a periodic setTimeout
} else { // so we avoid the overhead.
currentCallCount = 0; return nodeQueueMicrotaskImpl(global);
} else if (
setTimeoutImpl(fn); SAFARI ||
} j$.util.isUndefined(global.MessageChannel) /* tests */
}; ) {
} else if (!j$.util.isUndefined(global.MessageChannel)) { // queueMicrotask is dramatically faster than MessageChannel in Safari.
return messageChannelImpl(global, setTimeoutImpl); // Some of our own integration tests provide a mock queueMicrotask in all
// environments because it's simpler to mock than MessageChannel.
return browserQueueMicrotaskImpl(global);
} else { } else {
return setTimeoutImpl; // MessageChannel is faster than queueMicrotask in supported browsers
// other than Safari.
return messageChannelImpl(global);
} }
} }
@@ -4055,21 +4081,14 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
} }
}; };
if (global.addEventListener) { global.addEventListener('unhandledrejection', browserRejectionHandler);
global.addEventListener(
'unhandledrejection',
browserRejectionHandler
);
}
this.uninstall = function uninstall() { this.uninstall = function uninstall() {
global.onerror = originalHandler; global.onerror = originalHandler;
if (global.removeEventListener) { global.removeEventListener(
global.removeEventListener( 'unhandledrejection',
'unhandledrejection', browserRejectionHandler
browserRejectionHandler );
);
}
}; };
} }
}; };
@@ -10424,5 +10443,5 @@ getJasmineRequireObj().UserContext = function(j$) {
}; };
getJasmineRequireObj().version = function() { getJasmineRequireObj().version = function() {
return '4.2.0'; return '4.3.0';
}; };
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "jasmine-core", "name": "jasmine-core",
"license": "MIT", "license": "MIT",
"version": "4.2.0", "version": "4.3.0",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/jasmine/jasmine.git" "url": "https://github.com/jasmine/jasmine.git"
+40
View File
@@ -0,0 +1,40 @@
# Jasmine 4.3.0 Release Notes
## New Features
* Added [`jasmine.spyOnGlobalErrorsAsync`](https://jasmine.github.io/api/4.3/jasmine.html#.spyOnGlobalErrorsAsync),
to better support testing code that's
expected to produce unhandled exceptions or unhandled promise rejections
* Fixes [#1843](https://github.com/jasmine/jasmine/issues/1843)
* Fixes [#1453](https://github.com/jasmine/jasmine/issues/1453)
## Documentation updates
* Updated the README to reduce redundancy and update support links
## Internal improvements
* Split `Env` into several smaller classes
* Replaced uses of `var` with `const`/`let`
* Replaced most uses of `self = this` with arrow fns
* Removed obsolete and unused utility fns
* Separated reporter- and runable-specific queue runner configuration
* Added more test coverage for default spy strategies
* Converted integration specs to `async`/`await`
## Supported environments
jasmine-core 4.3.0 has been tested in the following environments.
| Environment | Supported versions |
|-------------------|--------------------|
| Node | 12.17+, 14, 16, 18 |
| Safari | 14-15 |
| Chrome | 103 |
| Firefox | 91, 102 |
| Edge | 103 |
------
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_
+1
View File
@@ -25,6 +25,7 @@ passfile=`mktemp -t jasmine-results.XXXXXX` || exit 1
failfile=`mktemp -t jasmine-results.XXXXXX` || exit 1 failfile=`mktemp -t jasmine-results.XXXXXX` || exit 1
run_browser chrome latest run_browser chrome latest
run_browser firefox latest run_browser firefox latest
run_browser firefox 102
run_browser firefox 91 run_browser firefox 91
run_browser safari 15 run_browser safari 15
run_browser safari 14 run_browser safari 14
+191 -135
View File
@@ -9,160 +9,216 @@ describe('ClearStack', function() {
}); });
}); });
it('uses setImmediate when available', function() { describe('in Safari', function() {
const setImmediate = jasmine usesQueueMicrotaskWithSetTimeout(function() {
.createSpy('setImmediate') return {
.and.callFake(function(fn) { navigator: {
fn(); userAgent:
}), 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.0.8 (KHTML, like Gecko) Version/15.1 Safari/605.0.8'
global = { setImmediate: setImmediate }, },
clearStack = jasmineUnderTest.getClearStack(global); // queueMicrotask should be used even though MessageChannel is present
let called = false; MessageChannel: fakeMessageChannel
};
});
});
clearStack(function() { describe('in browsers other than Safari', function() {
called = true; usesMessageChannel(function() {
return {
navigator: {
// Chrome's user agent string contains "Safari" so it's a good test case
userAgent:
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36'
}
};
}); });
expect(called).toBe(true); describe('when MessageChannel is unavailable', function() {
expect(setImmediate).toHaveBeenCalled(); usesQueueMicrotaskWithSetTimeout(function() {
return {
navigator: {
userAgent: 'CERN-LineMode/2.15 libwww/2.17b3',
MessageChannel: undefined
}
};
});
});
}); });
it('uses setTimeout instead of setImmediate every 10 calls to make sure we release the CPU', function() { describe('in Node', function() {
const setImmediate = jasmine.createSpy('setImmediate'), usesQueueMicrotaskWithoutSetTimeout(function() {
setTimeout = jasmine.createSpy('setTimeout'), return {
global = { setImmediate: setImmediate, setTimeout: setTimeout }, process: {
clearStack = jasmineUnderTest.getClearStack(global); versions: {
node: '3.1415927'
clearStack(function() {});
clearStack(function() {});
clearStack(function() {});
clearStack(function() {});
clearStack(function() {});
clearStack(function() {});
clearStack(function() {});
clearStack(function() {});
clearStack(function() {});
expect(setImmediate).toHaveBeenCalled();
expect(setTimeout).not.toHaveBeenCalled();
clearStack(function() {});
expect(setImmediate.calls.count()).toEqual(9);
expect(setTimeout.calls.count()).toEqual(1);
clearStack(function() {});
expect(setImmediate.calls.count()).toEqual(10);
expect(setTimeout.calls.count()).toEqual(1);
});
it('uses MessageChannels when available', function() {
const fakeChannel = {
port1: {},
port2: {
postMessage: function() {
fakeChannel.port1.onmessage();
} }
} }
}, };
global = { });
MessageChannel: function() { });
return fakeChannel;
}
},
clearStack = jasmineUnderTest.getClearStack(global);
let called = false;
clearStack(function() { function usesMessageChannel(makeGlobal) {
called = true; it('uses MessageChannel', function() {
const global = {
...makeGlobal(),
MessageChannel: fakeMessageChannel
};
const clearStack = jasmineUnderTest.getClearStack(global);
let called = false;
clearStack(function() {
called = true;
});
expect(called).toBe(true);
}); });
expect(called).toBe(true); it('uses setTimeout instead of MessageChannel every 10 calls to make sure we release the CPU', function() {
}); const fakeChannel = fakeMessageChannel();
spyOn(fakeChannel.port2, 'postMessage');
it('uses setTimeout instead of MessageChannel every 10 calls to make sure we release the CPU', function() { const setTimeout = jasmine.createSpy('setTimeout');
const fakeChannel = { const global = {
port1: {}, ...makeGlobal(),
port2: { setTimeout,
postMessage: jasmine
.createSpy('postMessage')
.and.callFake(function() {
fakeChannel.port1.onmessage();
})
}
},
setTimeout = jasmine.createSpy('setTimeout'),
global = {
MessageChannel: function() { MessageChannel: function() {
return fakeChannel; return fakeChannel;
},
setTimeout: setTimeout
},
clearStack = jasmineUnderTest.getClearStack(global);
clearStack(function() {});
clearStack(function() {});
clearStack(function() {});
clearStack(function() {});
clearStack(function() {});
clearStack(function() {});
clearStack(function() {});
clearStack(function() {});
clearStack(function() {});
expect(fakeChannel.port2.postMessage).toHaveBeenCalled();
expect(setTimeout).not.toHaveBeenCalled();
clearStack(function() {});
expect(fakeChannel.port2.postMessage.calls.count()).toEqual(9);
expect(setTimeout.calls.count()).toEqual(1);
clearStack(function() {});
expect(fakeChannel.port2.postMessage.calls.count()).toEqual(10);
expect(setTimeout.calls.count()).toEqual(1);
});
it('calls setTimeout when onmessage is called recursively', function() {
const fakeChannel = {
port1: {},
port2: {
postMessage: function() {
fakeChannel.port1.onmessage();
}
} }
}, };
setTimeout = jasmine.createSpy('setTimeout'), const clearStack = jasmineUnderTest.getClearStack(global);
global = {
MessageChannel: function() {
return fakeChannel;
},
setTimeout: setTimeout
},
clearStack = jasmineUnderTest.getClearStack(global),
fn = jasmine.createSpy('second clearStack function');
clearStack(function() { for (let i = 0; i < 9; i++) {
clearStack(fn); clearStack(function() {});
}
expect(fakeChannel.port2.postMessage).toHaveBeenCalled();
expect(setTimeout).not.toHaveBeenCalled();
clearStack(function() {});
expect(fakeChannel.port2.postMessage).toHaveBeenCalledTimes(9);
expect(setTimeout).toHaveBeenCalledTimes(1);
clearStack(function() {});
expect(fakeChannel.port2.postMessage).toHaveBeenCalledTimes(10);
expect(setTimeout).toHaveBeenCalledTimes(1);
}); });
expect(fn).not.toHaveBeenCalled(); it('calls setTimeout when onmessage is called recursively', function() {
expect(setTimeout).toHaveBeenCalledWith(fn, 0); const setTimeout = jasmine.createSpy('setTimeout');
}); const global = {
...makeGlobal(),
setTimeout,
MessageChannel: fakeMessageChannel
};
const clearStack = jasmineUnderTest.getClearStack(global);
const fn = jasmine.createSpy('second clearStack function');
it('falls back to setTimeout', function() { clearStack(function() {
const setTimeout = jasmine clearStack(fn);
.createSpy('setTimeout') });
.and.callFake(function(fn) {
expect(fn).not.toHaveBeenCalled();
expect(setTimeout).toHaveBeenCalledWith(fn, 0);
});
}
function usesQueueMicrotaskWithSetTimeout(makeGlobal) {
it('uses queueMicrotask', function() {
const global = {
...makeGlobal(),
queueMicrotask: function(fn) {
fn(); fn();
}), }
global = { setTimeout: setTimeout }, };
clearStack = jasmineUnderTest.getClearStack(global); const clearStack = jasmineUnderTest.getClearStack(global);
let called = false; let called = false;
clearStack(function() { clearStack(function() {
called = true; called = true;
});
expect(called).toBe(true);
}); });
expect(called).toBe(true); it('uses setTimeout instead of queueMicrotask every 10 calls to make sure we release the CPU', function() {
expect(setTimeout).toHaveBeenCalledWith(jasmine.any(Function), 0); const queueMicrotask = jasmine.createSpy('queueMicrotask');
}); const setTimeout = jasmine.createSpy('setTimeout');
const global = {
...makeGlobal(),
queueMicrotask,
setTimeout
};
const clearStack = jasmineUnderTest.getClearStack(global);
for (let i = 0; i < 9; i++) {
clearStack(function() {});
}
expect(queueMicrotask).toHaveBeenCalled();
expect(setTimeout).not.toHaveBeenCalled();
clearStack(function() {});
expect(queueMicrotask).toHaveBeenCalledTimes(9);
expect(setTimeout).toHaveBeenCalledTimes(1);
clearStack(function() {});
expect(queueMicrotask).toHaveBeenCalledTimes(10);
expect(setTimeout).toHaveBeenCalledTimes(1);
});
}
function usesQueueMicrotaskWithoutSetTimeout(makeGlobal) {
it('uses queueMicrotask', function() {
const global = {
...makeGlobal(),
queueMicrotask: function(fn) {
fn();
}
};
const clearStack = jasmineUnderTest.getClearStack(global);
let called = false;
clearStack(function() {
called = true;
});
expect(called).toBe(true);
});
it('does not use setTimeout', function() {
const queueMicrotask = jasmine.createSpy('queueMicrotask');
const setTimeout = jasmine.createSpy('setTimeout');
const global = {
...makeGlobal(),
queueMicrotask,
setTimeout
};
const clearStack = jasmineUnderTest.getClearStack(global);
clearStack(function() {});
clearStack(function() {});
clearStack(function() {});
clearStack(function() {});
clearStack(function() {});
clearStack(function() {});
clearStack(function() {});
clearStack(function() {});
clearStack(function() {});
clearStack(function() {});
expect(queueMicrotask).toHaveBeenCalledTimes(10);
expect(setTimeout).not.toHaveBeenCalled();
});
}
function fakeMessageChannel() {
const channel = {
port1: {},
port2: {
postMessage: function() {
channel.port1.onmessage();
}
}
};
return channel;
}
}); });
+40 -32
View File
@@ -1,8 +1,8 @@
describe('GlobalErrors', function() { describe('GlobalErrors', function() {
it('calls the added handler on error', function() { it('calls the added handler on error', function() {
const fakeGlobal = { onerror: null }, const fakeGlobal = minimalBrowserGlobal();
handler = jasmine.createSpy('errorHandler'), const handler = jasmine.createSpy('errorHandler');
errors = new jasmineUnderTest.GlobalErrors(fakeGlobal); const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
errors.install(); errors.install();
errors.pushListener(handler); errors.pushListener(handler);
@@ -13,10 +13,10 @@ describe('GlobalErrors', function() {
}); });
it('enables external interception of error by overriding global.onerror', function() { it('enables external interception of error by overriding global.onerror', function() {
const fakeGlobal = { onerror: null }, const fakeGlobal = minimalBrowserGlobal();
handler = jasmine.createSpy('errorHandler'), const handler = jasmine.createSpy('errorHandler');
hijackHandler = jasmine.createSpy('hijackErrorHandler'), const hijackHandler = jasmine.createSpy('hijackErrorHandler');
errors = new jasmineUnderTest.GlobalErrors(fakeGlobal); const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
errors.install(); errors.install();
errors.pushListener(handler); errors.pushListener(handler);
@@ -30,10 +30,10 @@ describe('GlobalErrors', function() {
}); });
it('calls the global error handler with all parameters', function() { it('calls the global error handler with all parameters', function() {
const fakeGlobal = { onerror: null }, const fakeGlobal = minimalBrowserGlobal();
handler = jasmine.createSpy('errorHandler'), const handler = jasmine.createSpy('errorHandler');
errors = new jasmineUnderTest.GlobalErrors(fakeGlobal), const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
fooError = new Error('foo'); const fooError = new Error('foo');
errors.install(); errors.install();
errors.pushListener(handler); errors.pushListener(handler);
@@ -50,10 +50,10 @@ describe('GlobalErrors', function() {
}); });
it('only calls the most recent handler', function() { it('only calls the most recent handler', function() {
const fakeGlobal = { onerror: null }, const fakeGlobal = minimalBrowserGlobal();
handler1 = jasmine.createSpy('errorHandler1'), const handler1 = jasmine.createSpy('errorHandler1');
handler2 = jasmine.createSpy('errorHandler2'), const handler2 = jasmine.createSpy('errorHandler2');
errors = new jasmineUnderTest.GlobalErrors(fakeGlobal); const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
errors.install(); errors.install();
errors.pushListener(handler1); errors.pushListener(handler1);
@@ -66,10 +66,10 @@ describe('GlobalErrors', function() {
}); });
it('calls previous handlers when one is removed', function() { it('calls previous handlers when one is removed', function() {
const fakeGlobal = { onerror: null }, const fakeGlobal = minimalBrowserGlobal();
handler1 = jasmine.createSpy('errorHandler1'), const handler1 = jasmine.createSpy('errorHandler1');
handler2 = jasmine.createSpy('errorHandler2'), const handler2 = jasmine.createSpy('errorHandler2');
errors = new jasmineUnderTest.GlobalErrors(fakeGlobal); const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
errors.install(); errors.install();
errors.pushListener(handler1); errors.pushListener(handler1);
@@ -91,9 +91,12 @@ describe('GlobalErrors', function() {
}); });
it('uninstalls itself, putting back a previous callback', function() { it('uninstalls itself, putting back a previous callback', function() {
const originalCallback = jasmine.createSpy('error'), const originalCallback = jasmine.createSpy('error');
fakeGlobal = { onerror: originalCallback }, const fakeGlobal = {
errors = new jasmineUnderTest.GlobalErrors(fakeGlobal); ...minimalBrowserGlobal(),
onerror: originalCallback
};
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
expect(fakeGlobal.onerror).toBe(originalCallback); expect(fakeGlobal.onerror).toBe(originalCallback);
@@ -107,9 +110,9 @@ describe('GlobalErrors', function() {
}); });
it('rethrows the original error when there is no handler', function() { it('rethrows the original error when there is no handler', function() {
const fakeGlobal = {}, const fakeGlobal = minimalBrowserGlobal();
errors = new jasmineUnderTest.GlobalErrors(fakeGlobal), const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
originalError = new Error('nope'); const originalError = new Error('nope');
errors.install(); errors.install();
@@ -407,7 +410,7 @@ describe('GlobalErrors', function() {
describe('#setOverrideListener', function() { describe('#setOverrideListener', function() {
it('overrides the existing handlers in browsers until removed', function() { it('overrides the existing handlers in browsers until removed', function() {
const fakeGlobal = { onerror: null }; const fakeGlobal = minimalBrowserGlobal();
const handler0 = jasmine.createSpy('handler0'); const handler0 = jasmine.createSpy('handler0');
const handler1 = jasmine.createSpy('handler1'); const handler1 = jasmine.createSpy('handler1');
const overrideHandler = jasmine.createSpy('overrideHandler'); const overrideHandler = jasmine.createSpy('overrideHandler');
@@ -529,8 +532,7 @@ describe('GlobalErrors', function() {
}); });
it('throws if there is already an override handler', function() { it('throws if there is already an override handler', function() {
const fakeGlobal = { onerror: null }; const errors = new jasmineUnderTest.GlobalErrors(minimalBrowserGlobal());
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
errors.setOverrideListener(() => {}, () => {}); errors.setOverrideListener(() => {}, () => {});
expect(function() { expect(function() {
@@ -541,9 +543,8 @@ describe('GlobalErrors', function() {
describe('#removeOverrideListener', function() { describe('#removeOverrideListener', function() {
it("calls the handler's onRemove callback", function() { it("calls the handler's onRemove callback", function() {
const fakeGlobal = { onerror: null };
const onRemove = jasmine.createSpy('onRemove'); const onRemove = jasmine.createSpy('onRemove');
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal); const errors = new jasmineUnderTest.GlobalErrors(minimalBrowserGlobal());
errors.setOverrideListener(() => {}, onRemove); errors.setOverrideListener(() => {}, onRemove);
errors.removeOverrideListener(); errors.removeOverrideListener();
@@ -552,10 +553,17 @@ describe('GlobalErrors', function() {
}); });
it('does not throw if there is no handler', function() { it('does not throw if there is no handler', function() {
const fakeGlobal = { onerror: null }; const errors = new jasmineUnderTest.GlobalErrors(minimalBrowserGlobal());
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
expect(() => errors.removeOverrideListener()).not.toThrow(); expect(() => errors.removeOverrideListener()).not.toThrow();
}); });
}); });
function minimalBrowserGlobal() {
return {
addEventListener() {},
removeEventListener() {},
onerror: null
};
}
}); });
+49 -6
View File
@@ -431,11 +431,15 @@ describe('Env integration', function() {
describe('Handling async errors', function() { describe('Handling async errors', function() {
it('routes async errors to a running spec', async function() { it('routes async errors to a running spec', async function() {
const global = { const global = {
...browserEventMethods(),
setTimeout: function(fn, delay) { setTimeout: function(fn, delay) {
return setTimeout(fn, delay); return setTimeout(fn, delay);
}, },
clearTimeout: function(fn, delay) { clearTimeout: function(fn, delay) {
clearTimeout(fn, delay); clearTimeout(fn, delay);
},
queueMicrotask: function(fn) {
queueMicrotask(fn);
} }
}; };
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global); spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
@@ -468,11 +472,15 @@ describe('Env integration', function() {
describe('When the running spec has reported specDone', function() { describe('When the running spec has reported specDone', function() {
it('routes async errors to an ancestor suite', async function() { it('routes async errors to an ancestor suite', async function() {
const global = { const global = {
...browserEventMethods(),
setTimeout: function(fn, delay) { setTimeout: function(fn, delay) {
return setTimeout(fn, delay); return setTimeout(fn, delay);
}, },
clearTimeout: function(fn) { clearTimeout: function(fn) {
clearTimeout(fn); clearTimeout(fn);
},
queueMicrotask: function(fn) {
queueMicrotask(fn);
} }
}; };
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global); spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
@@ -524,11 +532,15 @@ describe('Env integration', function() {
it('routes async errors to a running suite', async function() { it('routes async errors to a running suite', async function() {
const global = { const global = {
...browserEventMethods(),
setTimeout: function(fn, delay) { setTimeout: function(fn, delay) {
return setTimeout(fn, delay); return setTimeout(fn, delay);
}, },
clearTimeout: function(fn, delay) { clearTimeout: function(fn, delay) {
clearTimeout(fn, delay); clearTimeout(fn, delay);
},
queueMicrotask: function(fn) {
queueMicrotask(fn);
} }
}; };
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global); spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
@@ -545,8 +557,8 @@ describe('Env integration', function() {
env.it('fails', function(specDone) { env.it('fails', function(specDone) {
setTimeout(function() { setTimeout(function() {
specDone(); specDone();
setTimeout(function() { queueMicrotask(function() {
setTimeout(function() { queueMicrotask(function() {
global.onerror('fail'); global.onerror('fail');
}); });
}); });
@@ -573,11 +585,15 @@ describe('Env integration', function() {
describe('When the running suite has reported suiteDone', function() { describe('When the running suite has reported suiteDone', function() {
it('routes async errors to an ancestor suite', async function() { it('routes async errors to an ancestor suite', async function() {
const global = { const global = {
...browserEventMethods(),
setTimeout: function(fn, delay) { setTimeout: function(fn, delay) {
return setTimeout(fn, delay); return setTimeout(fn, delay);
}, },
clearTimeout: function(fn, delay) { clearTimeout: function(fn, delay) {
clearTimeout(fn, delay); clearTimeout(fn, delay);
},
queueMicrotask: function(fn) {
queueMicrotask(fn);
} }
}; };
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global); spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
@@ -633,11 +649,15 @@ describe('Env integration', function() {
describe('When the env has started reporting jasmineDone', function() { describe('When the env has started reporting jasmineDone', function() {
it('logs the error to the console', async function() { it('logs the error to the console', async function() {
const global = { const global = {
...browserEventMethods(),
setTimeout: function(fn, delay) { setTimeout: function(fn, delay) {
return setTimeout(fn, delay); return setTimeout(fn, delay);
}, },
clearTimeout: function(fn, delay) { clearTimeout: function(fn, delay) {
clearTimeout(fn, delay); clearTimeout(fn, delay);
},
queueMicrotask: function(fn) {
queueMicrotask(fn);
} }
}; };
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global); spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
@@ -672,11 +692,15 @@ describe('Env integration', function() {
it('routes all errors that occur during stack clearing somewhere', async function() { it('routes all errors that occur during stack clearing somewhere', async function() {
const global = { const global = {
...browserEventMethods(),
setTimeout: function(fn, delay) { setTimeout: function(fn, delay) {
return setTimeout(fn, delay); return setTimeout(fn, delay);
}, },
clearTimeout: function(fn) { clearTimeout: function(fn) {
clearTimeout(fn); clearTimeout(fn);
},
queueMicrotask: function(fn) {
queueMicrotask(fn);
} }
}; };
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global); spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
@@ -1427,8 +1451,8 @@ describe('Env integration', function() {
global: { global: {
setTimeout: globalSetTimeout, setTimeout: globalSetTimeout,
clearTimeout: clearTimeout, clearTimeout: clearTimeout,
setImmediate: function(cb) { queueMicrotask: function(fn) {
return setTimeout(cb, 0); queueMicrotask(fn);
} }
} }
}); });
@@ -1501,8 +1525,8 @@ describe('Env integration', function() {
clearTimeout: clearTimeout, clearTimeout: clearTimeout,
setInterval: setInterval, setInterval: setInterval,
clearInterval: clearInterval, clearInterval: clearInterval,
setImmediate: function(cb) { queueMicrotask: function(fn) {
return realSetTimeout(cb, 0); queueMicrotask(fn);
} }
} }
}); });
@@ -2619,12 +2643,16 @@ describe('Env integration', function() {
it('reports errors that occur during loading', async function() { it('reports errors that occur during loading', async function() {
const global = { const global = {
...browserEventMethods(),
setTimeout: function(fn, delay) { setTimeout: function(fn, delay) {
return setTimeout(fn, delay); return setTimeout(fn, delay);
}, },
clearTimeout: function(fn, delay) { clearTimeout: function(fn, delay) {
clearTimeout(fn, delay); clearTimeout(fn, delay);
}, },
queueMicrotask: function(fn) {
queueMicrotask(fn);
},
onerror: function() {} onerror: function() {}
}; };
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global); spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
@@ -2674,12 +2702,16 @@ describe('Env integration', function() {
it('does not install a global error handler during loading', async function() { it('does not install a global error handler during loading', async function() {
const originalOnerror = jasmine.createSpy('original onerror'); const originalOnerror = jasmine.createSpy('original onerror');
const global = { const global = {
...browserEventMethods(),
setTimeout: function(fn, delay) { setTimeout: function(fn, delay) {
return setTimeout(fn, delay); return setTimeout(fn, delay);
}, },
clearTimeout: function(fn, delay) { clearTimeout: function(fn, delay) {
clearTimeout(fn, delay); clearTimeout(fn, delay);
}, },
queueMicrotask: function(fn) {
queueMicrotask(fn);
},
onerror: originalOnerror onerror: originalOnerror
}; };
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global); spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
@@ -2864,11 +2896,15 @@ describe('Env integration', function() {
describe('When there are load errors', function() { describe('When there are load errors', function() {
it('is "failed"', async function() { it('is "failed"', async function() {
const global = { const global = {
...browserEventMethods(),
setTimeout: function(fn, delay) { setTimeout: function(fn, delay) {
return setTimeout(fn, delay); return setTimeout(fn, delay);
}, },
clearTimeout: function(fn, delay) { clearTimeout: function(fn, delay) {
return clearTimeout(fn, delay); return clearTimeout(fn, delay);
},
queueMicrotask: function(fn) {
queueMicrotask(fn);
} }
}; };
spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global); spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global);
@@ -3906,4 +3942,11 @@ describe('Env integration', function() {
); );
}); });
}); });
function browserEventMethods() {
return {
addEventListener() {},
removeEventListener() {}
};
}
}); });
+49 -23
View File
@@ -1,8 +1,32 @@
getJasmineRequireObj().clearStack = function(j$) { getJasmineRequireObj().clearStack = function(j$) {
const maxInlineCallCount = 10; const maxInlineCallCount = 10;
function messageChannelImpl(global, setTimeout) { function browserQueueMicrotaskImpl(global) {
const channel = new global.MessageChannel(); const { setTimeout, queueMicrotask } = global;
let currentCallCount = 0;
return function clearStack(fn) {
currentCallCount++;
if (currentCallCount < maxInlineCallCount) {
queueMicrotask(fn);
} else {
currentCallCount = 0;
setTimeout(fn);
}
};
}
function nodeQueueMicrotaskImpl(global) {
const { queueMicrotask } = global;
return function(fn) {
queueMicrotask(fn);
};
}
function messageChannelImpl(global) {
const { MessageChannel, setTimeout } = global;
const channel = new MessageChannel();
let head = {}; let head = {};
let tail = head; let tail = head;
@@ -13,7 +37,7 @@ getJasmineRequireObj().clearStack = function(j$) {
delete head.task; delete head.task;
if (taskRunning) { if (taskRunning) {
global.setTimeout(task, 0); setTimeout(task, 0);
} else { } else {
try { try {
taskRunning = true; taskRunning = true;
@@ -39,29 +63,31 @@ getJasmineRequireObj().clearStack = function(j$) {
} }
function getClearStack(global) { function getClearStack(global) {
let currentCallCount = 0; const NODE_JS =
const realSetTimeout = global.setTimeout; global.process &&
const setTimeoutImpl = function clearStack(fn) { global.process.versions &&
Function.prototype.apply.apply(realSetTimeout, [global, [fn, 0]]); typeof global.process.versions.node === 'string';
};
if (j$.isFunction_(global.setImmediate)) { const SAFARI =
const realSetImmediate = global.setImmediate; global.navigator &&
return function(fn) { /^((?!chrome|android).)*safari/i.test(global.navigator.userAgent);
currentCallCount++;
if (currentCallCount < maxInlineCallCount) { if (NODE_JS) {
realSetImmediate(fn); // Unlike browsers, Node doesn't require us to do a periodic setTimeout
} else { // so we avoid the overhead.
currentCallCount = 0; return nodeQueueMicrotaskImpl(global);
} else if (
setTimeoutImpl(fn); SAFARI ||
} j$.util.isUndefined(global.MessageChannel) /* tests */
}; ) {
} else if (!j$.util.isUndefined(global.MessageChannel)) { // queueMicrotask is dramatically faster than MessageChannel in Safari.
return messageChannelImpl(global, setTimeoutImpl); // Some of our own integration tests provide a mock queueMicrotask in all
// environments because it's simpler to mock than MessageChannel.
return browserQueueMicrotaskImpl(global);
} else { } else {
return setTimeoutImpl; // MessageChannel is faster than queueMicrotask in supported browsers
// other than Safari.
return messageChannelImpl(global);
} }
} }
+5 -12
View File
@@ -109,21 +109,14 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
} }
}; };
if (global.addEventListener) { global.addEventListener('unhandledrejection', browserRejectionHandler);
global.addEventListener(
'unhandledrejection',
browserRejectionHandler
);
}
this.uninstall = function uninstall() { this.uninstall = function uninstall() {
global.onerror = originalHandler; global.onerror = originalHandler;
if (global.removeEventListener) { global.removeEventListener(
global.removeEventListener( 'unhandledrejection',
'unhandledrejection', browserRejectionHandler
browserRejectionHandler );
);
}
}; };
} }
}; };