Compare commits

...

22 Commits

Author SHA1 Message Date
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
Steve Gravrock
d745d6b5f0 Bump version to 5.0.0 2023-05-13 15:14:53 -07:00
Steve Gravrock
fc2c2a477d Updated most dev dependencies
Not updating Prettier because newer versions impose significant formatting
changes. In particular, 2.0 changes every function definition from
`function() {` to `function () {` with no way to opt out. I'm not willing
to accept that kind of churn just becuse the Prettier devs changed their
mind about what color the bikeshed should be.

We'll most likely stay on Prettier 1.17 for as long as it remains viable,
then either switch to an autoformatter that offers stability or just
remove it.
2023-05-13 12:03:45 -07:00
Steve Gravrock
ff93277c0f Accessibility: Always provide a non-color indication that a spec is pending 2023-04-29 11:45:21 -07:00
Steve Gravrock
90741b3cee Accessibility: Improved contrast of version number and inactive tab links 2023-04-29 11:45:16 -07:00
Steve Gravrock
d1de59f0ed Updated jasmine dev dependency 2023-04-29 10:53:03 -07:00
Steve Gravrock
73f8e001ad Bump version to 5.0.0-beta-0 2023-04-29 10:10:05 -07:00
Steve Gravrock
390cc45af2 Dropped support for Node 16
Node 16 will reach EOL no later than a few months after Jasmine 5 is
released. Experience with Node 12 and Node 14 has shown that our
dependencies, especially dev dependencies, move on from past-EOL Node
versions fairly quickly. That can make it difficult to continue supporting
them. Since long term support for past EOL Node versions is a non-goal and
many users expect that Node versions will only be dropped in major
releases, it's better to drop it in 5.0.
2023-04-27 19:32:18 -07:00
Steve Gravrock
33118ac6e2 Merge branch 'main' into 5.0 2023-04-27 19:22:30 -07:00
Steve Gravrock
31ff9a300c Added Node 20 to supported environments 2023-04-22 08:12:49 -07:00
23 changed files with 634 additions and 142 deletions

View File

@@ -4,16 +4,14 @@
version: 2.1
executors:
node20:
docker:
- image: cimg/node:20.0.0
working_directory: ~/workspace
node18:
docker:
- image: cimg/node:18.0.0
working_directory: ~/workspace
node16:
docker:
# Oldest version with reliable support for error cause property,
# which jasmine-npm uses.
- image: cimg/node:16.14.0
working_directory: ~/workspace
jobs:
build:
@@ -62,7 +60,7 @@ jobs:
command: npx grunt execSpecsInParallel
test_browsers: &test_browsers
executor: node16
executor: node18
steps:
- attach_workspace:
at: .
@@ -82,7 +80,7 @@ jobs:
# cleanly if we kill it from a different step than it started in.
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
set +o errexit
scripts/run-all-browsers
@@ -96,35 +94,35 @@ workflows:
push:
jobs:
- build:
executor: node20
name: build_node_20
- build:
executor: node18
name: build_node_18
- build:
executor: node16
name: build_node_16
- test_node:
executor: node20
name: test_node_20
requires:
- build_node_20
- test_node:
executor: node18
name: test_node_18
requires:
- build_node_18
- test_node:
executor: node16
name: test_node_16
requires:
- build_node_16
- test_parallel:
executor: node16
name: test_parallel_node_16
requires:
- build_node_16
- build_node_18
- test_parallel:
executor: node18
name: test_parallel_node_18
requires:
- build_node_18
- test_parallel:
executor: node20
name: test_parallel_node_20
requires:
- build_node_20
- test_browsers:
requires:
- build_node_16
- build_node_18
filters:
branches:
ignore: /pull\/.*/ # Don't run on pull requests.

View File

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

View File

