Compare commits

...

40 Commits

Author SHA1 Message Date
Steve Gravrock
df2d9b282e Bump version to 5.0.0-alpha.0 2023-03-18 11:25:32 -07:00
Steve Gravrock
726d35c5c5 Merge remote-tracking branch 'origin/main' into 5.0 2023-03-15 17:53:08 -07:00
Steve Gravrock
8308515210 Ignore the number of CPUs reported by Circle CI 2023-03-12 15:31:28 -07:00
Steve Gravrock
ed838b3cbf Dropped support for Node <16.14
To match jasmine-npm.
2023-03-11 21:19:30 -08:00
Steve Gravrock
04fac300e8 Uninstall the global error at the end of env execution
jasmine-npm needs this so that it can do its own error handling during
globalTeardown.
2023-03-11 17:45:31 -08:00
Steve Gravrock
61505f4c59 Fixed post-merge test failures 2023-03-04 14:10:05 -08:00
Steve Gravrock
86eddb05b4 Merge remote-tracking branch 'origin/main' into 5.0 2023-03-04 14:06:43 -08:00
Steve Gravrock
8af5509581 Added a parallel flag to the jasmineStarted reporter event 2023-03-03 20:49:10 -08:00
Steve Gravrock
cbc03feb52 Merge branch 'main' into 5.0 2023-02-12 13:08:42 -08:00
Steve Gravrock
af9a4114f4 Parallel: Improved error messages for top-level before/afterAll 2022-11-25 13:13:05 -08:00
Steve Gravrock
75f97961f5 Dropped support for Safari 14 and Firefox 91 2022-11-24 13:25:39 -08:00
Steve Gravrock
25a7168286 Merge remote-tracking branch 'origin/main' into 5.0 2022-11-24 12:50:39 -08:00
Steve Gravrock
494e81f436 Document that stopOnSpecFailure is best-effort in parallel mode 2022-11-24 12:48:40 -08:00
Steve Gravrock
ed5e902106 Parallel: Don't allow reporters to be added or removed in worker processes 2022-10-22 09:56:52 -07:00
Steve Gravrock
47c64a86d5 Parallel: fail if randomization is disabled or a seed is specified 2022-10-12 20:08:42 -07:00
Steve Gravrock
bb497beeff Parallel: throw if Env#topSuite is called 2022-10-11 20:16:36 -07:00
Steve Gravrock
e14d9c4be3 Parallel: forbid beforeAll/afterAll at the top level
Either running these once total or running them once per process
would be the wrong choice for a significant chunk of users, so do
neither. Later we'll add a new API for exactly-once setup and teardown
in parallel mode.
2022-10-11 20:10:02 -07:00
Steve Gravrock
89e0b35c53 Parallel: throw an error if fit/fdescribe are used in parallel mode 2022-10-11 19:35:59 -07:00
Steve Gravrock
1e7b68236b Parallel: forbid beforeEach/afterEach at the top level of spec files
Each spec file is only loaded in a single worker, so top level
before/afterEach can't behave consistently.

beforeEach/afterEach are still supported in:
* Helper files
* describe() blocks
* At the top level of spec files in non-parallel mode
2022-10-11 19:25:39 -07:00
Steve Gravrock
394068f863 Depend on -npm 5.0 2022-10-08 15:39:57 -07:00
Steve Gravrock
dd98a45003 Use lcoal core when running our own tests in parallel 2022-09-18 19:59:21 -07:00
Steve Gravrock
fe6762b470 Merge branch '5.0' into parallel 2022-09-18 16:42:25 -07:00
Steve Gravrock
fa16b74500 Merge branch 'main' into 5.0 2022-09-18 13:39:05 -07:00
Steve Gravrock
6ada55ff77 Parallel: Fixed reporting of exceptions thrown by a describe 2022-09-18 12:10:34 -07:00
Steve Gravrock
735ce6f758 Merge remote-tracking branch 'origin/5.0' into parallel 2022-09-18 09:43:31 -07:00
Steve Gravrock
430324885b Merge branch 'main' into 5.0 2022-09-18 09:41:45 -07:00
Steve Gravrock
7c2e8ce7ca Merge branch 'main' into parallel 2022-09-17 13:26:37 -07:00
Steve Gravrock
871111424d Use one worker per CPU when running own specs in parallel 2022-09-17 12:20:05 -07:00
Steve Gravrock
213144413f Test parallel operation in CI 2022-09-17 11:44:24 -07:00
Steve Gravrock
2272f9aead Parallel: run our own specs in parallel 2022-09-17 11:35:03 -07:00
Steve Gravrock
4c8d57e14c Dropped support for Node 14 2022-08-27 10:47:40 -07:00
Steve Gravrock
543689e206 Depend on -npm from github 2022-08-27 10:46:25 -07:00
Steve Gravrock
ee524831f4 Merge branch 'main' into parallel 2022-08-27 10:30:21 -07:00
Steve Gravrock
0690500a0d Breaking change: Made Env#execute async
Errors related to invalid spec order are now reported via promise
rejection rather than synchronous throw.
2022-08-21 16:40:03 -07:00
Steve Gravrock
0bfbda720d Breaking change: Env#execute no longer takes a callback
Use the returned promise instead.
2022-08-21 16:35:12 -07:00
Steve Gravrock
4fcdbd39fb Breaking change: use addEventListener rather than setting window.onerror
* Generally simplifies error handling in browsers
* Makes Jasmine's own integration tests easier to debug
* Stack traces will be provided for more global errors
* ... but less error information will be provided in some browsers if the
  error comes from a file:// URL (use `npx serve` or similar instead)
* Jasmine will no longer override existing onerror handlers in browsers
* Setting window.onerror will no longer override Jasmine's global error
  handling (use jasmine.spyOnGlobalErrors instead)
2022-08-21 16:17:18 -07:00
Steve Gravrock
2e80ec0c22 Rm dead code for QueueRunner deprecations 2022-08-11 19:51:08 -07:00
Steve Gravrock
588283cfe5 Breaking change: support for -npm reporter handling in parallel mode
* 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
* JasmineStartedInfo does not have totalSpecsDefined or order in parallel mode
* JasmineDoneInfo does not have order in parallel mode
* Added incompleteCode and numWorkers to JasmineDoneInfo
2022-08-10 18:23:38 -07:00
Steve Gravrock
3a43871901 Reset the env state between parallel batches 2022-08-06 10:55:02 -07:00
Steve Gravrock
fcbab02b2d Droped support for Node 12 2022-08-06 10:55:02 -07:00
31 changed files with 1254 additions and 520 deletions

View File

@@ -6,23 +6,13 @@ version: 2.1
executors:
node18:
docker:
- image: cimg/node:18.0.0 # Latest 18.x
- image: cimg/node:18.0.0
working_directory: ~/workspace
node16:
docker:
- image: cimg/node:16.14.2 # Latest 16.x
working_directory: ~/workspace
node14:
docker:
- image: cimg/node:14.17.4 # Latest 14.x
working_directory: ~/workspace
node12_latest:
docker:
- image: cimg/node:12.22.10 # Latest 12.x
working_directory: ~/workspace
node12_17:
docker:
- image: cimg/node:12.17.0 # Oldest version supported by Jasmine
# Oldest version with reliable support for error cause property,
# which jasmine-npm uses.
- image: cimg/node:16.14.0
working_directory: ~/workspace
jobs:
@@ -59,8 +49,20 @@ jobs:
name: Run tests
command: npm test
test_parallel: &test_parallel
parameters:
executor:
type: executor
executor: << parameters.executor >>
steps:
- attach_workspace:
at: .
- run:
name: Run tests in parallel
command: npx grunt execSpecsInParallel
test_browsers: &test_browsers
executor: node14
executor: node16
steps:
- attach_workspace:
at: .
@@ -100,15 +102,6 @@ workflows:
- build:
executor: node16
name: build_node_16
- build:
executor: node14
name: build_node_14
- build:
executor: node12_latest
name: build_node_12_latest
- build:
executor: node12_17
name: build_node_12_17
- test_node:
executor: node18
name: test_node_18
@@ -119,24 +112,19 @@ workflows:
name: test_node_16
requires:
- build_node_16
- test_node:
executor: node14
name: test_node_14
- test_parallel:
executor: node16
name: test_parallel_node_16
requires:
- build_node_14
- test_node:
executor: node12_latest
name: test_node_12_latest
- build_node_16
- test_parallel:
executor: node18
name: test_parallel_node_18
requires:
- build_node_12_latest
- test_node:
executor: node12_17
name: test_node_12_17
requires:
- build_node_12_17
- build_node_18
- test_browsers:
requires:
- build_node_14
- build_node_16
filters:
branches:
ignore: /pull\/.*/ # Don't run on pull requests.

View File

