jasmine
Version:
CLI for Jasmine, a simple JavaScript testing framework for browsers and Node
302 lines (267 loc) • 8.85 kB
JavaScript
const ExitHandler = require('./exit_handler');
const regexSpecFilter = require('./filters/regex_spec_filter');
const pathSpecFilter = require('./filters/path_spec_filter');
const RunnerBase = require('./runner_base');
/**
* Options for the {@link Jasmine} constructor
* @name JasmineOptions
* @interface
*/
/**
* The path to the project's base directory. This can be absolute or relative
* to the current working directory. If it isn't specified, the current working
* directory will be used.
* @name JasmineOptions#projectBaseDir
* @type (string | undefined)
*/
/**
* Whether to create the globals (describe, it, etc) that make up Jasmine's
* spec-writing interface. If it is set to false, the spec-writing interface
* can be accessed via jasmine-core's `noGlobals` method, e.g.:
*
* `const {describe, it, expect, jasmine} = require('jasmine-core').noGlobals();`
*
* @name JasmineOptions#globals
* @type (boolean | undefined)
* @default true
*/
/**
* @classdesc Configures, builds, and executes a Jasmine test suite.<br>See also {@link ParallelRunner} which provides equivalent functionality for parallel execution.
* @param {(JasmineOptions | undefined)} options
* @constructor
* @name Jasmine
* @extends Runner
* @example
* const Jasmine = require('jasmine');
* const runner = new Jasmine();
*/
class Jasmine extends RunnerBase {
constructor(options) {
options = options || {};
super(options);
const jasmineCore = options.jasmineCore || require('jasmine-core');
if (options.globals === false) {
this.jasmine = jasmineCore.noGlobals().jasmine;
} else {
this.jasmine = jasmineCore.boot(true);
}
/**
* The Jasmine environment.
* @name Jasmine#env
* @readonly
* @see {@link https://jasmine.github.io/api/edge/Env.html|Env}
* @type {Env}
*/
this.env = this.jasmine.getEnv({suppressLoadErrors: true});
this.reportersCount = 0;
this.exit = process.exit;
this.addReporter(this.reporter_);
/**
* @function
* @name Jasmine#coreVersion
* @return {string} The version of jasmine-core in use
*/
this.coreVersion = function() {
return jasmineCore.version();
};
// Public. See RunnerBase.
this.exitOnCompletion = true;
}
/**
* Sets whether to randomize the order of specs.
* @function
* @name Jasmine#randomizeTests
* @param {boolean} value Whether to randomize
*/
randomizeTests(value) {
this.env.configure({random: value});
}
/**
* Sets the random seed.
* @function
* @name Jasmine#seed
* @param {number} seed The random seed
*/
seed(value) {
this.env.configure({seed: value});
}
// Public. See RunnerBase jsdocs.
addReporter(reporter) {
this.env.addReporter(reporter);
this.reportersCount++;
}
// Public. See RunnerBase jsdocs.
clearReporters() {
this.env.clearReporters();
this.reportersCount = 0;
}
/**
* Provide a fallback reporter if no other reporters have been specified.
* @function
* @name Jasmine#provideFallbackReporter
* @param reporter The fallback reporter
* @see custom_reporter
*/
provideFallbackReporter(reporter) {
this.env.provideFallbackReporter(reporter);
}
/**
* Add custom matchers for the current scope of specs.
*
* _Note:_ This is only callable from within a {@link beforeEach}, {@link it}, or {@link beforeAll}.
* @function
* @name Jasmine#addMatchers
* @param {Object} matchers - Keys from this object will be the new matcher names.
* @see custom_matcher
*/
addMatchers(matchers) {
this.env.addMatchers(matchers);
}
async loadSpecs() {
await this._loadFiles(this.specFiles);
}
async loadHelpers() {
await this._loadFiles(this.helperFiles);
}
async _loadFiles(files) {
for (const file of files) {
await this.loader.load(file);
}
}
async loadRequires() {
await this._loadFiles(this.requires);
}
configureEnv(envConfig) {
this.env.configure(envConfig);
}
/**
* Sets whether to cause specs to only have one expectation failure.
* @function
* @name Jasmine#stopSpecOnExpectationFailure
* @param {boolean} value Whether to cause specs to only have one expectation
* failure
*/
stopSpecOnExpectationFailure(value) {
this.env.configure({stopSpecOnExpectationFailure: value});
}
/**
* Sets whether to stop execution of the suite after the first spec failure.
* @function
* @name Jasmine#stopOnSpecFailure
* @param {boolean} value Whether to stop execution of the suite after the
* first spec failure
*/
stopOnSpecFailure(value) {
this.env.configure({stopOnSpecFailure: value});
}
/**
* Runs the test suite.
*
* _Note_: Set {@link Jasmine#exitOnCompletion|exitOnCompletion} to false if you
* intend to use the returned promise. Otherwise, the Node process will
* ordinarily exit before the promise is settled.
* @param {Array.<string>} [files] Spec files to run instead of the previously
* configured set
* @param {string|RegExp|object} [filter] Optional specification of what specs to run.
* Can be a RegExp, a string, or an object. If it's a RegExp, it will be matched
* against the full names of specs. If it's a string, it will be converted to
* a RegExp and then handled in the same way. If it's
* an object, it should have a path property whose value is an array of spec
* or suite descriptions.
* Regex used to filter specs. If specified, only
* specs with matching full names will be run.
* @return {Promise<JasmineDoneInfo>} Promise that is resolved when the suite completes.
* @example
* // Run all specs
* await jasmine.execute();
*
* // Run just spec/someSpec.js
* await jasmine.execute(['spec/someSpec.js']);
*
* // Run all specs with full paths starting with "a suite a child suite"
* await jasmine.execute(null, '^a suite a child suite');
*
* // Run all specs that are inside a suite named "a child suite" that is
* // a child of a top-level suite named "a suite"
* await jasmine.execute(null, {path: ['a suite', 'a child suite']});
*/
async execute(files, filter) {
await this.loadRequires();
await this.loadHelpers();
if (!this.defaultReporterConfigured) {
this.configureDefaultReporter({
showColors: this.showingColors,
alwaysListPendingSpecs: this.alwaysListPendingSpecs_
});
}
if (filter) {
if (typeof filter === 'string' || filter instanceof RegExp) {
this.env.configure({
specFilter: regexSpecFilter(filter)
});
} else if (filter.path) {
this.env.configure({
specFilter: pathSpecFilter(filter.path)
});
} else {
throw new Error('Unrecognized filter type');
}
}
if (files && files.length > 0) {
if (this.isVerbose_) {
console.log('Overriding previous specDir and specFiles because a list of spec files was provided on the command line or as an argument to Jasmine#execute');
}
this.specDir = '';
this.specFiles = [];
this.addMatchingSpecFiles(files);
}
const prematureExitHandler = new ExitHandler(() => this.exit(4));
prematureExitHandler.install();
let overallResult;
try {
await this.withinGlobalSetup_(async () => {
await this.loadSpecs();
overallResult = await this.env.execute();
});
} finally {
await this.flushOutput();
prematureExitHandler.uninstall();
}
if (this.exitOnCompletion) {
this.exit(RunnerBase.exitCodeForStatus(overallResult.overallStatus));
}
return overallResult;
}
/**
* Returns a tree of suites and specs without actually running the specs.
* @return {Promise<EnumeratedSuiteOrSpec[]>}
*/
async enumerate() {
await this.loadRequires();
await this.loadHelpers();
await this.loadSpecs();
return this.env.topSuite().children.map(toEnumerateResult);
}
}
function toEnumerateResult(suiteOrSpecMeta) {
// Omit parent links to avoid JSON serialization failure due to circular
// references. Omit IDs since they aren't stable across executions. Add
// type information to make interpreting the output easier.
/**
* @interface EnumeratedSuiteOrSpec
* @property {string} type - 'suite' or 'spec'
* @property {EnumeratedSuiteOrSpec[]} [children] - Only defined for suites
*/
const result = {
description: suiteOrSpecMeta.description
};
if (suiteOrSpecMeta.children === undefined) {
result.type = 'spec';
} else {
result.type = 'suite';
result.children = suiteOrSpecMeta.children.map(toEnumerateResult);
}
return result;
}
module.exports = Jasmine;
module.exports.ConsoleReporter = require('./reporters/console_reporter');