Compare commits

...

11 Commits

Author SHA1 Message Date
Steve Gravrock
73f8e001ad Bump version to 5.0.0-beta-0 2023-04-29 10:10:05 -07:00
Steve Gravrock
390cc45af2 Dropped support for Node 16
Node 16 will reach EOL no later than a few months after Jasmine 5 is
released. Experience with Node 12 and Node 14 has shown that our
dependencies, especially dev dependencies, move on from past-EOL Node
versions fairly quickly. That can make it difficult to continue supporting
them. Since long term support for past EOL Node versions is a non-goal and
many users expect that Node versions will only be dropped in major
releases, it's better to drop it in 5.0.
2023-04-27 19:32:18 -07:00
Steve Gravrock
33118ac6e2 Merge branch 'main' into 5.0 2023-04-27 19:22:30 -07:00
Steve Gravrock
31ff9a300c Added Node 20 to supported environments 2023-04-22 08:12:49 -07:00
Steve Gravrock
5cc739d879 Bump version to 5.0.0-alpha.1 2023-04-08 13:02:21 -07:00
Steve Gravrock
1e7f07259e API docs for parallel support things that jasmine-npm uses 2023-04-08 12:37:40 -07:00
Steve Gravrock
c36a5cfd96 Parallel: Cleaner interface for reporter dispatching
This gets jasmine-npm out of having to deal with QueueRunner, GlobalErrors,
and ReportDispatcher directly.
2023-04-08 11:41:15 -07:00
Steve Gravrock
299fd1f770 Removed unnecessary TODO 2023-03-25 11:21:04 -07:00
Steve Gravrock
656427d328 Parallel: Disallow calls to Env#config from spec and helper files
Such configuration changes only affect one worker, which is almost certainly
not the intent.
2023-03-25 11:16:54 -07:00
Steve Gravrock
621522fdd4 Updated Glob 2023-03-21 22:35:15 -07:00
Steve Gravrock
6e3589bf52 Updated most dev dependencies 2023-03-21 22:33:24 -07:00
16 changed files with 715 additions and 252 deletions

View File

@@ -4,16 +4,14 @@
version: 2.1
executors:
node20:
docker:
- image: cimg/node:20.0.0
working_directory: ~/workspace
node18:
docker:
- image: cimg/node:18.0.0
working_directory: ~/workspace
node16:
docker:
# Oldest version with reliable support for error cause property,
# which jasmine-npm uses.
- image: cimg/node:16.14.0
working_directory: ~/workspace
jobs:
build:
@@ -62,7 +60,7 @@ jobs:
command: npx grunt execSpecsInParallel
test_browsers: &test_browsers
executor: node16
executor: node18
steps:
- attach_workspace:
at: .
@@ -96,35 +94,35 @@ workflows:
push:
jobs:
- build:
executor: node20
name: build_node_20
- build:
executor: node18
name: build_node_18
- build:
executor: node16
name: build_node_16
- test_node:
executor: node20
name: test_node_20
requires:
- build_node_20
- test_node:
executor: node18
name: test_node_18
requires:
- build_node_18
- test_node:
executor: node16
name: test_node_16
requires:
- build_node_16
- test_parallel:
executor: node16
name: test_parallel_node_16
requires:
- build_node_16
- build_node_18
- test_parallel:
executor: node18
name: test_parallel_node_18
requires:
- build_node_18
- test_parallel:
executor: node20
name: test_parallel_node_20
requires:
- build_node_20
- test_browsers:
requires:
- build_node_16
- build_node_18
filters:
branches:
ignore: /pull\/.*/ # Don't run on pull requests.

View File

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

View File

