Compare commits

...

16 Commits

Author SHA1 Message Date
Steve Gravrock
cd1b7ce9c7 Bump version to 5.2.0 2024-07-20 08:44:53 -07:00
Steve Gravrock
3653d6e0ef Moved eslint and prettier configs out of package.json 2024-07-18 07:17:31 -07:00
Steve Gravrock
c3387f8dbf Merge branch 'stephanreiter-better-toHaveSize'
* Merges #2033 from @stephanreiter
2024-07-13 14:02:15 -07:00
Steve Gravrock
3d54184c7f Docs: Improved discoverability of asymmetric equality testers 2024-07-03 10:17:57 -07:00
Stephan Ferlin-Reiter
6f23e706d7 Improve the error message of the toHaveSize matcher.
We include the size of the thing that didn't meet the size expectation.
2024-07-02 20:28:16 +00:00
Steve Gravrock
cc69edf92c Fixed stack trace filtering in FF when the developer tools are open 2024-06-22 11:49:49 -07:00
Steve Gravrock
ba7560f65e HTML reporter: show debug logs with white-space: pre 2024-06-22 11:44:11 -07:00
Steve Gravrock
c3960c4a96 Test against Node 22 2024-06-18 18:13:51 -07:00
Steve Gravrock
5c21f94bb1 4.6.1 release notes 2024-05-25 10:24:11 -07:00
Steve Gravrock
8cd7c94490 Added a jsdoc example for withContext()
Fixes jasmine/jasmine.github.io#166
2024-04-16 16:22:25 -07:00
Steve Gravrock
6a6fa7b29a Tests for existing handling of non-Error global errors in Node 2024-03-22 09:15:22 -07:00
Steve Gravrock
4984548cab Clarify argument to spyOnGlobalErrorsAsync's spy 2024-03-22 08:53:19 -07:00
Steve Gravrock
6941bde7e2 Revert "Deprecate the suppressLoadErrors option"
jasmine-npm still needs this to enable the default behavior of crashing
with a stack trace on load errors.

This reverts commit 99e350ac85.
2024-03-21 09:34:26 -07:00
Steve Gravrock
1504f25ced Report the message when a browser error event with a message but no error occurs 2024-03-21 09:15:43 -07:00
Steve Gravrock
99e350ac85 Deprecate the suppressLoadErrors option
This was intended as a 3.0 migration aid for browser users who had
dependencies that triggered errors at load time. However, it was never
documented and never supported by jasmine-brower-runner, karma, or any
other commonly used tool for runing Jasmine in the browser. There is
no evidence of it actually being used. It is, however, starting to
show up in machine-generated "tutorials".
2024-03-04 19:35:31 -08:00
Steve Gravrock
1624b07589 Clarify spyOnGlobalErrorsAsync API docs 2024-03-04 19:35:24 -08:00
24 changed files with 636 additions and 173 deletions

View File

@@ -4,6 +4,10 @@
version: 2.1 version: 2.1
executors: executors:
node22:
docker:
- image: cimg/node:22.0.0
working_directory: ~/workspace
node20: node20:
docker: docker:
- image: cimg/node:20.0.0 - image: cimg/node:20.0.0
@@ -94,12 +98,20 @@ workflows:
push: push:
jobs: jobs:
- build:
executor: node22
name: build_node_22
- build: - build:
executor: node20 executor: node20
name: build_node_20 name: build_node_20
- build: - build:
executor: node18 executor: node18
name: build_node_18 name: build_node_18
- test_node:
executor: node22
name: test_node_22
requires:
- build_node_22
- test_node: - test_node:
executor: node20 executor: node20
name: test_node_20 name: test_node_20
@@ -115,6 +127,11 @@ workflows:
name: test_parallel_node_18 name: test_parallel_node_18
requires: requires:
- build_node_18 - build_node_18
- test_parallel:
executor: node22
name: test_parallel_node_22
requires:
- build_node_22
- test_parallel: - test_parallel:
executor: node20 executor: node20
name: test_parallel_node_20 name: test_parallel_node_20

46
.eslintrc Normal file
View File

@@ -0,0 +1,46 @@
{
"extends": [
"plugin:compat/recommended"
],
"env": {
"browser": true,
"node": true,
"es2017": true
},
"parserOptions": {
"ecmaVersion": 2018
},
"rules": {
"quotes": [
"error",
"single",
{
"avoidEscape": true
}
],
"no-unused-vars": [
"error",
{
"args": "none"
}
],
"no-implicit-globals": "error",
"block-spacing": "error",
"func-call-spacing": [
"error",
"never"
],
"key-spacing": "error",
"no-tabs": "error",
"no-trailing-spaces": "error",
"no-whitespace-before-property": "error",
"semi": [
"error",
"always"
],
"space-before-blocks": "error",
"no-eval": "error",
"no-var": "error",
"no-debugger": "error"
}
}

3
.prettierrc Normal file
View File

@@ -0,0 +1,3 @@
{
"singleQuote": true
}

View File

@@ -29,7 +29,7 @@ Microsoft Edge) as well as Node.
| Environment | Supported versions | | Environment | Supported versions |
|-------------------|---------------------| |-------------------|---------------------|
| Node | 18, 20 | | Node | 18, 20, 22 |
| Safari | 15-17 | | Safari | 15-17 |
| Chrome | Evergreen | | Chrome | Evergreen |
| Firefox | Evergreen, 102, 115 | | Firefox | Evergreen, 102, 115 |

View File

@@ -463,7 +463,11 @@ jasmineRequire.HtmlReporter = function(j$) {
'tr', 'tr',
{}, {},
createDom('td', {}, entry.timestamp.toString()), createDom('td', {}, entry.timestamp.toString()),
createDom('td', {}, entry.message) createDom(
'td',
{ className: 'jasmine-debug-log-msg' },
entry.message
)
) )
); );
}); });

View File

@@ -295,4 +295,7 @@ body {
} }
.jasmine_html-reporter .jasmine-debug-log table, .jasmine_html-reporter .jasmine-debug-log th, .jasmine_html-reporter .jasmine-debug-log td { .jasmine_html-reporter .jasmine-debug-log table, .jasmine_html-reporter .jasmine-debug-log th, .jasmine_html-reporter .jasmine-debug-log td {
border: 1px solid #ddd; border: 1px solid #ddd;
}
.jasmine_html-reporter .jasmine-debug-log .jasmine-debug-log-msg {
white-space: pre;
} }

