Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5cd7d47f72 | ||
|
|
d0fe5c4712 | ||
|
|
f602c4911c | ||
|
|
7aaf7eaf30 | ||
|
|
35f16e8125 | ||
|
|
acc777c267 | ||
|
|
1c74356691 | ||
|
|
bbebea0fa5 | ||
|
|
66eb27b0af | ||
|
|
7a63c06a65 | ||
|
|
554dfd4923 | ||
|
|
36a6e2aa1d | ||
|
|
3c4b73f136 | ||
|
|
bc3ed74336 | ||
|
|
97b6f33cc2 | ||
|
|
a9889ddb31 |
@@ -11,6 +11,7 @@
|
||||
"ecmaVersion": 2018
|
||||
},
|
||||
"rules": {
|
||||
"curly": "error",
|
||||
"quotes": [
|
||||
"error",
|
||||
"single",
|
||||
|
||||
@@ -2430,7 +2430,9 @@ getJasmineRequireObj().MapContaining = function(j$) {
|
||||
}
|
||||
|
||||
MapContaining.prototype.asymmetricMatch = function(other, matchersUtil) {
|
||||
if (!j$.isMap(other)) return false;
|
||||
if (!j$.isMap(other)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const [key, value] of this.sample) {
|
||||
// for each key/value pair in `sample`
|
||||
@@ -2569,7 +2571,9 @@ getJasmineRequireObj().SetContaining = function(j$) {
|
||||
}
|
||||
|
||||
SetContaining.prototype.asymmetricMatch = function(other, matchersUtil) {
|
||||
if (!j$.isSet(other)) return false;
|
||||
if (!j$.isSet(other)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const item of this.sample) {
|
||||
// for each item in `sample` there should be at least one matching item in `other`
|
||||
@@ -2876,7 +2880,8 @@ getJasmineRequireObj().clearStack = function(j$) {
|
||||
const maxInlineCallCount = 10;
|
||||
|
||||
function browserQueueMicrotaskImpl(global) {
|
||||
const { setTimeout, queueMicrotask } = global;
|
||||
const unclampedSetTimeout = getUnclampedSetTimeout(global);
|
||||
const { queueMicrotask } = global;
|
||||
let currentCallCount = 0;
|
||||
return function clearStack(fn) {
|
||||
currentCallCount++;
|
||||
@@ -2885,7 +2890,7 @@ getJasmineRequireObj().clearStack = function(j$) {
|
||||
queueMicrotask(fn);
|
||||
} else {
|
||||
currentCallCount = 0;
|
||||
setTimeout(fn);
|
||||
unclampedSetTimeout(fn);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -2899,6 +2904,37 @@ getJasmineRequireObj().clearStack = function(j$) {
|
||||
}
|
||||
|
||||
function messageChannelImpl(global) {
|
||||
const { setTimeout } = global;
|
||||
const postMessage = getPostMessage(global);
|
||||
|
||||
let currentCallCount = 0;
|
||||
return function clearStack(fn) {
|
||||
currentCallCount++;
|
||||
|
||||
if (currentCallCount < maxInlineCallCount) {
|
||||
postMessage(fn);
|
||||
} else {
|
||||
currentCallCount = 0;
|
||||
setTimeout(fn);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function getUnclampedSetTimeout(global) {
|
||||
const { setTimeout } = global;
|
||||
if (j$.util.isUndefined(global.MessageChannel)) {
|
||||
return setTimeout;
|
||||
}
|
||||
|
||||
const postMessage = getPostMessage(global);
|
||||
return function unclampedSetTimeout(fn) {
|
||||
postMessage(function() {
|
||||
setTimeout(fn);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function getPostMessage(global) {
|
||||
const { MessageChannel, setTimeout } = global;
|
||||
const channel = new MessageChannel();
|
||||
let head = {};
|
||||
@@ -2922,17 +2958,9 @@ getJasmineRequireObj().clearStack = function(j$) {
|
||||
}
|
||||
};
|
||||
|
||||
let currentCallCount = 0;
|
||||
return function clearStack(fn) {
|
||||
currentCallCount++;
|
||||
|
||||
if (currentCallCount < maxInlineCallCount) {
|
||||
tail = tail.next = { task: fn };
|
||||
channel.port2.postMessage(0);
|
||||
} else {
|
||||
currentCallCount = 0;
|
||||
setTimeout(fn);
|
||||
}
|
||||
return function postMessage(fn) {
|
||||
tail = tail.next = { task: fn };
|
||||
channel.port2.postMessage(0);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2942,20 +2970,25 @@ getJasmineRequireObj().clearStack = function(j$) {
|
||||
global.process.versions &&
|
||||
typeof global.process.versions.node === 'string';
|
||||
|
||||
const SAFARI =
|
||||
// Windows builds of WebKit have a fairly generic user agent string when no application name is provided:
|
||||
// e.g. "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/605.1.15 (KHTML, like Gecko)"
|
||||
const SAFARI_OR_WIN_WEBKIT =
|
||||
global.navigator &&
|
||||
/^((?!chrome|android).)*safari/i.test(global.navigator.userAgent);
|
||||
/(^((?!chrome|android).)*safari)|(Win64; x64\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\)$)/i.test(
|
||||
global.navigator.userAgent
|
||||
);
|
||||
|
||||
if (NODE_JS) {
|
||||
// Unlike browsers, Node doesn't require us to do a periodic setTimeout
|
||||
// so we avoid the overhead.
|
||||
return nodeQueueMicrotaskImpl(global);
|
||||
} else if (
|
||||
SAFARI ||
|
||||
SAFARI_OR_WIN_WEBKIT ||
|
||||
j$.util.isUndefined(global.MessageChannel) /* tests */
|
||||
) {
|
||||
// queueMicrotask is dramatically faster than MessageChannel in Safari,
|
||||
// at least through version 16.
|
||||
// queueMicrotask is dramatically faster than MessageChannel in Safari
|
||||
// and other WebKit-based browsers, such as the one distributed by Playwright
|
||||
// to test Safari-like behavior on Windows.
|
||||
// Some of our own integration tests provide a mock queueMicrotask in all
|
||||
// environments because it's simpler to mock than MessageChannel.
|
||||
return browserQueueMicrotaskImpl(global);
|
||||
@@ -6650,7 +6683,10 @@ getJasmineRequireObj().toHaveSpyInteractions = function(j$) {
|
||||
let hasSpy = false;
|
||||
const calledSpies = [];
|
||||
for (const spy of Object.values(actual)) {
|
||||
if (!j$.isSpy(spy)) continue;
|
||||
if (!j$.isSpy(spy)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
hasSpy = true;
|
||||
|
||||
if (spy.calls.any()) {
|
||||
@@ -8051,6 +8087,32 @@ getJasmineRequireObj().ReportDispatcher = function(j$) {
|
||||
};
|
||||
|
||||
getJasmineRequireObj().reporterEvents = function() {
|
||||
/**
|
||||
* Used to tell Jasmine what optional or uncommonly implemented features
|
||||
* the reporter supports. If not specified, the defaults described in
|
||||
* {@link ReporterCapabilities} will apply.
|
||||
* @name Reporter#reporterCapabilities
|
||||
* @type ReporterCapabilities | undefined
|
||||
* @since 5.0
|
||||
*/
|
||||
/**
|
||||
* Used to tell Jasmine what optional or uncommonly implemented features
|
||||
* the reporter supports.
|
||||
* @interface ReporterCapabilities
|
||||
* @see Reporter#reporterCapabilities
|
||||
* @since 5.0
|
||||
*/
|
||||
/**
|
||||
* Indicates whether the reporter supports parallel execution. Jasmine will
|
||||
* not allow parallel execution unless all reporters that are in use set this
|
||||
* capability to true.
|
||||
* @name ReporterCapabilities#parallel
|
||||
* @type boolean | undefined
|
||||
* @default false
|
||||
* @see running_specs_in_parallel
|
||||
* @since 5.0
|
||||
*/
|
||||
|
||||
const events = [
|
||||
/**
|
||||
* `jasmineStarted` is called after all of the specs have been loaded, but just before execution starts.
|
||||
@@ -9449,6 +9511,16 @@ getJasmineRequireObj().SpyRegistry = function(j$) {
|
||||
|
||||
obj[methodName] = spiedMethod;
|
||||
|
||||
// Check if setting the property actually worked. Some objects, such as
|
||||
// localStorage in Firefox and later Safari versions, have no-op setters.
|
||||
if (obj[methodName] !== spiedMethod) {
|
||||
throw new Error(
|
||||
j$.formatErrorMsg('<spyOn>')(
|
||||
`Can't spy on ${methodName} because assigning to it had no effect`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return spiedMethod;
|
||||
};
|
||||
|
||||
@@ -10910,5 +10982,5 @@ getJasmineRequireObj().UserContext = function(j$) {
|
||||
};
|
||||
|
||||
getJasmineRequireObj().version = function() {
|
||||
return '5.2.0';
|
||||
return '5.3.0';
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jasmine-core",
|
||||
"license": "MIT",
|
||||
"version": "5.2.0",
|
||||
"version": "5.3.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/jasmine/jasmine.git"
|
||||
|
||||
35
release_notes/5.3.0.md
Normal file
35
release_notes/5.3.0.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Jasmine Core 5.3.0 Release Notes
|
||||
|
||||
## Changes
|
||||
|
||||
* Improved performance in Safari
|
||||
* Merges [#2040](https://github.com/jasmine/jasmine/pull/2040) from @dcsaszar
|
||||
* Fixes [#2008](https://github.com/jasmine/jasmine/issues/2008)
|
||||
|
||||
* Improved performance in Playwright Webkit on Windows
|
||||
* Merges [#2034](https://github.com/jasmine/jasmine/pull/2034) from @m-akinc
|
||||
|
||||
* Throw if spying has no effect, as when spying on localStorage methods in Firefox and Safari 17
|
||||
* See [#2036](https://github.com/jasmine/jasmine/issues/2036) and [#2007](https://github.com/jasmine/jasmine/issues/2007)
|
||||
|
||||
|
||||
## Documentation improvements
|
||||
|
||||
* Added API reference for reporter capabilities
|
||||
|
||||
## Supported environments
|
||||
|
||||
This version has been tested in the following environments.
|
||||
|
||||
| Environment | Supported versions |
|
||||
|-------------------|--------------------|
|
||||
| Node | 18, 20, 22 |
|
||||
| Safari | 15-17 |
|
||||
| Chrome | 128 |
|
||||
| Firefox | 102, 115, 130 |
|
||||
| Edge | 128 |
|
||||
|
||||
|
||||
------
|
||||
|
||||
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_
|
||||
@@ -20,6 +20,47 @@ describe('ClearStack', function() {
|
||||
MessageChannel: fakeMessageChannel
|
||||
};
|
||||
});
|
||||
|
||||
it('uses MessageChannel to reduce setTimeout clamping', function() {
|
||||
const fakeChannel = fakeMessageChannel();
|
||||
spyOn(fakeChannel.port2, 'postMessage');
|
||||
const queueMicrotask = jasmine.createSpy('queueMicrotask');
|
||||
const global = {
|
||||
navigator: {
|
||||
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'
|
||||
},
|
||||
MessageChannel: function() {
|
||||
return fakeChannel;
|
||||
},
|
||||
queueMicrotask
|
||||
};
|
||||
|
||||
const clearStack = jasmineUnderTest.getClearStack(global);
|
||||
|
||||
for (let i = 0; i < 9; i++) {
|
||||
clearStack(function() {});
|
||||
}
|
||||
|
||||
expect(fakeChannel.port2.postMessage).not.toHaveBeenCalled();
|
||||
|
||||
clearStack(function() {});
|
||||
|
||||
expect(fakeChannel.port2.postMessage).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("in WebKit (Playwright's build for Windows)", function() {
|
||||
usesQueueMicrotaskWithSetTimeout(function() {
|
||||
return {
|
||||
navigator: {
|
||||
userAgent:
|
||||
'Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/605.1.15 (KHTML, like Gecko)'
|
||||
},
|
||||
// queueMicrotask should be used even though MessageChannel is present
|
||||
MessageChannel: fakeMessageChannel
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
describe('in browsers other than Safari', function() {
|
||||
|
||||
@@ -94,6 +94,30 @@ describe('SpyRegistry', function() {
|
||||
}).not.toThrowError(/is not declared writable or has no setter/);
|
||||
});
|
||||
|
||||
it('throws if assigning to the property is a no-op', function() {
|
||||
const scope = {};
|
||||
|
||||
function original() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
Object.defineProperty(scope, 'myFunc', {
|
||||
get() {
|
||||
return original;
|
||||
},
|
||||
set() {}
|
||||
});
|
||||
|
||||
const spyRegistry = new jasmineUnderTest.SpyRegistry({
|
||||
createSpy: createSpy
|
||||
});
|
||||
expect(function() {
|
||||
spyRegistry.spyOn(scope, 'myFunc');
|
||||
}).toThrowError(
|
||||
"<spyOn> : Can't spy on myFunc because assigning to it had no effect"
|
||||
);
|
||||
});
|
||||
|
||||
it('overrides the method on the object and returns the spy', function() {
|
||||
const originalFunctionWasCalled = false,
|
||||
spyRegistry = new jasmineUnderTest.SpyRegistry({
|
||||
|
||||
@@ -1315,8 +1315,9 @@ describe('Env integration', function() {
|
||||
'works with constructors when using callThrough spy strategy',
|
||||
function() {
|
||||
function MyClass(foo) {
|
||||
if (!(this instanceof MyClass))
|
||||
if (!(this instanceof MyClass)) {
|
||||
throw new Error('You must use the new keyword.');
|
||||
}
|
||||
this.foo = foo;
|
||||
}
|
||||
const subject = { MyClass: MyClass };
|
||||
|
||||
@@ -12,14 +12,12 @@
|
||||
};
|
||||
|
||||
function getSourceFiles() {
|
||||
const src_files = ['core/**/*.js', 'version.js'].map(function(file) {
|
||||
return path.join(__dirname, '../../', 'src/', file);
|
||||
});
|
||||
const globs = ['../../src/core/**/*.js', '../../src/version.js'];
|
||||
const srcFiles = globs.flatMap(g => glob.sync(g, { cwd: __dirname }));
|
||||
|
||||
const files = src_files.flatMap(g => glob.sync(g));
|
||||
files.forEach(function(resolvedFile) {
|
||||
require(resolvedFile);
|
||||
});
|
||||
for (const file of srcFiles) {
|
||||
require(file);
|
||||
}
|
||||
}
|
||||
|
||||
getSourceFiles();
|
||||
|
||||
@@ -2,7 +2,8 @@ getJasmineRequireObj().clearStack = function(j$) {
|
||||
const maxInlineCallCount = 10;
|
||||
|
||||
function browserQueueMicrotaskImpl(global) {
|
||||
const { setTimeout, queueMicrotask } = global;
|
||||
const unclampedSetTimeout = getUnclampedSetTimeout(global);
|
||||
const { queueMicrotask } = global;
|
||||
let currentCallCount = 0;
|
||||
return function clearStack(fn) {
|
||||
currentCallCount++;
|
||||
@@ -11,7 +12,7 @@ getJasmineRequireObj().clearStack = function(j$) {
|
||||
queueMicrotask(fn);
|
||||
} else {
|
||||
currentCallCount = 0;
|
||||
setTimeout(fn);
|
||||
unclampedSetTimeout(fn);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -25,6 +26,37 @@ getJasmineRequireObj().clearStack = function(j$) {
|
||||
}
|
||||
|
||||
function messageChannelImpl(global) {
|
||||
const { setTimeout } = global;
|
||||
const postMessage = getPostMessage(global);
|
||||
|
||||
let currentCallCount = 0;
|
||||
return function clearStack(fn) {
|
||||
currentCallCount++;
|
||||
|
||||
if (currentCallCount < maxInlineCallCount) {
|
||||
postMessage(fn);
|
||||
} else {
|
||||
currentCallCount = 0;
|
||||
setTimeout(fn);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function getUnclampedSetTimeout(global) {
|
||||
const { setTimeout } = global;
|
||||
if (j$.util.isUndefined(global.MessageChannel)) {
|
||||
return setTimeout;
|
||||
}
|
||||
|
||||
const postMessage = getPostMessage(global);
|
||||
return function unclampedSetTimeout(fn) {
|
||||
postMessage(function() {
|
||||
setTimeout(fn);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function getPostMessage(global) {
|
||||
const { MessageChannel, setTimeout } = global;
|
||||
const channel = new MessageChannel();
|
||||
let head = {};
|
||||
@@ -48,17 +80,9 @@ getJasmineRequireObj().clearStack = function(j$) {
|
||||
}
|
||||
};
|
||||
|
||||
let currentCallCount = 0;
|
||||
return function clearStack(fn) {
|
||||
currentCallCount++;
|
||||
|
||||
if (currentCallCount < maxInlineCallCount) {
|
||||
tail = tail.next = { task: fn };
|
||||
channel.port2.postMessage(0);
|
||||
} else {
|
||||
currentCallCount = 0;
|
||||
setTimeout(fn);
|
||||
}
|
||||
return function postMessage(fn) {
|
||||
tail = tail.next = { task: fn };
|
||||
channel.port2.postMessage(0);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -68,20 +92,25 @@ getJasmineRequireObj().clearStack = function(j$) {
|
||||
global.process.versions &&
|
||||
typeof global.process.versions.node === 'string';
|
||||
|
||||
const SAFARI =
|
||||
// Windows builds of WebKit have a fairly generic user agent string when no application name is provided:
|
||||
// e.g. "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/605.1.15 (KHTML, like Gecko)"
|
||||
const SAFARI_OR_WIN_WEBKIT =
|
||||
global.navigator &&
|
||||
/^((?!chrome|android).)*safari/i.test(global.navigator.userAgent);
|
||||
/(^((?!chrome|android).)*safari)|(Win64; x64\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\)$)/i.test(
|
||||
global.navigator.userAgent
|
||||
);
|
||||
|
||||
if (NODE_JS) {
|
||||
// Unlike browsers, Node doesn't require us to do a periodic setTimeout
|
||||
// so we avoid the overhead.
|
||||
return nodeQueueMicrotaskImpl(global);
|
||||
} else if (
|
||||
SAFARI ||
|
||||
SAFARI_OR_WIN_WEBKIT ||
|
||||
j$.util.isUndefined(global.MessageChannel) /* tests */
|
||||
) {
|
||||
// queueMicrotask is dramatically faster than MessageChannel in Safari,
|
||||
// at least through version 16.
|
||||
// queueMicrotask is dramatically faster than MessageChannel in Safari
|
||||
// and other WebKit-based browsers, such as the one distributed by Playwright
|
||||
// to test Safari-like behavior on Windows.
|
||||
// Some of our own integration tests provide a mock queueMicrotask in all
|
||||
// environments because it's simpler to mock than MessageChannel.
|
||||
return browserQueueMicrotaskImpl(global);
|
||||
|
||||
@@ -84,6 +84,16 @@ getJasmineRequireObj().SpyRegistry = function(j$) {
|
||||
|
||||
obj[methodName] = spiedMethod;
|
||||
|
||||
// Check if setting the property actually worked. Some objects, such as
|
||||
// localStorage in Firefox and later Safari versions, have no-op setters.
|
||||
if (obj[methodName] !== spiedMethod) {
|
||||
throw new Error(
|
||||
j$.formatErrorMsg('<spyOn>')(
|
||||
`Can't spy on ${methodName} because assigning to it had no effect`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return spiedMethod;
|
||||
};
|
||||
|
||||
|
||||
@@ -11,7 +11,9 @@ getJasmineRequireObj().MapContaining = function(j$) {
|
||||
}
|
||||
|
||||
MapContaining.prototype.asymmetricMatch = function(other, matchersUtil) {
|
||||
if (!j$.isMap(other)) return false;
|
||||
if (!j$.isMap(other)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const [key, value] of this.sample) {
|
||||
// for each key/value pair in `sample`
|
||||
|
||||
@@ -11,7 +11,9 @@ getJasmineRequireObj().SetContaining = function(j$) {
|
||||
}
|
||||
|
||||
SetContaining.prototype.asymmetricMatch = function(other, matchersUtil) {
|
||||
if (!j$.isSet(other)) return false;
|
||||
if (!j$.isSet(other)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const item of this.sample) {
|
||||
// for each item in `sample` there should be at least one matching item in `other`
|
||||
|
||||
@@ -32,7 +32,10 @@ getJasmineRequireObj().toHaveSpyInteractions = function(j$) {
|
||||
let hasSpy = false;
|
||||
const calledSpies = [];
|
||||
for (const spy of Object.values(actual)) {
|
||||
if (!j$.isSpy(spy)) continue;
|
||||
if (!j$.isSpy(spy)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
hasSpy = true;
|
||||
|
||||
if (spy.calls.any()) {
|
||||
|
||||
@@ -1,4 +1,30 @@
|
||||
getJasmineRequireObj().reporterEvents = function() {
|
||||
/**
|
||||
* Used to tell Jasmine what optional or uncommonly implemented features
|
||||
* the reporter supports. If not specified, the defaults described in
|
||||
* {@link ReporterCapabilities} will apply.
|
||||
* @name Reporter#reporterCapabilities
|
||||
* @type ReporterCapabilities | undefined
|
||||
* @since 5.0
|
||||
*/
|
||||
/**
|
||||
* Used to tell Jasmine what optional or uncommonly implemented features
|
||||
* the reporter supports.
|
||||
* @interface ReporterCapabilities
|
||||
* @see Reporter#reporterCapabilities
|
||||
* @since 5.0
|
||||
*/
|
||||
/**
|
||||
* Indicates whether the reporter supports parallel execution. Jasmine will
|
||||
* not allow parallel execution unless all reporters that are in use set this
|
||||
* capability to true.
|
||||
* @name ReporterCapabilities#parallel
|
||||
* @type boolean | undefined
|
||||
* @default false
|
||||
* @see running_specs_in_parallel
|
||||
* @since 5.0
|
||||
*/
|
||||
|
||||
const events = [
|
||||
/**
|
||||
* `jasmineStarted` is called after all of the specs have been loaded, but just before execution starts.
|
||||
|
||||
Reference in New Issue
Block a user