Compare commits

...

31 Commits

Author SHA1 Message Date
Steve Gravrock
d06dce4614 Bump version to 5.1.2 2024-02-08 17:22:46 -08:00
Steve Gravrock
03098e81f8 Fixed throwUnlessAsync
Fixes #2026
2024-02-05 18:49:19 -08:00
Steve Gravrock
726c152f6e Added Safari 17 to supported browsers 2024-02-05 18:44:11 -08:00
Steve Gravrock
409d2e29e5 Fixed formatting of copyright notice in README 2023-10-14 17:38:30 -07:00
Steve Gravrock
01e2bd5050 Updated copyright notices
The Pivotal copyright notice needs to be retained. That's the right
thing to do and the MIT license requires it. However, using it by itself
becomes more obviously incorrect with each passing year since Pivotal
ceased to exist. Moreover, Pivotal hasn't actually been the sole
copyright owner since the first external contribution was merged.
Contributors were not asked to sign a copyright assignment, so they
retain copyright to their contributions.

"Copyright (c) 2008-2019 Pivotal Labs" complies with the terms of the
license and acknowledges Pivotal's outsized role in developing Jasmine.
"Copyright (c) 2008-$YEAR The Jasmine developers" acknowledges all
authors and will remain correct in the future.
2023-10-14 08:55:48 -07:00
Steve Gravrock
96033e38ea Merge branch 'main' of https://github.com/jd-apprentice/jasmine
* Merges #2017 from @jd-apprentice
2023-10-07 10:34:06 -07:00
Steve Gravrock
ed75290ef7 Removed badges from README
The CI status badge mostly just shows whether Saucelabas was flaky
last night. Code Triage was a nice idea but it's attracted at most
one new contributor over 5.5 years.
2023-09-30 10:22:37 -07:00
Steve Gravrock
a14dbf012a Fix Chrome on CI 2023-09-30 08:33:27 -07:00
Jonathan
17c11ba7b9 fix: updated remaining files 2023-09-21 11:48:52 -03:00
Jonathan
2a1daca1ca chore: rename license file 2023-09-19 14:41:21 -03:00
Steve Gravrock
f0db5ce350 Added Firefox 115 (current ESR) to supported browsers 2023-09-07 21:43:24 -07:00
Steve Gravrock
39f9c2e1a0 Don't attach spec helpers to the env 2023-08-26 11:52:26 -07:00
Steve Gravrock
bff612a169 Bump version to 5.1.1 2023-08-24 19:00:26 -07:00
Steve Gravrock
4ba42f3746 Fixed global variable leak when using ParallelReportDispatcher 2023-08-22 19:34:22 -07:00
Steve Gravrock
58bee05c36 Documented usage of eval in DelayedFunctionScheduler 2023-08-22 19:28:20 -07:00
Steve Gravrock
c1871b0f0c Removed unnecessary throw when building stack trace
Since 4.0, all supported JS runtimes populate the stack property of
Error objects when the Error is instantiated, not when it's thrown.
2023-08-19 09:54:03 -07:00
Steve Gravrock
c16974b091 Improved jsdocs for originalFn argument to createSpy
Fixes jasmine.github.io#137.
2023-08-14 18:32:51 -07:00
Steve Gravrock
bfedda9764 Link to 5.0 upgrade guide in README, not 4.0 2023-08-12 17:26:39 -07:00
Steve Gravrock
a67b7276be Fixed jsdocs for throwUnless and throwUnlessAsync 2023-07-22 09:36:16 -07:00
Steve Gravrock
47f3105ef0 Bump version to 5.1.0 2023-07-22 09:12:36 -07:00
Steve Gravrock
aeb56539c9 Built distribution 2023-07-22 09:12:18 -07:00
Steve Gravrock
75d45efa16 Exclude inherited Error properties from stack trace
These are likely to be methods or other things that aren't meaningful in
Jasmine's output.
2023-07-19 19:13:24 -07:00
Steve Gravrock
59d1c5bebb Use "bug report" tag, not "unconfirmed bug" 2023-07-19 18:29:51 -07:00
Steve Gravrock
040983c979 Merge branch 'skip-non-error-cause' of https://github.com/angrycat9000/jasmine
* Merges #2013 from @angrycat9000
* Fixes #2011
2023-07-19 18:28:56 -07:00
angrycat9000
2ddb344bac Skip parsing cause if it is not an Error object 2023-07-19 10:00:50 -04:00
Steve Gravrock
e56bd3918b Added throwUnless and throwUnlessAsync
These are similar to `expect` and `expectAsync` except that they throw
exceptions rather than recording matcher failures as spec/suite failures.
They're intended to support using Jasmine matchers in testing-library's
`waitFor`, and also provide a way to integration-test custom matchers.

These funtions are not equivalent to `expect` and `expectAsync` and should
not be used in situations where you want a matcher failure to reliably fail
the spec. Whether that happens depends on the structure of the surrounding
code. In general, you should only use `throwUnless` when you expect
something (which could be your own code or library code like `waitFor`) to
catch the resulting exception.

Fixes #2003.
Fixes #1980.
2023-07-15 12:08:11 -07:00
Steve Gravrock
59600a1c29 Removed expect/expectAsync indirection through spec/suite 2023-07-15 12:08:11 -07:00
Steve Gravrock
f8e4ea868f CI: Use a globally-unique Sauce tunnel ID
CIRCLE_BUILD_NUM is only unique per-repo, and we have multiple repos
that can concurrently run Sauce builds.
2023-07-01 11:05:04 -07:00
Steve Gravrock
0ff56c53b1 Dogfood remote Selenium grid support 2023-07-01 10:17:05 -07:00
Steve Gravrock
b617d983de Bump version to 5.0.1 2023-06-09 16:24:00 -07:00
Steve Gravrock
8e0f0e8e8c Optionally restore the pre-5.0 behavior of boot() always creating a new instance
This is needed by jasmine-npm (and likely other tools like it) that may
need to create and use multiple envs in sequence.
2023-06-05 19:44:06 -07:00
39 changed files with 620 additions and 147 deletions

View File

@@ -80,7 +80,7 @@ jobs:
# cleanly if we kill it from a different step than it started in. # cleanly if we kill it from a different step than it started in.
export PATH=$PATH:$HOME/workspace/bin export PATH=$PATH:$HOME/workspace/bin
export SAUCE_TUNNEL_IDENTIFIER=$CIRCLE_BUILD_NUM export SAUCE_TUNNEL_IDENTIFIER=$CIRCLE_WORKFLOW_JOB_ID
scripts/start-sauce-connect sauce-pidfile scripts/start-sauce-connect sauce-pidfile
set +o errexit set +o errexit
scripts/run-all-browsers scripts/run-all-browsers

View File

@@ -1,6 +1,6 @@
name: Bug Report name: Bug Report
description: I think I've found a bug in Jasmine description: I think I've found a bug in Jasmine
labels: ["unconfirmed bug"] labels: ["bug report"]
body: body:
- type: markdown - type: markdown
attributes: attributes:

View File