@@ -30,13 +30,13 @@ for information on writing specs, and [the FAQ](https://jasmine.github.io/pages/
Jasmine tests itself across popular browsers (Safari, Chrome, Firefox, and
Microsoft Edge) as well as Node.
| Environment | Supported versions |
|-------------------|--------------------|
| Node | 16.14-16.19, 18 |
| Safari | 15-16 |
| Chrome | Evergreen |
| Firefox | Evergreen, 102 |
| Edge | Evergreen |
| Environment | Supported versions |
|-------------------|---------------------|
| Node | 18, 20 |
| Safari | 15-16 |
| Chrome | Evergreen |
| Firefox | Evergreen, 102 |
| Edge | Evergreen |
For evergreen browsers, each version of Jasmine is tested against the version of the browser that is available to us
at the time of release. Other browsers, as well as older & newer versions of some supported browsers, are likely to work.

View File

@@ -6,11 +6,11 @@
const jasmineRequire = require('./jasmine-core/jasmine.js');
module.exports = jasmineRequire;
const bootOnce = (function() {
const boot = (function() {
let jasmine, jasmineInterface;
return function bootWithoutGlobals() {
if (!jasmineInterface) {
return function bootWithoutGlobals(reinitialize) {
if (!jasmineInterface || reinitialize === true) {
jasmine = jasmineRequire.core(jasmineRequire);
const env = jasmine.getEnv({ suppressLoadErrors: true });
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}.
* 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}
* @return {jasmine}
*/
module.exports.boot = function() {
const {jasmine, jasmineInterface} = bootOnce();
module.exports.boot = function(reinitialize) {
const {jasmine, jasmineInterface} = boot(reinitialize);
for (const k in jasmineInterface) {
global[k] = jasmineInterface[k];
@@ -39,13 +41,14 @@ module.exports.boot = function() {
/**
* 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
* 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
* const {describe, beforeEach, it, expect, jasmine} = require('jasmine-core').noGlobals();
*/
module.exports.noGlobals = function() {
const {jasmineInterface} = bootOnce();
module.exports.noGlobals = function(reinitialize) {
const {jasmineInterface} = boot(reinitialize);
return jasmineInterface;
};

View File

@@ -530,14 +530,13 @@ jasmineRequire.HtmlReporter = function(j$) {
if (noExpectations(resultNode.result)) {
specDescription = 'SPEC HAS NO EXPECTATIONS ' + specDescription;
}
if (
resultNode.result.status === 'pending' &&
resultNode.result.pendingReason !== ''
) {
specDescription =
specDescription +
' PENDING WITH MESSAGE: ' +
resultNode.result.pendingReason;
if (resultNode.result.status === 'pending') {
if (resultNode.result.pendingReason !== '') {
specDescription +=
' PENDING WITH MESSAGE: ' + resultNode.result.pendingReason;
} else {
specDescription += ' PENDING';
}
}
specListNode.appendChild(
createDom(

View File

@@ -55,9 +55,6 @@ body {
position: fixed;
right: 100%;
}
.jasmine_html-reporter .jasmine-version {
color: #aaa;
}
.jasmine_html-reporter .jasmine-banner {
margin-top: 14px;
}
@@ -169,10 +166,11 @@ body {
}
.jasmine_html-reporter .jasmine-bar.jasmine-menu {
background-color: #fff;
color: #aaa;
color: #000;
}
.jasmine_html-reporter .jasmine-bar.jasmine-menu a {
color: #333;
color: blue;
text-decoration: underline;
}
.jasmine_html-reporter .jasmine-bar a {
color: white;

View File

@@ -809,14 +809,6 @@ getJasmineRequireObj().Spec = function(j$) {
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(
queueRunnerFactory,
onComplete,
@@ -1401,6 +1393,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
// diagnostic info added by the latter is probably useful in most cases.
function recordLateError(error) {
@@ -1904,23 +1939,37 @@ getJasmineRequireObj().Env = function(j$) {
};
this.expect = function(actual) {
if (!runner.currentRunable()) {
const runable = runner.currentRunable();
if (!runable) {
throw new Error(
"'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) {
if (!runner.currentRunable()) {
const runable = runner.currentRunable();
if (!runable) {
throw new Error(
"'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) {
@@ -3531,7 +3580,7 @@ getJasmineRequireObj().ExceptionFormatter = function(j$) {
lines.unshift(stackTrace.message);
}
if (error.cause) {
if (error.cause && error.cause instanceof Error) {
const substack = this.stack_(error.cause, {
messageHandling: 'require'
});
@@ -3566,7 +3615,7 @@ getJasmineRequireObj().ExceptionFormatter = function(j$) {
const result = {};
let empty = true;
for (const prop in error) {
for (const prop of Object.keys(error)) {
if (ignoredProperties.includes(prop)) {
continue;
}
@@ -8232,6 +8281,54 @@ getJasmineRequireObj().interface = function(jasmine, env) {
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.throwUnless(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.
* @name pending
@@ -9755,14 +9852,6 @@ getJasmineRequireObj().Suite = function(j$) {
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() {
const fullName = [];
for (
@@ -10713,5 +10802,5 @@ getJasmineRequireObj().UserContext = function(j$) {
};
getJasmineRequireObj().version = function() {
return '5.0.0-alpha.1';
return '5.1.0';
};

View File

@@ -1,7 +1,7 @@
{
"name": "jasmine-core",
"license": "MIT",
"version": "5.0.0-alpha.1",
"version": "5.1.0",
"repository": {
"type": "git",
"url": "https://github.com/jasmine/jasmine.git"
@@ -36,16 +36,16 @@
"devDependencies": {
"eslint": "^8.36.0",
"eslint-plugin-compat": "^4.0.0",
"glob": "^9.3.1",
"glob": "^10.2.3",
"grunt": "^1.0.4",
"grunt-cli": "^1.3.2",
"grunt-contrib-compress": "^2.0.0",
"grunt-contrib-concat": "^2.0.0",
"grunt-css-url-embed": "^1.11.1",
"grunt-sass": "^3.0.2",
"jasmine": "github:jasmine/jasmine-npm#5.0",
"jasmine-browser-runner": "^1.0.0",
"jsdom": "^21.1.1",
"jasmine": "^5.0.0",
"jasmine-browser-runner": "github:jasmine/jasmine-browser-runner",
"jsdom": "^22.0.0",
"load-grunt-tasks": "^5.1.0",
"prettier": "1.17.1",
"sass": "^1.58.3",
@@ -97,7 +97,8 @@
],
"space-before-blocks": "error",
"no-eval": "error",
"no-var": "error"
"no-var": "error",
"no-debugger": "error"
}
},
"browserslist": [

View File

@@ -0,0 +1,28 @@
# Jasmine Core 5.0.0-beta.0 Release Notes
This release supports the 5.0.0-beta-0 release of the `jasmine` package.
## Breaking changes
* Dropped support for Node 16
## New features
* Added support for Node 20
## Supported environments
jasmine-core 5.0.0-beta.0 has been tested in the following environments.
| Environment | Supported versions |
|-------------------|--------------------|
| Node | 18, 20 |
| Safari | 15-16 |
| Chrome | 112 |
| Firefox | 102, 112 |
| Edge | 112 |
------
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_

55
release_notes/5.0.0.md Normal file
View File

@@ -0,0 +1,55 @@
# Jasmine Core 5.0.0 Release Notes
## Summary
This is a major release that includes breaking changes. It primarily adds
support for parallel execution in Node via the `jasmine` package. Most users
should be able to upgrade without changes, but please read the list of breaking
changes below and see the [migration guide](https://jasmine.github.io/tutorials/upgrading_to_Jasmine_5.0)
for more information.
## Breaking changes
* Dropped support for Node 12, 14, and 16
* Dropped support for Safari 14 and Firefox 91
* Made Env#execute async and removed the callback parameter
* Global errors are detected via addEventListener rather than setting window.onerror
* The `boot` function exported by the core module returns the same object
every time it's called.
* Removed node_boot.js. Use the exported `boot` function instead.
## New features
* Support for parallel execution in Node via the `jasmine` package
See the [parallel guide](https://jasmine.github.io/tutorials/running_specs_in_parallel)
for more information.
* Added Node 20 to supported environments
## Bug fixes
* Accessibility: Always provide a non-color indication that a spec is pending
* Accessibility: Improved contrast of version number and inactive tab links
* Uninstall the global error handler at the end of env execution
## Internal improvements
* Updated dev dependencies
* Dogfood parallel execution feature in CI
## Supported environments
jasmine-core 5.0.0 has been tested in the following environments.
| Environment | Supported versions |
|-------------------|--------------------|
| Node | 18, 20 |
| Safari | 15-16 |
| Chrome | 113 |
| Firefox | 102, 113 |
| Edge | 113 |
------
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_

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)_

View File

@@ -197,7 +197,7 @@ describe('ExceptionFormatter', function() {
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');
error.someProperty = 'hello there';
@@ -206,6 +206,19 @@ describe('ExceptionFormatter', function() {
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() {
it('filters the message from V8-style stack traces', function() {
const error = {
@@ -291,6 +304,26 @@ describe('ExceptionFormatter', function() {
.withContext('first root cause stack frame')
.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

@@ -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() {
return {
listeners_: { error: [], unhandledrejection: [] },

View File

@@ -1411,6 +1411,23 @@ describe('HtmlReporter', function() {
describe('and there are pending specs', function() {
let container, reporter;
function pendingSpecStatus() {
return {
id: 123,
description: 'with a spec',
fullName: 'A Suite with a spec',
status: 'pending',
passedExpectations: [],
failedExpectations: []
};
}
function reportWithSpecStatus(specStatus) {
reporter.specStarted(specStatus);
reporter.specDone(specStatus);
reporter.jasmineDone({});
}
beforeEach(function() {
container = document.createElement('div');
const getContainer = function() {
@@ -1429,21 +1446,10 @@ describe('HtmlReporter', function() {
reporter.initialize();
reporter.jasmineStarted({ totalSpecsDefined: 1 });
const specStatus = {
id: 123,
description: 'with a spec',
fullName: 'A Suite with a spec',
status: 'pending',
passedExpectations: [],
failedExpectations: [],
pendingReason: 'my custom pending reason'
};
reporter.specStarted(specStatus);
reporter.specDone(specStatus);
reporter.jasmineDone({});
});
it('reports the pending specs count', function() {
reportWithSpecStatus(pendingSpecStatus());
const alertBar = container.querySelector('.jasmine-alert .jasmine-bar');
expect(alertBar.innerHTML).toMatch(
@@ -1452,17 +1458,36 @@ describe('HtmlReporter', function() {
});
it('reports no failure details', function() {
reportWithSpecStatus(pendingSpecStatus());
const specFailure = container.querySelector('.jasmine-failures');
expect(specFailure.childNodes.length).toEqual(0);
});
it('displays the custom pending reason', function() {
reportWithSpecStatus({
...pendingSpecStatus(),
pendingReason: 'my custom pending reason'
});
const pendingDetails = container.querySelector(
'.jasmine-summary .jasmine-pending'
);
expect(pendingDetails.innerHTML).toContain('my custom pending reason');
expect(pendingDetails.innerHTML).toContain(
'PENDING WITH MESSAGE: my custom pending reason'
);
});
it('indicates that the spec is pending even if there is no reason', function() {
reportWithSpecStatus({
...pendingSpecStatus(),
pendingReason: ''
});
const pendingDetails = container.querySelector(
'.jasmine-summary .jasmine-pending'
);
expect(pendingDetails.innerHTML).toContain('PENDING');
});
});

View File

@@ -28,16 +28,19 @@ module.exports = {
random: true,
browser: {
name: process.env.JASMINE_BROWSER || 'firefox',
useSauce: process.env.USE_SAUCE === 'true',
sauce: {
name: `jasmine-core ${new Date().toISOString()}`,
os: process.env.SAUCE_OS,
useRemoteSeleniumGrid: process.env.USE_SAUCE === 'true',
remoteSeleniumGrid: {
url: 'https://ondemand.saucelabs.com/wd/hub',
browserVersion: process.env.SAUCE_BROWSER_VERSION,
build: `Core ${process.env.TRAVIS_BUILD_NUMBER || 'Ran locally'}`,
tags: ['Jasmine-Core'],
tunnelIdentifier: process.env.SAUCE_TUNNEL_IDENTIFIER,
username: process.env.SAUCE_USERNAME,
accessKey: process.env.SAUCE_ACCESS_KEY
platformName: process.env.SAUCE_OS,
'sauce:options': {
name: `jasmine-core ${new Date().toISOString()}`,
build: `Core ${process.env.CIRCLE_BUILD_NUM || 'Ran locally'}`,
tags: ['Jasmine-Core'],
tunnelIdentifier: process.env.SAUCE_TUNNEL_IDENTIFIER,
username: process.env.SAUCE_USERNAME,
accessKey: process.env.SAUCE_ACCESS_KEY
}
}
}
};

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
// diagnostic info added by the latter is probably useful in most cases.
function recordLateError(error) {
@@ -767,23 +810,37 @@ getJasmineRequireObj().Env = function(j$) {
};
this.expect = function(actual) {
if (!runner.currentRunable()) {
const runable = runner.currentRunable();
if (!runable) {
throw new Error(
"'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) {
if (!runner.currentRunable()) {
const runable = runner.currentRunable();
if (!runable) {
throw new Error(
"'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) {

View File

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

View File

@@ -70,14 +70,6 @@ getJasmineRequireObj().Spec = function(j$) {
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(
queueRunnerFactory,
onComplete,

View File

@@ -28,14 +28,6 @@ getJasmineRequireObj().Suite = function(j$) {
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() {
const fullName = [];
for (

View File

@@ -225,6 +225,54 @@ getJasmineRequireObj().interface = function(jasmine, env) {
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.throwUnless(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.
* @name pending

View File

@@ -498,14 +498,13 @@ jasmineRequire.HtmlReporter = function(j$) {
if (noExpectations(resultNode.result)) {
specDescription = 'SPEC HAS NO EXPECTATIONS ' + specDescription;
}
if (
resultNode.result.status === 'pending' &&
resultNode.result.pendingReason !== ''
) {
specDescription =
specDescription +
' PENDING WITH MESSAGE: ' +
resultNode.result.pendingReason;
if (resultNode.result.status === 'pending') {
if (resultNode.result.pendingReason !== '') {
specDescription +=
' PENDING WITH MESSAGE: ' + resultNode.result.pendingReason;
} else {
specDescription += ' PENDING';
}
}
specListNode.appendChild(
createDom(

View File

@@ -3,11 +3,13 @@
$line-height: 14px;
$margin-unit: 14px;
$faint-text-color: #aaa;
$light-text-color: #666;
$text-color: #333;
$inactive-tab-text-color: blue;
$active-tab-text-color: #000;
$page-background-color: #eee;
$menu-background-color: #aaa;
$passing-color: #007069;
$failing-color: #ca3a11;
@@ -91,10 +93,6 @@ body {
right: 100%;
}
.jasmine-version {
color: $faint-text-color;
}
//--- Banner ---//
.jasmine-banner {
@@ -238,10 +236,11 @@ body {
&.jasmine-menu {
background-color: #fff;
color: $faint-text-color;
color: $active-tab-text-color;
a {
color: $text-color;
color: $inactive-tab-text-color;
text-decoration: underline;
}
}