View File

@@ -400,9 +400,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* that will succeed if the actual value being compared is an instance of the specified class/constructor. * value being compared is an instance of the specified class/constructor.
* @name jasmine.any * @name asymmetricEqualityTesters.any
* @emittedName jasmine.any
* @since 1.3.0 * @since 1.3.0
* @function * @function
* @param {Constructor} clazz - The constructor to check against. * @param {Constructor} clazz - The constructor to check against.
@@ -412,9 +413,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* that will succeed if the actual value being compared is not `null` and not `undefined`. * value being compared is not `null` and not `undefined`.
* @name jasmine.anything * @name asymmetricEqualityTesters.anything
* @emittedName jasmine.anything
* @since 2.2.0 * @since 2.2.0
* @function * @function
*/ */
@@ -423,9 +425,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* that will succeed if the actual value being compared is `true` or anything truthy. * value being compared is `true` or anything truthy.
* @name jasmine.truthy * @name asymmetricEqualityTesters.truthy
* @emittedName jasmine.truthy
* @since 3.1.0 * @since 3.1.0
* @function * @function
*/ */
@@ -434,9 +437,11 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* that will succeed if the actual value being compared is `null`, `undefined`, `0`, `false` or anything falsey. * value being compared is `null`, `undefined`, `0`, `false` or anything
* @name jasmine.falsy * falsy.
* @name asymmetricEqualityTesters.falsy
* @emittedName jasmine.falsy
* @since 3.1.0 * @since 3.1.0
* @function * @function
*/ */
@@ -445,9 +450,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* that will succeed if the actual value being compared is empty. * value being compared is empty.
* @name jasmine.empty * @name asymmetricEqualityTesters.empty
* @emittedName jasmine.empty
* @since 3.1.0 * @since 3.1.0
* @function * @function
*/ */
@@ -456,10 +462,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} * Get an {@link AsymmetricEqualityTester} that passes if the actual value is
* that passes if the actual value is the same as the sample as determined * the same as the sample as determined by the `===` operator.
* by the `===` operator. * @name asymmetricEqualityTesters.is
* @name jasmine.is * @emittedName jasmine.is
* @function * @function
* @param {Object} sample - The value to compare the actual to. * @param {Object} sample - The value to compare the actual to.
*/ */
@@ -468,9 +474,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* that will succeed if the actual value being compared is not empty. * value being compared is not empty.
* @name jasmine.notEmpty * @name asymmetricEqualityTesters.notEmpty
* @emittedName jasmine.notEmpty
* @since 3.1.0 * @since 3.1.0
* @function * @function
*/ */
@@ -479,9 +486,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* that will succeed if the actual value being compared contains at least the keys and values. * value being compared contains at least the specified keys and values.
* @name jasmine.objectContaining * @name asymmetricEqualityTesters.objectContaining
* @emittedName jasmine.objectContaining
* @since 1.3.0 * @since 1.3.0
* @function * @function
* @param {Object} sample - The subset of properties that _must_ be in the actual. * @param {Object} sample - The subset of properties that _must_ be in the actual.
@@ -491,9 +499,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* that will succeed if the actual value is a `String` that matches the `RegExp` or `String`. * value is a `String` that matches the `RegExp` or `String`.
* @name jasmine.stringMatching * @name asymmetricEqualityTesters.stringMatching
* @emittedName jasmine.stringMatching
* @since 2.2.0 * @since 2.2.0
* @function * @function
* @param {RegExp|String} expected * @param {RegExp|String} expected
@@ -503,9 +512,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* that will succeed if the actual value is a `String` that contains the specified `String`. * value is a `String` that contains the specified `String`.
* @name jasmine.stringContaining * @name asymmetricEqualityTesters.stringContaining
* @emittedName jasmine.stringContaining
* @since 3.10.0 * @since 3.10.0
* @function * @function
* @param {String} expected * @param {String} expected
@@ -515,9 +525,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* that will succeed if the actual value is an `Array` that contains at least the elements in the sample. * value is an `Array` that contains at least the elements in the sample.
* @name jasmine.arrayContaining * @name asymmetricEqualityTesters.arrayContaining
* @emittedName jasmine.arrayContaining
* @since 2.2.0 * @since 2.2.0
* @function * @function
* @param {Array} sample * @param {Array} sample
@@ -527,9 +538,11 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* that will succeed if the actual value is an `Array` that contains all of the elements in the sample in any order. * value is an `Array` that contains all of the elements in the sample in
* @name jasmine.arrayWithExactContents * any order.
* @name asymmetricEqualityTesters.arrayWithExactContents
* @emittedName jasmine.arrayWithExactContents
* @since 2.8.0 * @since 2.8.0
* @function * @function
* @param {Array} sample * @param {Array} sample
@@ -539,10 +552,11 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * Get an {@link AsymmetricEqualityTester} that will succeed if every
* that will succeed if every key/value pair in the sample passes the deep equality comparison * key/value pair in the sample passes the deep equality comparison
* with at least one key/value pair in the actual value being compared * with at least one key/value pair in the actual value being compared
* @name jasmine.mapContaining * @name asymmetricEqualityTesters.mapContaining
* @emittedName jasmine.mapContaining
* @since 3.5.0 * @since 3.5.0
* @function * @function
* @param {Map} sample - The subset of items that _must_ be in the actual. * @param {Map} sample - The subset of items that _must_ be in the actual.
@@ -552,10 +566,11 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * Get an {@link AsymmetricEqualityTester} that will succeed if every item
* that will succeed if every item in the sample passes the deep equality comparison * in the sample passes the deep equality comparison
* with at least one item in the actual value being compared * with at least one item in the actual value being compared
* @name jasmine.setContaining * @name asymmetricEqualityTesters.setContaining
* @emittedName jasmine.setContaining
* @since 3.5.0 * @since 3.5.0
* @function * @function
* @param {Set} sample - The subset of items that _must_ be in the actual. * @param {Set} sample - The subset of items that _must_ be in the actual.
@@ -611,14 +626,26 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
* handling will be restored when the promise returned from the callback is * handling will be restored when the promise returned from the callback is
* settled. * settled.
* *
* When the JavaScript runtime reports an uncaught error or unhandled rejection,
* the spy will be called with a single parameter representing Jasmine's best
* effort at describing the error. This parameter may be of any type, because
* JavaScript allows anything to be thrown or used as the reason for a
* rejected promise, but Error instances and strings are most common.
*
* Note: The JavaScript runtime may deliver uncaught error events and unhandled * Note: The JavaScript runtime may deliver uncaught error events and unhandled
* rejection events asynchronously, especially in browsers. If the event * rejection events asynchronously, especially in browsers. If the event
* occurs after the promise returned from the callback is settled, it won't * occurs after the promise returned from the callback is settled, it won't
* be routed to the spy even if the underlying error occurred previously. * be routed to the spy even if the underlying error occurred previously.
* It's up to you to ensure that the returned promise isn't resolved until * It's up to you to ensure that all of the error/rejection events that you
* all of the error/rejection events that you want to handle have occurred. * want to handle have occurred before you resolve the promise returned from
* the callback.
* *
* You must await the return value of spyOnGlobalErrorsAsync. * You must ensure that the `it`/`beforeEach`/etc fn that called
* `spyOnGlobalErrorsAsync` does not signal completion until after the
* promise returned by `spyOnGlobalErrorsAsync` is resolved. Normally this is
* done by `await`ing the returned promise. Leaving the global error spy
* installed after the `it`/`beforeEach`/etc fn that installed it signals
* completion is likely to cause problems and is not supported.
* @name jasmine.spyOnGlobalErrorsAsync * @name jasmine.spyOnGlobalErrorsAsync
* @function * @function
* @async * @async
@@ -3655,19 +3682,24 @@ getJasmineRequireObj().Expectation = function(j$) {
} }
/** /**
* Add some context for an {@link expect} * Add some context to be included in matcher failures for an
* {@link expect|expectation}, so that it can be more easily distinguished
* from similar expectations.
* @function * @function
* @name matchers#withContext * @name matchers#withContext
* @since 3.3.0 * @since 3.3.0
* @param {String} message - Additional context to show when the matcher fails * @param {String} message - Additional context to show when the matcher fails
* @return {matchers} * @return {matchers}
* @example
* expect(things[0]).withContext('thing 0').toEqual('a');
* expect(things[1]).withContext('thing 1').toEqual('b');
*/ */
Expectation.prototype.withContext = function withContext(message) { Expectation.prototype.withContext = function withContext(message) {
return addFilter(this, new ContextAddingFilter(message)); return addFilter(this, new ContextAddingFilter(message));
}; };
/** /**
* Invert the matcher following this {@link expect} * Invert the matcher following this {@link expect|expectation}
* @member * @member
* @name matchers#not * @name matchers#not
* @since 1.3.0 * @since 1.3.0
@@ -4070,6 +4102,7 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
function dispatchBrowserError(error, event) { function dispatchBrowserError(error, event) {
if (overrideHandler) { if (overrideHandler) {
// See discussion of spyOnGlobalErrorsAsync in base.js
overrideHandler(error); overrideHandler(error);
return; return;
} }
@@ -4113,6 +4146,7 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
const handler = handlers[handlers.length - 1]; const handler = handlers[handlers.length - 1];
if (overrideHandler) { if (overrideHandler) {
// See discussion of spyOnGlobalErrorsAsync in base.js
overrideHandler(error); overrideHandler(error);
return; return;
} }
@@ -6524,7 +6558,7 @@ getJasmineRequireObj().toHaveSize = function(j$) {
* array = [1,2]; * array = [1,2];
* expect(array).toHaveSize(2); * expect(array).toHaveSize(2);
*/ */
function toHaveSize() { function toHaveSize(matchersUtil) {
return { return {
compare: function(actual, expected) { compare: function(actual, expected) {
const result = { const result = {
@@ -6539,12 +6573,29 @@ getJasmineRequireObj().toHaveSize = function(j$) {
throw new Error('Cannot get size of ' + actual + '.'); throw new Error('Cannot get size of ' + actual + '.');
} }
let actualSize;
if (j$.isSet(actual) || j$.isMap(actual)) { if (j$.isSet(actual) || j$.isMap(actual)) {
result.pass = actual.size === expected; actualSize = actual.size;
} else if (isLength(actual.length)) { } else if (isLength(actual.length)) {
result.pass = actual.length === expected; actualSize = actual.length;
} else { } else {
result.pass = Object.keys(actual).length === expected; actualSize = Object.keys(actual).length;
}
result.pass = actualSize === expected;
if (!result.pass) {
result.message = function() {
return (
'Expected ' +
matchersUtil.pp(actual) +
' with size ' +
actualSize +
' to have size ' +
expected +
'.'
);
};
} }
return result; return result;
@@ -7684,8 +7735,8 @@ getJasmineRequireObj().QueueRunner = function(j$) {
} }
QueueRunner.prototype.execute = function() { QueueRunner.prototype.execute = function() {
this.handleFinalError = error => { this.handleFinalError = (error, event) => {
this.onException(error); this.onException(errorOrMsgForGlobalError(error, event));
}; };
this.globalErrors.pushListener(this.handleFinalError); this.globalErrors.pushListener(this.handleFinalError);
this.run(0); this.run(0);
@@ -7715,10 +7766,8 @@ getJasmineRequireObj().QueueRunner = function(j$) {
this.recordError_(iterativeIndex); this.recordError_(iterativeIndex);
}; };
function handleError(error) { function handleError(error, event) {
// TODO probably shouldn't next() right away here. onException(errorOrMsgForGlobalError(error, event));
// That makes debugging async failures much more confusing.
onException(error);
} }
const cleanup = once(() => { const cleanup = once(() => {
if (timeoutId !== void 0) { if (timeoutId !== void 0) {
@@ -7904,6 +7953,17 @@ getJasmineRequireObj().QueueRunner = function(j$) {
}; };
} }
function errorOrMsgForGlobalError(error, event) {
// TODO: In cases where error is a string or undefined, the error message
// that gets sent to reporters will be `${message} thrown`, which could
// be improved to not say "thrown" when the cause wasn't necessarily
// an exception or to provide hints about throwing Errors rather than
// strings.
return (
error || (event && event.message) || 'Global error event with no message'
);
}
return QueueRunner; return QueueRunner;
}; };
@@ -8408,6 +8468,12 @@ getJasmineRequireObj().interface = function(jasmine, env) {
}), }),
/** /**
* <p>Members of the jasmine global.</p>
* <p>Note: The members of the
* {@link asymmetricEqualityTesters|asymmetricEqualityTesters namespace}
* are also accessed via the jasmine global, but due to jsdoc limitations
* they are not listed here.</p>
*
* @namespace jasmine * @namespace jasmine
*/ */
jasmine: jasmine jasmine: jasmine
@@ -8541,6 +8607,28 @@ getJasmineRequireObj().interface = function(jasmine, env) {
return env.setDefaultSpyStrategy(defaultStrategyFn); return env.setDefaultSpyStrategy(defaultStrategyFn);
}; };
/**
* {@link AsymmetricEqualityTester|Asymmetric equality testers} allow for
* non-exact matching in matchers that use Jasmine's deep value equality
* semantics, such as {@link matchers#toEqual|toEqual},
* {@link matchers#toContain|toContain}, and
* {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}.
*
* @example
* const someComplexObject = {
* foo: 'bar',
* baz: 'a string that contains "something"',
* qux: 'whatever'
* };
* // Passes.
* expect(someComplexObject).toEqual(jasmine.objectContaining({
* foo: 'bar',
* baz: jasmine.stringContaining('something')
* });
*
* @namespace asymmetricEqualityTesters
*/
return jasmineInterface; return jasmineInterface;
}; };
@@ -9746,7 +9834,7 @@ getJasmineRequireObj().StackTrace = function(j$) {
// e.g. " at /some/path:4320:20 // e.g. " at /some/path:4320:20
{ re: /\s*at (.+)$/, fileLineColIx: 1, style: 'v8' }, { re: /\s*at (.+)$/, fileLineColIx: 1, style: 'v8' },
// PhantomJS on OS X, Safari, Firefox // Safari, most Firefox stack frames
// e.g. "run@http://localhost:8888/__jasmine__/jasmine.js:4320:27" // e.g. "run@http://localhost:8888/__jasmine__/jasmine.js:4320:27"
// or "http://localhost:8888/__jasmine__/jasmine.js:4320:27" // or "http://localhost:8888/__jasmine__/jasmine.js:4320:27"
{ {
@@ -9754,6 +9842,15 @@ getJasmineRequireObj().StackTrace = function(j$) {
fnIx: 2, fnIx: 2,
fileLineColIx: 3, fileLineColIx: 3,
style: 'webkit' style: 'webkit'
},
// Some Firefox stack frames when the developer tools are open
// e.g. "promise callback*specStarted@http://localhost:8888/__jasmine__/jasmine.js:1880:41"
{
re: /^^(?:((?:promise callback|[^\s]+ handler)\*([^@\s]+)@)|@)?([^\s]+)$/,
fnIx: 2,
fileLineColIx: 3,
style: 'webkit'
} }
]; ];
@@ -10813,5 +10910,5 @@ getJasmineRequireObj().UserContext = function(j$) {
}; };
getJasmineRequireObj().version = function() { getJasmineRequireObj().version = function() {
return '5.1.2'; return '5.2.0';
}; };

View File

@@ -1,7 +1,7 @@
{ {
"name": "jasmine-core", "name": "jasmine-core",
"license": "MIT", "license": "MIT",
"version": "5.1.2", "version": "5.2.0",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/jasmine/jasmine.git" "url": "https://github.com/jasmine/jasmine.git"
@@ -52,55 +52,6 @@
"shelljs": "^0.8.3", "shelljs": "^0.8.3",
"temp": "^0.9.0" "temp": "^0.9.0"
}, },
"prettier": {
"singleQuote": true
},
"eslintConfig": {
"extends": [
"plugin:compat/recommended"
],
"env": {
"browser": true,
"node": true,
"es2017": true
},
"parserOptions": {
"ecmaVersion": 2018
},
"rules": {
"quotes": [
"error",
"single",
{
"avoidEscape": true
}
],
"no-unused-vars": [
"error",
{
"args": "none"
}
],
"no-implicit-globals": "error",
"block-spacing": "error",
"func-call-spacing": [
"error",
"never"
],
"key-spacing": "error",
"no-tabs": "error",
"no-trailing-spaces": "error",
"no-whitespace-before-property": "error",
"semi": [
"error",
"always"
],
"space-before-blocks": "error",
"no-eval": "error",
"no-var": "error",
"no-debugger": "error"
}
},
"browserslist": [ "browserslist": [
"Safari >= 15", "Safari >= 15",
"Firefox >= 102", "Firefox >= 102",

51
release_notes/4.6.1.md Normal file
View File

@@ -0,0 +1,51 @@
# Jasmine Core 4.6.1 Release Notes
## Summary
This is a one-time backport of bug fixes from 5.x, for the benefit of Karma
users who may not be aware that they're still using 4.x.
No further 4.x releases are planned. If possible, you should upgrade to the
latest 5.x instead of 4.6.1. If you're using Karma, you can do this by
installing jasmine-core 5.x and adding an override to package.json:
```
{
// ...
"overrides": {
"karma-jasmine": {
"jasmine-core": "^5.0.0"
}
}
}
```
## Bug Fixes
* Removed unnecessary throw when building stack trace
* Fixed error when formatting Error object with non-Error cause property
Merges [#2013](https://github.com/jasmine/jasmine/pull/2013) from @angrycat9000.
Fixes [#2011](https://github.com/jasmine/jasmine/issues/2011).
* Accessibility: Always provide a non-color indication that a spec is pending
* Accessibility: Improved contrast of version number and inactive tab links
## Supported environments
jasmine-core 4.6.1 has been tested in the following environments.
| Environment | Supported versions |
|-------------------|--------------------|
| Node | 12.17+, 14, 16, 18 |
| Safari | 14-16 |
| Chrome | 125 |
| Firefox | 91, 102, 126 |
| Edge | 124 |
------
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_

35
release_notes/5.2.0.md Normal file
View File

@@ -0,0 +1,35 @@
# Jasmine Core 5.2.0 Release Notes
## Bug Fixes
* Fixed stack trace filtering in FF when the developer tools are open
* Fixed handling of browser `error` events with message but no error
## New Features
* Improved the error message of the toHaveSize matcher.
* Merges [#2033](https://github.com/jasmine/jasmine/pull/2033) from @stephanreiter
* HTML reporter: show debug logs with white-space: pre
## Documentation improvements
* Improved discoverability of asymmetric equality testers
* Added an example for withContext()
* Clarified spyOnGlobalErrorsAsync API docs
* Added Node 22 to supported environments
## Supported environments
This version has been tested in the following environments.
| Environment | Supported versions |
|-------------------|--------------------|
| Node | 18, 20, 22 |
| Safari | 15-17 |
| Chrome | 126 |
| Firefox | 102, 115, 128 |
| Edge | 126 |
------
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_

View File

@@ -153,12 +153,33 @@ describe('ExceptionFormatter', function() {
); );
}); });
it('filters Jasmine stack frames with Firefox async annotations', function() {
const error = {
stack:
'http://localhost:8888/__spec__/core/UtilSpec.js:115:28\n' +
'promise callback*fn1@http://localhost:8888/__jasmine__/jasmine.js:4320:27\n' +
'setTimeout handler*fn2@http://localhost:8888/__jasmine__/jasmine.js:4320:27\n' +
'http://localhost:8888/__spec__/core/UtilSpec.js:115:28'
};
const subject = new jasmineUnderTest.ExceptionFormatter({
jasmineFile: 'http://localhost:8888/__jasmine__/jasmine.js'
});
const result = subject.stack(error);
expect(result).toEqual(
'http://localhost:8888/__spec__/core/UtilSpec.js:115:28\n' +
'<Jasmine>\n' +
'http://localhost:8888/__spec__/core/UtilSpec.js:115:28'
);
});
it('filters Jasmine stack frames in this environment', function() { it('filters Jasmine stack frames in this environment', function() {
const error = new Error('an error'); const error = new Error('an error');
const subject = new jasmineUnderTest.ExceptionFormatter({ const subject = new jasmineUnderTest.ExceptionFormatter({
jasmineFile: jasmine.util.jasmineFile() jasmineFile: jasmine.util.jasmineFile()
}); });
const result = subject.stack(error); const result = subject.stack(error);
jasmine.debugLog('Original stack trace: ' + error.stack);
jasmine.debugLog('Filtered stack trace: ' + result);
const lines = result.split('\n'); const lines = result.split('\n');
if (lines[0].match(/an error/)) { if (lines[0].match(/an error/)) {

View File

@@ -328,6 +328,62 @@ describe('GlobalErrors', function() {
}); });
}); });
describe('Reporting uncaught exceptions in node.js', function() {
it('prepends a descriptive message when the error is not an `Error`', function() {
const fakeGlobal = {
process: {
on: jasmine.createSpy('process.on'),
removeListener: function() {},
listeners: function() {
return [];
},
removeAllListeners: function() {}
}
};
const handler = jasmine.createSpy('errorHandler');
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
errors.install();
errors.pushListener(handler);
uncaughtExceptionListener(fakeGlobal)(17);
expect(handler).toHaveBeenCalledWith(new Error('Uncaught exception: 17'));
});
it('substitutes a descriptive message when the error is falsy', function() {
const fakeGlobal = {
process: {
on: jasmine.createSpy('process.on'),
removeListener: function() {},
listeners: function() {
return [];
},
removeAllListeners: function() {}
}
};
const handler = jasmine.createSpy('errorHandler');
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
errors.install();
errors.pushListener(handler);
uncaughtExceptionListener(fakeGlobal)();
expect(handler).toHaveBeenCalledWith(
new Error('Uncaught exception with no error or message')
);
});
function uncaughtExceptionListener(global) {
// Grab the right listener
expect(global.process.on.calls.argsFor(0)[0]).toEqual(
'uncaughtException'
);
return global.process.on.calls.argsFor(0)[1];
}
});
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 = browserGlobal(); const fakeGlobal = browserGlobal();

View File

@@ -459,6 +459,32 @@ describe('QueueRunner', function() {
expect(nextQueueableFn.fn).toHaveBeenCalled(); expect(nextQueueableFn.fn).toHaveBeenCalled();
}); });
it('handles a global error event with a message but no error', function() {
const queueableFn = {
// eslint-disable-next-line no-unused-vars
fn: function(done) {
const currentHandler = globalErrors.pushListener.calls.mostRecent()
.args[0];
currentHandler(undefined, { message: 'nope' });
},
timeout: 1
};
const onException = jasmine.createSpy('onException');
const globalErrors = {
pushListener: jasmine.createSpy('pushListener'),
popListener: jasmine.createSpy('popListener')
};
const queueRunner = new jasmineUnderTest.QueueRunner({
queueableFns: [queueableFn],
onException: onException,
globalErrors: globalErrors
});
queueRunner.execute();
expect(onException).toHaveBeenCalledWith('nope');
});
it('handles exceptions thrown while waiting for the stack to clear', function() { it('handles exceptions thrown while waiting for the stack to clear', function() {
const queueableFn = { const queueableFn = {
fn: function(done) { fn: function(done) {
@@ -492,6 +518,40 @@ describe('QueueRunner', function() {
clearStack.calls.argsFor(0)[0](); clearStack.calls.argsFor(0)[0]();
expect(onException).toHaveBeenCalledWith(error); expect(onException).toHaveBeenCalledWith(error);
}); });
it('handles a global error event with no error while waiting for the stack to clear', function() {
const queueableFn = {
fn: function(done) {
done();
}
};
const errorListeners = [];
const globalErrors = {
pushListener: function(f) {
errorListeners.push(f);
},
popListener: function() {
errorListeners.pop();
}
};
const clearStack = jasmine.createSpy('clearStack');
const onException = jasmine.createSpy('onException');
const queueRunner = new jasmineUnderTest.QueueRunner({
queueableFns: [queueableFn],
globalErrors: globalErrors,
clearStack: clearStack,
onException: onException
});
queueRunner.execute();
jasmine.clock().tick();
expect(clearStack).toHaveBeenCalled();
expect(errorListeners.length).toEqual(1);
errorListeners[0](undefined, { message: 'nope' });
clearStack.calls.argsFor(0)[0]();
expect(onException).toHaveBeenCalledWith('nope');
});
}); });
describe('with a function that returns a promise', function() { describe('with a function that returns a promise', function() {

View File

@@ -179,7 +179,10 @@ describe('base helpers', function() {
f2 = jasmine.createSpy('setTimeout callback for ' + (max + 1)); f2 = jasmine.createSpy('setTimeout callback for ' + (max + 1));
// Suppress printing of TimeoutOverflowWarning in node // Suppress printing of TimeoutOverflowWarning in node
spyOn(console, 'error'); if (typeof process !== 'undefined' && process.emitWarning) {
spyOn(process, 'emitWarning'); // Node 22
}
spyOn(console, 'error'); // Node <22
let id = setTimeout(f1, max); let id = setTimeout(f1, max);
setTimeout(function() { setTimeout(function() {

View File

@@ -15,6 +15,17 @@ describe('toHaveSize', function() {
expect(result.pass).toBe(false); expect(result.pass).toBe(false);
}); });
it('informs about the size of an array whose length does not match', function() {
const matcher = jasmineUnderTest.matchers.toHaveSize({
pp: jasmineUnderTest.makePrettyPrinter()
}),
result = matcher.compare([1, 2, 3], 2);
expect(result.message()).toEqual(
'Expected [ 1, 2, 3 ] with size 3 to have size 2.'
);
});
it('passes for an object with the proper number of keys', function() { it('passes for an object with the proper number of keys', function() {
const matcher = jasmineUnderTest.matchers.toHaveSize(), const matcher = jasmineUnderTest.matchers.toHaveSize(),
result = matcher.compare({ a: 1, b: 2 }, 2); result = matcher.compare({ a: 1, b: 2 }, 2);

View File

@@ -16,19 +16,24 @@ getJasmineRequireObj().Expectation = function(j$) {
} }
/** /**
* Add some context for an {@link expect} * Add some context to be included in matcher failures for an
* {@link expect|expectation}, so that it can be more easily distinguished
* from similar expectations.
* @function * @function
* @name matchers#withContext * @name matchers#withContext
* @since 3.3.0 * @since 3.3.0
* @param {String} message - Additional context to show when the matcher fails * @param {String} message - Additional context to show when the matcher fails
* @return {matchers} * @return {matchers}
* @example
* expect(things[0]).withContext('thing 0').toEqual('a');
* expect(things[1]).withContext('thing 1').toEqual('b');
*/ */
Expectation.prototype.withContext = function withContext(message) { Expectation.prototype.withContext = function withContext(message) {
return addFilter(this, new ContextAddingFilter(message)); return addFilter(this, new ContextAddingFilter(message));
}; };
/** /**
* Invert the matcher following this {@link expect} * Invert the matcher following this {@link expect|expectation}
* @member * @member
* @name matchers#not * @name matchers#not
* @since 1.3.0 * @since 1.3.0

View File

@@ -12,6 +12,7 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
function dispatchBrowserError(error, event) { function dispatchBrowserError(error, event) {
if (overrideHandler) { if (overrideHandler) {
// See discussion of spyOnGlobalErrorsAsync in base.js
overrideHandler(error); overrideHandler(error);
return; return;
} }
@@ -55,6 +56,7 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
const handler = handlers[handlers.length - 1]; const handler = handlers[handlers.length - 1];
if (overrideHandler) { if (overrideHandler) {
// See discussion of spyOnGlobalErrorsAsync in base.js
overrideHandler(error); overrideHandler(error);
return; return;
} }

View File

@@ -65,8 +65,8 @@ getJasmineRequireObj().QueueRunner = function(j$) {
} }
QueueRunner.prototype.execute = function() { QueueRunner.prototype.execute = function() {
this.handleFinalError = error => { this.handleFinalError = (error, event) => {
this.onException(error); this.onException(errorOrMsgForGlobalError(error, event));
}; };
this.globalErrors.pushListener(this.handleFinalError); this.globalErrors.pushListener(this.handleFinalError);
this.run(0); this.run(0);
@@ -96,10 +96,8 @@ getJasmineRequireObj().QueueRunner = function(j$) {
this.recordError_(iterativeIndex); this.recordError_(iterativeIndex);
}; };
function handleError(error) { function handleError(error, event) {
// TODO probably shouldn't next() right away here. onException(errorOrMsgForGlobalError(error, event));
// That makes debugging async failures much more confusing.
onException(error);
} }
const cleanup = once(() => { const cleanup = once(() => {
if (timeoutId !== void 0) { if (timeoutId !== void 0) {
@@ -285,5 +283,16 @@ getJasmineRequireObj().QueueRunner = function(j$) {
}; };
} }
function errorOrMsgForGlobalError(error, event) {
// TODO: In cases where error is a string or undefined, the error message
// that gets sent to reporters will be `${message} thrown`, which could
// be improved to not say "thrown" when the cause wasn't necessarily
// an exception or to provide hints about throwing Errors rather than
// strings.
return (
error || (event && event.message) || 'Global error event with no message'
);
}
return QueueRunner; return QueueRunner;
}; };

View File

@@ -32,7 +32,7 @@ getJasmineRequireObj().StackTrace = function(j$) {
// e.g. " at /some/path:4320:20 // e.g. " at /some/path:4320:20
{ re: /\s*at (.+)$/, fileLineColIx: 1, style: 'v8' }, { re: /\s*at (.+)$/, fileLineColIx: 1, style: 'v8' },
// PhantomJS on OS X, Safari, Firefox // Safari, most Firefox stack frames
// e.g. "run@http://localhost:8888/__jasmine__/jasmine.js:4320:27" // e.g. "run@http://localhost:8888/__jasmine__/jasmine.js:4320:27"
// or "http://localhost:8888/__jasmine__/jasmine.js:4320:27" // or "http://localhost:8888/__jasmine__/jasmine.js:4320:27"
{ {
@@ -40,6 +40,15 @@ getJasmineRequireObj().StackTrace = function(j$) {
fnIx: 2, fnIx: 2,
fileLineColIx: 3, fileLineColIx: 3,
style: 'webkit' style: 'webkit'
},
// Some Firefox stack frames when the developer tools are open
// e.g. "promise callback*specStarted@http://localhost:8888/__jasmine__/jasmine.js:1880:41"
{
re: /^^(?:((?:promise callback|[^\s]+ handler)\*([^@\s]+)@)|@)?([^\s]+)$/,
fnIx: 2,
fileLineColIx: 3,
style: 'webkit'
} }
]; ];

View File

@@ -224,9 +224,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* that will succeed if the actual value being compared is an instance of the specified class/constructor. * value being compared is an instance of the specified class/constructor.
* @name jasmine.any * @name asymmetricEqualityTesters.any
* @emittedName jasmine.any
* @since 1.3.0 * @since 1.3.0
* @function * @function
* @param {Constructor} clazz - The constructor to check against. * @param {Constructor} clazz - The constructor to check against.
@@ -236,9 +237,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* that will succeed if the actual value being compared is not `null` and not `undefined`. * value being compared is not `null` and not `undefined`.
* @name jasmine.anything * @name asymmetricEqualityTesters.anything
* @emittedName jasmine.anything
* @since 2.2.0 * @since 2.2.0
* @function * @function
*/ */
@@ -247,9 +249,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* that will succeed if the actual value being compared is `true` or anything truthy. * value being compared is `true` or anything truthy.
* @name jasmine.truthy * @name asymmetricEqualityTesters.truthy
* @emittedName jasmine.truthy
* @since 3.1.0 * @since 3.1.0
* @function * @function
*/ */
@@ -258,9 +261,11 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* that will succeed if the actual value being compared is `null`, `undefined`, `0`, `false` or anything falsey. * value being compared is `null`, `undefined`, `0`, `false` or anything
* @name jasmine.falsy * falsy.
* @name asymmetricEqualityTesters.falsy
* @emittedName jasmine.falsy
* @since 3.1.0 * @since 3.1.0
* @function * @function
*/ */
@@ -269,9 +274,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* that will succeed if the actual value being compared is empty. * value being compared is empty.
* @name jasmine.empty * @name asymmetricEqualityTesters.empty
* @emittedName jasmine.empty
* @since 3.1.0 * @since 3.1.0
* @function * @function
*/ */
@@ -280,10 +286,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} * Get an {@link AsymmetricEqualityTester} that passes if the actual value is
* that passes if the actual value is the same as the sample as determined * the same as the sample as determined by the `===` operator.
* by the `===` operator. * @name asymmetricEqualityTesters.is
* @name jasmine.is * @emittedName jasmine.is
* @function * @function
* @param {Object} sample - The value to compare the actual to. * @param {Object} sample - The value to compare the actual to.
*/ */
@@ -292,9 +298,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* that will succeed if the actual value being compared is not empty. * value being compared is not empty.
* @name jasmine.notEmpty * @name asymmetricEqualityTesters.notEmpty
* @emittedName jasmine.notEmpty
* @since 3.1.0 * @since 3.1.0
* @function * @function
*/ */
@@ -303,9 +310,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* that will succeed if the actual value being compared contains at least the keys and values. * value being compared contains at least the specified keys and values.
* @name jasmine.objectContaining * @name asymmetricEqualityTesters.objectContaining
* @emittedName jasmine.objectContaining
* @since 1.3.0 * @since 1.3.0
* @function * @function
* @param {Object} sample - The subset of properties that _must_ be in the actual. * @param {Object} sample - The subset of properties that _must_ be in the actual.
@@ -315,9 +323,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* that will succeed if the actual value is a `String` that matches the `RegExp` or `String`. * value is a `String` that matches the `RegExp` or `String`.
* @name jasmine.stringMatching * @name asymmetricEqualityTesters.stringMatching
* @emittedName jasmine.stringMatching
* @since 2.2.0 * @since 2.2.0
* @function * @function
* @param {RegExp|String} expected * @param {RegExp|String} expected
@@ -327,9 +336,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* that will succeed if the actual value is a `String` that contains the specified `String`. * value is a `String` that contains the specified `String`.
* @name jasmine.stringContaining * @name asymmetricEqualityTesters.stringContaining
* @emittedName jasmine.stringContaining
* @since 3.10.0 * @since 3.10.0
* @function * @function
* @param {String} expected * @param {String} expected
@@ -339,9 +349,10 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* that will succeed if the actual value is an `Array` that contains at least the elements in the sample. * value is an `Array` that contains at least the elements in the sample.
* @name jasmine.arrayContaining * @name asymmetricEqualityTesters.arrayContaining
* @emittedName jasmine.arrayContaining
* @since 2.2.0 * @since 2.2.0
* @function * @function
* @param {Array} sample * @param {Array} sample
@@ -351,9 +362,11 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * Get an {@link AsymmetricEqualityTester} that will succeed if the actual
* that will succeed if the actual value is an `Array` that contains all of the elements in the sample in any order. * value is an `Array` that contains all of the elements in the sample in
* @name jasmine.arrayWithExactContents * any order.
* @name asymmetricEqualityTesters.arrayWithExactContents
* @emittedName jasmine.arrayWithExactContents
* @since 2.8.0 * @since 2.8.0
* @function * @function
* @param {Array} sample * @param {Array} sample
@@ -363,10 +376,11 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * Get an {@link AsymmetricEqualityTester} that will succeed if every
* that will succeed if every key/value pair in the sample passes the deep equality comparison * key/value pair in the sample passes the deep equality comparison
* with at least one key/value pair in the actual value being compared * with at least one key/value pair in the actual value being compared
* @name jasmine.mapContaining * @name asymmetricEqualityTesters.mapContaining
* @emittedName jasmine.mapContaining
* @since 3.5.0 * @since 3.5.0
* @function * @function
* @param {Map} sample - The subset of items that _must_ be in the actual. * @param {Map} sample - The subset of items that _must_ be in the actual.
@@ -376,10 +390,11 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
}; };
/** /**
* Get an {@link AsymmetricEqualityTester}, usable in any {@link matchers|matcher} that uses Jasmine's equality (e.g. {@link matchers#toEqual|toEqual}, {@link matchers#toContain|toContain}, or {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}), * Get an {@link AsymmetricEqualityTester} that will succeed if every item
* that will succeed if every item in the sample passes the deep equality comparison * in the sample passes the deep equality comparison
* with at least one item in the actual value being compared * with at least one item in the actual value being compared
* @name jasmine.setContaining * @name asymmetricEqualityTesters.setContaining
* @emittedName jasmine.setContaining
* @since 3.5.0 * @since 3.5.0
* @function * @function
* @param {Set} sample - The subset of items that _must_ be in the actual. * @param {Set} sample - The subset of items that _must_ be in the actual.
@@ -435,14 +450,26 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
* handling will be restored when the promise returned from the callback is * handling will be restored when the promise returned from the callback is
* settled. * settled.
* *
* When the JavaScript runtime reports an uncaught error or unhandled rejection,
* the spy will be called with a single parameter representing Jasmine's best
* effort at describing the error. This parameter may be of any type, because
* JavaScript allows anything to be thrown or used as the reason for a
* rejected promise, but Error instances and strings are most common.
*
* Note: The JavaScript runtime may deliver uncaught error events and unhandled * Note: The JavaScript runtime may deliver uncaught error events and unhandled
* rejection events asynchronously, especially in browsers. If the event * rejection events asynchronously, especially in browsers. If the event
* occurs after the promise returned from the callback is settled, it won't * occurs after the promise returned from the callback is settled, it won't
* be routed to the spy even if the underlying error occurred previously. * be routed to the spy even if the underlying error occurred previously.
* It's up to you to ensure that the returned promise isn't resolved until * It's up to you to ensure that all of the error/rejection events that you
* all of the error/rejection events that you want to handle have occurred. * want to handle have occurred before you resolve the promise returned from
* the callback.
* *
* You must await the return value of spyOnGlobalErrorsAsync. * You must ensure that the `it`/`beforeEach`/etc fn that called
* `spyOnGlobalErrorsAsync` does not signal completion until after the
* promise returned by `spyOnGlobalErrorsAsync` is resolved. Normally this is
* done by `await`ing the returned promise. Leaving the global error spy
* installed after the `it`/`beforeEach`/etc fn that installed it signals
* completion is likely to cause problems and is not supported.
* @name jasmine.spyOnGlobalErrorsAsync * @name jasmine.spyOnGlobalErrorsAsync
* @function * @function
* @async * @async

View File

@@ -9,7 +9,7 @@ getJasmineRequireObj().toHaveSize = function(j$) {
* array = [1,2]; * array = [1,2];
* expect(array).toHaveSize(2); * expect(array).toHaveSize(2);
*/ */
function toHaveSize() { function toHaveSize(matchersUtil) {
return { return {
compare: function(actual, expected) { compare: function(actual, expected) {
const result = { const result = {
@@ -24,12 +24,29 @@ getJasmineRequireObj().toHaveSize = function(j$) {
throw new Error('Cannot get size of ' + actual + '.'); throw new Error('Cannot get size of ' + actual + '.');
} }
let actualSize;
if (j$.isSet(actual) || j$.isMap(actual)) { if (j$.isSet(actual) || j$.isMap(actual)) {
result.pass = actual.size === expected; actualSize = actual.size;
} else if (isLength(actual.length)) { } else if (isLength(actual.length)) {
result.pass = actual.length === expected; actualSize = actual.length;
} else { } else {
result.pass = Object.keys(actual).length === expected; actualSize = Object.keys(actual).length;
}
result.pass = actualSize === expected;
if (!result.pass) {
result.message = function() {
return (
'Expected ' +
matchersUtil.pp(actual) +
' with size ' +
actualSize +
' to have size ' +
expected +
'.'
);
};
} }
return result; return result;

View File

@@ -345,6 +345,12 @@ getJasmineRequireObj().interface = function(jasmine, env) {
}), }),
/** /**
* <p>Members of the jasmine global.</p>
* <p>Note: The members of the
* {@link asymmetricEqualityTesters|asymmetricEqualityTesters namespace}
* are also accessed via the jasmine global, but due to jsdoc limitations
* they are not listed here.</p>
*
* @namespace jasmine * @namespace jasmine
*/ */
jasmine: jasmine jasmine: jasmine
@@ -478,5 +484,27 @@ getJasmineRequireObj().interface = function(jasmine, env) {
return env.setDefaultSpyStrategy(defaultStrategyFn); return env.setDefaultSpyStrategy(defaultStrategyFn);
}; };
/**
* {@link AsymmetricEqualityTester|Asymmetric equality testers} allow for
* non-exact matching in matchers that use Jasmine's deep value equality
* semantics, such as {@link matchers#toEqual|toEqual},
* {@link matchers#toContain|toContain}, and
* {@link matchers#toHaveBeenCalledWith|toHaveBeenCalledWith}.
*
* @example
* const someComplexObject = {
* foo: 'bar',
* baz: 'a string that contains "something"',
* qux: 'whatever'
* };
* // Passes.
* expect(someComplexObject).toEqual(jasmine.objectContaining({
* foo: 'bar',
* baz: jasmine.stringContaining('something')
* });
*
* @namespace asymmetricEqualityTesters
*/
return jasmineInterface; return jasmineInterface;
}; };

View File

@@ -430,7 +430,11 @@ jasmineRequire.HtmlReporter = function(j$) {
'tr', 'tr',
{}, {},
createDom('td', {}, entry.timestamp.toString()), createDom('td', {}, entry.timestamp.toString()),
createDom('td', {}, entry.message) createDom(
'td',
{ className: 'jasmine-debug-log-msg' },
entry.message
)
) )
); );
}); });

View File

@@ -424,5 +424,9 @@ body {
table, th, td { table, th, td {
border: 1px solid #ddd; border: 1px solid #ddd;
} }
.jasmine-debug-log-msg {
white-space: pre;
}
} }
} }