@@ -1,4 +1,5 @@
Copyright (c) 2008-2019 Pivotal Labs Copyright (c) 2008-2019 Pivotal Labs
Copyright (c) 2008-2023 The Jasmine developers
Permission is hereby granted, free of charge, to any person obtaining Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the a copy of this software and associated documentation files (the

View File

@@ -1,13 +1,10 @@
<a name="README">[<img src="https://rawgithub.com/jasmine/jasmine/main/images/jasmine-horizontal.svg" width="400px" />](http://jasmine.github.io)</a> <a name="README">[<img src="https://rawgithub.com/jasmine/jasmine/main/images/jasmine-horizontal.svg" width="400px" />](http://jasmine.github.io)</a>
[![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)
# 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.
Upgrading from Jasmine 3.x? Check out the [upgrade guide](https://jasmine.github.io/tutorials/upgrading_to_Jasmine_4.0). Upgrading from Jasmine 4.x? Check out the [upgrade guide](https://jasmine.github.io/tutorials/upgrading_to_Jasmine_5.0).
## Contributing ## Contributing
@@ -33,9 +30,9 @@ Microsoft Edge) as well as Node.
| Environment | Supported versions | | Environment | Supported versions |
|-------------------|---------------------| |-------------------|---------------------|
| Node | 18, 20 | | Node | 18, 20 |
| Safari | 15-16 | | Safari | 15-17 |
| Chrome | Evergreen | | Chrome | Evergreen |
| Firefox | Evergreen, 102 | | Firefox | Evergreen, 102, 115 |
| Edge | Evergreen | | Edge | Evergreen |
For evergreen browsers, each version of Jasmine is tested against the version of the browser that is available to us For evergreen browsers, each version of Jasmine is tested against the version of the browser that is available to us
@@ -58,4 +55,6 @@ To find out what environments work with a particular Jasmine release, see the [r
* [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](https://github.com/jasmine/jasmine/blob/main/MIT.LICENSE). Copyright (c) 2008-2019 Pivotal Labs<br>
Copyright (c) 2008-2023 The Jasmine developers<br>
This software is licensed under the [MIT License](https://github.com/jasmine/jasmine/blob/main/LICENSE).

View File

@@ -11,7 +11,7 @@ module.exports = {
}, },
files: [ files: [
{ src: [ root("MIT.LICENSE") ] }, { src: [ root("LICENSE") ] },
{ {
src: [ "jasmine_favicon.png"], src: [ "jasmine_favicon.png"],
dest: standaloneLibDir, dest: standaloneLibDir,

View File

@@ -1,5 +1,6 @@
/* /*
Copyright (c) 2008-<%= currentYear %> Pivotal Labs Copyright (c) 2008-2019 Pivotal Labs
Copyright (c) 2008-<%= currentYear %> The Jasmine developers
Permission is hereby granted, free of charge, to any person obtaining Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the a copy of this software and associated documentation files (the

View File

@@ -6,11 +6,11 @@
const jasmineRequire = require('./jasmine-core/jasmine.js'); const jasmineRequire = require('./jasmine-core/jasmine.js');
module.exports = jasmineRequire; module.exports = jasmineRequire;
const bootOnce = (function() { const boot = (function() {
let jasmine, jasmineInterface; let jasmine, jasmineInterface;
return function bootWithoutGlobals() { return function bootWithoutGlobals(reinitialize) {
if (!jasmineInterface) { if (!jasmineInterface || reinitialize === true) {
jasmine = jasmineRequire.core(jasmineRequire); jasmine = jasmineRequire.core(jasmineRequire);
const env = jasmine.getEnv({ suppressLoadErrors: true }); const env = jasmine.getEnv({ suppressLoadErrors: true });
jasmineInterface = jasmineRequire.interface(jasmine, env); jasmineInterface = jasmineRequire.interface(jasmine, env);
@@ -22,12 +22,14 @@ const bootOnce = (function() {
/** /**
* Boots a copy of Jasmine and returns an object as described in {@link jasmine}. * Boots a copy of Jasmine and returns an object as described in {@link jasmine}.
* If boot is called multiple times, the same object is returned every time. * If boot is called multiple times, the same object is returned every time
* unless true is passed.
* @param {boolean} [reinitialize=false] Whether to create a new copy of Jasmine if one already exists
* @type {function} * @type {function}
* @return {jasmine} * @return {jasmine}
*/ */
module.exports.boot = function() { module.exports.boot = function(reinitialize) {
const {jasmine, jasmineInterface} = bootOnce(); const {jasmine, jasmineInterface} = boot(reinitialize);
for (const k in jasmineInterface) { for (const k in jasmineInterface) {
global[k] = jasmineInterface[k]; global[k] = jasmineInterface[k];
@@ -39,13 +41,14 @@ module.exports.boot = function() {
/** /**
* Boots a copy of Jasmine and returns an object containing the properties * Boots a copy of Jasmine and returns an object containing the properties
* that would normally be added to the global object. If noGlobals is called * that would normally be added to the global object. If noGlobals is called
* multiple times, the same object is returned every time. * multiple times, the same object is returned every time unless true is passed.
* *
* @param {boolean} [reinitialize=false] Whether to create a new copy of Jasmine if one already exists
* @example * @example
* const {describe, beforeEach, it, expect, jasmine} = require('jasmine-core').noGlobals(); * const {describe, beforeEach, it, expect, jasmine} = require('jasmine-core').noGlobals();
*/ */
module.exports.noGlobals = function() { module.exports.noGlobals = function(reinitialize) {
const {jasmineInterface} = bootOnce(); const {jasmineInterface} = boot(reinitialize);
return jasmineInterface; return jasmineInterface;
}; };

View File

@@ -1,5 +1,6 @@
/* /*
Copyright (c) 2008-2023 Pivotal Labs Copyright (c) 2008-2019 Pivotal Labs
Copyright (c) 2008-2024 The Jasmine developers
Permission is hereby granted, free of charge, to any person obtaining Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the a copy of this software and associated documentation files (the

View File

@@ -1,5 +1,6 @@
/* /*
Copyright (c) 2008-2023 Pivotal Labs Copyright (c) 2008-2019 Pivotal Labs
Copyright (c) 2008-2024 The Jasmine developers
Permission is hereby granted, free of charge, to any person obtaining Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the a copy of this software and associated documentation files (the

View File

@@ -1,5 +1,6 @@
/* /*
Copyright (c) 2008-2023 Pivotal Labs Copyright (c) 2008-2019 Pivotal Labs
Copyright (c) 2008-2024 The Jasmine developers
Permission is hereby granted, free of charge, to any person obtaining Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the a copy of this software and associated documentation files (the

View File

@@ -1,5 +1,6 @@
/* /*
Copyright (c) 2008-2023 Pivotal Labs Copyright (c) 2008-2019 Pivotal Labs
Copyright (c) 2008-2024 The Jasmine developers
Permission is hereby granted, free of charge, to any person obtaining Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the a copy of this software and associated documentation files (the
@@ -809,14 +810,6 @@ getJasmineRequireObj().Spec = function(j$) {
this.result.properties[key] = value; this.result.properties[key] = value;
}; };
Spec.prototype.expect = function(actual) {
return this.expectationFactory(actual, this);
};
Spec.prototype.expectAsync = function(actual) {
return this.asyncExpectationFactory(actual, this);
};
Spec.prototype.execute = function( Spec.prototype.execute = function(
queueRunnerFactory, queueRunnerFactory,
onComplete, onComplete,
@@ -1401,6 +1394,49 @@ getJasmineRequireObj().Env = function(j$) {
} }
}; };
const handleThrowUnlessFailure = function(passed, result) {
if (!passed) {
/**
* @interface
* @name ThrowUnlessFailure
* @extends Error
* @description Represents a failure of an expectation evaluated with
* {@link throwUnless}. Properties of this error are a subset of the
* properties of {@link Expectation} and have the same values.
* @property {String} matcherName - The name of the matcher that was executed for this expectation.
* @property {String} message - The failure message for the expectation.
* @property {Boolean} passed - Whether the expectation passed or failed.
* @property {Object} expected - If the expectation failed, what was the expected value.
* @property {Object} actual - If the expectation failed, what actual value was produced.
*/
const error = new Error(result.message);
error.passed = result.passed;
error.message = result.message;
error.expected = result.expected;
error.actual = result.actual;
error.matcherName = result.matcherName;
throw error;
}
};
const throwUnlessFactory = function(actual, spec) {
return j$.Expectation.factory({
matchersUtil: runableResources.makeMatchersUtil(),
customMatchers: runableResources.customMatchers(),
actual: actual,
addExpectationResult: handleThrowUnlessFailure
});
};
const throwUnlessAsyncFactory = function(actual, spec) {
return j$.Expectation.asyncFactory({
matchersUtil: runableResources.makeMatchersUtil(),
customAsyncMatchers: runableResources.customAsyncMatchers(),
actual: actual,
addExpectationResult: handleThrowUnlessFailure
});
};
// TODO: Unify recordLateError with recordLateExpectation? The extra // TODO: Unify recordLateError with recordLateExpectation? The extra
// diagnostic info added by the latter is probably useful in most cases. // diagnostic info added by the latter is probably useful in most cases.
function recordLateError(error) { function recordLateError(error) {
@@ -1904,23 +1940,37 @@ getJasmineRequireObj().Env = function(j$) {
}; };
this.expect = function(actual) { this.expect = function(actual) {
if (!runner.currentRunable()) { const runable = runner.currentRunable();
if (!runable) {
throw new Error( throw new Error(
"'expect' was used when there was no current spec, this could be because an asynchronous test timed out" "'expect' was used when there was no current spec, this could be because an asynchronous test timed out"
); );
} }
return runner.currentRunable().expect(actual); return runable.expectationFactory(actual, runable);
}; };
this.expectAsync = function(actual) { this.expectAsync = function(actual) {
if (!runner.currentRunable()) { const runable = runner.currentRunable();
if (!runable) {
throw new Error( throw new Error(
"'expectAsync' was used when there was no current spec, this could be because an asynchronous test timed out" "'expectAsync' was used when there was no current spec, this could be because an asynchronous test timed out"
); );
} }
return runner.currentRunable().expectAsync(actual); return runable.asyncExpectationFactory(actual, runable);
};
this.throwUnless = function(actual) {
const runable = runner.currentRunable();
return throwUnlessFactory(actual, runable);
};
this.throwUnlessAsync = function(actual) {
const runable = runner.currentRunable();
return throwUnlessAsyncFactory(actual, runable);
}; };
this.beforeEach = function(beforeEachFunction, timeout) { this.beforeEach = function(beforeEachFunction, timeout) {
@@ -2651,11 +2701,7 @@ getJasmineRequireObj().buildExpectationResult = function(j$) {
} else if (options.stack) { } else if (options.stack) {
error = options; error = options;
} else { } else {
try { error = new Error(message());
throw new Error(message());
} catch (e) {
error = e;
}
} }
} }
// Omit the message from the stack trace because it will be // Omit the message from the stack trace because it will be
@@ -3179,6 +3225,9 @@ getJasmineRequireObj().CompleteOnFirstErrorSkipPolicy = function(j$) {
return CompleteOnFirstErrorSkipPolicy; return CompleteOnFirstErrorSkipPolicy;
}; };
// Warning: don't add "use strict" to this file. Doing so potentially changes
// the behavior of user code that does things like setTimeout("var x = 1;")
// while the mock clock is installed.
getJasmineRequireObj().DelayedFunctionScheduler = function(j$) { getJasmineRequireObj().DelayedFunctionScheduler = function(j$) {
function DelayedFunctionScheduler() { function DelayedFunctionScheduler() {
this.scheduledLookup_ = []; this.scheduledLookup_ = [];
@@ -3204,6 +3253,9 @@ getJasmineRequireObj().DelayedFunctionScheduler = function(j$) {
) { ) {
let f; let f;
if (typeof funcToCall === 'string') { if (typeof funcToCall === 'string') {
// setTimeout("some code") and setInterval("some code") are legal, if
// not recommended. We don't do that ourselves, but user code might.
// This allows such code to work when the mock clock is installed.
f = function() { f = function() {
// eslint-disable-next-line no-eval // eslint-disable-next-line no-eval
return eval(funcToCall); return eval(funcToCall);
@@ -3531,7 +3583,7 @@ getJasmineRequireObj().ExceptionFormatter = function(j$) {
lines.unshift(stackTrace.message); lines.unshift(stackTrace.message);
} }
if (error.cause) { if (error.cause && error.cause instanceof Error) {
const substack = this.stack_(error.cause, { const substack = this.stack_(error.cause, {
messageHandling: 'require' messageHandling: 'require'
}); });
@@ -3566,7 +3618,7 @@ getJasmineRequireObj().ExceptionFormatter = function(j$) {
const result = {}; const result = {};
let empty = true; let empty = true;
for (const prop in error) { for (const prop of Object.keys(error)) {
if (ignoredProperties.includes(prop)) { if (ignoredProperties.includes(prop)) {
continue; continue;
} }
@@ -7110,6 +7162,8 @@ getJasmineRequireObj().NeverSkipPolicy = function(j$) {
}; };
getJasmineRequireObj().ParallelReportDispatcher = function(j$) { getJasmineRequireObj().ParallelReportDispatcher = function(j$) {
'use strict';
/** /**
* @class ParallelReportDispatcher * @class ParallelReportDispatcher
* @implements Reporter * @implements Reporter
@@ -7128,7 +7182,7 @@ getJasmineRequireObj().ParallelReportDispatcher = function(j$) {
const ReportDispatcher = deps.ReportDispatcher || j$.ReportDispatcher; const ReportDispatcher = deps.ReportDispatcher || j$.ReportDispatcher;
const QueueRunner = deps.QueueRunner || j$.QueueRunner; const QueueRunner = deps.QueueRunner || j$.QueueRunner;
const globalErrors = deps.globalErrors || new j$.GlobalErrors(); const globalErrors = deps.globalErrors || new j$.GlobalErrors();
const dispatcher = ReportDispatcher( const dispatcher = new ReportDispatcher(
j$.reporterEvents, j$.reporterEvents,
function(queueRunnerOptions) { function(queueRunnerOptions) {
queueRunnerOptions = { queueRunnerOptions = {
@@ -7854,6 +7908,8 @@ getJasmineRequireObj().QueueRunner = function(j$) {
}; };
getJasmineRequireObj().ReportDispatcher = function(j$) { getJasmineRequireObj().ReportDispatcher = function(j$) {
'use strict';
function ReportDispatcher(methods, queueRunnerFactory, onLateError) { function ReportDispatcher(methods, queueRunnerFactory, onLateError) {
const dispatchedMethods = methods || []; const dispatchedMethods = methods || [];
@@ -8232,6 +8288,54 @@ getJasmineRequireObj().interface = function(jasmine, env) {
return env.expectAsync(actual); return env.expectAsync(actual);
}, },
/**
* Create an asynchronous expectation for a spec and throw an error if it fails.
*
* This is intended to allow Jasmine matchers to be used with tools like
* testing-library's `waitFor`, which expect matcher failures to throw
* exceptions and not trigger a spec failure if the exception is caught.
* It can also be used to integration-test custom matchers.
*
* If the resulting expectation fails, a {@link ThrowUnlessFailure} will be
* thrown. A failed expectation will not result in a spec failure unless the
* exception propagates back to Jasmine, either via the call stack or via
* the global unhandled exception/unhandled promise rejection events.
* @name throwUnlessAsync
* @since 5.1.0
* @function
* @param actual
* @global
* @param {Object} actual - Actual computed value to test expectations against.
* @return {matchers}
*/
throwUnlessAsync: function(actual) {
return env.throwUnlessAsync(actual);
},
/**
* Create an expectation for a spec and throw an error if it fails.
*
* This is intended to allow Jasmine matchers to be used with tools like
* testing-library's `waitFor`, which expect matcher failures to throw
* exceptions and not trigger a spec failure if the exception is caught.
* It can also be used to integration-test custom matchers.
*
* If the resulting expectation fails, a {@link ThrowUnlessFailure} will be
* thrown. A failed expectation will not result in a spec failure unless the
* exception propagates back to Jasmine, either via the call stack or via
* the global unhandled exception/unhandled promise rejection events.
* @name throwUnless
* @since 5.1.0
* @function
* @param actual
* @global
* @param {Object} actual - Actual computed value to test expectations against.
* @return {matchers}
*/
throwUnless: function(actual) {
return env.throwUnless(actual);
},
/** /**
* Mark a spec as pending, expectation results will be ignored. * Mark a spec as pending, expectation results will be ignored.
* @name pending * @name pending
@@ -8382,7 +8486,11 @@ getJasmineRequireObj().interface = function(jasmine, env) {
* @since 1.3.0 * @since 1.3.0
* @function * @function
* @param {String} [name] - Name to give the spy. This will be displayed in failure messages. * @param {String} [name] - Name to give the spy. This will be displayed in failure messages.
* @param {Function} [originalFn] - Function to act as the real implementation. * @param {Function} [originalFn] - The "real" function. This will
* be used for subsequent calls to the spy after you call
* `mySpy.and.callThrough()`. In most cases you should omit this parameter.
* The usual way to supply an original function is to call {@link spyOn}
* instead of createSpy.
* @return {Spy} * @return {Spy}
*/ */
jasmine.createSpy = function(name, originalFn) { jasmine.createSpy = function(name, originalFn) {
@@ -9755,14 +9863,6 @@ getJasmineRequireObj().Suite = function(j$) {
this.result.properties[key] = value; this.result.properties[key] = value;
}; };
Suite.prototype.expect = function(actual) {
return this.expectationFactory(actual, this);
};
Suite.prototype.expectAsync = function(actual) {
return this.asyncExpectationFactory(actual, this);
};
Suite.prototype.getFullName = function() { Suite.prototype.getFullName = function() {
const fullName = []; const fullName = [];
for ( for (
@@ -10713,5 +10813,5 @@ getJasmineRequireObj().UserContext = function(j$) {
}; };
getJasmineRequireObj().version = function() { getJasmineRequireObj().version = function() {
return '5.0.0'; return '5.1.2';
}; };

View File

@@ -1,7 +1,7 @@
{ {
"name": "jasmine-core", "name": "jasmine-core",
"license": "MIT", "license": "MIT",
"version": "5.0.0", "version": "5.1.2",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/jasmine/jasmine.git" "url": "https://github.com/jasmine/jasmine.git"
@@ -27,7 +27,7 @@
"homepage": "https://jasmine.github.io", "homepage": "https://jasmine.github.io",
"main": "./lib/jasmine-core.js", "main": "./lib/jasmine-core.js",
"files": [ "files": [
"MIT.LICENSE", "LICENSE",
"README.md", "README.md",
"images/*.{png,svg}", "images/*.{png,svg}",
"lib/**/*.{js,css}", "lib/**/*.{js,css}",
@@ -43,8 +43,8 @@
"grunt-contrib-concat": "^2.0.0", "grunt-contrib-concat": "^2.0.0",
"grunt-css-url-embed": "^1.11.1", "grunt-css-url-embed": "^1.11.1",
"grunt-sass": "^3.0.2", "grunt-sass": "^3.0.2",
"jasmine": "5.0.0-beta.0", "jasmine": "^5.0.0",
"jasmine-browser-runner": "^1.0.0", "jasmine-browser-runner": "github:jasmine/jasmine-browser-runner",
"jsdom": "^22.0.0", "jsdom": "^22.0.0",
"load-grunt-tasks": "^5.1.0", "load-grunt-tasks": "^5.1.0",
"prettier": "1.17.1", "prettier": "1.17.1",
@@ -97,7 +97,8 @@
], ],
"space-before-blocks": "error", "space-before-blocks": "error",
"no-eval": "error", "no-eval": "error",
"no-var": "error" "no-var": "error",
"no-debugger": "error"
} }
}, },
"browserslist": [ "browserslist": [

25
release_notes/5.0.1.md Normal file
View File

@@ -0,0 +1,25 @@
# Jasmine Core 5.0.1 Release Notes
## Changes
* Optionally restore the pre-5.0 behavior of boot() always creating a new instance
This is needed by jasmine-npm (and likely other tools like it) that may
need to create and use multiple envs in sequence.
## Supported environments
jasmine-core 5.0.1 has been tested in the following environments.
| Environment | Supported versions |
|-------------------|--------------------|
| Node | 18, 20 |
| Safari | 15-16 |
| Chrome | 114 |
| Firefox | 102, 113 |
| Edge | 113 |
------
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_

39
release_notes/5.1.0.md Normal file
View File

@@ -0,0 +1,39 @@
# Jasmine Core 5.1.0 Release Notes
## Changes
* Exclude inherited Error properties from 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).
* Added `throwUnless` and `throwUnlessAsync`
These are similar to `expect` and `expectAsync` except that they throw
exceptions rather than recording matcher failures as spec/suite failures.
They're intended to support using Jasmine matchers in [testing-library's](https://testing-library.com/)
`waitFor`, and also provide a way to integration-test custom matchers.
Fixes [#2003](https://github.com/jasmine/jasmine/issues/2003).
Fixes [#1980](https://github.com/jasmine/jasmine/issues/1980).
## Supported environments
jasmine-core 5.1.0 has been tested in the following environments.
| Environment | Supported versions |
|-------------------|--------------------|
| Node | 18, 20 |
| Safari | 15-16 |
| Chrome | 114 |
| Firefox | 102, 113 |
| Edge | 113 |
------
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_

28
release_notes/5.1.1.md Normal file
View File

@@ -0,0 +1,28 @@
# Jasmine Core 5.1.1 Release Notes
## Bug Fixes
* Fixed global variable leak in the main process when running in parallel mode
* Removed unnecessary throw when building expectation results
## Documentation Improvements
* Improved jsdocs for originalFn argument to createSpy
* Link to 5.0 upgrade guide in README, not 4.0
## Supported environments
jasmine-core 5.1.1 has been tested in the following environments.
| Environment | Supported versions |
|-------------------|--------------------|
| Node | 18, 20 |
| Safari | 15-16 |
| Chrome | 116 |
| Firefox | 102, 116 |
| Edge | 115 |
------
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_

27
release_notes/5.1.2.md Normal file
View File

@@ -0,0 +1,27 @@
# Jasmine Core 5.1.2 Release Notes
## Bug Fixes
* Fixed `throwUnlessAsync`
* Fixes [#2026](https://github.com/jasmine/jasmine/issues/2026)
# Documentation improvements
* Added Safari 17 to supported browsers
* Added Firefox 115 (current ESR) to supported browsers
## Supported environments
This version has been tested in the following environments.
| Environment | Supported versions |
|-------------------|--------------------|
| Node | 18, 20 |
| Safari | 15-17 |
| Chrome | 121 |
| Firefox | 102, 115, 122 |
| Edge | 121 |
------
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_

View File

@@ -3,6 +3,7 @@
run_browser() { run_browser() {
browser=$1 browser=$1
version=$2 version=$2
os="$3"
description="$browser $version" description="$browser $version"
if [ $version = "latest" ]; then if [ $version = "latest" ]; then
version="" version=""
@@ -12,7 +13,7 @@ run_browser() {
echo echo
echo "Running $description" echo "Running $description"
echo echo
USE_SAUCE=true JASMINE_BROWSER=$browser SAUCE_BROWSER_VERSION=$version npm run ci USE_SAUCE=true JASMINE_BROWSER=$browser SAUCE_BROWSER_VERSION=$version SAUCE_OS="$os" npm run ci
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
echo "PASS: $description" >> "$passfile" echo "PASS: $description" >> "$passfile"
@@ -23,9 +24,21 @@ run_browser() {
passfile=`mktemp -t jasmine-results.XXXXXX` || exit 1 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
# As of 2023-09-30, Sauce Connect doesn't work with the latest Chrome version
# on the default Linux. Run on Mac OS instead. The OS specification may need to
# be updated or removed when new Chrome versions stop being available on Mac OS
# 12, although historically this has taken several major OS versions.
# See <https://saucelabs.com/products/supported-browsers-devices>.
# On Saucelabs, the test suite frequently runs ~30s slower on Mac OS than it
# does on Linux, so it's probably worth removing the OS specification once Sauce
# Connect works with Chrome latest on Linux again.
run_browser chrome latest "macOS 12"
run_browser firefox latest run_browser firefox latest
run_browser firefox 115
run_browser firefox 102 run_browser firefox 102
run_browser safari 17
run_browser safari 16 run_browser safari 16
run_browser safari 15 run_browser safari 15
run_browser MicrosoftEdge latest run_browser MicrosoftEdge latest

View File

@@ -197,7 +197,7 @@ describe('ExceptionFormatter', function() {
expect(new jasmineUnderTest.ExceptionFormatter().stack()).toBeNull(); expect(new jasmineUnderTest.ExceptionFormatter().stack()).toBeNull();
}); });
it('includes error properties in stack', function() { it("includes the error's own properties in stack", function() {
const error = new Error('an error'); const error = new Error('an error');
error.someProperty = 'hello there'; error.someProperty = 'hello there';
@@ -206,6 +206,19 @@ describe('ExceptionFormatter', function() {
expect(result).toMatch(/error properties:.*someProperty.*hello there/); expect(result).toMatch(/error properties:.*someProperty.*hello there/);
}); });
it('does not include inherited error properties', function() {
function CustomError(msg) {
Error.call(this, msg);
}
CustomError.prototype = new Error();
CustomError.prototype.anInheritedProp = 'something';
const error = new CustomError('nope');
const result = new jasmineUnderTest.ExceptionFormatter().stack(error);
expect(result).not.toContain('anInheritedProp');
});
describe('When omitMessage is true', function() { describe('When omitMessage is true', function() {
it('filters the message from V8-style stack traces', function() { it('filters the message from V8-style stack traces', function() {
const error = { const error = {
@@ -291,6 +304,26 @@ describe('ExceptionFormatter', function() {
.withContext('first root cause stack frame') .withContext('first root cause stack frame')
.toContain('ExceptionFormatterSpec.js'); .toContain('ExceptionFormatterSpec.js');
}); });
it('does not throw if cause is a non Error', function() {
const formatter = new jasmineUnderTest.ExceptionFormatter();
expect(function() {
formatter.stack(
new Error('error', {
cause: function() {}
})
);
}).not.toThrowError();
expect(function() {
formatter.stack(
new Error('error', {
cause: 'another error'
})
);
}).not.toThrowError();
});
}); });
}); });
}); });

View File

@@ -3,7 +3,7 @@ describe('Env integration', function() {
const isBrowser = typeof window !== 'undefined'; const isBrowser = typeof window !== 'undefined';
beforeEach(function() { beforeEach(function() {
jasmine.getEnv().registerIntegrationMatchers(); specHelpers.registerIntegrationMatchers();
env = new jasmineUnderTest.Env(); env = new jasmineUnderTest.Env();
}); });
@@ -4334,6 +4334,115 @@ describe('Env integration', function() {
} }
}); });
describe('throwUnless', function() {
it('throws when the matcher fails', async function() {
let thrown;
env.it('a spec', function() {
try {
env.throwUnless(1).toEqual(2);
} catch (e) {
thrown = e;
}
});
await env.execute();
expect(thrown).toBeInstanceOf(Error);
expect(thrown.passed).toEqual(false);
expect(thrown.matcherName).toEqual('toEqual');
expect(thrown.message).toEqual('Expected 1 to equal 2.');
expect(thrown.actual).toEqual(1);
expect(thrown.expected).toEqual(2);
});
it('does not throw when the matcher passes', async function() {
let threw = false;
env.it('a spec', function() {
try {
env.throwUnless(1).toEqual(1);
} catch (e) {
threw = true;
}
});
await env.execute();
expect(threw).toBe(false);
});
it('does not cause a failure if the error does not propagate back to jasmine', async function() {
env.it('a spec', function() {
try {
env.throwUnless(1).toEqual(2);
} catch (e) {}
});
const reporter = jasmine.createSpyObj('reporter', ['specDone']);
env.addReporter(reporter);
await env.execute();
expect(reporter.specDone).toHaveBeenCalledWith(
jasmine.objectContaining({ status: 'passed' })
);
});
});
describe('throwUnlessAsync', function() {
it('throws when the matcher fails', async function() {
const promise = Promise.resolve('a');
let thrown;
env.it('a spec', async function() {
try {
await env.throwUnlessAsync(promise).toBeResolvedTo('b');
} catch (e) {
thrown = e;
}
});
await env.execute();
expect(thrown).toBeInstanceOf(Error);
expect(thrown.passed).toEqual(false);
expect(thrown.matcherName).toEqual('toBeResolvedTo');
expect(thrown.message).toEqual(
"Expected a promise to be resolved to 'b' but it was resolved to 'a'."
);
expect(thrown.actual).toBe(promise);
expect(thrown.expected).toEqual('b');
});
it('does not throw when the matcher passes', async function() {
let threw = false;
env.it('a spec', async function() {
try {
await env.throwUnlessAsync(Promise.resolve()).toBeResolved();
} catch (e) {
threw = true;
}
});
await env.execute();
expect(threw).toBe(false);
});
it('does not cause a failure if the error does not propagate back to jasmine', async function() {
env.it('a spec', async function() {
try {
await env.throwUnlessAsync(Promise.resolve()).toBeRejected();
} catch (e) {}
});
const reporter = jasmine.createSpyObj('reporter', ['specDone']);
env.addReporter(reporter);
await env.execute();
expect(reporter.specDone).toHaveBeenCalledWith(
jasmine.objectContaining({ status: 'passed' })
);
});
});
function browserEventMethods() { function browserEventMethods() {
return { return {
listeners_: { error: [], unhandledrejection: [] }, listeners_: { error: [], unhandledrejection: [] },

View File

@@ -610,19 +610,13 @@ describe('Matchers (Integration)', function() {
}); });
describe('toHaveClass', function() { describe('toHaveClass', function() {
beforeEach(function() {
this.domHelpers = jasmine.getEnv().domHelpers();
});
verifyPasses(function(env) { verifyPasses(function(env) {
const domHelpers = jasmine.getEnv().domHelpers(); const el = specHelpers.domHelpers.createElementWithClassName('foo');
const el = domHelpers.createElementWithClassName('foo');
env.expect(el).toHaveClass('foo'); env.expect(el).toHaveClass('foo');
}); });
verifyFails(function(env) { verifyFails(function(env) {
const domHelpers = jasmine.getEnv().domHelpers(); const el = specHelpers.domHelpers.createElementWithClassName('foo');
const el = domHelpers.createElementWithClassName('foo');
env.expect(el).toHaveClass('bar'); env.expect(el).toHaveClass('bar');
}); });
}); });

View File

@@ -2,7 +2,7 @@ describe('spec running', function() {
let env; let env;
beforeEach(function() { beforeEach(function() {
jasmine.getEnv().registerIntegrationMatchers(); specHelpers.registerIntegrationMatchers();
env = new jasmineUnderTest.Env(); env = new jasmineUnderTest.Env();
env.configure({ random: false }); env.configure({ random: false });
}); });

View File

@@ -1,12 +1,8 @@
describe('toHaveClass', function() { describe('toHaveClass', function() {
beforeEach(function() {
this.domHelpers = jasmine.getEnv().domHelpers();
});
it('fails for a DOM element that lacks the expected class', function() { it('fails for a DOM element that lacks the expected class', function() {
const matcher = jasmineUnderTest.matchers.toHaveClass(), const matcher = jasmineUnderTest.matchers.toHaveClass(),
result = matcher.compare( result = matcher.compare(
this.domHelpers.createElementWithClassName(''), specHelpers.domHelpers.createElementWithClassName(''),
'foo' 'foo'
); );
@@ -15,7 +11,7 @@ describe('toHaveClass', function() {
it('passes for a DOM element that has the expected class', function() { it('passes for a DOM element that has the expected class', function() {
const matcher = jasmineUnderTest.matchers.toHaveClass(), const matcher = jasmineUnderTest.matchers.toHaveClass(),
el = this.domHelpers.createElementWithClassName('foo bar baz'); el = specHelpers.domHelpers.createElementWithClassName('foo bar baz');
expect(matcher.compare(el, 'foo').pass).toBe(true); expect(matcher.compare(el, 'foo').pass).toBe(true);
expect(matcher.compare(el, 'bar').pass).toBe(true); expect(matcher.compare(el, 'bar').pass).toBe(true);
@@ -24,7 +20,7 @@ describe('toHaveClass', function() {
it('fails for a DOM element that only has other classes', function() { it('fails for a DOM element that only has other classes', function() {
const matcher = jasmineUnderTest.matchers.toHaveClass(), const matcher = jasmineUnderTest.matchers.toHaveClass(),
el = this.domHelpers.createElementWithClassName('foo bar'); el = specHelpers.domHelpers.createElementWithClassName('foo bar');
expect(matcher.compare(el, 'fo').pass).toBe(false); expect(matcher.compare(el, 'fo').pass).toBe(false);
}); });
@@ -42,7 +38,7 @@ describe('toHaveClass', function() {
matcher.compare(undefined, 'foo'); matcher.compare(undefined, 'foo');
}).toThrowError('undefined is not a DOM element'); }).toThrowError('undefined is not a DOM element');
const textNode = this.domHelpers.document.createTextNode(''); const textNode = specHelpers.domHelpers.document.createTextNode('');
expect(function() { expect(function() {
matcher.compare(textNode, 'foo'); matcher.compare(textNode, 'foo');
}).toThrowError('HTMLNode is not a DOM element'); }).toThrowError('HTMLNode is not a DOM element');

View File

@@ -1,4 +1,4 @@
(function(env) { (function() {
function browserVersion(matchFn) { function browserVersion(matchFn) {
const userAgent = jasmine.getGlobal().navigator.userAgent; const userAgent = jasmine.getGlobal().navigator.userAgent;
if (!userAgent) { if (!userAgent) {
@@ -10,7 +10,7 @@
return match ? parseFloat(match[1]) : void 0; return match ? parseFloat(match[1]) : void 0;
} }
env.firefoxVersion = browserVersion(function(userAgent) { specHelpers.firefoxVersion = browserVersion(function(userAgent) {
return /Firefox\/([0-9]{0,})/.exec(userAgent); return /Firefox\/([0-9]{0,})/.exec(userAgent);
}); });
})(jasmine.getEnv()); })();

View File

@@ -1,24 +1,20 @@
(function(env) { (function() {
function domHelpers() { let doc;
let doc;
if (typeof document !== 'undefined') { if (typeof document !== 'undefined') {
doc = document; doc = document;
} else { } else {
const JSDOM = require('jsdom').JSDOM; const JSDOM = require('jsdom').JSDOM;
const dom = new JSDOM(); const dom = new JSDOM();
doc = dom.window.document; doc = dom.window.document;
}
return {
document: doc,
createElementWithClassName: function(className) {
const el = this.document.createElement('div');
el.className = className;
return el;
}
};
} }
env.domHelpers = domHelpers; specHelpers.domHelpers = {
})(jasmine.getEnv()); document: doc,
createElementWithClassName(className) {
const el = this.document.createElement('div');
el.className = className;
return el;
}
};
})();

1
spec/helpers/init.js Normal file
View File

@@ -0,0 +1 @@
globalThis.specHelpers = {};

View File

@@ -1,5 +1,5 @@
(function(env) { (function() {
env.registerIntegrationMatchers = function() { specHelpers.registerIntegrationMatchers = function() {
jasmine.addMatchers({ jasmine.addMatchers({
toHaveFailedExpectationsForRunnable: function() { toHaveFailedExpectationsForRunnable: function() {
return { return {
@@ -51,4 +51,4 @@
} }
}); });
}; };
})(jasmine.getEnv()); })();

View File

@@ -22,7 +22,7 @@ describe('PrettyPrinter (HTML Dependent)', function() {
}); });
it("should print Firefox's wrapped native objects correctly", function() { it("should print Firefox's wrapped native objects correctly", function() {
if (jasmine.getEnv().firefoxVersion) { if (specHelpers.firefoxVersion) {
const pp = jasmineUnderTest.makePrettyPrinter(); const pp = jasmineUnderTest.makePrettyPrinter();
let err; let err;
try { try {

View File

@@ -113,7 +113,7 @@ describe('npm package', function() {
const files = fs.readdirSync(path.resolve(this.tmpDir, 'package')); const files = fs.readdirSync(path.resolve(this.tmpDir, 'package'));
files.sort(); files.sort();
expect(files).toEqual([ expect(files).toEqual([
'MIT.LICENSE', 'LICENSE',
'README.md', 'README.md',
'images', 'images',
'lib', 'lib',

View File

@@ -18,6 +18,7 @@ module.exports = {
specDir: 'spec', specDir: 'spec',
specFiles: ['**/*[Ss]pec.js', '!npmPackage/**/*'], specFiles: ['**/*[Ss]pec.js', '!npmPackage/**/*'],
helpers: [ helpers: [
'helpers/init.js',
'helpers/generator.js', 'helpers/generator.js',
'helpers/BrowserFlags.js', 'helpers/BrowserFlags.js',
'helpers/domHelpers.js', 'helpers/domHelpers.js',
@@ -28,16 +29,19 @@ module.exports = {
random: true, random: true,
browser: { browser: {
name: process.env.JASMINE_BROWSER || 'firefox', name: process.env.JASMINE_BROWSER || 'firefox',
useSauce: process.env.USE_SAUCE === 'true', useRemoteSeleniumGrid: process.env.USE_SAUCE === 'true',
sauce: { remoteSeleniumGrid: {
name: `jasmine-core ${new Date().toISOString()}`, url: 'https://ondemand.saucelabs.com/wd/hub',
os: process.env.SAUCE_OS,
browserVersion: process.env.SAUCE_BROWSER_VERSION, browserVersion: process.env.SAUCE_BROWSER_VERSION,
build: `Core ${process.env.TRAVIS_BUILD_NUMBER || 'Ran locally'}`, platformName: process.env.SAUCE_OS,
tags: ['Jasmine-Core'], 'sauce:options': {
tunnelIdentifier: process.env.SAUCE_TUNNEL_IDENTIFIER, name: `jasmine-core ${new Date().toISOString()}`,
username: process.env.SAUCE_USERNAME, build: `Core ${process.env.CIRCLE_BUILD_NUM || 'Ran locally'}`,
accessKey: process.env.SAUCE_ACCESS_KEY tags: ['Jasmine-Core'],
tunnelIdentifier: process.env.SAUCE_TUNNEL_IDENTIFIER,
username: process.env.SAUCE_USERNAME,
accessKey: process.env.SAUCE_ACCESS_KEY
}
} }
} }
}; };

View File

@@ -5,6 +5,7 @@
"npmPackage/**/*[Ss]pec.js" "npmPackage/**/*[Ss]pec.js"
], ],
"helpers": [ "helpers": [
"helpers/init.js",
"helpers/domHelpers.js", "helpers/domHelpers.js",
"helpers/integrationMatchers.js", "helpers/integrationMatchers.js",
"helpers/overrideConsoleLogForCircleCi.js", "helpers/overrideConsoleLogForCircleCi.js",

View File

@@ -1,3 +1,6 @@
// Warning: don't add "use strict" to this file. Doing so potentially changes
// the behavior of user code that does things like setTimeout("var x = 1;")
// while the mock clock is installed.
getJasmineRequireObj().DelayedFunctionScheduler = function(j$) { getJasmineRequireObj().DelayedFunctionScheduler = function(j$) {
function DelayedFunctionScheduler() { function DelayedFunctionScheduler() {
this.scheduledLookup_ = []; this.scheduledLookup_ = [];
@@ -23,6 +26,9 @@ getJasmineRequireObj().DelayedFunctionScheduler = function(j$) {
) { ) {
let f; let f;
if (typeof funcToCall === 'string') { if (typeof funcToCall === 'string') {
// setTimeout("some code") and setInterval("some code") are legal, if
// not recommended. We don't do that ourselves, but user code might.
// This allows such code to work when the mock clock is installed.
f = function() { f = function() {
// eslint-disable-next-line no-eval // eslint-disable-next-line no-eval
return eval(funcToCall); return eval(funcToCall);

View File

@@ -264,6 +264,49 @@ getJasmineRequireObj().Env = function(j$) {
} }
}; };
const handleThrowUnlessFailure = function(passed, result) {
if (!passed) {
/**
* @interface
* @name ThrowUnlessFailure
* @extends Error
* @description Represents a failure of an expectation evaluated with
* {@link throwUnless}. Properties of this error are a subset of the
* properties of {@link Expectation} and have the same values.
* @property {String} matcherName - The name of the matcher that was executed for this expectation.
* @property {String} message - The failure message for the expectation.
* @property {Boolean} passed - Whether the expectation passed or failed.
* @property {Object} expected - If the expectation failed, what was the expected value.
* @property {Object} actual - If the expectation failed, what actual value was produced.
*/
const error = new Error(result.message);
error.passed = result.passed;
error.message = result.message;
error.expected = result.expected;
error.actual = result.actual;
error.matcherName = result.matcherName;
throw error;
}
};
const throwUnlessFactory = function(actual, spec) {
return j$.Expectation.factory({
matchersUtil: runableResources.makeMatchersUtil(),
customMatchers: runableResources.customMatchers(),
actual: actual,
addExpectationResult: handleThrowUnlessFailure
});
};
const throwUnlessAsyncFactory = function(actual, spec) {
return j$.Expectation.asyncFactory({
matchersUtil: runableResources.makeMatchersUtil(),
customAsyncMatchers: runableResources.customAsyncMatchers(),
actual: actual,
addExpectationResult: handleThrowUnlessFailure
});
};
// TODO: Unify recordLateError with recordLateExpectation? The extra // TODO: Unify recordLateError with recordLateExpectation? The extra
// diagnostic info added by the latter is probably useful in most cases. // diagnostic info added by the latter is probably useful in most cases.
function recordLateError(error) { function recordLateError(error) {
@@ -767,23 +810,37 @@ getJasmineRequireObj().Env = function(j$) {
}; };
this.expect = function(actual) { this.expect = function(actual) {
if (!runner.currentRunable()) { const runable = runner.currentRunable();
if (!runable) {
throw new Error( throw new Error(
"'expect' was used when there was no current spec, this could be because an asynchronous test timed out" "'expect' was used when there was no current spec, this could be because an asynchronous test timed out"
); );
} }
return runner.currentRunable().expect(actual); return runable.expectationFactory(actual, runable);
}; };
this.expectAsync = function(actual) { this.expectAsync = function(actual) {
if (!runner.currentRunable()) { const runable = runner.currentRunable();
if (!runable) {
throw new Error( throw new Error(
"'expectAsync' was used when there was no current spec, this could be because an asynchronous test timed out" "'expectAsync' was used when there was no current spec, this could be because an asynchronous test timed out"
); );
} }
return runner.currentRunable().expectAsync(actual); return runable.asyncExpectationFactory(actual, runable);
};
this.throwUnless = function(actual) {
const runable = runner.currentRunable();
return throwUnlessFactory(actual, runable);
};
this.throwUnlessAsync = function(actual) {
const runable = runner.currentRunable();
return throwUnlessAsyncFactory(actual, runable);
}; };
this.beforeEach = function(beforeEachFunction, timeout) { this.beforeEach = function(beforeEachFunction, timeout) {

View File

@@ -67,7 +67,7 @@ getJasmineRequireObj().ExceptionFormatter = function(j$) {
lines.unshift(stackTrace.message); lines.unshift(stackTrace.message);
} }
if (error.cause) { if (error.cause && error.cause instanceof Error) {
const substack = this.stack_(error.cause, { const substack = this.stack_(error.cause, {
messageHandling: 'require' messageHandling: 'require'
}); });
@@ -102,7 +102,7 @@ getJasmineRequireObj().ExceptionFormatter = function(j$) {
const result = {}; const result = {};
let empty = true; let empty = true;
for (const prop in error) { for (const prop of Object.keys(error)) {
if (ignoredProperties.includes(prop)) { if (ignoredProperties.includes(prop)) {
continue; continue;
} }

View File

@@ -1,4 +1,6 @@
getJasmineRequireObj().ParallelReportDispatcher = function(j$) { getJasmineRequireObj().ParallelReportDispatcher = function(j$) {
'use strict';
/** /**
* @class ParallelReportDispatcher * @class ParallelReportDispatcher
* @implements Reporter * @implements Reporter
@@ -17,7 +19,7 @@ getJasmineRequireObj().ParallelReportDispatcher = function(j$) {
const ReportDispatcher = deps.ReportDispatcher || j$.ReportDispatcher; const ReportDispatcher = deps.ReportDispatcher || j$.ReportDispatcher;
const QueueRunner = deps.QueueRunner || j$.QueueRunner; const QueueRunner = deps.QueueRunner || j$.QueueRunner;
const globalErrors = deps.globalErrors || new j$.GlobalErrors(); const globalErrors = deps.globalErrors || new j$.GlobalErrors();
const dispatcher = ReportDispatcher( const dispatcher = new ReportDispatcher(
j$.reporterEvents, j$.reporterEvents,
function(queueRunnerOptions) { function(queueRunnerOptions) {
queueRunnerOptions = { queueRunnerOptions = {

View File

@@ -1,4 +1,6 @@
getJasmineRequireObj().ReportDispatcher = function(j$) { getJasmineRequireObj().ReportDispatcher = function(j$) {
'use strict';
function ReportDispatcher(methods, queueRunnerFactory, onLateError) { function ReportDispatcher(methods, queueRunnerFactory, onLateError) {
const dispatchedMethods = methods || []; const dispatchedMethods = methods || [];

View File

@@ -70,14 +70,6 @@ getJasmineRequireObj().Spec = function(j$) {
this.result.properties[key] = value; this.result.properties[key] = value;
}; };
Spec.prototype.expect = function(actual) {
return this.expectationFactory(actual, this);
};
Spec.prototype.expectAsync = function(actual) {
return this.asyncExpectationFactory(actual, this);
};
Spec.prototype.execute = function( Spec.prototype.execute = function(
queueRunnerFactory, queueRunnerFactory,
onComplete, onComplete,

View File

@@ -28,14 +28,6 @@ getJasmineRequireObj().Suite = function(j$) {
this.result.properties[key] = value; this.result.properties[key] = value;
}; };
Suite.prototype.expect = function(actual) {
return this.expectationFactory(actual, this);
};
Suite.prototype.expectAsync = function(actual) {
return this.asyncExpectationFactory(actual, this);
};
Suite.prototype.getFullName = function() { Suite.prototype.getFullName = function() {
const fullName = []; const fullName = [];
for ( for (

View File

@@ -69,11 +69,7 @@ getJasmineRequireObj().buildExpectationResult = function(j$) {
} else if (options.stack) { } else if (options.stack) {
error = options; error = options;
} else { } else {
try { error = new Error(message());
throw new Error(message());
} catch (e) {
error = e;
}
} }
} }
// Omit the message from the stack trace because it will be // Omit the message from the stack trace because it will be

View File

@@ -225,6 +225,54 @@ getJasmineRequireObj().interface = function(jasmine, env) {
return env.expectAsync(actual); return env.expectAsync(actual);
}, },
/**
* Create an asynchronous expectation for a spec and throw an error if it fails.
*
* This is intended to allow Jasmine matchers to be used with tools like
* testing-library's `waitFor`, which expect matcher failures to throw
* exceptions and not trigger a spec failure if the exception is caught.
* It can also be used to integration-test custom matchers.
*
* If the resulting expectation fails, a {@link ThrowUnlessFailure} will be
* thrown. A failed expectation will not result in a spec failure unless the
* exception propagates back to Jasmine, either via the call stack or via
* the global unhandled exception/unhandled promise rejection events.
* @name throwUnlessAsync
* @since 5.1.0
* @function
* @param actual
* @global
* @param {Object} actual - Actual computed value to test expectations against.
* @return {matchers}
*/
throwUnlessAsync: function(actual) {
return env.throwUnlessAsync(actual);
},
/**
* Create an expectation for a spec and throw an error if it fails.
*
* This is intended to allow Jasmine matchers to be used with tools like
* testing-library's `waitFor`, which expect matcher failures to throw
* exceptions and not trigger a spec failure if the exception is caught.
* It can also be used to integration-test custom matchers.
*
* If the resulting expectation fails, a {@link ThrowUnlessFailure} will be
* thrown. A failed expectation will not result in a spec failure unless the
* exception propagates back to Jasmine, either via the call stack or via
* the global unhandled exception/unhandled promise rejection events.
* @name throwUnless
* @since 5.1.0
* @function
* @param actual
* @global
* @param {Object} actual - Actual computed value to test expectations against.
* @return {matchers}
*/
throwUnless: function(actual) {
return env.throwUnless(actual);
},
/** /**
* Mark a spec as pending, expectation results will be ignored. * Mark a spec as pending, expectation results will be ignored.
* @name pending * @name pending
@@ -375,7 +423,11 @@ getJasmineRequireObj().interface = function(jasmine, env) {
* @since 1.3.0 * @since 1.3.0
* @function * @function
* @param {String} [name] - Name to give the spy. This will be displayed in failure messages. * @param {String} [name] - Name to give the spy. This will be displayed in failure messages.
* @param {Function} [originalFn] - Function to act as the real implementation. * @param {Function} [originalFn] - The "real" function. This will
* be used for subsequent calls to the spy after you call
* `mySpy.and.callThrough()`. In most cases you should omit this parameter.
* The usual way to supply an original function is to call {@link spyOn}
* instead of createSpy.
* @return {Spy} * @return {Spy}
*/ */
jasmine.createSpy = function(name, originalFn) { jasmine.createSpy = function(name, originalFn) {