@@ -30,9 +30,9 @@ module.exports = function(grunt) {
function() {
verifyNoGlobals(() => require('./lib/jasmine-core.js').noGlobals());
const done = this.async(),
Jasmine = require('jasmine'),
jasmineCore = require('./lib/jasmine-core.js'),
jasmine = new Jasmine({jasmineCore: jasmineCore});
Jasmine = require('jasmine'),
jasmineCore = require('./lib/jasmine-core.js'),
jasmine = new Jasmine({jasmineCore: jasmineCore});
jasmine.loadConfigFile('./spec/support/jasmine.json');
jasmine.exitOnCompletion = false;
@@ -40,12 +40,50 @@ module.exports = function(grunt) {
result => done(result.overallStatus === 'passed'),
err => {
console.error(err);
exit(1);
done(false);
}
);
}
);
grunt.registerTask("execSpecsInParallel",
"Run Jasmine core specs in parallel in Node.js",
function() {
// Need to require this here rather than at the top of the file
// so that we don't break verifyNoGlobals above by loading jasmine-core
// too early
const ParallelRunner = require('jasmine/parallel');
let numWorkers = require('os').cpus().length;
if (process.env['CIRCLECI']) {
// On Circle CI, the above gives the number of CPU cores on the host
// computer, which is unrelated to the resources actually available
// to the container. 2 workers gives peak performance with our current
// configuration, but 4 might increase the odds of discovering any
// parallel-specific bugs.
numWorkers = 4;
}
const done = this.async();
const runner = new ParallelRunner({
jasmineCore: require('./lib/jasmine-core.js'),
numWorkers
});
runner.loadConfigFile('./spec/support/jasmine.json')
.then(() => {
runner.exitOnCompletion = false;
return runner.execute();
}).then(
jasmineDoneInfo => done(jasmineDoneInfo.overallStatus === 'passed'),
err => {
console.error(err);
done(false);
}
);
}
);
grunt.registerTask("execSpecsInNode:performance",
"Run Jasmine performance specs in Node.js",
function() {

View File

@@ -32,10 +32,10 @@ Microsoft Edge) as well as Node.
| Environment | Supported versions |
|-------------------|--------------------|
| Node | 12.17+, 14, 16, 18 |
| Safari | 14-16 |
| Node | 16.14-16.19, 18 |
| Safari | 15-16 |
| Chrome | Evergreen |
| Firefox | Evergreen, 91, 102 |
| 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

View File

@@ -45,10 +45,6 @@ module.exports = {
src: ['src/boot/boot1.js'],
dest: 'lib/jasmine-core/boot1.js'
},
nodeBoot: {
src: ['src/boot/node_boot.js'],
dest: 'lib/jasmine-core/node_boot.js'
},
options: {
banner: license(),
process: {

View File

@@ -6,36 +6,48 @@
const jasmineRequire = require('./jasmine-core/jasmine.js');
module.exports = jasmineRequire;
const bootOnce = (function() {
let jasmine, jasmineInterface;
return function bootWithoutGlobals() {
if (!jasmineInterface) {
jasmine = jasmineRequire.core(jasmineRequire);
const env = jasmine.getEnv({ suppressLoadErrors: true });
jasmineInterface = jasmineRequire.interface(jasmine, env);
}
return {jasmine, jasmineInterface};
};
}());
/**
* 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.
* @type {function}
* @return {jasmine}
*/
module.exports.boot = require('./jasmine-core/node_boot.js');
module.exports.boot = function() {
const {jasmine, jasmineInterface} = bootOnce();
for (const k in jasmineInterface) {
global[k] = jasmineInterface[k];
}
return jasmine;
};
/**
* 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.
*
* Do not call boot() if you also call noGlobals().
*
* @example
* const {describe, beforeEach, it, expect, jasmine} = require('jasmine-core').noGlobals();
*/
module.exports.noGlobals = (function() {
let jasmineInterface;
return function bootWithoutGlobals() {
if (!jasmineInterface) {
const jasmine = jasmineRequire.core(jasmineRequire);
const env = jasmine.getEnv({ suppressLoadErrors: true });
jasmineInterface = jasmineRequire.interface(jasmine, env);
}
return jasmineInterface;
};
}());
module.exports.noGlobals = function() {
const {jasmineInterface} = bootOnce();
return jasmineInterface;
};
const path = require('path'),
fs = require('fs');
@@ -43,10 +55,9 @@ const path = require('path'),
const rootPath = path.join(__dirname, 'jasmine-core'),
bootFiles = ['boot0.js', 'boot1.js'],
legacyBootFiles = ['boot.js'],
nodeBootFiles = ['node_boot.js'],
cssFiles = [],
jsFiles = [],
jsFilesToSkip = ['jasmine.js'].concat(bootFiles, legacyBootFiles, nodeBootFiles);
jsFilesToSkip = ['jasmine.js'].concat(bootFiles, legacyBootFiles);
fs.readdirSync(rootPath).forEach(function(file) {
if(fs.statSync(path.join(rootPath, file)).isFile()) {
@@ -56,18 +67,18 @@ fs.readdirSync(rootPath).forEach(function(file) {
break;
case '.js':
if (jsFilesToSkip.indexOf(file) < 0) {
jsFiles.push(file);
}
jsFiles.push(file);
}
break;
}
}
});
module.exports.files = {
self: __filename,
path: rootPath,
bootDir: rootPath,
bootFiles: bootFiles,
nodeBootFiles: nodeBootFiles,
cssFiles: cssFiles,
jsFiles: ['jasmine.js'].concat(jsFiles),
imagesDir: path.join(__dirname, '../images')

View File

@@ -1181,6 +1181,7 @@ getJasmineRequireObj().Env = function(j$) {
let reporter;
let topSuite;
let runner;
let parallelLoadingState = null; // 'specs', 'helpers', or null for non-parallel
/**
* This represents the available options to configure Jasmine.
@@ -1209,6 +1210,10 @@ getJasmineRequireObj().Env = function(j$) {
seed: null,
/**
* Whether to stop execution of the suite after the first spec failure
*
* <p>In parallel mode, `stopOnSpecFailure` works on a "best effort"
* basis. Jasmine will stop execution as soon as practical after a failure
* but it might not be immediate.</p>
* @name Configuration#stopOnSpecFailure
* @since 3.9.0
* @type Boolean
@@ -1284,20 +1289,14 @@ getJasmineRequireObj().Env = function(j$) {
if (!options.suppressLoadErrors) {
installGlobalErrors();
globalErrors.pushListener(function loadtimeErrorHandler(
message,
filename,
lineno,
colNo,
err
) {
globalErrors.pushListener(function loadtimeErrorHandler(error, event) {
topSuite.result.failedExpectations.push({
passed: false,
globalErrorType: 'load',
message: message,
stack: err && err.stack,
filename: filename,
lineno: lineno
message: error ? error.message : event.message,
stack: error && error.stack,
filename: event && event.filename,
lineno: event && event.lineno
});
});
}
@@ -1515,7 +1514,6 @@ getJasmineRequireObj().Env = function(j$) {
function(e) {
(runner.currentRunable() || topSuite).handleException(e);
};
options.deprecated = self.deprecated;
new j$.QueueRunner(options).execute();
}
@@ -1541,6 +1539,7 @@ getJasmineRequireObj().Env = function(j$) {
* @since 2.0.0
*/
this.topSuite = function() {
ensureNonParallel('topSuite');
return topSuite.metadata;
};
@@ -1634,21 +1633,25 @@ getJasmineRequireObj().Env = function(j$) {
reportSpecDone
});
this.setParallelLoadingState = function(state) {
parallelLoadingState = state;
};
this.parallelReset = function() {
// TODO: ensure that autoCleanClosures was false
suiteBuilder.parallelReset();
runner.parallelReset();
};
/**
* Executes the specs.
*
* If called with no parameters or with a falsy value as the first parameter,
* If called with no parameter or with a falsy parameter,
* all specs will be executed except those that are excluded by a
* [spec filter]{@link Configuration#specFilter} or other mechanism. If the
* first parameter is a list of spec/suite IDs, only those specs/suites will
* parameter is a list of spec/suite IDs, only those specs/suites will
* be run.
*
* Both parameters are optional, but a completion callback is only valid as
* the second parameter. To specify a completion callback but not a list of
* specs/suites to run, pass null or undefined as the first parameter. The
* completion callback is supported for backward compatibility. In most
* cases it will be more convenient to use the returned promise instead.
*
* execute should not be called more than once unless the env has been
* configured with `{autoCleanClosures: false}`.
*
@@ -1656,25 +1659,26 @@ getJasmineRequireObj().Env = function(j$) {
* {@link JasmineDoneInfo|overall result} that's passed to a reporter's
* `jasmineDone` method, even if the suite did not pass. To determine
* whether the suite passed, check the value that the promise resolves to
* or use a {@link Reporter}.
* or use a {@link Reporter}. The promise will be rejected in the case of
* certain serious errors that prevent execution from starting.
*
* @name Env#execute
* @since 2.0.0
* @function
* @async
* @param {(string[])=} runablesToRun IDs of suites and/or specs to run
* @param {Function=} onComplete Function that will be called after all specs have run
* @return {Promise<JasmineDoneInfo>}
*/
this.execute = function(runablesToRun, onComplete) {
this.execute = async function(runablesToRun) {
installGlobalErrors();
return runner.execute(runablesToRun).then(function(jasmineDoneInfo) {
if (onComplete) {
onComplete();
}
if (parallelLoadingState) {
validateConfigForParallel();
}
return jasmineDoneInfo;
});
const result = await runner.execute(runablesToRun);
this.cleanup_();
return result;
};
/**
@@ -1686,6 +1690,10 @@ getJasmineRequireObj().Env = function(j$) {
* @see custom_reporter
*/
this.addReporter = function(reporterToAdd) {
if (parallelLoadingState) {
throw new Error('Reporters cannot be added via Env in parallel mode');
}
reporter.addReporter(reporterToAdd);
};
@@ -1708,6 +1716,10 @@ getJasmineRequireObj().Env = function(j$) {
* @function
*/
this.clearReporters = function() {
if (parallelLoadingState) {
throw new Error('Reporters cannot be removed via Env in parallel mode');
}
reporter.clearReporters();
};
@@ -1807,6 +1819,38 @@ getJasmineRequireObj().Env = function(j$) {
}
}
function ensureNonParallel(method) {
if (parallelLoadingState) {
throw new Error(`'${method}' is not available in parallel mode`);
}
}
function ensureNonParallelOrInDescribe(msg) {
if (parallelLoadingState && !suiteBuilder.inDescribe()) {
throw new Error(msg);
}
}
function ensureNonParallelOrInHelperOrInDescribe(method) {
if (parallelLoadingState === 'specs' && !suiteBuilder.inDescribe()) {
throw new Error(
'In parallel mode, ' +
method +
' must be in a describe block or in a helper file'
);
}
}
function validateConfigForParallel() {
if (!config.random) {
throw new Error('Randomization cannot be disabled in parallel mode');
}
if (config.seed !== null && config.seed !== undefined) {
throw new Error('Random seed cannot be set in parallel mode');
}
}
this.describe = function(description, definitionFn) {
ensureIsNotNested('describe');
const filename = callerCallerFilename();
@@ -1823,6 +1867,7 @@ getJasmineRequireObj().Env = function(j$) {
this.fdescribe = function(description, definitionFn) {
ensureIsNotNested('fdescribe');
ensureNonParallel('fdescribe');
const filename = callerCallerFilename();
return suiteBuilder.fdescribe(description, definitionFn, filename)
.metadata;
@@ -1864,6 +1909,7 @@ getJasmineRequireObj().Env = function(j$) {
this.fit = function(description, fn, timeout) {
ensureIsNotNested('fit');
ensureNonParallel('fit');
const filename = callerCallerFilename();
return suiteBuilder.fit(description, fn, timeout, filename).metadata;
};
@@ -1937,21 +1983,37 @@ getJasmineRequireObj().Env = function(j$) {
this.beforeEach = function(beforeEachFunction, timeout) {
ensureIsNotNested('beforeEach');
ensureNonParallelOrInHelperOrInDescribe('beforeEach');
suiteBuilder.beforeEach(beforeEachFunction, timeout);
};
this.beforeAll = function(beforeAllFunction, timeout) {
ensureIsNotNested('beforeAll');
// This message is -npm-specific, but currently parallel operation is
// only supported via -npm.
ensureNonParallelOrInDescribe(
"In parallel mode, 'beforeAll' " +
'must be in a describe block. Use the globalSetup config ' +
'property for exactly-once setup in parallel mode.'
);
suiteBuilder.beforeAll(beforeAllFunction, timeout);
};
this.afterEach = function(afterEachFunction, timeout) {
ensureIsNotNested('afterEach');
ensureNonParallelOrInHelperOrInDescribe('afterEach');
suiteBuilder.afterEach(afterEachFunction, timeout);
};
this.afterAll = function(afterAllFunction, timeout) {
ensureIsNotNested('afterAll');
// This message is -npm-specific, but currently parallel operation is
// only supported via -npm.
ensureNonParallelOrInDescribe(
"In parallel mode, 'afterAll' " +
'must be in a describe block. Use the globalTeardown config ' +
'property for exactly-once teardown in parallel mode.'
);
suiteBuilder.afterAll(afterAllFunction, timeout);
};
@@ -2006,7 +2068,10 @@ getJasmineRequireObj().Env = function(j$) {
}
function callerCallerFilename() {
return new j$.StackTrace(new Error()).frames[3].file;
const frames = new j$.StackTrace(new Error()).frames;
// frames[3] should always exist except in Jasmine's own tests, which bypass
// the global it/describe layer, but don't crash if it doesn't.
return frames[3] && frames[3].file;
}
return Env;
@@ -4005,18 +4070,22 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
let overrideHandler = null,
onRemoveOverrideHandler = null;
function onerror(message, source, lineno, colno, error) {
function onBrowserError(event) {
dispatchBrowserError(event.error, event);
}
function dispatchBrowserError(error, event) {
if (overrideHandler) {
overrideHandler(error || message);
overrideHandler(error);
return;
}
const handler = handlers[handlers.length - 1];
if (handler) {
handler.apply(null, Array.prototype.slice.call(arguments, 0));
handler(error, event);
} else {
throw arguments[0];
throw error;
}
}
@@ -4093,8 +4162,7 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
this.installOne_('uncaughtException', 'Uncaught exception');
this.installOne_('unhandledRejection', 'Unhandled promise rejection');
} else {
const originalHandler = global.onerror;
global.onerror = onerror;
global.addEventListener('error', onBrowserError);
const browserRejectionHandler = function browserRejectionHandler(
event
@@ -4102,16 +4170,19 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
if (j$.isError_(event.reason)) {
event.reason.jasmineMessage =
'Unhandled promise rejection: ' + event.reason;
global.onerror(event.reason);
dispatchBrowserError(event.reason, event);
} else {
global.onerror('Unhandled promise rejection: ' + event.reason);
dispatchBrowserError(
'Unhandled promise rejection: ' + event.reason,
event
);
}
};
global.addEventListener('unhandledrejection', browserRejectionHandler);
this.uninstall = function uninstall() {
global.onerror = originalHandler;
global.removeEventListener('error', onBrowserError);
global.removeEventListener(
'unhandledrejection',
browserRejectionHandler
@@ -4120,6 +4191,13 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
}
};
// The listener at the top of the stack will be called with two arguments:
// the error and the event. Either of them may be falsy.
// The error will normally be provided, but will be falsy in the case of
// some browser load-time errors. The event will normally be provided in
// browsers but will be falsy in Node.
// Listeners that are pushed after spec files have been loaded should be
// able to just use the error parameter.
this.pushListener = function pushListener(listener) {
handlers.push(listener);
};
@@ -7450,6 +7528,15 @@ getJasmineRequireObj().makePrettyPrinter = function(j$) {
};
getJasmineRequireObj().QueueRunner = function(j$) {
/*
QueueRunner isn't part of the public interface, but it is used by
jasmine-npm. -core and -npm version in lockstep at the major and minor
levels but version independently at the patch level. Any changes that
would break -npm should be done in a major or minor release, never a
patch release, and accompanied by a change to -npm that's released in
the same version.
*/
let nextid = 1;
function StopExecutionError() {}
@@ -7513,15 +7600,11 @@ getJasmineRequireObj().QueueRunner = function(j$) {
if (typeof this.onComplete !== 'function') {
throw new Error('invalid onComplete ' + JSON.stringify(this.onComplete));
}
this.deprecated = attrs.deprecated;
}
QueueRunner.prototype.execute = function() {
this.handleFinalError = (message, source, lineno, colno, error) => {
// Older browsers would send the error as the first parameter. HTML5
// specifies the the five parameters above. The error instance should
// be preffered, otherwise the call stack would get lost.
this.onException(error || message);
this.handleFinalError = error => {
this.onException(error);
};
this.globalErrors.pushListener(this.handleFinalError);
this.run(0);
@@ -7740,6 +7823,15 @@ getJasmineRequireObj().QueueRunner = function(j$) {
};
getJasmineRequireObj().ReportDispatcher = function(j$) {
/*
ReportDispatcher isn't part of the public interface, but it is used by
jasmine-npm. -core and -npm version in lockstep at the major and minor
levels but version independently at the patch level. Any changes that
would break -npm should be done in a major or minor release, never a
patch release, and accompanied by a change to -npm that's released in
the same version.
*/
function ReportDispatcher(methods, queueRunnerFactory, onLateError) {
const dispatchedMethods = methods || [];
@@ -8411,6 +8503,7 @@ getJasmineRequireObj().Runner = function(j$) {
class Runner {
constructor(options) {
this.topSuite_ = options.topSuite;
// TODO use names that read like getters
this.totalSpecsDefined_ = options.totalSpecsDefined;
this.focusedRunables_ = options.focusedRunables;
this.runableResources_ = options.runableResources;
@@ -8435,11 +8528,11 @@ getJasmineRequireObj().Runner = function(j$) {
];
}
// Although execute returns a promise, it isn't async for backwards
// compatibility: The "Invalid order" exception needs to be propagated
// synchronously from Env#execute.
// TODO: make this and Env#execute async in the next major release
execute(runablesToRun) {
parallelReset() {
this.executedBefore_ = false;
}
async execute(runablesToRun) {
if (this.executedBefore_) {
this.topSuite_.reset();
}
@@ -8535,13 +8628,17 @@ getJasmineRequireObj().Runner = function(j$) {
/**
* Information passed to the {@link Reporter#jasmineStarted} event.
* @typedef JasmineStartedInfo
* @property {Int} totalSpecsDefined - The total number of specs defined in this suite.
* @property {Order} order - Information about the ordering (random or not) of this execution of the suite.
* @property {Int} totalSpecsDefined - The total number of specs defined in this suite. Note that this property is not present when Jasmine is run in parallel mode.
* @property {Order} order - Information about the ordering (random or not) of this execution of the suite. Note that this property is not present when Jasmine is run in parallel mode.
* @property {Boolean} parallel - Whether Jasmine is being run in parallel mode.
* @since 2.0.0
*/
await this.reporter_.jasmineStarted({
// In parallel mode, the jasmineStarted event is separately dispatched
// by jasmine-npm. This event only reaches reporters in non-parallel.
totalSpecsDefined,
order: order
order: order,
parallel: false
});
this.currentlyExecutingSuites_.push(this.topSuite_);
@@ -8553,7 +8650,7 @@ getJasmineRequireObj().Runner = function(j$) {
this.runableResources_.clearForRunable(this.topSuite_.id);
this.currentlyExecutingSuites_.pop();
let overallStatus, incompleteReason;
let overallStatus, incompleteReason, incompleteCode;
if (
this.hasFailures ||
@@ -8563,9 +8660,11 @@ getJasmineRequireObj().Runner = function(j$) {
} else if (this.focusedRunables_().length > 0) {
overallStatus = 'incomplete';
incompleteReason = 'fit() or fdescribe() was found';
incompleteCode = 'focused';
} else if (totalSpecsDefined === 0) {
overallStatus = 'incomplete';
incompleteReason = 'No specs found';
incompleteCode = 'noSpecsFound';
} else {
overallStatus = 'passed';
}
@@ -8575,8 +8674,10 @@ getJasmineRequireObj().Runner = function(j$) {
* @typedef JasmineDoneInfo
* @property {OverallStatus} overallStatus - The overall result of the suite: 'passed', 'failed', or 'incomplete'.
* @property {Int} totalTime - The total time (in ms) that it took to execute the suite
* @property {IncompleteReason} incompleteReason - Explanation of why the suite was incomplete.
* @property {Order} order - Information about the ordering (random or not) of this execution of the suite.
* @property {String} incompleteReason - Human-readable explanation of why the suite was incomplete.
* @property {String} incompleteCode - Machine-readable explanation of why the suite was incomplete: 'focused', 'noSpecsFound', or undefined.
* @property {Order} order - Information about the ordering (random or not) of this execution of the suite. Note that this property is not present when Jasmine is run in parallel mode.
* @property {Int} numWorkers - Number of parallel workers. Note that this property is only present when Jasmine is run in parallel mode.
* @property {Expectation[]} failedExpectations - List of expectations that failed in an {@link afterAll} at the global level.
* @property {Expectation[]} deprecationWarnings - List of deprecation warnings that occurred at the global level.
* @since 2.4.0
@@ -8585,6 +8686,7 @@ getJasmineRequireObj().Runner = function(j$) {
overallStatus: overallStatus,
totalTime: jasmineTimer.elapsed(),
incompleteReason: incompleteReason,
incompleteCode: incompleteCode,
order: order,
failedExpectations: this.topSuite_.result.failedExpectations,
deprecationWarnings: this.topSuite_.result.deprecationWarnings
@@ -9670,6 +9772,10 @@ getJasmineRequireObj().Suite = function(j$) {
this.reportedDone = false;
};
Suite.prototype.removeChildren = function() {
this.children = [];
};
Suite.prototype.addChild = function(child) {
this.children.push(child);
};
@@ -9885,6 +9991,17 @@ getJasmineRequireObj().SuiteBuilder = function(j$) {
this.focusedRunables = [];
}
inDescribe() {
return this.currentDeclarationSuite_ !== this.topSuite;
}
parallelReset() {
this.topSuite.removeChildren();
this.topSuite.reset();
this.totalSpecsDefined = 0;
this.focusedRunables = [];
}
describe(description, definitionFn, filename) {
ensureIsFunction(definitionFn, 'describe');
const suite = this.suiteFactory_(description, filename);
@@ -10176,6 +10293,15 @@ getJasmineRequireObj().SuiteBuilder = function(j$) {
};
getJasmineRequireObj().Timer = function() {
/*
Timer isn't part of the public interface, but it is used by
jasmine-npm. -core and -npm version in lockstep at the major and minor
levels but version independently at the patch level. Any changes that
would break -npm should be done in a major or minor release, never a
patch release, and accompanied by a change to -npm that's released in
the same version.
*/
const defaultNow = (function(Date) {
return function() {
return new Date().getTime();
@@ -10484,5 +10610,5 @@ getJasmineRequireObj().UserContext = function(j$) {
};
getJasmineRequireObj().version = function() {
return '4.6.0';
return '5.0.0-alpha.0';
};

View File

@@ -1,38 +0,0 @@
/*
Copyright (c) 2008-2023 Pivotal Labs
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
module.exports = function(jasmineRequire) {
const jasmine = jasmineRequire.core(jasmineRequire);
const env = jasmine.getEnv({ suppressLoadErrors: true });
const jasmineInterface = jasmineRequire.interface(jasmine, env);
extend(global, jasmineInterface);
function extend(destination, source) {
for (const property in source) destination[property] = source[property];
return destination;
}
return jasmine;
};

View File

@@ -1,7 +1,7 @@
{
"name": "jasmine-core",
"license": "MIT",
"version": "4.6.0",
"version": "5.0.0-alpha.0",
"repository": {
"type": "git",
"url": "https://github.com/jasmine/jasmine.git"
@@ -43,7 +43,7 @@
"grunt-contrib-concat": "^2.0.0",
"grunt-css-url-embed": "^1.11.1",
"grunt-sass": "^3.0.2",
"jasmine": "^4.1.0",
"jasmine": "github:jasmine/jasmine-npm#5.0",
"jasmine-browser-runner": "^1.0.0",
"jsdom": "^19.0.0",
"load-grunt-tasks": "^5.1.0",
@@ -101,10 +101,9 @@
}
},
"browserslist": [
"Safari >= 14",
"Safari >= 15",
"Firefox >= 102",
"last 2 Chrome versions",
"last 2 Firefox versions",
"Firefox >= 91",
"last 2 Edge versions"
]
}

View File

@@ -0,0 +1,68 @@
# Jasmine Core 5.0.0-alpha.0 Release Notes
## Summary
This release primarily adds support for parallel execution via the `jasmine`
package. Please see its release notes and the
[parallel documentation](https://jasmine.github.io/tutorials/running_specs_in_parallel)
for more information. Additionally, this release cleans up a few outdated
interfaces.
This is a pre-release for a major version. It contains breaking changes, and
there may be further breaking changes between this release and the final 5.0.0
release.
## Breaking changes
* Use addEventListener in browsers rather than setting window.onerror
This simplifies error handling in browsers, makes Jasmine's own integration
tests easier to debug, and provides stack traces for more unhandled
exceptions. However, some browsers will provide less error information when
the error comes from a file:// URL. Additionally, Jasmine will no longer
override existing onerror handlers, and setting window.onerror will no longer
override Jasmine's global error handling. (Use `jasmine.spyOnGlobalErrors`
instead.)
* Made Env#execute async
* Env#execute no longer takes a callback
* 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.
### Changes to supported environments
The following previously supported environments are no longer supported:
* Node <16.14
* Safari 14
* Firefox 91
Although this release may still work in some of those environments, we no
longer test against them and won't try to maintain compatibility with them in
future releases.
## New features
* Support for parallel execution in Node.js using the `jasmine` package
## Bug fixes
* The global error handler is uninstalled at the end of env execution.
## Supported environments
jasmine-core 5.0.0-alpha.0 has been tested in the following environments.
| Environment | Supported versions |
|-------------------|--------------------|
| Node | 16.14+, 18 |
| Safari | 15-16 |
| Chrome | 111 |
| Firefox | 102, 111 |
| Edge | 111 |
------
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_

View File

@@ -26,10 +26,8 @@ failfile=`mktemp -t jasmine-results.XXXXXX` || exit 1
run_browser chrome latest
run_browser firefox latest
run_browser firefox 102
run_browser firefox 91
run_browser safari 16
run_browser safari 15
run_browser safari 14
run_browser MicrosoftEdge latest
echo

View File

@@ -29,7 +29,7 @@ describe('AsyncExpectation', function() {
it('converts a fail to a pass', function() {
const addExpectationResult = jasmine.createSpy('addExpectationResult'),
actual = Promise.reject(),
actual = Promise.reject(new Error('nope')),
expectation = jasmineUnderTest.Expectation.asyncFactory({
matchersUtil: new jasmineUnderTest.MatchersUtil({
pp: function() {}
@@ -138,7 +138,7 @@ describe('AsyncExpectation', function() {
}
},
addExpectationResult = jasmine.createSpy('addExpectationResult'),
actual = Promise.reject(),
actual = Promise.reject(new Error('nope')),
expectation = jasmineUnderTest.Expectation.asyncFactory({
actual: actual,
addExpectationResult: addExpectationResult,

View File

@@ -80,6 +80,19 @@ describe('Env', function() {
);
expect(suite.children[1].children[1].children[0].children).toBeFalsy();
});
it('throws if called in parallel mode', function() {
env.setParallelLoadingState('helpers');
check();
env.setParallelLoadingState('specs');
check();
function check() {
expect(function() {
env.topSuite();
}).toThrowError("'topSuite' is not available in parallel mode");
}
});
});
it('accepts its own current configureation', function() {
@@ -251,6 +264,15 @@ describe('Env', function() {
describe('#fdescribe', function() {
behavesLikeDescribe('fdescribe');
it('throws an error in parallel mode', function() {
env.setParallelLoadingState('specs');
expect(function() {
env.fdescribe('a suite', function() {
env.it('a spec');
});
}).toThrowError("'fdescribe' is not available in parallel mode");
});
});
describe('xdescribe', function() {
@@ -372,6 +394,13 @@ describe('Env', function() {
env.fit('huge timeout', function() {}, 2147483648);
}).toThrowError('Timeout value cannot be greater than 2147483647');
});
it('throws an error in parallel mode', function() {
env.setParallelLoadingState('specs');
expect(function() {
env.fit('a spec', function() {});
}).toThrowError("'fit' is not available in parallel mode");
});
});
describe('#beforeEach', function() {
@@ -394,6 +423,28 @@ describe('Env', function() {
env.beforeEach(function() {}, 2147483648);
}).toThrowError('Timeout value cannot be greater than 2147483647');
});
it('throws when called at the top level in a spec file in parallel mode', function() {
env.setParallelLoadingState('specs');
expect(function() {
env.beforeEach(function() {});
}).toThrowError(
'In parallel mode, beforeEach must be in a describe block or in a helper file'
);
});
it('does not throw when called at the top level in a helper file in parallel mode', function() {
env.setParallelLoadingState('helpers');
env.beforeEach(function() {});
});
it('does not throw when called in a describe in a spec file in parallel mode', function() {
env.setParallelLoadingState('specs');
env.describe('a suite', function() {
env.beforeEach(function() {});
env.it('a spec');
});
});
});
describe('#beforeAll', function() {
@@ -416,6 +467,47 @@ describe('Env', function() {
env.beforeAll(function() {}, 2147483648);
}).toThrowError('Timeout value cannot be greater than 2147483647');
});
describe('in parallel mode', function() {
it('throws an error when called at the top level', function() {
env.setParallelLoadingState('helpers');
check();
env.setParallelLoadingState('specs');
check();
function check() {
expect(function() {
env.beforeAll(function() {});
}).toThrowError(
"In parallel mode, 'beforeAll' must be in a describe block. " +
'Use the globalSetup config property for exactly-once setup in' +
' parallel mode.'
);
}
});
it('does not throw an error when called in a describe', function() {
env.setParallelLoadingState('helpers');
check();
env.setParallelLoadingState('specs');
check();
function check() {
let done = false;
env.describe('a suite', function() {
expect(function() {
env.it('a spec');
env.beforeAll(function() {});
}).not.toThrow();
done = true;
});
expect(done).toBeTrue();
}
});
});
});
describe('#afterEach', function() {
@@ -438,6 +530,28 @@ describe('Env', function() {
env.afterEach(function() {}, 2147483648);
}).toThrowError('Timeout value cannot be greater than 2147483647');
});
it('throws when called at the top level in a spec file in parallel mode', function() {
env.setParallelLoadingState('specs');
expect(function() {
env.afterEach(function() {});
}).toThrowError(
'In parallel mode, afterEach must be in a describe block or in a helper file'
);
});
it('does not throw when called at the top level in a helper file in parallel mode', function() {
env.setParallelLoadingState('helpers');
env.afterEach(function() {});
});
it('does not throw when called in a describe in a spec file in parallel mode', function() {
env.setParallelLoadingState('specs');
env.describe('a suite', function() {
env.afterEach(function() {});
env.it('a spec');
});
});
});
describe('#afterAll', function() {
@@ -460,6 +574,47 @@ describe('Env', function() {
env.afterAll(function() {}, 2147483648);
}).toThrowError('Timeout value cannot be greater than 2147483647');
});
describe('in parallel mode', function() {
it('throws an error when called at the top level', function() {
env.setParallelLoadingState('helpers');
check();
env.setParallelLoadingState('specs');
check();
function check() {
expect(function() {
env.afterAll(function() {});
}).toThrowError(
"In parallel mode, 'afterAll' must be in a describe block. " +
'Use the globalTeardown config property for exactly-once ' +
'teardown in parallel mode.'
);
}
});
it('does not throw an error when called in a describe', function() {
env.setParallelLoadingState('helpers');
check();
env.setParallelLoadingState('specs');
check();
function check() {
let done = false;
env.describe('a suite', function() {
expect(function() {
env.it('a spec');
env.afterAll(function() {});
}).not.toThrow();
done = true;
});
expect(done).toBeTrue();
}
});
});
});
describe('when not constructed with suppressLoadErrors: true', function() {
@@ -592,6 +747,32 @@ describe('Env', function() {
expect(id).toEqual(env.topSuite().id);
});
});
it('should not reset the topSuite if parallelReset was called since the last run', async function() {
await env.execute();
env.parallelReset();
spyOn(jasmineUnderTest.Suite.prototype, 'reset');
await env.execute();
expect(jasmineUnderTest.Suite.prototype.reset).not.toHaveBeenCalled();
});
describe('In parallel mode', function() {
it('rejects if random is set to false', async function() {
env.setParallelLoadingState('specs');
env.configure({ random: false });
await expectAsync(env.execute()).toBeRejectedWithError(
'Randomization cannot be disabled in parallel mode'
);
});
it('rejects if seed is set', async function() {
env.setParallelLoadingState('specs');
env.configure({ seed: 1234 });
await expectAsync(env.execute()).toBeRejectedWithError(
'Random seed cannot be set in parallel mode'
);
});
});
});
describe('#spyOnGlobalErrorsAsync', function() {
@@ -608,4 +789,32 @@ describe('Env', function() {
).toBeRejectedWithError(msg);
});
});
describe('#addReporter', function() {
it('throws when called in parallel mode', function() {
env.setParallelLoadingState('helpers');
expect(function() {
env.addReporter({});
}).toThrowError('Reporters cannot be added via Env in parallel mode');
env.setParallelLoadingState('specs');
expect(function() {
env.addReporter({});
}).toThrowError('Reporters cannot be added via Env in parallel mode');
});
});
describe('#clearReporters', function() {
it('throws when called in parallel mode', function() {
env.setParallelLoadingState('helpers');
expect(function() {
env.clearReporters();
}).toThrowError('Reporters cannot be removed via Env in parallel mode');
env.setParallelLoadingState('specs');
expect(function() {
env.clearReporters();
}).toThrowError('Reporters cannot be removed via Env in parallel mode');
});
});
});

View File

@@ -257,17 +257,7 @@ describe('ExceptionFormatter', function() {
});
});
describe('In environments that support the cause property of Errors', function() {
beforeEach(function() {
const inner = new Error('inner');
const outer = new Error('outer', { cause: inner });
if (!outer.cause) {
// Currently: Node 12, Node 14, Safari 14
pending('Environment does not support error cause');
}
});
describe('when the error has a cause property', function() {
it('recursively includes the cause in the stack trace in this environment', function() {
const subject = new jasmineUnderTest.ExceptionFormatter();
const rootCause = new Error('root cause');

View File

@@ -1,56 +1,42 @@
describe('GlobalErrors', function() {
it('calls the added handler on error', function() {
const fakeGlobal = minimalBrowserGlobal();
const fakeGlobal = browserGlobal();
const handler = jasmine.createSpy('errorHandler');
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
errors.install();
errors.pushListener(handler);
fakeGlobal.onerror('foo');
expect(handler).toHaveBeenCalledWith('foo');
});
it('enables external interception of error by overriding global.onerror', function() {
const fakeGlobal = minimalBrowserGlobal();
const handler = jasmine.createSpy('errorHandler');
const hijackHandler = jasmine.createSpy('hijackErrorHandler');
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
errors.install();
errors.pushListener(handler);
fakeGlobal.onerror = hijackHandler;
fakeGlobal.onerror('foo');
expect(hijackHandler).toHaveBeenCalledWith('foo');
expect(handler).not.toHaveBeenCalled();
});
it('calls the global error handler with all parameters', function() {
const fakeGlobal = minimalBrowserGlobal();
const handler = jasmine.createSpy('errorHandler');
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
const fooError = new Error('foo');
errors.install();
errors.pushListener(handler);
fakeGlobal.onerror(fooError.message, 'foo.js', 1, 1, fooError);
const error = new Error('nope');
dispatchErrorEvent(fakeGlobal, { error });
expect(handler).toHaveBeenCalledWith(
fooError.message,
'foo.js',
1,
1,
fooError
jasmine.is(error),
jasmine.objectContaining({ error: jasmine.is(error) })
);
});
it('is not affected by overriding global.onerror', function() {
const fakeGlobal = browserGlobal();
const handler = jasmine.createSpy('errorHandler');
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
errors.install();
errors.pushListener(handler);
fakeGlobal.onerror = () => {};
const error = new Error('nope');
dispatchErrorEvent(fakeGlobal, { error });
expect(handler).toHaveBeenCalledWith(
jasmine.is(error),
jasmine.objectContaining({ error: jasmine.is(error) })
);
});
it('only calls the most recent handler', function() {
const fakeGlobal = minimalBrowserGlobal();
const fakeGlobal = browserGlobal();
const handler1 = jasmine.createSpy('errorHandler1');
const handler2 = jasmine.createSpy('errorHandler2');
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
@@ -59,14 +45,18 @@ describe('GlobalErrors', function() {
errors.pushListener(handler1);
errors.pushListener(handler2);
fakeGlobal.onerror('foo');
const error = new Error('nope');
dispatchErrorEvent(fakeGlobal, { error });
expect(handler1).not.toHaveBeenCalled();
expect(handler2).toHaveBeenCalledWith('foo');
expect(handler2).toHaveBeenCalledWith(
jasmine.is(error),
jasmine.objectContaining({ error: jasmine.is(error) })
);
});
it('calls previous handlers when one is removed', function() {
const fakeGlobal = minimalBrowserGlobal();
const fakeGlobal = browserGlobal();
const handler1 = jasmine.createSpy('errorHandler1');
const handler2 = jasmine.createSpy('errorHandler2');
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
@@ -77,9 +67,13 @@ describe('GlobalErrors', function() {
errors.popListener(handler2);
fakeGlobal.onerror('foo');
const error = new Error('nope');
dispatchErrorEvent(fakeGlobal, { error });
expect(handler1).toHaveBeenCalledWith('foo');
expect(handler1).toHaveBeenCalledWith(
jasmine.is(error),
jasmine.objectContaining({ error: jasmine.is(error) })
);
expect(handler2).not.toHaveBeenCalled();
});
@@ -90,34 +84,27 @@ describe('GlobalErrors', function() {
}).toThrowError('popListener expects a listener');
});
it('uninstalls itself, putting back a previous callback', function() {
const originalCallback = jasmine.createSpy('error');
const fakeGlobal = {
...minimalBrowserGlobal(),
onerror: originalCallback
};
it('uninstalls itself', function() {
const fakeGlobal = browserGlobal();
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
expect(fakeGlobal.onerror).toBe(originalCallback);
function unrelatedListener() {}
errors.install();
expect(fakeGlobal.onerror).not.toBe(originalCallback);
fakeGlobal.addEventListener('error', unrelatedListener);
errors.uninstall();
expect(fakeGlobal.onerror).toBe(originalCallback);
expect(fakeGlobal.listeners_.error).toEqual([unrelatedListener]);
});
it('rethrows the original error when there is no handler', function() {
const fakeGlobal = minimalBrowserGlobal();
const fakeGlobal = browserGlobal();
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
const originalError = new Error('nope');
errors.install();
try {
fakeGlobal.onerror(originalError);
dispatchErrorEvent(fakeGlobal, { error: originalError });
} catch (e) {
expect(e).toBe(originalError);
}
@@ -289,128 +276,61 @@ describe('GlobalErrors', function() {
describe('Reporting unhandled promise rejections in the browser', function() {
it('subscribes and unsubscribes from the unhandledrejection event', function() {
const fakeGlobal = jasmine.createSpyObj('globalErrors', [
'addEventListener',
'removeEventListener',
'onerror'
]),
errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
const fakeGlobal = browserGlobal();
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
errors.install();
expect(fakeGlobal.addEventListener).toHaveBeenCalledWith(
'unhandledrejection',
expect(fakeGlobal.listeners_.unhandledrejection).toEqual([
jasmine.any(Function)
);
]);
const addedListener = fakeGlobal.addEventListener.calls.argsFor(0)[1];
errors.uninstall();
expect(fakeGlobal.removeEventListener).toHaveBeenCalledWith(
'unhandledrejection',
addedListener
);
expect(fakeGlobal.listeners_.unhandledrejection).toEqual([]);
});
it('reports rejections whose reason is a string', function() {
const fakeGlobal = jasmine.createSpyObj('globalErrors', [
'addEventListener',
'removeEventListener',
'onerror'
]),
handler = jasmine.createSpy('errorHandler'),
errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
const fakeGlobal = browserGlobal();
const handler = jasmine.createSpy('errorHandler');
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
errors.install();
errors.pushListener(handler);
const addedListener = fakeGlobal.addEventListener.calls.argsFor(0)[1];
addedListener({ reason: 'nope' });
const event = { reason: 'nope' };
dispatchUnhandledRejectionEvent(fakeGlobal, event);
expect(handler).toHaveBeenCalledWith('Unhandled promise rejection: nope');
expect(handler).toHaveBeenCalledWith(
'Unhandled promise rejection: nope',
event
);
});
it('reports rejections whose reason is an Error', function() {
const fakeGlobal = jasmine.createSpyObj('globalErrors', [
'addEventListener',
'removeEventListener',
'onerror'
]),
handler = jasmine.createSpy('errorHandler'),
errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
const fakeGlobal = browserGlobal();
const handler = jasmine.createSpy('errorHandler');
const errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
errors.install();
errors.pushListener(handler);
const addedListener = fakeGlobal.addEventListener.calls.argsFor(0)[1];
const reason = new Error('bar');
addedListener({ reason: reason });
const event = { reason };
dispatchUnhandledRejectionEvent(fakeGlobal, event);
expect(handler).toHaveBeenCalledWith(
jasmine.objectContaining({
jasmineMessage: 'Unhandled promise rejection: Error: bar',
message: reason.message,
stack: reason.stack
})
}),
event
);
});
describe('Enabling external interception of reported rejections by overriding global.onerror', function() {
it('overriding global.onerror intercepts rejections whose reason is a string', function() {
const fakeGlobal = jasmine.createSpyObj('globalErrors', [
'addEventListener'
]),
handler = jasmine.createSpy('errorHandler'),
hijackHandler = jasmine.createSpy('hijackErrorHandler'),
errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
errors.install();
errors.pushListener(handler);
fakeGlobal.onerror = hijackHandler;
const addedListener = fakeGlobal.addEventListener.calls.argsFor(0)[1];
addedListener({ reason: 'nope' });
expect(hijackHandler).toHaveBeenCalledWith(
'Unhandled promise rejection: nope'
);
expect(handler).not.toHaveBeenCalled();
});
it('overriding global.onerror intercepts rejections whose reason is an Error', function() {
const fakeGlobal = jasmine.createSpyObj('globalErrors', [
'addEventListener'
]),
handler = jasmine.createSpy('errorHandler'),
hijackHandler = jasmine.createSpy('hijackErrorHandler'),
errors = new jasmineUnderTest.GlobalErrors(fakeGlobal);
errors.install();
errors.pushListener(handler);
fakeGlobal.onerror = hijackHandler;
const addedListener = fakeGlobal.addEventListener.calls.argsFor(0)[1];
const reason = new Error('bar');
addedListener({ reason: reason });
expect(hijackHandler).toHaveBeenCalledWith(
jasmine.objectContaining({
jasmineMessage: 'Unhandled promise rejection: Error: bar',
message: reason.message,
stack: reason.stack
})
);
expect(handler).not.toHaveBeenCalled();
});
});
});
describe('#setOverrideListener', function() {
it('overrides the existing handlers in browsers until removed', function() {
const fakeGlobal = minimalBrowserGlobal();
const fakeGlobal = browserGlobal();
const handler0 = jasmine.createSpy('handler0');
const handler1 = jasmine.createSpy('handler1');
const overrideHandler = jasmine.createSpy('overrideHandler');
@@ -420,19 +340,18 @@ describe('GlobalErrors', function() {
errors.pushListener(handler0);
errors.setOverrideListener(overrideHandler, () => {});
errors.pushListener(handler1);
fakeGlobal.onerror('foo');
fakeGlobal.onerror(null, null, null, null, new Error('bar'));
dispatchErrorEvent(fakeGlobal, { error: 'foo' });
expect(overrideHandler).toHaveBeenCalledWith('foo');
expect(overrideHandler).toHaveBeenCalledWith(new Error('bar'));
expect(handler0).not.toHaveBeenCalled();
expect(handler1).not.toHaveBeenCalled();
errors.removeOverrideListener();
fakeGlobal.onerror('baz');
const event = { error: 'baz' };
dispatchErrorEvent(fakeGlobal, event);
expect(overrideHandler).not.toHaveBeenCalledWith('baz');
expect(handler1).toHaveBeenCalledWith('baz');
expect(handler1).toHaveBeenCalledWith('baz', event);
});
it('overrides the existing handlers in Node until removed', function() {
@@ -532,7 +451,7 @@ describe('GlobalErrors', function() {
});
it('throws if there is already an override handler', function() {
const errors = new jasmineUnderTest.GlobalErrors(minimalBrowserGlobal());
const errors = new jasmineUnderTest.GlobalErrors(browserGlobal());
errors.setOverrideListener(() => {}, () => {});
expect(function() {
@@ -544,7 +463,7 @@ describe('GlobalErrors', function() {
describe('#removeOverrideListener', function() {
it("calls the handler's onRemove callback", function() {
const onRemove = jasmine.createSpy('onRemove');
const errors = new jasmineUnderTest.GlobalErrors(minimalBrowserGlobal());
const errors = new jasmineUnderTest.GlobalErrors(browserGlobal());
errors.setOverrideListener(() => {}, onRemove);
errors.removeOverrideListener();
@@ -553,17 +472,43 @@ describe('GlobalErrors', function() {
});
it('does not throw if there is no handler', function() {
const errors = new jasmineUnderTest.GlobalErrors(minimalBrowserGlobal());
const errors = new jasmineUnderTest.GlobalErrors(browserGlobal());
expect(() => errors.removeOverrideListener()).not.toThrow();
});
});
function minimalBrowserGlobal() {
function browserGlobal() {
return {
addEventListener() {},
removeEventListener() {},
onerror: null
listeners_: { error: [], unhandledrejection: [] },
addEventListener(eventName, listener) {
this.listeners_[eventName].push(listener);
},
removeEventListener(eventName, listener) {
this.listeners_[eventName] = this.listeners_[eventName].filter(
l => l !== listener
);
}
};
}
function dispatchErrorEvent(global, event) {
expect(global.listeners_.error.length)
.withContext('number of error listeners')
.toBeGreaterThan(0);
for (const l of global.listeners_.error) {
l(event);
}
}
function dispatchUnhandledRejectionEvent(global, event) {
expect(global.listeners_.unhandledrejection.length)
.withContext('number of unhandledrejection listeners')
.toBeGreaterThan(0);
for (const l of global.listeners_.unhandledrejection) {
l(event);
}
}
});

View File

@@ -185,43 +185,20 @@ describe('QueueRunner', function() {
queueRunner.execute();
});
it('does not log a deprecation', function(done) {
const err = new Error('foo'),
queueableFn1 = {
fn: function() {
return Promise.resolve(err);
}
},
deprecated = jasmine.createSpy('deprecated'),
queueRunner = new jasmineUnderTest.QueueRunner({
queueableFns: [queueableFn1],
deprecated: deprecated,
onComplete: function() {
expect(deprecated).not.toHaveBeenCalled();
done();
}
});
queueRunner.execute();
});
});
describe('and the argument is not an Error', function() {
it('does not log a deprecation or report a failure', function(done) {
it('does not report a failure', function(done) {
const queueableFn1 = {
fn: function() {
return Promise.resolve('not an error');
}
},
failFn = jasmine.createSpy('fail'),
deprecated = jasmine.createSpy('deprecated'),
queueRunner = new jasmineUnderTest.QueueRunner({
queueableFns: [queueableFn1],
deprecated: deprecated,
fail: failFn,
onComplete: function() {
expect(deprecated).not.toHaveBeenCalled();
expect(failFn).not.toHaveBeenCalled();
done();
}
@@ -406,17 +383,12 @@ describe('QueueRunner', function() {
}
},
nextQueueableFn = { fn: jasmine.createSpy('nextFn') },
deprecated = jasmine.createSpy('deprecated'),
queueRunner = new jasmineUnderTest.QueueRunner({
deprecated: deprecated,
queueableFns: [queueableFn, nextQueueableFn]
});
queueRunner.execute();
jasmine.clock().tick(1);
expect(nextQueueableFn.fn.calls.count()).toEqual(1);
// Don't issue a deprecation. The error already tells the user that
// something went wrong.
expect(deprecated).not.toHaveBeenCalled();
});
it('should return a null when you call done', function() {
@@ -651,7 +623,7 @@ describe('QueueRunner', function() {
});
});
it('passes the error instance to exception handlers in HTML browsers', function() {
it('passes final errors to exception handlers', function() {
const error = new Error('fake error'),
onExceptionCallback = jasmine.createSpy('on exception callback'),
queueRunner = new jasmineUnderTest.QueueRunner({
@@ -659,24 +631,11 @@ describe('QueueRunner', function() {
});
queueRunner.execute();
queueRunner.handleFinalError(error.message, 'fake.js', 1, 1, error);
queueRunner.handleFinalError(error);
expect(onExceptionCallback).toHaveBeenCalledWith(error);
});
it('passes the first argument to exception handlers for compatibility', function() {
const error = new Error('fake error'),
onExceptionCallback = jasmine.createSpy('on exception callback'),
queueRunner = new jasmineUnderTest.QueueRunner({
onException: onExceptionCallback
});
queueRunner.execute();
queueRunner.handleFinalError(error.message);
expect(onExceptionCallback).toHaveBeenCalledWith(error.message);
});
it('calls exception handlers when an exception is thrown in a fn', function() {
const queueableFn = {
type: 'queueable',

View File

@@ -175,4 +175,91 @@ describe('SuiteBuilder', function() {
}
};
}
describe('#parallelReset', function() {
it('resets the top suite result', function() {
jasmineUnderTest.Suite.prototype.handleException.and.callThrough();
const env = { configuration: () => ({}) };
const suiteBuilder = new jasmineUnderTest.SuiteBuilder({ env });
suiteBuilder.topSuite.handleException(new Error('nope'));
suiteBuilder.parallelReset();
expect(suiteBuilder.topSuite.result).toEqual({
id: suiteBuilder.topSuite.id,
description: 'Jasmine__TopLevel__Suite',
fullName: '',
failedExpectations: [],
deprecationWarnings: [],
duration: null,
properties: null,
parentSuiteId: null,
filename: undefined
});
});
it('removes children of the top suite', function() {
const env = { configuration: () => ({}) };
const suiteBuilder = new jasmineUnderTest.SuiteBuilder({ env });
suiteBuilder.describe('a suite', function() {
suiteBuilder.it('a nested spec');
});
suiteBuilder.it('a spec');
suiteBuilder.parallelReset();
expect(suiteBuilder.topSuite.children).toEqual([]);
});
it('preserves top suite befores and afters', function() {
const env = { configuration: () => ({}) };
const suiteBuilder = new jasmineUnderTest.SuiteBuilder({ env });
function beforeAll() {}
function beforeEach() {}
function afterEach() {}
function afterAll() {}
suiteBuilder.beforeAll(beforeAll);
suiteBuilder.beforeEach(beforeEach);
suiteBuilder.afterEach(afterEach);
suiteBuilder.afterAll(afterAll);
suiteBuilder.parallelReset();
expect(suiteBuilder.topSuite.beforeAllFns).toEqual([
jasmine.objectContaining({ fn: beforeAll })
]);
expect(suiteBuilder.topSuite.beforeFns).toEqual([
jasmine.objectContaining({ fn: beforeEach })
]);
expect(suiteBuilder.topSuite.afterFns).toEqual([
jasmine.objectContaining({ fn: afterEach })
]);
expect(suiteBuilder.topSuite.afterAllFns).toEqual([
jasmine.objectContaining({ fn: afterAll })
]);
});
it('resets totalSpecsDefined', function() {
const env = { configuration: () => ({}) };
const suiteBuilder = new jasmineUnderTest.SuiteBuilder({ env });
suiteBuilder.it('a spec');
suiteBuilder.parallelReset();
expect(suiteBuilder.totalSpecsDefined).toEqual(0);
});
it('resets focusedRunables', function() {
const env = { configuration: () => ({}) };
const suiteBuilder = new jasmineUnderTest.SuiteBuilder({ env });
suiteBuilder.fit('a spec', function() {});
suiteBuilder.parallelReset();
expect(suiteBuilder.focusedRunables).toEqual([]);
});
});
});

View File

@@ -145,7 +145,7 @@ describe('base helpers', function() {
});
it('returns a promise that resolves to false when the promise is rejected', function() {
const promise = Promise.reject();
const promise = Promise.reject(new Error('nope'));
return expectAsync(jasmineUnderTest.isPending_(promise)).toBeResolvedTo(
false
);

View File

@@ -1,5 +1,6 @@
describe('Env integration', function() {
let env;
const isBrowser = typeof window !== 'undefined';
beforeEach(function() {
jasmine.getEnv().registerIntegrationMatchers();
@@ -455,7 +456,7 @@ describe('Env integration', function() {
env.describe('A suite', function() {
env.it('fails', function(specDone) {
setTimeout(function() {
global.onerror('fail');
dispatchErrorEvent(global, { error: 'fail' });
specDone();
});
});
@@ -509,10 +510,14 @@ describe('Env integration', function() {
},
specDone: function() {
clearStackCallbacks[clearStackCallCount + 1] = function() {
global.onerror('fail at the end of the reporter queue');
dispatchErrorEvent(global, {
error: 'fail at the end of the reporter queue'
});
};
clearStackCallbacks[clearStackCallCount + 2] = function() {
global.onerror('fail at the end of the spec queue');
dispatchErrorEvent(global, {
error: 'fail at the end of the spec queue'
});
};
}
});
@@ -559,7 +564,7 @@ describe('Env integration', function() {
specDone();
queueMicrotask(function() {
queueMicrotask(function() {
global.onerror('fail');
dispatchErrorEvent(global, { error: 'fail' });
});
});
});
@@ -622,10 +627,14 @@ describe('Env integration', function() {
if (result.description === 'A nested suite') {
clearStackCallbacks[clearStackCallCount + 1] = function() {
global.onerror('fail at the end of the reporter queue');
dispatchErrorEvent(global, {
error: 'fail at the end of the reporter queue'
});
};
clearStackCallbacks[clearStackCallCount + 2] = function() {
global.onerror('fail at the end of the suite queue');
dispatchErrorEvent(global, {
error: 'fail at the end of the suite queue'
});
};
}
}
@@ -668,7 +677,7 @@ describe('Env integration', function() {
env.addReporter({
jasmineDone: function() {
global.onerror('a very late error');
dispatchErrorEvent(global, { error: 'a very late error' });
}
});
@@ -720,7 +729,7 @@ describe('Env integration', function() {
expectedErrors.push(`${msg} thrown`);
}
global.onerror(msg);
dispatchErrorEvent(global, { error: msg });
realClearStack(fn);
});
spyOn(console, 'error');
@@ -1879,7 +1888,8 @@ describe('Env integration', function() {
expect(reporter.jasmineStarted).toHaveBeenCalledWith({
totalSpecsDefined: 1,
order: jasmine.any(jasmineUnderTest.Order)
order: jasmine.any(jasmineUnderTest.Order),
parallel: false
});
expect(reporter.specDone).toHaveBeenCalledWith(
@@ -1913,7 +1923,8 @@ describe('Env integration', function() {
expect(reporter.jasmineStarted).toHaveBeenCalledWith({
totalSpecsDefined: 1,
order: jasmine.any(jasmineUnderTest.Order)
order: jasmine.any(jasmineUnderTest.Order),
parallel: false
});
expect(reporter.specDone).toHaveBeenCalledWith(
@@ -1967,7 +1978,8 @@ describe('Env integration', function() {
expect(reporter.jasmineStarted).toHaveBeenCalledWith({
totalSpecsDefined: 6,
order: jasmine.any(jasmineUnderTest.Order)
order: jasmine.any(jasmineUnderTest.Order),
parallel: false
});
expect(reporter.specStarted.calls.count()).toBe(6);
@@ -2304,7 +2316,8 @@ describe('Env integration', function() {
expect(reporter.jasmineStarted).toHaveBeenCalledWith({
totalSpecsDefined: 1,
order: jasmine.any(jasmineUnderTest.Order)
order: jasmine.any(jasmineUnderTest.Order),
parallel: false
});
expect(reporter.specDone).toHaveBeenCalledWith(
@@ -2708,15 +2721,24 @@ describe('Env integration', function() {
);
});
await env.execute();
await jasmine.spyOnGlobalErrorsAsync(async function(globalErrorSpy) {
await env.execute();
if (isBrowser) {
// Verify that there were no unexpected errors
expect(globalErrorSpy).toHaveBeenCalledTimes(2);
expect(globalErrorSpy).toHaveBeenCalledWith(new Error('suite'));
expect(globalErrorSpy).toHaveBeenCalledWith(new Error('spec'));
}
});
expect(reporter.suiteDone).toHaveFailedExpectationsForRunnable(
'async suite',
[/^(((Uncaught )?(exception: )?Error: suite( thrown)?)|(suite thrown))$/]
[/Error: suite/]
);
expect(reporter.specDone).toHaveFailedExpectationsForRunnable(
'suite async spec',
[/^(((Uncaught )?(exception: )?Error: spec( thrown)?)|(spec thrown))$/]
[/Error: spec/]
);
});
@@ -2850,14 +2872,14 @@ describe('Env integration', function() {
]);
env.addReporter(reporter);
global.onerror(
'Uncaught SyntaxError: Unexpected end of input',
'borkenSpec.js',
42,
undefined,
{ stack: 'a stack' }
);
global.onerror('Uncaught Error: ENOCHEESE');
dispatchErrorEvent(global, {
message: 'Uncaught SyntaxError: Unexpected end of input',
error: undefined,
filename: 'borkenSpec.js',
lineno: 42
});
const error = new Error('ENOCHEESE');
dispatchErrorEvent(global, { error });
await env.execute();
@@ -2867,15 +2889,15 @@ describe('Env integration', function() {
passed: false,
globalErrorType: 'load',
message: 'Uncaught SyntaxError: Unexpected end of input',
stack: 'a stack',
stack: undefined,
filename: 'borkenSpec.js',
lineno: 42
},
{
passed: false,
globalErrorType: 'load',
message: 'Uncaught Error: ENOCHEESE',
stack: undefined,
message: 'ENOCHEESE',
stack: error.stack,
filename: undefined,
lineno: undefined
}
@@ -3107,7 +3129,7 @@ describe('Env integration', function() {
env.addReporter(reporter);
env.it('passes', function() {});
global.onerror('Uncaught Error: ENOCHEESE');
dispatchErrorEvent(global, { error: 'ENOCHEESE' });
await env.execute();
expect(reporter.jasmineDone).toHaveBeenCalled();
@@ -3128,6 +3150,7 @@ describe('Env integration', function() {
const e = reporter.jasmineDone.calls.argsFor(0)[0];
expect(e.overallStatus).toEqual('incomplete');
expect(e.incompleteReason).toEqual('No specs found');
expect(e.incompleteCode).toEqual('noSpecsFound');
});
});
@@ -3146,6 +3169,7 @@ describe('Env integration', function() {
const e = reporter.jasmineDone.calls.argsFor(0)[0];
expect(e.overallStatus).toEqual('incomplete');
expect(e.incompleteReason).toEqual('fit() or fdescribe() was found');
expect(e.incompleteCode).toEqual('focused');
});
});
@@ -3166,6 +3190,7 @@ describe('Env integration', function() {
const e = reporter.jasmineDone.calls.argsFor(0)[0];
expect(e.overallStatus).toEqual('incomplete');
expect(e.incompleteReason).toEqual('fit() or fdescribe() was found');
expect(e.incompleteCode).toEqual('focused');
});
});
@@ -3186,6 +3211,7 @@ describe('Env integration', function() {
const e = reporter.jasmineDone.calls.argsFor(0)[0];
expect(e.overallStatus).toEqual('failed');
expect(e.incompleteReason).toBeUndefined();
expect(e.incompleteCode).toBeUndefined();
});
});
});
@@ -3836,16 +3862,6 @@ describe('Env integration', function() {
expect(failedExpectations).toEqual([]);
});
it('calls the optional done callback when finished', function(done) {
const reporter = jasmine.createSpyObj('reporter', ['jasmineDone']);
env.addReporter(reporter);
env.execute(null, function() {
expect(reporter.jasmineDone).toHaveBeenCalled();
done();
});
});
describe('#spyOnGlobalErrorsAsync', function() {
const leftInstalledMessage =
'Global error spy was not uninstalled. ' +
@@ -3886,7 +3902,16 @@ describe('Env integration', function() {
const reporter = jasmine.createSpyObj('reporter', ['specDone']);
env.addReporter(reporter);
await env.execute();
await jasmine.spyOnGlobalErrorsAsync(async function(globalErrorSpy) {
await env.execute();
if (isBrowser) {
// Verify that there were no unexpected errors
expect(globalErrorSpy).toHaveBeenCalledTimes(2);
expect(globalErrorSpy).toHaveBeenCalledWith(new Error('nope'));
expect(globalErrorSpy).toHaveBeenCalledWith(new Error('yep'));
}
});
const passingResult = resultForRunable(
reporter.specDone,
@@ -3933,7 +3958,17 @@ describe('Env integration', function() {
'suiteDone'
]);
env.addReporter(reporter);
await env.execute();
await jasmine.spyOnGlobalErrorsAsync(async function(globalErrorSpy) {
await env.execute();
if (isBrowser) {
// Verify that there were no unexpected errors
expect(globalErrorSpy).toHaveBeenCalledTimes(1);
expect(globalErrorSpy).toHaveBeenCalledWith(
new Error('should fail the spec')
);
}
});
const suiteResult = resultForRunable(reporter.suiteDone, 'Suite 1');
expect(suiteResult.status).toEqual('failed');
@@ -3978,7 +4013,17 @@ describe('Env integration', function() {
'suiteDone'
]);
env.addReporter(reporter);
await env.execute();
await jasmine.spyOnGlobalErrorsAsync(async function(globalErrorSpy) {
await env.execute();
if (isBrowser) {
// Verify that there were no unexpected errors
expect(globalErrorSpy).toHaveBeenCalledTimes(1);
expect(globalErrorSpy).toHaveBeenCalledWith(
new Error('should fail the spec')
);
}
});
expect(reporter.suiteDone).toHaveFailedExpectationsForRunnable(
'Suite 1',
@@ -4028,7 +4073,18 @@ describe('Env integration', function() {
'suiteDone'
]);
env.addReporter(reporter);
await env.execute();
await jasmine.spyOnGlobalErrorsAsync(async function(globalErrorSpy) {
await env.execute();
if (isBrowser) {
// Verify that there were no unexpected errors
expect(globalErrorSpy).toHaveBeenCalledTimes(1);
expect(globalErrorSpy).toHaveBeenCalledWith(
new Error('should fail the spec')
);
}
});
const spec1Result = resultForRunable(reporter.specDone, 'Suite 1 a spec');
expect(spec1Result.status).toEqual('failed');
@@ -4064,7 +4120,17 @@ describe('Env integration', function() {
const reporter = jasmine.createSpyObj('reporter', ['specDone']);
env.addReporter(reporter);
await env.execute();
await jasmine.spyOnGlobalErrorsAsync(async function(globalErrorSpy) {
await env.execute();
if (isBrowser) {
// Verify that there were no unexpected errors
expect(globalErrorSpy).toHaveBeenCalledTimes(1);
expect(globalErrorSpy).toHaveBeenCalledWith(
new Error('should fail the spec')
);
}
});
const spec1Result = resultForRunable(reporter.specDone, 'spec 1');
expect(spec1Result.status).toEqual('failed');
@@ -4109,7 +4175,17 @@ describe('Env integration', function() {
'suiteDone'
]);
env.addReporter(reporter);
await env.execute();
await jasmine.spyOnGlobalErrorsAsync(async function(globalErrorSpy) {
await env.execute();
if (isBrowser) {
// Verify that there were no unexpected errors
expect(globalErrorSpy).toHaveBeenCalledTimes(1);
expect(globalErrorSpy).toHaveBeenCalledWith(
new Error('should fail the spec')
);
}
});
const spec1Result = resultForRunable(reporter.specDone, 'Suite 1 a spec');
expect(spec1Result.status).toEqual('failed');
@@ -4260,8 +4336,25 @@ describe('Env integration', function() {
function browserEventMethods() {
return {
addEventListener() {},
removeEventListener() {}
listeners_: { error: [], unhandledrejection: [] },
addEventListener(eventName, listener) {
this.listeners_[eventName].push(listener);
},
removeEventListener(eventName, listener) {
this.listeners_[eventName] = this.listeners_[eventName].filter(
l => l !== listener
);
}
};
}
function dispatchErrorEvent(global, event) {
expect(global.listeners_.error.length)
.withContext('number of error listeners')
.toBeGreaterThan(0);
for (const l of global.listeners_.error) {
l(event);
}
}
});

View File

@@ -349,7 +349,7 @@ describe('Matchers (Integration)', function() {
});
verifyFailsAsync(function(env) {
return env.expectAsync(Promise.reject()).toBeResolved();
return env.expectAsync(Promise.reject(new Error('nope'))).toBeResolved();
});
});

View File

@@ -0,0 +1,162 @@
describe('Support for parallel execution', function() {
let env;
beforeEach(function() {
env = new jasmineUnderTest.Env();
});
afterEach(function() {
env.cleanup_();
});
it('removes specs and suites from previous batches', async function() {
env.describe('a suite', function() {
env.it('a spec', function() {});
});
env.it('a spec', function() {});
await env.execute();
env.parallelReset();
env.describe('a suite in a new batch', function() {
env.it('a spec in a new batch', function() {});
});
const reporter = jasmine.createSpyObj('reporter', [
'suiteDone',
'specDone'
]);
env.addReporter(reporter);
await env.execute();
expect(reporter.suiteDone).toHaveBeenCalledOnceWith(
jasmine.objectContaining({
fullName: 'a suite in a new batch'
})
);
expect(reporter.specDone).toHaveBeenCalledOnceWith(
jasmine.objectContaining({
fullName: 'a suite in a new batch a spec in a new batch'
})
);
});
it('preserves top-level before and after fns from previous batches', async function() {
const beforeAll = jasmine.createSpy('beforeAll');
const beforeEach = jasmine.createSpy('beforeEach');
const afterEach = jasmine.createSpy('afterEach');
const afterAll = jasmine.createSpy('afterAll');
env.beforeAll(beforeAll);
env.beforeEach(beforeEach);
env.afterEach(afterEach);
env.afterAll(afterAll);
env.parallelReset();
env.it('a spec', function() {});
await env.execute();
expect(beforeAll).toHaveBeenCalled();
expect(beforeEach).toHaveBeenCalled();
expect(afterEach).toHaveBeenCalled();
expect(afterAll).toHaveBeenCalled();
});
it('does not remember focused runables from previous batches', async function() {
env.fit('a focused spec', function() {});
env.parallelReset();
env.it('a spec', function() {});
const reporter = jasmine.createSpyObj('reporter', [
'specDone',
'jasmineDone'
]);
env.addReporter(reporter);
await env.execute();
expect(reporter.specDone).toHaveBeenCalledOnceWith(
jasmine.objectContaining({
fullName: 'a spec',
status: 'passed'
})
);
expect(reporter.jasmineDone).toHaveBeenCalledWith(
jasmine.objectContaining({ overallStatus: 'passed' })
);
});
it('does not remember failures from previous batches', async function() {
env.it('a failing spec', function() {
env.expect(true).toBe(false);
});
await env.execute();
env.parallelReset();
env.it('a spec', function() {});
const reporter = jasmine.createSpyObj('reporter', [
'specDone',
'jasmineDone'
]);
env.addReporter(reporter);
await env.execute();
expect(reporter.jasmineDone).toHaveBeenCalledWith(
jasmine.objectContaining({ overallStatus: 'passed' })
);
});
it('reports errors thrown from describe', async function() {
const reporter = jasmine.createSpyObj('reporter', ['suiteDone']);
env.addReporter(reporter);
env.describe('borken', function() {
throw new Error('nope');
});
await env.execute();
expect(reporter.suiteDone).toHaveBeenCalledWith(
jasmine.objectContaining({
description: 'borken',
status: 'failed',
failedExpectations: [
jasmine.objectContaining({
message: jasmine.stringContaining('Error: nope')
})
]
})
);
// Errors in subsequent suites should also be reported
reporter.suiteDone.calls.reset();
env.parallelReset();
env.describe('zarro boogs', function() {
throw new Error('nor that either');
});
await env.execute();
expect(reporter.suiteDone).toHaveBeenCalledOnceWith(
jasmine.objectContaining({
description: 'zarro boogs',
status: 'failed',
failedExpectations: [
jasmine.objectContaining({
message: jasmine.stringContaining('Error: nor that either')
})
]
})
);
// Failure state should not persist across resets
reporter.suiteDone.calls.reset();
env.parallelReset();
env.describe('actually works', function() {
env.it('a spec', function() {});
});
await env.execute();
expect(reporter.suiteDone).toHaveBeenCalledOnceWith(
jasmine.objectContaining({
description: 'actually works',
status: 'passed',
failedExpectations: []
})
);
});
});

View File

@@ -626,7 +626,7 @@ describe('spec running', function() {
expect(actions).toEqual(['spec2', 'spec3', 'spec1']);
});
it('refuses to re-enter suites with a beforeAll', function() {
it('refuses to re-enter suites with a beforeAll', async function() {
const actions = [];
let spec1;
let spec2;
@@ -648,13 +648,12 @@ describe('spec running', function() {
actions.push('spec3');
});
expect(function() {
env.execute([spec2.id, spec3.id, spec1.id]);
}).toThrowError(/beforeAll/);
const promise = env.execute([spec2.id, spec3.id, spec1.id]);
await expectAsync(promise).toBeRejectedWithError(/beforeAll/);
expect(actions).toEqual([]);
});
it('refuses to re-enter suites with a afterAll', function() {
it('refuses to re-enter suites with a afterAll', async function() {
const actions = [];
let spec1;
let spec2;
@@ -676,9 +675,8 @@ describe('spec running', function() {
actions.push('spec3');
});
expect(function() {
env.execute([spec2.id, spec3.id, spec1.id]);
}).toThrowError(/afterAll/);
const promise = env.execute([spec2.id, spec3.id, spec1.id]);
await expectAsync(promise).toBeRejectedWithError(/afterAll/);
expect(actions).toEqual([]);
});

View File

@@ -78,15 +78,10 @@ describe('npm package', function() {
it('has bootFiles', function() {
expect(this.packagedCore.files.bootFiles).toEqual(['boot0.js', 'boot1.js']);
expect(this.packagedCore.files.nodeBootFiles).toEqual(['node_boot.js']);
for (const fileName of this.packagedCore.files.bootFiles) {
expect(fileName).toExistInPath(this.packagedCore.files.bootDir);
}
for (const fileName of this.packagedCore.files.nodeBootFiles) {
expect(fileName).toExistInPath(this.packagedCore.files.bootDir);
}
});
it('has an imagesDir', function() {

View File

@@ -1,16 +0,0 @@
module.exports = function(jasmineRequire) {
const jasmine = jasmineRequire.core(jasmineRequire);
const env = jasmine.getEnv({ suppressLoadErrors: true });
const jasmineInterface = jasmineRequire.interface(jasmine, env);
extend(global, jasmineInterface);
function extend(destination, source) {
for (const property in source) destination[property] = source[property];
return destination;
}
return jasmine;
};

View File

@@ -46,6 +46,7 @@ getJasmineRequireObj().Env = function(j$) {
let reporter;
let topSuite;
let runner;
let parallelLoadingState = null; // 'specs', 'helpers', or null for non-parallel
/**
* This represents the available options to configure Jasmine.
@@ -74,6 +75,10 @@ getJasmineRequireObj().Env = function(j$) {
seed: null,
/**
* Whether to stop execution of the suite after the first spec failure
*
* <p>In parallel mode, `stopOnSpecFailure` works on a "best effort"
* basis. Jasmine will stop execution as soon as practical after a failure
* but it might not be immediate.</p>
* @name Configuration#stopOnSpecFailure
* @since 3.9.0
* @type Boolean
@@ -149,20 +154,14 @@ getJasmineRequireObj().Env = function(j$) {
if (!options.suppressLoadErrors) {
installGlobalErrors();
globalErrors.pushListener(function loadtimeErrorHandler(
message,
filename,
lineno,
colNo,
err
) {
globalErrors.pushListener(function loadtimeErrorHandler(error, event) {
topSuite.result.failedExpectations.push({
passed: false,
globalErrorType: 'load',
message: message,
stack: err && err.stack,
filename: filename,
lineno: lineno
message: error ? error.message : event.message,
stack: error && error.stack,
filename: event && event.filename,
lineno: event && event.lineno
});
});
}
@@ -380,7 +379,6 @@ getJasmineRequireObj().Env = function(j$) {
function(e) {
(runner.currentRunable() || topSuite).handleException(e);
};
options.deprecated = self.deprecated;
new j$.QueueRunner(options).execute();
}
@@ -406,6 +404,7 @@ getJasmineRequireObj().Env = function(j$) {
* @since 2.0.0
*/
this.topSuite = function() {
ensureNonParallel('topSuite');
return topSuite.metadata;
};
@@ -499,21 +498,25 @@ getJasmineRequireObj().Env = function(j$) {
reportSpecDone
});
this.setParallelLoadingState = function(state) {
parallelLoadingState = state;
};
this.parallelReset = function() {
// TODO: ensure that autoCleanClosures was false
suiteBuilder.parallelReset();
runner.parallelReset();
};
/**
* Executes the specs.
*
* If called with no parameters or with a falsy value as the first parameter,
* If called with no parameter or with a falsy parameter,
* all specs will be executed except those that are excluded by a
* [spec filter]{@link Configuration#specFilter} or other mechanism. If the
* first parameter is a list of spec/suite IDs, only those specs/suites will
* parameter is a list of spec/suite IDs, only those specs/suites will
* be run.
*
* Both parameters are optional, but a completion callback is only valid as
* the second parameter. To specify a completion callback but not a list of
* specs/suites to run, pass null or undefined as the first parameter. The
* completion callback is supported for backward compatibility. In most
* cases it will be more convenient to use the returned promise instead.
*
* execute should not be called more than once unless the env has been
* configured with `{autoCleanClosures: false}`.
*
@@ -521,25 +524,26 @@ getJasmineRequireObj().Env = function(j$) {
* {@link JasmineDoneInfo|overall result} that's passed to a reporter's
* `jasmineDone` method, even if the suite did not pass. To determine
* whether the suite passed, check the value that the promise resolves to
* or use a {@link Reporter}.
* or use a {@link Reporter}. The promise will be rejected in the case of
* certain serious errors that prevent execution from starting.
*
* @name Env#execute
* @since 2.0.0
* @function
* @async
* @param {(string[])=} runablesToRun IDs of suites and/or specs to run
* @param {Function=} onComplete Function that will be called after all specs have run
* @return {Promise<JasmineDoneInfo>}
*/
this.execute = function(runablesToRun, onComplete) {
this.execute = async function(runablesToRun) {
installGlobalErrors();
return runner.execute(runablesToRun).then(function(jasmineDoneInfo) {
if (onComplete) {
onComplete();
}
if (parallelLoadingState) {
validateConfigForParallel();
}
return jasmineDoneInfo;
});
const result = await runner.execute(runablesToRun);
this.cleanup_();
return result;
};
/**
@@ -551,6 +555,10 @@ getJasmineRequireObj().Env = function(j$) {
* @see custom_reporter
*/
this.addReporter = function(reporterToAdd) {
if (parallelLoadingState) {
throw new Error('Reporters cannot be added via Env in parallel mode');
}
reporter.addReporter(reporterToAdd);
};
@@ -573,6 +581,10 @@ getJasmineRequireObj().Env = function(j$) {
* @function
*/
this.clearReporters = function() {
if (parallelLoadingState) {
throw new Error('Reporters cannot be removed via Env in parallel mode');
}
reporter.clearReporters();
};
@@ -672,6 +684,38 @@ getJasmineRequireObj().Env = function(j$) {
}
}
function ensureNonParallel(method) {
if (parallelLoadingState) {
throw new Error(`'${method}' is not available in parallel mode`);
}
}
function ensureNonParallelOrInDescribe(msg) {
if (parallelLoadingState && !suiteBuilder.inDescribe()) {
throw new Error(msg);
}
}
function ensureNonParallelOrInHelperOrInDescribe(method) {
if (parallelLoadingState === 'specs' && !suiteBuilder.inDescribe()) {
throw new Error(
'In parallel mode, ' +
method +
' must be in a describe block or in a helper file'
);
}
}
function validateConfigForParallel() {
if (!config.random) {
throw new Error('Randomization cannot be disabled in parallel mode');
}
if (config.seed !== null && config.seed !== undefined) {
throw new Error('Random seed cannot be set in parallel mode');
}
}
this.describe = function(description, definitionFn) {
ensureIsNotNested('describe');
const filename = callerCallerFilename();
@@ -688,6 +732,7 @@ getJasmineRequireObj().Env = function(j$) {
this.fdescribe = function(description, definitionFn) {
ensureIsNotNested('fdescribe');
ensureNonParallel('fdescribe');
const filename = callerCallerFilename();
return suiteBuilder.fdescribe(description, definitionFn, filename)
.metadata;
@@ -729,6 +774,7 @@ getJasmineRequireObj().Env = function(j$) {
this.fit = function(description, fn, timeout) {
ensureIsNotNested('fit');
ensureNonParallel('fit');
const filename = callerCallerFilename();
return suiteBuilder.fit(description, fn, timeout, filename).metadata;
};
@@ -802,21 +848,37 @@ getJasmineRequireObj().Env = function(j$) {
this.beforeEach = function(beforeEachFunction, timeout) {
ensureIsNotNested('beforeEach');
ensureNonParallelOrInHelperOrInDescribe('beforeEach');
suiteBuilder.beforeEach(beforeEachFunction, timeout);
};
this.beforeAll = function(beforeAllFunction, timeout) {
ensureIsNotNested('beforeAll');
// This message is -npm-specific, but currently parallel operation is
// only supported via -npm.
ensureNonParallelOrInDescribe(
"In parallel mode, 'beforeAll' " +
'must be in a describe block. Use the globalSetup config ' +
'property for exactly-once setup in parallel mode.'
);
suiteBuilder.beforeAll(beforeAllFunction, timeout);
};
this.afterEach = function(afterEachFunction, timeout) {
ensureIsNotNested('afterEach');
ensureNonParallelOrInHelperOrInDescribe('afterEach');
suiteBuilder.afterEach(afterEachFunction, timeout);
};
this.afterAll = function(afterAllFunction, timeout) {
ensureIsNotNested('afterAll');
// This message is -npm-specific, but currently parallel operation is
// only supported via -npm.
ensureNonParallelOrInDescribe(
"In parallel mode, 'afterAll' " +
'must be in a describe block. Use the globalTeardown config ' +
'property for exactly-once teardown in parallel mode.'
);
suiteBuilder.afterAll(afterAllFunction, timeout);
};
@@ -871,7 +933,10 @@ getJasmineRequireObj().Env = function(j$) {
}
function callerCallerFilename() {
return new j$.StackTrace(new Error()).frames[3].file;
const frames = new j$.StackTrace(new Error()).frames;
// frames[3] should always exist except in Jasmine's own tests, which bypass
// the global it/describe layer, but don't crash if it doesn't.
return frames[3] && frames[3].file;
}
return Env;

View File

@@ -6,18 +6,22 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
let overrideHandler = null,
onRemoveOverrideHandler = null;
function onerror(message, source, lineno, colno, error) {
function onBrowserError(event) {
dispatchBrowserError(event.error, event);
}
function dispatchBrowserError(error, event) {
if (overrideHandler) {
overrideHandler(error || message);
overrideHandler(error);
return;
}
const handler = handlers[handlers.length - 1];
if (handler) {
handler.apply(null, Array.prototype.slice.call(arguments, 0));
handler(error, event);
} else {
throw arguments[0];
throw error;
}
}
@@ -94,8 +98,7 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
this.installOne_('uncaughtException', 'Uncaught exception');
this.installOne_('unhandledRejection', 'Unhandled promise rejection');
} else {
const originalHandler = global.onerror;
global.onerror = onerror;
global.addEventListener('error', onBrowserError);
const browserRejectionHandler = function browserRejectionHandler(
event
@@ -103,16 +106,19 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
if (j$.isError_(event.reason)) {
event.reason.jasmineMessage =
'Unhandled promise rejection: ' + event.reason;
global.onerror(event.reason);
dispatchBrowserError(event.reason, event);
} else {
global.onerror('Unhandled promise rejection: ' + event.reason);
dispatchBrowserError(
'Unhandled promise rejection: ' + event.reason,
event
);
}
};
global.addEventListener('unhandledrejection', browserRejectionHandler);
this.uninstall = function uninstall() {
global.onerror = originalHandler;
global.removeEventListener('error', onBrowserError);
global.removeEventListener(
'unhandledrejection',
browserRejectionHandler
@@ -121,6 +127,13 @@ getJasmineRequireObj().GlobalErrors = function(j$) {
}
};
// The listener at the top of the stack will be called with two arguments:
// the error and the event. Either of them may be falsy.
// The error will normally be provided, but will be falsy in the case of
// some browser load-time errors. The event will normally be provided in
// browsers but will be falsy in Node.
// Listeners that are pushed after spec files have been loaded should be
// able to just use the error parameter.
this.pushListener = function pushListener(listener) {
handlers.push(listener);
};

View File

@@ -1,4 +1,13 @@
getJasmineRequireObj().QueueRunner = function(j$) {
/*
QueueRunner isn't part of the public interface, but it is used by
jasmine-npm. -core and -npm version in lockstep at the major and minor
levels but version independently at the patch level. Any changes that
would break -npm should be done in a major or minor release, never a
patch release, and accompanied by a change to -npm that's released in
the same version.
*/
let nextid = 1;
function StopExecutionError() {}
@@ -62,15 +71,11 @@ getJasmineRequireObj().QueueRunner = function(j$) {
if (typeof this.onComplete !== 'function') {
throw new Error('invalid onComplete ' + JSON.stringify(this.onComplete));
}
this.deprecated = attrs.deprecated;
}
QueueRunner.prototype.execute = function() {
this.handleFinalError = (message, source, lineno, colno, error) => {
// Older browsers would send the error as the first parameter. HTML5
// specifies the the five parameters above. The error instance should
// be preffered, otherwise the call stack would get lost.
this.onException(error || message);
this.handleFinalError = error => {
this.onException(error);
};
this.globalErrors.pushListener(this.handleFinalError);
this.run(0);

View File

@@ -1,4 +1,13 @@
getJasmineRequireObj().ReportDispatcher = function(j$) {
/*
ReportDispatcher isn't part of the public interface, but it is used by
jasmine-npm. -core and -npm version in lockstep at the major and minor
levels but version independently at the patch level. Any changes that
would break -npm should be done in a major or minor release, never a
patch release, and accompanied by a change to -npm that's released in
the same version.
*/
function ReportDispatcher(methods, queueRunnerFactory, onLateError) {
const dispatchedMethods = methods || [];

View File

@@ -2,6 +2,7 @@ getJasmineRequireObj().Runner = function(j$) {
class Runner {
constructor(options) {
this.topSuite_ = options.topSuite;
// TODO use names that read like getters
this.totalSpecsDefined_ = options.totalSpecsDefined;
this.focusedRunables_ = options.focusedRunables;
this.runableResources_ = options.runableResources;
@@ -26,11 +27,11 @@ getJasmineRequireObj().Runner = function(j$) {
];
}
// Although execute returns a promise, it isn't async for backwards
// compatibility: The "Invalid order" exception needs to be propagated
// synchronously from Env#execute.
// TODO: make this and Env#execute async in the next major release
execute(runablesToRun) {
parallelReset() {
this.executedBefore_ = false;
}
async execute(runablesToRun) {
if (this.executedBefore_) {
this.topSuite_.reset();
}
@@ -126,13 +127,17 @@ getJasmineRequireObj().Runner = function(j$) {
/**
* Information passed to the {@link Reporter#jasmineStarted} event.
* @typedef JasmineStartedInfo
* @property {Int} totalSpecsDefined - The total number of specs defined in this suite.
* @property {Order} order - Information about the ordering (random or not) of this execution of the suite.
* @property {Int} totalSpecsDefined - The total number of specs defined in this suite. Note that this property is not present when Jasmine is run in parallel mode.
* @property {Order} order - Information about the ordering (random or not) of this execution of the suite. Note that this property is not present when Jasmine is run in parallel mode.
* @property {Boolean} parallel - Whether Jasmine is being run in parallel mode.
* @since 2.0.0
*/
await this.reporter_.jasmineStarted({
// In parallel mode, the jasmineStarted event is separately dispatched
// by jasmine-npm. This event only reaches reporters in non-parallel.
totalSpecsDefined,
order: order
order: order,
parallel: false
});
this.currentlyExecutingSuites_.push(this.topSuite_);
@@ -144,7 +149,7 @@ getJasmineRequireObj().Runner = function(j$) {
this.runableResources_.clearForRunable(this.topSuite_.id);
this.currentlyExecutingSuites_.pop();
let overallStatus, incompleteReason;
let overallStatus, incompleteReason, incompleteCode;
if (
this.hasFailures ||
@@ -154,9 +159,11 @@ getJasmineRequireObj().Runner = function(j$) {
} else if (this.focusedRunables_().length > 0) {
overallStatus = 'incomplete';
incompleteReason = 'fit() or fdescribe() was found';
incompleteCode = 'focused';
} else if (totalSpecsDefined === 0) {
overallStatus = 'incomplete';
incompleteReason = 'No specs found';
incompleteCode = 'noSpecsFound';
} else {
overallStatus = 'passed';
}
@@ -166,8 +173,10 @@ getJasmineRequireObj().Runner = function(j$) {
* @typedef JasmineDoneInfo
* @property {OverallStatus} overallStatus - The overall result of the suite: 'passed', 'failed', or 'incomplete'.
* @property {Int} totalTime - The total time (in ms) that it took to execute the suite
* @property {IncompleteReason} incompleteReason - Explanation of why the suite was incomplete.
* @property {Order} order - Information about the ordering (random or not) of this execution of the suite.
* @property {String} incompleteReason - Human-readable explanation of why the suite was incomplete.
* @property {String} incompleteCode - Machine-readable explanation of why the suite was incomplete: 'focused', 'noSpecsFound', or undefined.
* @property {Order} order - Information about the ordering (random or not) of this execution of the suite. Note that this property is not present when Jasmine is run in parallel mode.
* @property {Int} numWorkers - Number of parallel workers. Note that this property is only present when Jasmine is run in parallel mode.
* @property {Expectation[]} failedExpectations - List of expectations that failed in an {@link afterAll} at the global level.
* @property {Expectation[]} deprecationWarnings - List of deprecation warnings that occurred at the global level.
* @since 2.4.0
@@ -176,6 +185,7 @@ getJasmineRequireObj().Runner = function(j$) {
overallStatus: overallStatus,
totalTime: jasmineTimer.elapsed(),
incompleteReason: incompleteReason,
incompleteCode: incompleteCode,
order: order,
failedExpectations: this.topSuite_.result.failedExpectations,
deprecationWarnings: this.topSuite_.result.deprecationWarnings

View File

@@ -138,6 +138,10 @@ getJasmineRequireObj().Suite = function(j$) {
this.reportedDone = false;
};
Suite.prototype.removeChildren = function() {
this.children = [];
};
Suite.prototype.addChild = function(child) {
this.children.push(child);
};

View File

@@ -22,6 +22,17 @@ getJasmineRequireObj().SuiteBuilder = function(j$) {
this.focusedRunables = [];
}
inDescribe() {
return this.currentDeclarationSuite_ !== this.topSuite;
}
parallelReset() {
this.topSuite.removeChildren();
this.topSuite.reset();
this.totalSpecsDefined = 0;
this.focusedRunables = [];
}
describe(description, definitionFn, filename) {
ensureIsFunction(definitionFn, 'describe');
const suite = this.suiteFactory_(description, filename);

View File

@@ -1,4 +1,13 @@
getJasmineRequireObj().Timer = function() {
/*
Timer isn't part of the public interface, but it is used by
jasmine-npm. -core and -npm version in lockstep at the major and minor
levels but version independently at the patch level. Any changes that
would break -npm should be done in a major or minor release, never a
patch release, and accompanied by a change to -npm that's released in
the same version.
*/
const defaultNow = (function(Date) {
return function() {
return new Date().getTime();