@@ -89,7 +89,9 @@ var getJasmineRequireObj = (function(jasmineGlobal) {
j$.CompleteOnFirstErrorSkipPolicy = jRequire.CompleteOnFirstErrorSkipPolicy(
j$
);
j$.reporterEvents = jRequire.reporterEvents(j$);
j$.ReportDispatcher = jRequire.ReportDispatcher(j$);
j$.ParallelReportDispatcher = jRequire.ParallelReportDispatcher(j$);
j$.RunableResources = jRequire.RunableResources(j$);
j$.Runner = jRequire.Runner(j$);
j$.Spec = jRequire.Spec(j$);
@@ -1309,6 +1311,12 @@ getJasmineRequireObj().Env = function(j$) {
* @function
*/
this.configure = function(configuration) {
if (parallelLoadingState) {
throw new Error(
'Jasmine cannot be configured via Env in parallel mode'
);
}
const booleanProps = [
'random',
'failSpecWithNoExpectations',
@@ -1549,72 +1557,7 @@ getJasmineRequireObj().Env = function(j$) {
* @see custom_reporter
*/
reporter = new j$.ReportDispatcher(
[
/**
* `jasmineStarted` is called after all of the specs have been loaded, but just before execution starts.
* @function
* @name Reporter#jasmineStarted
* @param {JasmineStartedInfo} suiteInfo Information about the full Jasmine suite that is being run
* @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on.
* @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion.
* @see async
*/
'jasmineStarted',
/**
* When the entire suite has finished execution `jasmineDone` is called
* @function
* @name Reporter#jasmineDone
* @param {JasmineDoneInfo} suiteInfo Information about the full Jasmine suite that just finished running.
* @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on.
* @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion.
* @see async
*/
'jasmineDone',
/**
* `suiteStarted` is invoked when a `describe` starts to run
* @function
* @name Reporter#suiteStarted
* @param {SuiteResult} result Information about the individual {@link describe} being run
* @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on.
* @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion.
* @see async
*/
'suiteStarted',
/**
* `suiteDone` is invoked when all of the child specs and suites for a given suite have been run
*
* While jasmine doesn't require any specific functions, not defining a `suiteDone` will make it impossible for a reporter to know when a suite has failures in an `afterAll`.
* @function
* @name Reporter#suiteDone
* @param {SuiteResult} result
* @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on.
* @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion.
* @see async
*/
'suiteDone',
/**
* `specStarted` is invoked when an `it` starts to run (including associated `beforeEach` functions)
* @function
* @name Reporter#specStarted
* @param {SpecResult} result Information about the individual {@link it} being run
* @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on.
* @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion.
* @see async
*/
'specStarted',
/**
* `specDone` is invoked when an `it` and its associated `beforeEach` and `afterEach` functions have been run.
*
* While jasmine doesn't require any specific functions, not defining a `specDone` will make it impossible for a reporter to know when a spec has failed.
* @function
* @name Reporter#specDone
* @param {SpecResult} result
* @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on.
* @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion.
* @see async
*/
'specDone'
],
j$.reporterEvents,
function(options) {
options.SkipPolicy = j$.NeverSkipPolicy;
return queueRunnerFactory(options);
@@ -1638,7 +1581,6 @@ getJasmineRequireObj().Env = function(j$) {
};
this.parallelReset = function() {
// TODO: ensure that autoCleanClosures was false
suiteBuilder.parallelReset();
runner.parallelReset();
};
@@ -7167,6 +7109,100 @@ getJasmineRequireObj().NeverSkipPolicy = function(j$) {
return NeverSkipPolicy;
};
getJasmineRequireObj().ParallelReportDispatcher = function(j$) {
/**
* @class ParallelReportDispatcher
* @implements Reporter
* @classdesc A report dispatcher packaged for convenient use from outside jasmine-core.
*
* This is intended to help packages like `jasmine` (the Jasmine runner for
* Node.js) do their own report dispatching in order to support parallel
* execution. If you aren't implementing a runner package that supports
* parallel execution, this class probably isn't what you're looking for.
*
* Warning: Do not use ParallelReportDispatcher in the same process that
* Jasmine specs run in. Doing so will break Jasmine's error handling.
* @param onError {function} Function called when an unhandled exception, unhandled promise rejection, or explicit reporter failure occurs
*/
function ParallelReportDispatcher(onError, deps = {}) {
const ReportDispatcher = deps.ReportDispatcher || j$.ReportDispatcher;
const QueueRunner = deps.QueueRunner || j$.QueueRunner;
const globalErrors = deps.globalErrors || new j$.GlobalErrors();
const dispatcher = ReportDispatcher(
j$.reporterEvents,
function(queueRunnerOptions) {
queueRunnerOptions = {
...queueRunnerOptions,
globalErrors,
timeout: { setTimeout, clearTimeout },
fail: function(error) {
// A callback-style async reporter called either done.fail()
// or done(anError).
if (!error) {
error = new Error('A reporter called done.fail()');
}
onError(error);
},
onException: function(error) {
// A reporter method threw an exception or returned a rejected
// promise, or there was an unhandled exception or unhandled promise
// rejection while an asynchronous reporter method was running.
onError(error);
}
};
new QueueRunner(queueRunnerOptions).execute();
},
function(error) {
// A reporter called done() more than once.
onError(error);
}
);
const self = {
/**
* Adds a reporter to the list of reporters that events will be dispatched to.
* @function
* @name ParallelReportDispatcher#addReporter
* @param {Reporter} reporterToAdd The reporter to be added.
* @see custom_reporter
*/
addReporter: dispatcher.addReporter.bind(dispatcher),
/**
* Clears all registered reporters.
* @function
* @name ParallelReportDispatcher#clearReporters
*/
clearReporters: dispatcher.clearReporters.bind(dispatcher),
/**
* Installs a global error handler. After this method is called, any
* unhandled exceptions or unhandled promise rejections will be passed to
* the onError callback that was passed to the constructor.
* @function
* @name ParallelReportDispatcher#installGlobalErrors
*/
installGlobalErrors: globalErrors.install.bind(globalErrors),
/**
* Uninstalls the global error handler.
* @function
* @name ParallelReportDispatcher#uninstallGlobalErrors
*/
uninstallGlobalErrors: function() {
// late-bind uninstall because it doesn't exist until install is called
globalErrors.uninstall(globalErrors);
}
};
for (const eventName of j$.reporterEvents) {
self[eventName] = dispatcher[eventName].bind(dispatcher);
}
return self;
}
return ParallelReportDispatcher;
};
getJasmineRequireObj().makePrettyPrinter = function(j$) {
class SinglePrettyPrintRun {
constructor(customObjectFormatters, pp) {
@@ -7528,15 +7564,6 @@ 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() {}
@@ -7793,17 +7820,21 @@ getJasmineRequireObj().QueueRunner = function(j$) {
// on the stack at this point.
if (j$.isAsyncFunction_(fn)) {
this.onException(
'An asynchronous before/it/after ' +
'function was defined with the async keyword but also took a ' +
'done callback. Either remove the done callback (recommended) or ' +
'remove the async keyword.'
new Error(
'An asynchronous before/it/after ' +
'function was defined with the async keyword but also took a ' +
'done callback. Either remove the done callback (recommended) or ' +
'remove the async keyword.'
)
);
} else {
this.onException(
'An asynchronous before/it/after ' +
'function took a done callback but also returned a promise. ' +
'Either remove the done callback (recommended) or change the ' +
'function to not return a promise.'
new Error(
'An asynchronous before/it/after ' +
'function took a done callback but also returned a promise. ' +
'Either remove the done callback (recommended) or change the ' +
'function to not return a promise.'
)
);
}
}
@@ -7823,15 +7854,6 @@ 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 || [];
@@ -7912,6 +7934,77 @@ getJasmineRequireObj().ReportDispatcher = function(j$) {
return ReportDispatcher;
};
getJasmineRequireObj().reporterEvents = function() {
const events = [
/**
* `jasmineStarted` is called after all of the specs have been loaded, but just before execution starts.
* @function
* @name Reporter#jasmineStarted
* @param {JasmineStartedInfo} suiteInfo Information about the full Jasmine suite that is being run
* @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on.
* @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion.
* @see async
*/
'jasmineStarted',
/**
* When the entire suite has finished execution `jasmineDone` is called
* @function
* @name Reporter#jasmineDone
* @param {JasmineDoneInfo} suiteInfo Information about the full Jasmine suite that just finished running.
* @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on.
* @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion.
* @see async
*/
'jasmineDone',
/**
* `suiteStarted` is invoked when a `describe` starts to run
* @function
* @name Reporter#suiteStarted
* @param {SuiteResult} result Information about the individual {@link describe} being run
* @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on.
* @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion.
* @see async
*/
'suiteStarted',
/**
* `suiteDone` is invoked when all of the child specs and suites for a given suite have been run
*
* While jasmine doesn't require any specific functions, not defining a `suiteDone` will make it impossible for a reporter to know when a suite has failures in an `afterAll`.
* @function
* @name Reporter#suiteDone
* @param {SuiteResult} result
* @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on.
* @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion.
* @see async
*/
'suiteDone',
/**
* `specStarted` is invoked when an `it` starts to run (including associated `beforeEach` functions)
* @function
* @name Reporter#specStarted
* @param {SpecResult} result Information about the individual {@link it} being run
* @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on.
* @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion.
* @see async
*/
'specStarted',
/**
* `specDone` is invoked when an `it` and its associated `beforeEach` and `afterEach` functions have been run.
*
* While jasmine doesn't require any specific functions, not defining a `specDone` will make it impossible for a reporter to know when a spec has failed.
* @function
* @name Reporter#specDone
* @param {SpecResult} result
* @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on.
* @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion.
* @see async
*/
'specDone'
];
Object.freeze(events);
return events;
};
getJasmineRequireObj().interface = function(jasmine, env) {
const jasmineInterface = {
/**
@@ -10293,31 +10386,41 @@ 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();
};
})(Date);
/**
* @class Timer
* @classdesc Tracks elapsed time
* @example
* const timer = new jasmine.Timer();
* timer.start();
* const elapsed = timer.elapsed()
*/
function Timer(options) {
options = options || {};
const now = options.now || defaultNow;
let startTime;
/**
* Starts the timer.
* @function
* @name Timer#start
*/
this.start = function() {
startTime = now();
};
/**
* Determines the time since the timer was started.
* @function
* @name Timer#elapsed
* @returns {number} Elapsed time in milliseconds, or NaN if the timer has not been started
*/
this.elapsed = function() {
return now() - startTime;
};
@@ -10610,5 +10713,5 @@ getJasmineRequireObj().UserContext = function(j$) {
};
getJasmineRequireObj().version = function() {
return '5.0.0-alpha.0';
return '5.0.0-beta.0';
};

View File

@@ -1,7 +1,7 @@
{
"name": "jasmine-core",
"license": "MIT",
"version": "5.0.0-alpha.0",
"version": "5.0.0-beta.0",
"repository": {
"type": "git",
"url": "https://github.com/jasmine/jasmine.git"
@@ -34,10 +34,10 @@
"package.json"
],
"devDependencies": {
"eslint": "^7.32.0",
"eslint-plugin-compat": ">=4.0.0 <4.1.0",
"glob": "^7.2.0",
"grunt": ">=1.0.4 <1.6.0",
"eslint": "^8.36.0",
"eslint-plugin-compat": "^4.0.0",
"glob": "^9.3.1",
"grunt": "^1.0.4",
"grunt-cli": "^1.3.2",
"grunt-contrib-compress": "^2.0.0",
"grunt-contrib-concat": "^2.0.0",
@@ -45,10 +45,10 @@
"grunt-sass": "^3.0.2",
"jasmine": "github:jasmine/jasmine-npm#5.0",
"jasmine-browser-runner": "^1.0.0",
"jsdom": "^19.0.0",
"jsdom": "^21.1.1",
"load-grunt-tasks": "^5.1.0",
"prettier": "1.17.1",
"sass": "1.58.3",
"sass": "^1.58.3",
"shelljs": "^0.8.3",
"temp": "^0.9.0"
},

View File

@@ -0,0 +1,39 @@
# Jasmine Core 5.0.0-alpha.1 Release Notes
## Summary
This release provides improved 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.
## New features and bug fixes
* Parallel: Cleaner interface for reporter dispatching
* When Env#config is called from spec and helper files in parallel mode, throw
an error rather than behaving unpredictably.
## Documentation improvements
* API reference docs for parallel support APIs that jasmine-npm uses
## Internal improvements
* Updated dev dependencies
## Supported environments
jasmine-core 5.0.0-alpha.1 has been tested in the following environments.
| Environment | Supported versions |
|-------------------|--------------------|
| Node | 16.14+, 18 |
| Safari | 15-16 |
| Chrome | 112 |
| Firefox | 102, 111 |
| Edge | 111 |
------
_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_

View File

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

View File

@@ -758,16 +758,16 @@ describe('Env', function() {
describe('In parallel mode', function() {
it('rejects if random is set to false', async function() {
env.setParallelLoadingState('specs');
env.configure({ random: false });
env.setParallelLoadingState('specs');
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 });
env.setParallelLoadingState('specs');
await expectAsync(env.execute()).toBeRejectedWithError(
'Random seed cannot be set in parallel mode'
);
@@ -817,4 +817,18 @@ describe('Env', function() {
}).toThrowError('Reporters cannot be removed via Env in parallel mode');
});
});
describe('#configure', function() {
it('throws when called in parallel mode', function() {
env.setParallelLoadingState('helpers');
expect(function() {
env.configure({});
}).toThrowError('Jasmine cannot be configured via Env in parallel mode');
env.setParallelLoadingState('specs');
expect(function() {
env.configure({});
}).toThrowError('Jasmine cannot be configured via Env in parallel mode');
});
});
});

View File

@@ -0,0 +1,176 @@
describe('ParallelReportDispatcher', function() {
it('dispatches the standard reporter events', async function() {
const subject = new jasmineUnderTest.ParallelReportDispatcher(() => {}, {
globalErrors: mockGlobalErrors()
});
const events = [
'jasmineStarted',
'jasmineDone',
'suiteStarted',
'suiteDone',
'specStarted',
'specDone'
];
const reporter = jasmine.createSpyObj('reporter', events);
subject.addReporter(reporter);
for (const eventName of events) {
const payload = { payloadFor: eventName };
await subject[eventName](payload);
expect(reporter[eventName]).toHaveBeenCalledWith(payload);
}
});
it('installs and uninstalls the global error handler', function() {
const globalErrors = mockGlobalErrors();
const subject = new jasmineUnderTest.ParallelReportDispatcher(() => {}, {
globalErrors
});
subject.installGlobalErrors();
expect(globalErrors.install).toHaveBeenCalled();
subject.uninstallGlobalErrors();
expect(globalErrors.uninstall).toHaveBeenCalled();
});
it('handles global errors from async reporters', async function() {
const globalErrors = mockGlobalErrors();
const onError = jasmine.createSpy('onError');
const subject = new jasmineUnderTest.ParallelReportDispatcher(onError, {
globalErrors
});
const reporter = jasmine.createSpyObj('reporter', [
'jasmineStarted',
'jasmineDone'
]);
let resolveStarted;
reporter.jasmineStarted.and.callFake(function() {
return new Promise(function(res) {
resolveStarted = res;
});
});
subject.addReporter(reporter);
const promise = subject.jasmineStarted({});
expect(globalErrors.pushListener).toHaveBeenCalled();
expect(globalErrors.popListener).not.toHaveBeenCalled();
const error = new Error('nope');
globalErrors.pushListener.calls.argsFor(0)[0](error);
expect(onError).toHaveBeenCalledWith(error);
resolveStarted();
await promise;
expect(globalErrors.popListener).toHaveBeenCalled();
});
it('handles done(error) from callback-style async reporters', function() {
const globalErrors = mockGlobalErrors();
const onError = jasmine.createSpy('onError');
const subject = new jasmineUnderTest.ParallelReportDispatcher(onError, {
globalErrors
});
const reporter = jasmine.createSpyObj('reporter', [
'jasmineStarted',
'jasmineDone'
]);
let callback;
reporter.jasmineStarted = function(event, cb) {
callback = cb;
};
subject.addReporter(reporter);
subject.jasmineStarted({});
expect(callback).toBeInstanceOf(Function);
const error = new Error('nope');
callback(error);
expect(onError).toHaveBeenCalledWith(error);
});
it('handles done.fail() from callback-style async reporters', function() {
const globalErrors = mockGlobalErrors();
const onError = jasmine.createSpy('onError');
const subject = new jasmineUnderTest.ParallelReportDispatcher(onError, {
globalErrors
});
const reporter = jasmine.createSpyObj('reporter', [
'jasmineStarted',
'jasmineDone'
]);
let callback;
reporter.jasmineStarted = function(event, cb) {
callback = cb;
};
subject.addReporter(reporter);
subject.jasmineStarted({});
expect(callback).toBeInstanceOf(Function);
const error = new Error('nope');
callback.fail(error);
expect(onError).toHaveBeenCalledWith(error);
onError.calls.reset();
callback.fail();
expect(onError).toHaveBeenCalledWith(
new Error('A reporter called done.fail()')
);
});
it('handles errors due to mixed async style in reporters', async function() {
const globalErrors = mockGlobalErrors();
const onError = jasmine.createSpy('onError');
const subject = new jasmineUnderTest.ParallelReportDispatcher(onError, {
globalErrors
});
subject.addReporter({
async jasmineStarted(event, done) {
done();
}
});
await subject.jasmineStarted({});
expect(onError).toHaveBeenCalledWith(
new Error(
'An asynchronous before/it/after function took a done callback but also returned a promise. Either remove the done callback (recommended) or change the function to not return a promise.'
)
);
});
it('handles errors due to multiple done calls in reporters', async function() {
const globalErrors = mockGlobalErrors();
const onError = jasmine.createSpy('onError');
const subject = new jasmineUnderTest.ParallelReportDispatcher(onError, {
globalErrors
});
subject.addReporter({
jasmineStarted(event, done) {
done();
done();
}
});
await subject.jasmineStarted({});
expect(onError).toHaveBeenCalledWith(
new Error(
"An asynchronous reporter callback called its 'done' callback more than once."
)
);
});
function mockGlobalErrors() {
const globalErrors = jasmine.createSpyObj('globalErrors', [
'install',
'pushListener',
'popListener'
]);
globalErrors.install.and.callFake(function() {
globalErrors.uninstall = jasmine.createSpy('globalErrors.uninstall');
});
return globalErrors;
}
});

View File

@@ -595,11 +595,13 @@ describe('QueueRunner', function() {
queueRunner.execute();
expect(onException).toHaveBeenCalledWith(
'An asynchronous ' +
'before/it/after function took a done callback but also returned a ' +
'promise. ' +
'Either remove the done callback (recommended) or change the function ' +
'to not return a promise.'
new Error(
'An asynchronous ' +
'before/it/after function took a done callback but also returned a ' +
'promise. ' +
'Either remove the done callback (recommended) or change the function ' +
'to not return a promise.'
)
);
});
@@ -615,10 +617,12 @@ describe('QueueRunner', function() {
queueRunner.execute();
expect(onException).toHaveBeenCalledWith(
'An asynchronous ' +
'before/it/after function was defined with the async keyword but ' +
'also took a done callback. Either remove the done callback ' +
'(recommended) or remove the async keyword.'
new Error(
'An asynchronous ' +
'before/it/after function was defined with the async keyword but ' +
'also took a done callback. Either remove the done callback ' +
'(recommended) or remove the async keyword.'
)
);
});
});

View File

@@ -174,6 +174,12 @@ getJasmineRequireObj().Env = function(j$) {
* @function
*/
this.configure = function(configuration) {
if (parallelLoadingState) {
throw new Error(
'Jasmine cannot be configured via Env in parallel mode'
);
}
const booleanProps = [
'random',
'failSpecWithNoExpectations',
@@ -414,72 +420,7 @@ getJasmineRequireObj().Env = function(j$) {
* @see custom_reporter
*/
reporter = new j$.ReportDispatcher(
[
/**
* `jasmineStarted` is called after all of the specs have been loaded, but just before execution starts.
* @function
* @name Reporter#jasmineStarted
* @param {JasmineStartedInfo} suiteInfo Information about the full Jasmine suite that is being run
* @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on.
* @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion.
* @see async
*/
'jasmineStarted',
/**
* When the entire suite has finished execution `jasmineDone` is called
* @function
* @name Reporter#jasmineDone
* @param {JasmineDoneInfo} suiteInfo Information about the full Jasmine suite that just finished running.
* @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on.
* @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion.
* @see async
*/
'jasmineDone',
/**
* `suiteStarted` is invoked when a `describe` starts to run
* @function
* @name Reporter#suiteStarted
* @param {SuiteResult} result Information about the individual {@link describe} being run
* @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on.
* @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion.
* @see async
*/
'suiteStarted',
/**
* `suiteDone` is invoked when all of the child specs and suites for a given suite have been run
*
* While jasmine doesn't require any specific functions, not defining a `suiteDone` will make it impossible for a reporter to know when a suite has failures in an `afterAll`.
* @function
* @name Reporter#suiteDone
* @param {SuiteResult} result
* @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on.
* @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion.
* @see async
*/
'suiteDone',
/**
* `specStarted` is invoked when an `it` starts to run (including associated `beforeEach` functions)
* @function
* @name Reporter#specStarted
* @param {SpecResult} result Information about the individual {@link it} being run
* @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on.
* @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion.
* @see async
*/
'specStarted',
/**
* `specDone` is invoked when an `it` and its associated `beforeEach` and `afterEach` functions have been run.
*
* While jasmine doesn't require any specific functions, not defining a `specDone` will make it impossible for a reporter to know when a spec has failed.
* @function
* @name Reporter#specDone
* @param {SpecResult} result
* @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on.
* @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion.
* @see async
*/
'specDone'
],
j$.reporterEvents,
function(options) {
options.SkipPolicy = j$.NeverSkipPolicy;
return queueRunnerFactory(options);
@@ -503,7 +444,6 @@ getJasmineRequireObj().Env = function(j$) {
};
this.parallelReset = function() {
// TODO: ensure that autoCleanClosures was false
suiteBuilder.parallelReset();
runner.parallelReset();
};

View File

@@ -0,0 +1,93 @@
getJasmineRequireObj().ParallelReportDispatcher = function(j$) {
/**
* @class ParallelReportDispatcher
* @implements Reporter
* @classdesc A report dispatcher packaged for convenient use from outside jasmine-core.
*
* This is intended to help packages like `jasmine` (the Jasmine runner for
* Node.js) do their own report dispatching in order to support parallel
* execution. If you aren't implementing a runner package that supports
* parallel execution, this class probably isn't what you're looking for.
*
* Warning: Do not use ParallelReportDispatcher in the same process that
* Jasmine specs run in. Doing so will break Jasmine's error handling.
* @param onError {function} Function called when an unhandled exception, unhandled promise rejection, or explicit reporter failure occurs
*/
function ParallelReportDispatcher(onError, deps = {}) {
const ReportDispatcher = deps.ReportDispatcher || j$.ReportDispatcher;
const QueueRunner = deps.QueueRunner || j$.QueueRunner;
const globalErrors = deps.globalErrors || new j$.GlobalErrors();
const dispatcher = ReportDispatcher(
j$.reporterEvents,
function(queueRunnerOptions) {
queueRunnerOptions = {
...queueRunnerOptions,
globalErrors,
timeout: { setTimeout, clearTimeout },
fail: function(error) {
// A callback-style async reporter called either done.fail()
// or done(anError).
if (!error) {
error = new Error('A reporter called done.fail()');
}
onError(error);
},
onException: function(error) {
// A reporter method threw an exception or returned a rejected
// promise, or there was an unhandled exception or unhandled promise
// rejection while an asynchronous reporter method was running.
onError(error);
}
};
new QueueRunner(queueRunnerOptions).execute();
},
function(error) {
// A reporter called done() more than once.
onError(error);
}
);
const self = {
/**
* Adds a reporter to the list of reporters that events will be dispatched to.
* @function
* @name ParallelReportDispatcher#addReporter
* @param {Reporter} reporterToAdd The reporter to be added.
* @see custom_reporter
*/
addReporter: dispatcher.addReporter.bind(dispatcher),
/**
* Clears all registered reporters.
* @function
* @name ParallelReportDispatcher#clearReporters
*/
clearReporters: dispatcher.clearReporters.bind(dispatcher),
/**
* Installs a global error handler. After this method is called, any
* unhandled exceptions or unhandled promise rejections will be passed to
* the onError callback that was passed to the constructor.
* @function
* @name ParallelReportDispatcher#installGlobalErrors
*/
installGlobalErrors: globalErrors.install.bind(globalErrors),
/**
* Uninstalls the global error handler.
* @function
* @name ParallelReportDispatcher#uninstallGlobalErrors
*/
uninstallGlobalErrors: function() {
// late-bind uninstall because it doesn't exist until install is called
globalErrors.uninstall(globalErrors);
}
};
for (const eventName of j$.reporterEvents) {
self[eventName] = dispatcher[eventName].bind(dispatcher);
}
return self;
}
return ParallelReportDispatcher;
};

View File

@@ -1,13 +1,4 @@
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() {}
@@ -264,17 +255,21 @@ getJasmineRequireObj().QueueRunner = function(j$) {
// on the stack at this point.
if (j$.isAsyncFunction_(fn)) {
this.onException(
'An asynchronous before/it/after ' +
'function was defined with the async keyword but also took a ' +
'done callback. Either remove the done callback (recommended) or ' +
'remove the async keyword.'
new Error(
'An asynchronous before/it/after ' +
'function was defined with the async keyword but also took a ' +
'done callback. Either remove the done callback (recommended) or ' +
'remove the async keyword.'
)
);
} else {
this.onException(
'An asynchronous before/it/after ' +
'function took a done callback but also returned a promise. ' +
'Either remove the done callback (recommended) or change the ' +
'function to not return a promise.'
new Error(
'An asynchronous before/it/after ' +
'function took a done callback but also returned a promise. ' +
'Either remove the done callback (recommended) or change the ' +
'function to not return a promise.'
)
);
}
}

View File

@@ -1,13 +1,4 @@
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

@@ -1,29 +1,39 @@
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();
};
})(Date);
/**
* @class Timer
* @classdesc Tracks elapsed time
* @example
* const timer = new jasmine.Timer();
* timer.start();
* const elapsed = timer.elapsed()
*/
function Timer(options) {
options = options || {};
const now = options.now || defaultNow;
let startTime;
/**
* Starts the timer.
* @function
* @name Timer#start
*/
this.start = function() {
startTime = now();
};
/**
* Determines the time since the timer was started.
* @function
* @name Timer#elapsed
* @returns {number} Elapsed time in milliseconds, or NaN if the timer has not been started
*/
this.elapsed = function() {
return now() - startTime;
};

View File

@@ -0,0 +1,70 @@
getJasmineRequireObj().reporterEvents = function() {
const events = [
/**
* `jasmineStarted` is called after all of the specs have been loaded, but just before execution starts.
* @function
* @name Reporter#jasmineStarted
* @param {JasmineStartedInfo} suiteInfo Information about the full Jasmine suite that is being run
* @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on.
* @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion.
* @see async
*/
'jasmineStarted',
/**
* When the entire suite has finished execution `jasmineDone` is called
* @function
* @name Reporter#jasmineDone
* @param {JasmineDoneInfo} suiteInfo Information about the full Jasmine suite that just finished running.
* @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on.
* @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion.
* @see async
*/
'jasmineDone',
/**
* `suiteStarted` is invoked when a `describe` starts to run
* @function
* @name Reporter#suiteStarted
* @param {SuiteResult} result Information about the individual {@link describe} being run
* @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on.
* @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion.
* @see async
*/
'suiteStarted',
/**
* `suiteDone` is invoked when all of the child specs and suites for a given suite have been run
*
* While jasmine doesn't require any specific functions, not defining a `suiteDone` will make it impossible for a reporter to know when a suite has failures in an `afterAll`.
* @function
* @name Reporter#suiteDone
* @param {SuiteResult} result
* @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on.
* @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion.
* @see async
*/
'suiteDone',
/**
* `specStarted` is invoked when an `it` starts to run (including associated `beforeEach` functions)
* @function
* @name Reporter#specStarted
* @param {SpecResult} result Information about the individual {@link it} being run
* @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on.
* @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion.
* @see async
*/
'specStarted',
/**
* `specDone` is invoked when an `it` and its associated `beforeEach` and `afterEach` functions have been run.
*
* While jasmine doesn't require any specific functions, not defining a `specDone` will make it impossible for a reporter to know when a spec has failed.
* @function
* @name Reporter#specDone
* @param {SpecResult} result
* @param {Function} [done] Used to specify to Jasmine that this callback is asynchronous and Jasmine should wait until it has been called before moving on.
* @returns {} Optionally return a Promise instead of using `done` to cause Jasmine to wait for completion.
* @see async
*/
'specDone'
];
Object.freeze(events);
return events;
};

View File

@@ -67,7 +67,9 @@ var getJasmineRequireObj = (function(jasmineGlobal) {
j$.CompleteOnFirstErrorSkipPolicy = jRequire.CompleteOnFirstErrorSkipPolicy(
j$
);
j$.reporterEvents = jRequire.reporterEvents(j$);
j$.ReportDispatcher = jRequire.ReportDispatcher(j$);
j$.ParallelReportDispatcher = jRequire.ParallelReportDispatcher(j$);
j$.RunableResources = jRequire.RunableResources(j$);
j$.Runner = jRequire.Runner(j$);
j$.Spec = jRequire.Spec(j$);