solved task 2
This commit is contained in:
+20
@@ -0,0 +1,20 @@
|
||||
Copyright (c) 2014-2019 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.
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
[](https://circleci.com/gh/jasmine/jasmine-npm)
|
||||
[](https://app.fossa.io/projects/git%2Bgithub.com%2Fjasmine%2Fjasmine-npm?ref=badge_shield)
|
||||
|
||||
# The Jasmine Module
|
||||
|
||||
The `jasmine` module is a command line interface and supporting code for running
|
||||
[Jasmine](https://github.com/jasmine/jasmine) specs under Node.
|
||||
|
||||
The core of jasmine lives at https://github.com/jasmine/jasmine and is `jasmine-core` in npm.
|
||||
|
||||
## Contents
|
||||
|
||||
This module allows you to run Jasmine specs for your Node.js code. The output will be displayed in your terminal by default.
|
||||
|
||||
## Documentation
|
||||
|
||||
https://jasmine.github.io/setup/nodejs.html
|
||||
|
||||
## Quick Start
|
||||
|
||||
Installation:
|
||||
|
||||
```sh
|
||||
npm install --save-dev jasmine
|
||||
```
|
||||
|
||||
To initialize a project for Jasmine:
|
||||
|
||||
```sh
|
||||
npx jasmine init
|
||||
````
|
||||
|
||||
To seed your project with some examples:
|
||||
|
||||
```sh
|
||||
npx jasmine examples
|
||||
````
|
||||
|
||||
To run your test suite:
|
||||
|
||||
```sh
|
||||
npx jasmine
|
||||
````
|
||||
|
||||
## ES and CommonJS module compatibility
|
||||
|
||||
Jasmine is compatible with both ES modules and CommonJS modules. See the
|
||||
[setup guide](https://jasmine.github.io/setup/nodejs.html) for more information.
|
||||
|
||||
|
||||
## Node version compatibility
|
||||
|
||||
Jasmine supports Node 18, 16, 14, and 12.17-12.22.
|
||||
|
||||
## Support
|
||||
|
||||
Documentation: [jasmine.github.io](https://jasmine.github.io)
|
||||
Jasmine Mailing list: [jasmine-js@googlegroups.com](mailto:jasmine-js@googlegroups.com)
|
||||
Twitter: [@jasminebdd](http://twitter.com/jasminebdd)
|
||||
|
||||
Please file issues here at Github
|
||||
|
||||
Copyright (c) 2008-2017 Pivotal Labs. This software is licensed under the MIT License.
|
||||
|
||||
|
||||
## License
|
||||
[](https://app.fossa.io/projects/git%2Bgithub.com%2Fjasmine%2Fjasmine-npm?ref=badge_large)
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const path = require('path');
|
||||
const Command = require('../lib/command');
|
||||
const Jasmine = require('../lib/jasmine');
|
||||
|
||||
const jasmine = new Jasmine({ projectBaseDir: path.resolve() });
|
||||
const examplesDir = path.join(path.dirname(require.resolve('jasmine-core')), 'jasmine-core', 'example', 'node_example');
|
||||
const command = new Command(path.resolve(), examplesDir, console.log);
|
||||
|
||||
command.run(jasmine, process.argv.slice(2));
|
||||
+317
@@ -0,0 +1,317 @@
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
exports = module.exports = Command;
|
||||
|
||||
const subCommands = {
|
||||
init: {
|
||||
description: 'initialize jasmine',
|
||||
action: initJasmine
|
||||
},
|
||||
examples: {
|
||||
description: 'install examples',
|
||||
action: installExamples
|
||||
},
|
||||
help: {
|
||||
description: 'show help',
|
||||
action: help,
|
||||
alias: '-h'
|
||||
},
|
||||
version: {
|
||||
description: 'show jasmine and jasmine-core versions',
|
||||
action: version,
|
||||
alias: '-v'
|
||||
}
|
||||
};
|
||||
|
||||
function Command(projectBaseDir, examplesDir, print) {
|
||||
this.projectBaseDir = projectBaseDir;
|
||||
this.specDir = path.join(projectBaseDir, 'spec');
|
||||
|
||||
const command = this;
|
||||
|
||||
this.run = async function(jasmine, commands) {
|
||||
setEnvironmentVariables(commands);
|
||||
|
||||
let commandToRun;
|
||||
Object.keys(subCommands).forEach(function(cmd) {
|
||||
const commandObject = subCommands[cmd];
|
||||
if (commands.indexOf(cmd) >= 0) {
|
||||
commandToRun = commandObject;
|
||||
} else if(commandObject.alias && commands.indexOf(commandObject.alias) >= 0) {
|
||||
commandToRun = commandObject;
|
||||
}
|
||||
});
|
||||
|
||||
if (commandToRun) {
|
||||
commandToRun.action({jasmine: jasmine, projectBaseDir: command.projectBaseDir, specDir: command.specDir, examplesDir: examplesDir, print: print});
|
||||
} else {
|
||||
const env = parseOptions(commands);
|
||||
if (env.unknownOptions.length > 0) {
|
||||
process.exitCode = 1;
|
||||
print('Unknown options: ' + env.unknownOptions.join(', '));
|
||||
print('');
|
||||
help({print: print});
|
||||
} else {
|
||||
await runJasmine(jasmine, env);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function isFileArg(arg) {
|
||||
return arg.indexOf('--') !== 0 && !isEnvironmentVariable(arg);
|
||||
}
|
||||
|
||||
function parseOptions(argv) {
|
||||
let files = [],
|
||||
helpers = [],
|
||||
requires = [],
|
||||
unknownOptions = [],
|
||||
color = process.stdout.isTTY || false,
|
||||
reporter,
|
||||
configPath,
|
||||
filter,
|
||||
failFast,
|
||||
random,
|
||||
seed;
|
||||
|
||||
for (const arg of argv) {
|
||||
if (arg === '--no-color') {
|
||||
color = false;
|
||||
} else if (arg === '--color') {
|
||||
color = true;
|
||||
} else if (arg.match("^--filter=")) {
|
||||
filter = arg.match("^--filter=(.*)")[1];
|
||||
} else if (arg.match("^--helper=")) {
|
||||
helpers.push(arg.match("^--helper=(.*)")[1]);
|
||||
} else if (arg.match("^--require=")) {
|
||||
requires.push(arg.match("^--require=(.*)")[1]);
|
||||
} else if (arg === '--fail-fast') {
|
||||
failFast = true;
|
||||
} else if (arg.match("^--random=")) {
|
||||
random = arg.match("^--random=(.*)")[1] === 'true';
|
||||
} else if (arg.match("^--seed=")) {
|
||||
seed = arg.match("^--seed=(.*)")[1];
|
||||
} else if (arg.match("^--config=")) {
|
||||
configPath = arg.match("^--config=(.*)")[1];
|
||||
} else if (arg.match("^--reporter=")) {
|
||||
reporter = arg.match("^--reporter=(.*)")[1];
|
||||
} else if (arg === '--') {
|
||||
break;
|
||||
} else if (isFileArg(arg)) {
|
||||
files.push(arg);
|
||||
} else if (!isEnvironmentVariable(arg)) {
|
||||
unknownOptions.push(arg);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
color,
|
||||
configPath,
|
||||
filter,
|
||||
failFast,
|
||||
helpers,
|
||||
requires,
|
||||
reporter,
|
||||
files,
|
||||
random,
|
||||
seed,
|
||||
unknownOptions
|
||||
};
|
||||
}
|
||||
|
||||
async function runJasmine(jasmine, options) {
|
||||
await jasmine.loadConfigFile(options.configPath || process.env.JASMINE_CONFIG_PATH);
|
||||
|
||||
if (options.failFast !== undefined) {
|
||||
jasmine.env.configure({
|
||||
stopSpecOnExpectationFailure: options.failFast,
|
||||
stopOnSpecFailure: options.failFast
|
||||
});
|
||||
}
|
||||
|
||||
if (options.seed !== undefined) {
|
||||
jasmine.seed(options.seed);
|
||||
}
|
||||
|
||||
if (options.random !== undefined) {
|
||||
jasmine.randomizeTests(options.random);
|
||||
}
|
||||
|
||||
if (options.helpers !== undefined && options.helpers.length) {
|
||||
jasmine.addMatchingHelperFiles(options.helpers);
|
||||
}
|
||||
|
||||
if (options.requires !== undefined && options.requires.length) {
|
||||
jasmine.addRequires(options.requires);
|
||||
}
|
||||
|
||||
if (options.reporter !== undefined) {
|
||||
await registerReporter(options.reporter, jasmine);
|
||||
}
|
||||
|
||||
jasmine.showColors(options.color);
|
||||
|
||||
try {
|
||||
await jasmine.execute(options.files, options.filter);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
async function registerReporter(reporterModuleName, jasmine) {
|
||||
let Reporter;
|
||||
|
||||
try {
|
||||
Reporter = await jasmine.loader.load(resolveReporter(reporterModuleName));
|
||||
} catch (e) {
|
||||
throw new Error('Failed to load reporter module '+ reporterModuleName +
|
||||
'\nUnderlying error: ' + e.stack + '\n(end underlying error)');
|
||||
}
|
||||
|
||||
let reporter;
|
||||
|
||||
try {
|
||||
reporter = new Reporter();
|
||||
} catch (e) {
|
||||
throw new Error('Failed to instantiate reporter from '+ reporterModuleName +
|
||||
'\nUnderlying error: ' + e.stack + '\n(end underlying error)');
|
||||
|
||||
}
|
||||
jasmine.clearReporters();
|
||||
jasmine.addReporter(reporter);
|
||||
}
|
||||
|
||||
function resolveReporter(nameOrPath) {
|
||||
if (nameOrPath.startsWith('./') || nameOrPath.startsWith('../')) {
|
||||
return path.resolve(nameOrPath);
|
||||
} else {
|
||||
return nameOrPath;
|
||||
}
|
||||
}
|
||||
|
||||
function initJasmine(options) {
|
||||
const print = options.print;
|
||||
const specDir = options.specDir;
|
||||
makeDirStructure(path.join(specDir, 'support/'));
|
||||
if(!fs.existsSync(path.join(specDir, 'support/jasmine.json'))) {
|
||||
fs.writeFileSync(path.join(specDir, 'support/jasmine.json'), fs.readFileSync(path.join(__dirname, '../lib/examples/jasmine.json'), 'utf-8'));
|
||||
}
|
||||
else {
|
||||
print('spec/support/jasmine.json already exists in your project.');
|
||||
}
|
||||
}
|
||||
|
||||
function installExamples(options) {
|
||||
const specDir = options.specDir;
|
||||
const projectBaseDir = options.projectBaseDir;
|
||||
const examplesDir = options.examplesDir;
|
||||
|
||||
makeDirStructure(path.join(specDir, 'support'));
|
||||
makeDirStructure(path.join(specDir, 'jasmine_examples'));
|
||||
makeDirStructure(path.join(specDir, 'helpers', 'jasmine_examples'));
|
||||
makeDirStructure(path.join(projectBaseDir, 'lib', 'jasmine_examples'));
|
||||
|
||||
copyFiles(
|
||||
path.join(examplesDir, 'spec', 'helpers', 'jasmine_examples'),
|
||||
path.join(specDir, 'helpers', 'jasmine_examples'),
|
||||
new RegExp(/[Hh]elper\.js/)
|
||||
);
|
||||
|
||||
copyFiles(
|
||||
path.join(examplesDir, 'lib', 'jasmine_examples'),
|
||||
path.join(projectBaseDir, 'lib', 'jasmine_examples'),
|
||||
new RegExp(/\.js/)
|
||||
);
|
||||
|
||||
copyFiles(
|
||||
path.join(examplesDir, 'spec', 'jasmine_examples'),
|
||||
path.join(specDir, 'jasmine_examples'),
|
||||
new RegExp(/[Ss]pec.js/)
|
||||
);
|
||||
}
|
||||
|
||||
function help(options) {
|
||||
const print = options.print;
|
||||
print('Usage: jasmine [command] [options] [files] [--]');
|
||||
print('');
|
||||
print('Commands:');
|
||||
Object.keys(subCommands).forEach(function(cmd) {
|
||||
let commandNameText = cmd;
|
||||
if(subCommands[cmd].alias) {
|
||||
commandNameText = commandNameText + ',' + subCommands[cmd].alias;
|
||||
}
|
||||
print('%s\t%s', lPad(commandNameText, 10), subCommands[cmd].description);
|
||||
});
|
||||
print('');
|
||||
print('If no command is given, jasmine specs will be run');
|
||||
print('');
|
||||
print('');
|
||||
|
||||
print('Options:');
|
||||
print('%s\tturn off color in spec output', lPad('--no-color', 18));
|
||||
print('%s\tforce turn on color in spec output', lPad('--color', 18));
|
||||
print('%s\tfilter specs to run only those that match the given string', lPad('--filter=', 18));
|
||||
print('%s\tload helper files that match the given string', lPad('--helper=', 18));
|
||||
print('%s\tload module that match the given string', lPad('--require=', 18));
|
||||
print('%s\tstop Jasmine execution on spec failure', lPad('--fail-fast', 18));
|
||||
print('%s\tpath to your optional jasmine.json', lPad('--config=', 18));
|
||||
print('%s\tpath to reporter to use instead of the default Jasmine reporter', lPad('--reporter=', 18));
|
||||
print('%s\tmarker to signal the end of options meant for Jasmine', lPad('--', 18));
|
||||
print('');
|
||||
print('The given arguments take precedence over options in your jasmine.json');
|
||||
print('The path to your optional jasmine.json can also be configured by setting the JASMINE_CONFIG_PATH environment variable');
|
||||
}
|
||||
|
||||
function version(options) {
|
||||
const print = options.print;
|
||||
print('jasmine v' + require('../package.json').version);
|
||||
print('jasmine-core v' + options.jasmine.coreVersion());
|
||||
}
|
||||
|
||||
function lPad(str, length) {
|
||||
if (str.length >= length) {
|
||||
return str;
|
||||
} else {
|
||||
return lPad(' ' + str, length);
|
||||
}
|
||||
}
|
||||
|
||||
function copyFiles(srcDir, destDir, pattern) {
|
||||
const srcDirFiles = fs.readdirSync(srcDir);
|
||||
srcDirFiles.forEach(function(file) {
|
||||
if (file.search(pattern) !== -1) {
|
||||
fs.writeFileSync(path.join(destDir, file), fs.readFileSync(path.join(srcDir, file)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function makeDirStructure(absolutePath) {
|
||||
const splitPath = absolutePath.split(path.sep);
|
||||
splitPath.forEach(function(dir, index) {
|
||||
if(index > 1) {
|
||||
const fullPath = path.join(splitPath.slice(0, index).join('/'), dir);
|
||||
if (!fs.existsSync(fullPath)) {
|
||||
fs.mkdirSync(fullPath);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function isEnvironmentVariable(command) {
|
||||
const envRegExp = /(.*)=(.*)/;
|
||||
return command.match(envRegExp);
|
||||
}
|
||||
|
||||
function setEnvironmentVariables(commands) {
|
||||
commands.forEach(function (command) {
|
||||
const regExpMatch = isEnvironmentVariable(command);
|
||||
if(regExpMatch) {
|
||||
const key = regExpMatch[1];
|
||||
const value = regExpMatch[2];
|
||||
process.env[key] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"spec_dir": "spec",
|
||||
"spec_files": [
|
||||
"**/*[sS]pec.?(m)js"
|
||||
],
|
||||
"helpers": [
|
||||
"helpers/**/*.?(m)js"
|
||||
],
|
||||
"env": {
|
||||
"stopSpecOnExpectationFailure": false,
|
||||
"random": true
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
class ExitHandler {
|
||||
constructor(onExit) {
|
||||
this._onExit = onExit;
|
||||
}
|
||||
|
||||
install() {
|
||||
process.on('exit', this._onExit);
|
||||
}
|
||||
|
||||
uninstall() {
|
||||
process.removeListener('exit', this._onExit);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ExitHandler;
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
module.exports = exports = ConsoleSpecFilter;
|
||||
|
||||
function ConsoleSpecFilter(options) {
|
||||
const filterString = options && options.filterString;
|
||||
const filterPattern = new RegExp(filterString);
|
||||
|
||||
this.matches = function(specName) {
|
||||
return filterPattern.test(specName);
|
||||
};
|
||||
}
|
||||
+556
@@ -0,0 +1,556 @@
|
||||
const path = require('path');
|
||||
const util = require('util');
|
||||
const glob = require('glob');
|
||||
const Loader = require('./loader');
|
||||
const ExitHandler = require('./exit_handler');
|
||||
const ConsoleSpecFilter = require('./filters/console_spec_filter');
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param {(JasmineOptions | undefined)} options
|
||||
* @constructor
|
||||
* @name Jasmine
|
||||
* @example
|
||||
* const Jasmine = require('jasmine');
|
||||
* const jasmine = new Jasmine();
|
||||
*/
|
||||
class Jasmine {
|
||||
constructor(options) {
|
||||
options = options || {};
|
||||
this.loader = options.loader || new Loader();
|
||||
const jasmineCore = options.jasmineCore || require('jasmine-core');
|
||||
|
||||
if (options.globals === false) {
|
||||
this.jasmine = jasmineCore.noGlobals().jasmine;
|
||||
} else {
|
||||
this.jasmine = jasmineCore.boot(jasmineCore);
|
||||
}
|
||||
|
||||
this.projectBaseDir = options.projectBaseDir || path.resolve();
|
||||
this.specDir = '';
|
||||
this.specFiles = [];
|
||||
this.helperFiles = [];
|
||||
this.requires = [];
|
||||
/**
|
||||
* 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.showingColors = true;
|
||||
this.alwaysListPendingSpecs_ = true;
|
||||
this.reporter = new module.exports.ConsoleReporter();
|
||||
this.addReporter(this.reporter);
|
||||
this.defaultReporterConfigured = false;
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @name Jasmine#coreVersion
|
||||
* @return {string} The version of jasmine-core in use
|
||||
*/
|
||||
this.coreVersion = function() {
|
||||
return jasmineCore.version();
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether to cause the Node process to exit when the suite finishes executing.
|
||||
*
|
||||
* @name Jasmine#exitOnCompletion
|
||||
* @type {boolean}
|
||||
* @default true
|
||||
*/
|
||||
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});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to show colors in the console reporter.
|
||||
* @function
|
||||
* @name Jasmine#showColors
|
||||
* @param {boolean} value Whether to show colors
|
||||
*/
|
||||
showColors(value) {
|
||||
this.showingColors = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the console reporter should list pending specs even when there
|
||||
* are failures.
|
||||
* @name Jasmine#alwaysListPendingSpecs
|
||||
* @param value {boolean}
|
||||
*/
|
||||
alwaysListPendingSpecs(value) {
|
||||
this.alwaysListPendingSpecs_ = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a spec file to the list that will be loaded when the suite is executed.
|
||||
* @function
|
||||
* @name Jasmine#addSpecFile
|
||||
* @param {string} filePath The path to the file to be loaded.
|
||||
*/
|
||||
addSpecFile(filePath) {
|
||||
this.specFiles.push(filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a helper file to the list that will be loaded when the suite is executed.
|
||||
* @function
|
||||
* @name Jasmine#addHelperFile
|
||||
* @param {string} filePath The path to the file to be loaded.
|
||||
*/
|
||||
addHelperFile(filePath) {
|
||||
this.helperFiles.push(filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a custom reporter to the Jasmine environment.
|
||||
* @function
|
||||
* @name Jasmine#addReporter
|
||||
* @param {Reporter} reporter The reporter to add
|
||||
* @see custom_reporter
|
||||
*/
|
||||
addReporter(reporter) {
|
||||
this.env.addReporter(reporter);
|
||||
this.reportersCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all registered reporters.
|
||||
* @function
|
||||
* @name Jasmine#clearReporters
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the default reporter that is installed if no other reporter is
|
||||
* specified.
|
||||
* @param {ConsoleReporterOptions} options
|
||||
*/
|
||||
configureDefaultReporter(options) {
|
||||
options.print = options.print || function() {
|
||||
process.stdout.write(util.format.apply(this, arguments));
|
||||
};
|
||||
options.showColors = options.hasOwnProperty('showColors') ? options.showColors : true;
|
||||
|
||||
this.reporter.setOptions(options);
|
||||
this.defaultReporterConfigured = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads configuration from the specified file. The file can be a JSON file or
|
||||
* any JS file that's loadable via require and provides a Jasmine config
|
||||
* as its default export.
|
||||
* @param {string} [configFilePath=spec/support/jasmine.json]
|
||||
* @return Promise
|
||||
*/
|
||||
async loadConfigFile(configFilePath) {
|
||||
if (configFilePath) {
|
||||
await this.loadSpecificConfigFile_(configFilePath);
|
||||
} else {
|
||||
for (const ext of ['json', 'js']) {
|
||||
try {
|
||||
await this.loadSpecificConfigFile_(`spec/support/jasmine.${ext}`);
|
||||
} catch (e) {
|
||||
if (e.code !== 'MODULE_NOT_FOUND' && e.code !== 'ERR_MODULE_NOT_FOUND') {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async loadSpecificConfigFile_(relativePath) {
|
||||
const absolutePath = path.resolve(this.projectBaseDir, relativePath);
|
||||
const config = await this.loader.load(absolutePath);
|
||||
this.loadConfig(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads configuration from the specified object.
|
||||
* @param {Configuration} config
|
||||
*/
|
||||
loadConfig(config) {
|
||||
/**
|
||||
* @interface Configuration
|
||||
*/
|
||||
const envConfig = {...config.env};
|
||||
|
||||
/**
|
||||
* The directory that spec files are contained in, relative to the project
|
||||
* base directory.
|
||||
* @name Configuration#spec_dir
|
||||
* @type string | undefined
|
||||
*/
|
||||
this.specDir = config.spec_dir || this.specDir;
|
||||
|
||||
/**
|
||||
* Whether to fail specs that contain no expectations.
|
||||
* @name Configuration#failSpecWithNoExpectations
|
||||
* @type boolean | undefined
|
||||
* @default false
|
||||
*/
|
||||
if (config.failSpecWithNoExpectations !== undefined) {
|
||||
envConfig.failSpecWithNoExpectations = config.failSpecWithNoExpectations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to stop each spec on the first expectation failure.
|
||||
* @name Configuration#stopSpecOnExpectationFailure
|
||||
* @type boolean | undefined
|
||||
* @default false
|
||||
*/
|
||||
if (config.stopSpecOnExpectationFailure !== undefined) {
|
||||
envConfig.stopSpecOnExpectationFailure = config.stopSpecOnExpectationFailure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to stop suite execution on the first spec failure.
|
||||
* @name Configuration#stopOnSpecFailure
|
||||
* @type boolean | undefined
|
||||
* @default false
|
||||
*/
|
||||
if (config.stopOnSpecFailure !== undefined) {
|
||||
envConfig.stopOnSpecFailure = config.stopOnSpecFailure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the default reporter should list pending specs even if there are
|
||||
* failures.
|
||||
* @name Configuration#alwaysListPendingSpecs
|
||||
* @type boolean | undefined
|
||||
* @default false
|
||||
*/
|
||||
if (config.alwaysListPendingSpecs !== undefined) {
|
||||
this.alwaysListPendingSpecs(config.alwaysListPendingSpecs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to run specs in a random order.
|
||||
* @name Configuration#random
|
||||
* @type boolean | undefined
|
||||
* @default true
|
||||
*/
|
||||
if (config.random !== undefined) {
|
||||
envConfig.random = config.random;
|
||||
}
|
||||
|
||||
if (config.verboseDeprecations !== undefined) {
|
||||
envConfig.verboseDeprecations = config.verboseDeprecations;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Specifies how to load files with names ending in .js. Valid values are
|
||||
* "require" and "import". "import" should be safe in all cases, and is
|
||||
* required if your project contains ES modules with filenames ending in .js.
|
||||
* @name Configuration#jsLoader
|
||||
* @type string | undefined
|
||||
* @default "require"
|
||||
*/
|
||||
if (config.jsLoader === 'import' || config.jsLoader === undefined) {
|
||||
this.loader.alwaysImport = true;
|
||||
} else if (config.jsLoader === 'require') {
|
||||
this.loader.alwaysImport = false;
|
||||
} else {
|
||||
throw new Error(`"${config.jsLoader}" is not a valid value for the ` +
|
||||
'jsLoader configuration property. Valid values are "import", ' +
|
||||
'"require", and undefined.');
|
||||
}
|
||||
|
||||
if (Object.keys(envConfig).length > 0) {
|
||||
this.env.configure(envConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* An array of helper file paths or {@link https://github.com/isaacs/node-glob#glob-primer|globs}
|
||||
* that match helper files. Each path or glob will be evaluated relative to
|
||||
* the spec directory. Helpers are loaded before specs.
|
||||
* @name Configuration#helpers
|
||||
* @type string[] | undefined
|
||||
*/
|
||||
if(config.helpers) {
|
||||
this.addMatchingHelperFiles(config.helpers);
|
||||
}
|
||||
|
||||
/**
|
||||
* An array of module names to load via require() at the start of execution.
|
||||
* @name Configuration#requires
|
||||
* @type string[] | undefined
|
||||
*/
|
||||
if(config.requires) {
|
||||
this.addRequires(config.requires);
|
||||
}
|
||||
|
||||
/**
|
||||
* An array of spec file paths or {@link https://github.com/isaacs/node-glob#glob-primer|globs}
|
||||
* that match helper files. Each path or glob will be evaluated relative to
|
||||
* the spec directory.
|
||||
* @name Configuration#spec_files
|
||||
* @type string[] | undefined
|
||||
*/
|
||||
if(config.spec_files) {
|
||||
this.addMatchingSpecFiles(config.spec_files);
|
||||
}
|
||||
}
|
||||
|
||||
addRequires(requires) {
|
||||
const jasmineRunner = this;
|
||||
requires.forEach(function(r) {
|
||||
jasmineRunner.requires.push(r);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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});
|
||||
}
|
||||
|
||||
async flushOutput() {
|
||||
// Ensure that all data has been written to stdout and stderr,
|
||||
// then exit with an appropriate status code. Otherwise, we
|
||||
// might exit before all previous writes have actually been
|
||||
// written when Jasmine is piped to another process that isn't
|
||||
// reading quickly enough.
|
||||
var streams = [process.stdout, process.stderr];
|
||||
var promises = streams.map(stream => {
|
||||
return new Promise(resolve => stream.write('', null, resolve));
|
||||
});
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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} [filterString] 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.
|
||||
*/
|
||||
async execute(files, filterString) {
|
||||
await this.loadRequires();
|
||||
await this.loadHelpers();
|
||||
if (!this.defaultReporterConfigured) {
|
||||
this.configureDefaultReporter({
|
||||
showColors: this.showingColors,
|
||||
alwaysListPendingSpecs: this.alwaysListPendingSpecs_
|
||||
});
|
||||
}
|
||||
|
||||
if (filterString) {
|
||||
const specFilter = new ConsoleSpecFilter({
|
||||
filterString: filterString
|
||||
});
|
||||
this.env.configure({specFilter: function(spec) {
|
||||
return specFilter.matches(spec.getFullName());
|
||||
}});
|
||||
}
|
||||
|
||||
if (files && files.length > 0) {
|
||||
this.specDir = '';
|
||||
this.specFiles = [];
|
||||
this.addMatchingSpecFiles(files);
|
||||
}
|
||||
|
||||
await this.loadSpecs();
|
||||
|
||||
const prematureExitHandler = new ExitHandler(() => this.exit(4));
|
||||
prematureExitHandler.install();
|
||||
const overallResult = await this.env.execute();
|
||||
await this.flushOutput();
|
||||
prematureExitHandler.uninstall();
|
||||
|
||||
if (this.exitOnCompletion) {
|
||||
this.exit(exitCodeForStatus(overallResult.overallStatus));
|
||||
}
|
||||
|
||||
return overallResult;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds files that match the specified patterns to the list of spec files.
|
||||
* @function
|
||||
* @name Jasmine#addMatchingSpecFiles
|
||||
* @param {Array<string>} patterns An array of spec file paths
|
||||
* or {@link https://github.com/isaacs/node-glob#glob-primer|globs} that match
|
||||
* spec files. Each path or glob will be evaluated relative to the spec directory.
|
||||
*/
|
||||
Jasmine.prototype.addMatchingSpecFiles = addFiles('specFiles');
|
||||
/**
|
||||
* Adds files that match the specified patterns to the list of helper files.
|
||||
* @function
|
||||
* @name Jasmine#addMatchingHelperFiles
|
||||
* @param {Array<string>} patterns An array of helper file paths
|
||||
* or {@link https://github.com/isaacs/node-glob#glob-primer|globs} that match
|
||||
* helper files. Each path or glob will be evaluated relative to the spec directory.
|
||||
*/
|
||||
Jasmine.prototype.addMatchingHelperFiles = addFiles('helperFiles');
|
||||
|
||||
function addFiles(kind) {
|
||||
return function (files) {
|
||||
const jasmineRunner = this;
|
||||
const fileArr = this[kind];
|
||||
|
||||
const {includeFiles, excludeFiles} = files.reduce(function(ongoing, file) {
|
||||
const hasNegation = file.startsWith('!');
|
||||
|
||||
if (hasNegation) {
|
||||
file = file.substring(1);
|
||||
}
|
||||
|
||||
if (!path.isAbsolute(file)) {
|
||||
file = path.join(jasmineRunner.projectBaseDir, jasmineRunner.specDir, file);
|
||||
}
|
||||
|
||||
return {
|
||||
includeFiles: ongoing.includeFiles.concat(!hasNegation ? [file] : []),
|
||||
excludeFiles: ongoing.excludeFiles.concat(hasNegation ? [file] : [])
|
||||
};
|
||||
}, { includeFiles: [], excludeFiles: [] });
|
||||
|
||||
includeFiles.forEach(function(file) {
|
||||
const filePaths = glob
|
||||
.sync(file, { ignore: excludeFiles })
|
||||
.filter(function(filePath) {
|
||||
// glob will always output '/' as a segment separator but the fileArr may use \ on windows
|
||||
// fileArr needs to be checked for both versions
|
||||
return fileArr.indexOf(filePath) === -1 && fileArr.indexOf(path.normalize(filePath)) === -1;
|
||||
});
|
||||
|
||||
filePaths.forEach(function(filePath) {
|
||||
fileArr.push(filePath);
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function exitCodeForStatus(status) {
|
||||
switch (status) {
|
||||
case 'passed':
|
||||
return 0;
|
||||
case 'incomplete':
|
||||
return 2;
|
||||
case 'failed':
|
||||
return 3;
|
||||
default:
|
||||
console.error(`Unrecognized overall status: ${status}`);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Jasmine;
|
||||
module.exports.ConsoleReporter = require('./reporters/console_reporter');
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
const path = require('path');
|
||||
|
||||
class Loader {
|
||||
constructor(options) {
|
||||
options = options || {};
|
||||
this.require_ = options.requireShim || requireShim;
|
||||
this.import_ = options.importShim || importShim;
|
||||
this.resolvePath_ = options.resolvePath || path.resolve.bind(path);
|
||||
this.alwaysImport = true;
|
||||
}
|
||||
|
||||
load(modulePath) {
|
||||
if ((this.alwaysImport && !modulePath.endsWith('.json')) || modulePath.endsWith('.mjs')) {
|
||||
let importSpecifier;
|
||||
|
||||
if (modulePath.indexOf(path.sep) === -1 && modulePath.indexOf('/') === -1) {
|
||||
importSpecifier = modulePath;
|
||||
} else {
|
||||
// The ES module spec requires import paths to be valid URLs. As of v14,
|
||||
// Node enforces this on Windows but not on other OSes. On OS X, import
|
||||
// paths that are URLs must not contain parent directory references.
|
||||
importSpecifier = `file://${this.resolvePath_(modulePath)}`;
|
||||
}
|
||||
|
||||
return this.import_(importSpecifier)
|
||||
.then(
|
||||
mod => mod.default,
|
||||
e => {
|
||||
if (e.code === 'ERR_UNKNOWN_FILE_EXTENSION') {
|
||||
// Extension isn't supported by import, e.g. .jsx. Fall back to
|
||||
// require(). This could lead to confusing error messages if someone
|
||||
// tries to use ES module syntax without transpiling in a file with
|
||||
// an unsupported extension, but it shouldn't break anything and it
|
||||
// should work well in the normal case where the file is loadable
|
||||
// as a CommonJS module, either directly or with the help of a
|
||||
// loader like `@babel/register`.
|
||||
return this.require_(modulePath);
|
||||
} else {
|
||||
return Promise.reject(fixupImportException(e, modulePath));
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
return new Promise(resolve => {
|
||||
const result = this.require_(modulePath);
|
||||
resolve(result);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function requireShim(modulePath) {
|
||||
return require(modulePath);
|
||||
}
|
||||
|
||||
function importShim(modulePath) {
|
||||
return import(modulePath);
|
||||
}
|
||||
|
||||
|
||||
function fixupImportException(e, importedPath) {
|
||||
// When an ES module has a syntax error, the resulting exception does not
|
||||
// include the filename, which the user will need to debug the problem. We
|
||||
// need to fix those up to include the filename. However, other kinds of load-
|
||||
// time errors *do* include the filename and usually the line number. We need
|
||||
// to leave those alone.
|
||||
//
|
||||
// Some examples of load-time errors that we need to deal with:
|
||||
// 1. Syntax error in an ESM spec:
|
||||
// SyntaxError: missing ) after argument list
|
||||
// at Loader.moduleStrategy (node:internal/modules/esm/translators:147:18)
|
||||
// at async link (node:internal/modules/esm/module_job:64:21)
|
||||
//
|
||||
// 2. Syntax error in an ES module imported from an ESM spec. This is exactly
|
||||
// the same as #1: there is no way to tell which file actually has the syntax
|
||||
// error.
|
||||
//
|
||||
// 3. Syntax error in a CommonJS module imported by an ES module:
|
||||
// /path/to/commonjs_with_syntax_error.js:2
|
||||
//
|
||||
//
|
||||
//
|
||||
// SyntaxError: Unexpected end of input
|
||||
// at Object.compileFunction (node:vm:355:18)
|
||||
// at wrapSafe (node:internal/modules/cjs/loader:1038:15)
|
||||
// at Module._compile (node:internal/modules/cjs/loader:1072:27)
|
||||
// at Object.Module._extensions..js (node:internal/modules/cjs/loader:1137:10)
|
||||
// at Module.load (node:internal/modules/cjs/loader:988:32)
|
||||
// at Function.Module._load (node:internal/modules/cjs/loader:828:14)
|
||||
// at ModuleWrap.<anonymous> (node:internal/modules/esm/translators:201:29)
|
||||
// at ModuleJob.run (node:internal/modules/esm/module_job:175:25)
|
||||
// at async Loader.import (node:internal/modules/esm/loader:178:24)
|
||||
// at async file:///path/to/esm_that_imported_cjs.mjs:2:11
|
||||
//
|
||||
// Note: For Jasmine's purposes, case 3 only occurs in Node >= 14.8. Older
|
||||
// versions don't support top-level await, without which it's not possible to
|
||||
// load a CommonJS module from an ES module at load-time. The entire content
|
||||
// above, including the file path and the three blank lines, is part of the
|
||||
// error's `stack` property. There may or may not be any stack trace after the
|
||||
// SyntaxError line, and if there's a stack trace it may or may not contain
|
||||
// any useful information.
|
||||
//
|
||||
// 4. Any other kind of exception thrown at load time
|
||||
//
|
||||
// Error: nope
|
||||
// at Object.<anonymous> (/path/to/file_throwing_error.js:1:7)
|
||||
// at Module._compile (node:internal/modules/cjs/loader:1108:14)
|
||||
// at Object.Module._extensions..js (node:internal/modules/cjs/loader:1137:10)
|
||||
// at Module.load (node:internal/modules/cjs/loader:988:32)
|
||||
// at Function.Module._load (node:internal/modules/cjs/loader:828:14)
|
||||
// at ModuleWrap.<anonymous> (node:internal/modules/esm/translators:201:29)
|
||||
// at ModuleJob.run (node:internal/modules/esm/module_job:175:25)
|
||||
// at async Loader.import (node:internal/modules/esm/loader:178:24)
|
||||
// at async file:///path_to_file_importing_broken_file.mjs:1:1
|
||||
//
|
||||
// We need to replace the error with a useful one in cases 1 and 2, but not in
|
||||
// cases 3 and 4. Distinguishing among them can be tricky. Simple heuristics
|
||||
// like checking the stack trace for the name of the file we imported fail
|
||||
// because it often shows up even when the error was elsewhere, e.g. at the
|
||||
// bottom of the stack traces in the examples for cases 3 and 4 above. To add
|
||||
// to the fun, file paths in errors on Windows can be either Windows style
|
||||
// paths (c:\path\to\file.js) or URLs (file:///c:/path/to/file.js).
|
||||
|
||||
if (!(e instanceof SyntaxError)) {
|
||||
return e;
|
||||
}
|
||||
|
||||
const escapedWin = escapeStringForRegexp(importedPath.replace(/\//g, '\\'));
|
||||
const windowsPathRegex = new RegExp('[a-zA-z]:\\\\([^\\s]+\\\\|)' + escapedWin);
|
||||
const windowsUrlRegex = new RegExp('file:///[a-zA-z]:\\\\([^\\s]+\\\\|)' + escapedWin);
|
||||
const anyUnixPathFirstLineRegex = /^\/[^\s:]+:\d/;
|
||||
const anyWindowsPathFirstLineRegex = /^[a-zA-Z]:(\\[^\s\\:]+)+:/;
|
||||
|
||||
if (e.message.indexOf(importedPath) !== -1
|
||||
|| e.stack.indexOf(importedPath) !== -1
|
||||
|| e.stack.match(windowsPathRegex) || e.stack.match(windowsUrlRegex)
|
||||
|| e.stack.match(anyUnixPathFirstLineRegex)
|
||||
|| e.stack.match(anyWindowsPathFirstLineRegex)) {
|
||||
return e;
|
||||
} else {
|
||||
return new Error(`While loading ${importedPath}: ${e.constructor.name}: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Adapted from Sindre Sorhus's escape-string-regexp (MIT license)
|
||||
function escapeStringForRegexp(string) {
|
||||
// Escape characters with special meaning either inside or outside character sets.
|
||||
// Use a simple backslash escape when it’s always valid, and a `\xnn` escape when the simpler form would be disallowed by Unicode patterns’ stricter grammar.
|
||||
return string
|
||||
.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&')
|
||||
.replace(/-/g, '\\x2d');
|
||||
}
|
||||
|
||||
module.exports = Loader;
|
||||
+289
@@ -0,0 +1,289 @@
|
||||
module.exports = exports = ConsoleReporter;
|
||||
|
||||
/**
|
||||
* @classdesc A reporter that prints spec and suite results to the console.
|
||||
* A ConsoleReporter is installed by default.
|
||||
*
|
||||
* @constructor
|
||||
* @example
|
||||
* const {ConsoleReporter} = require('jasmine');
|
||||
* const reporter = new ConsoleReporter();
|
||||
*/
|
||||
function ConsoleReporter() {
|
||||
let print = function() {},
|
||||
showColors = false,
|
||||
specCount,
|
||||
executableSpecCount,
|
||||
failureCount,
|
||||
failedSpecs = [],
|
||||
pendingSpecs = [],
|
||||
alwaysListPendingSpecs = true,
|
||||
ansi = {
|
||||
green: '\x1B[32m',
|
||||
red: '\x1B[31m',
|
||||
yellow: '\x1B[33m',
|
||||
none: '\x1B[0m'
|
||||
},
|
||||
failedSuites = [],
|
||||
stackFilter = stack => stack;
|
||||
|
||||
/**
|
||||
* Configures the reporter.
|
||||
* @function
|
||||
* @name ConsoleReporter#setOptions
|
||||
* @param {ConsoleReporterOptions} options
|
||||
*/
|
||||
this.setOptions = function(options) {
|
||||
if (options.print) {
|
||||
print = options.print;
|
||||
}
|
||||
|
||||
/**
|
||||
* @interface ConsoleReporterOptions
|
||||
*/
|
||||
/**
|
||||
* Whether to colorize the output
|
||||
* @name ConsoleReporterOptions#showColors
|
||||
* @type Boolean|undefined
|
||||
* @default false
|
||||
*/
|
||||
showColors = options.showColors || false;
|
||||
if (options.stackFilter) {
|
||||
stackFilter = options.stackFilter;
|
||||
}
|
||||
/**
|
||||
* A function that takes a random seed and returns the command to reproduce
|
||||
* that seed. Use this to customize the output when using ConsoleReporter
|
||||
* in a different command line tool.
|
||||
* @name ConsoleReporterOptions#randomSeedReproductionCmd
|
||||
* @type Function|undefined
|
||||
*/
|
||||
if (options.randomSeedReproductionCmd) {
|
||||
this.randomSeedReproductionCmd = options.randomSeedReproductionCmd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to list pending specs even if there are failures.
|
||||
* @name ConsoleReporterOptions#alwaysListPendingSpecs
|
||||
* @type Boolean|undefined
|
||||
* @default true
|
||||
*/
|
||||
if (options.alwaysListPendingSpecs !== undefined) {
|
||||
alwaysListPendingSpecs = options.alwaysListPendingSpecs;
|
||||
}
|
||||
};
|
||||
|
||||
this.jasmineStarted = function(options) {
|
||||
specCount = 0;
|
||||
executableSpecCount = 0;
|
||||
failureCount = 0;
|
||||
if (options && options.order && options.order.random) {
|
||||
print('Randomized with seed ' + options.order.seed);
|
||||
printNewline();
|
||||
}
|
||||
print('Started');
|
||||
printNewline();
|
||||
};
|
||||
|
||||
this.jasmineDone = function(result) {
|
||||
if (result.failedExpectations) {
|
||||
failureCount += result.failedExpectations.length;
|
||||
}
|
||||
|
||||
printNewline();
|
||||
printNewline();
|
||||
if (failedSpecs.length > 0) {
|
||||
print('Failures:');
|
||||
}
|
||||
for (let i = 0; i < failedSpecs.length; i++) {
|
||||
specFailureDetails(failedSpecs[i], i + 1);
|
||||
}
|
||||
|
||||
for(let i = 0; i < failedSuites.length; i++) {
|
||||
suiteFailureDetails(failedSuites[i]);
|
||||
}
|
||||
|
||||
if (result && result.failedExpectations && result.failedExpectations.length > 0) {
|
||||
suiteFailureDetails({ fullName: 'top suite', ...result });
|
||||
}
|
||||
|
||||
if (alwaysListPendingSpecs || result.overallStatus === 'passed') {
|
||||
if (pendingSpecs.length > 0) {
|
||||
print("Pending:");
|
||||
}
|
||||
for (let i = 0; i < pendingSpecs.length; i++) {
|
||||
pendingSpecDetails(pendingSpecs[i], i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if(specCount > 0) {
|
||||
printNewline();
|
||||
|
||||
if(executableSpecCount !== specCount) {
|
||||
print('Ran ' + executableSpecCount + ' of ' + specCount + plural(' spec', specCount));
|
||||
printNewline();
|
||||
}
|
||||
let specCounts = executableSpecCount + ' ' + plural('spec', executableSpecCount) + ', ' +
|
||||
failureCount + ' ' + plural('failure', failureCount);
|
||||
|
||||
if (pendingSpecs.length) {
|
||||
specCounts += ', ' + pendingSpecs.length + ' pending ' + plural('spec', pendingSpecs.length);
|
||||
}
|
||||
|
||||
print(specCounts);
|
||||
} else {
|
||||
print('No specs found');
|
||||
}
|
||||
|
||||
printNewline();
|
||||
const seconds = result ? result.totalTime / 1000 : 0;
|
||||
print('Finished in ' + seconds + ' ' + plural('second', seconds));
|
||||
printNewline();
|
||||
|
||||
if (result && result.overallStatus === 'incomplete') {
|
||||
print('Incomplete: ' + result.incompleteReason);
|
||||
printNewline();
|
||||
}
|
||||
|
||||
if (result && result.order && result.order.random) {
|
||||
print('Randomized with seed ' + result.order.seed);
|
||||
print(' (' + this.randomSeedReproductionCmd(result.order.seed) + ')');
|
||||
printNewline();
|
||||
}
|
||||
};
|
||||
|
||||
this.randomSeedReproductionCmd = function(seed) {
|
||||
return 'jasmine --random=true --seed=' + seed;
|
||||
};
|
||||
|
||||
this.specDone = function(result) {
|
||||
specCount++;
|
||||
|
||||
if (result.status == 'pending') {
|
||||
pendingSpecs.push(result);
|
||||
executableSpecCount++;
|
||||
print(colored('yellow', '*'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.status == 'passed') {
|
||||
executableSpecCount++;
|
||||
print(colored('green', '.'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.status == 'failed') {
|
||||
failureCount++;
|
||||
failedSpecs.push(result);
|
||||
executableSpecCount++;
|
||||
print(colored('red', 'F'));
|
||||
}
|
||||
};
|
||||
|
||||
this.suiteDone = function(result) {
|
||||
if (result.failedExpectations && result.failedExpectations.length > 0) {
|
||||
failureCount++;
|
||||
failedSuites.push(result);
|
||||
}
|
||||
};
|
||||
|
||||
return this;
|
||||
|
||||
function printNewline() {
|
||||
print('\n');
|
||||
}
|
||||
|
||||
function colored(color, str) {
|
||||
return showColors ? (ansi[color] + str + ansi.none) : str;
|
||||
}
|
||||
|
||||
function plural(str, count) {
|
||||
return count == 1 ? str : str + 's';
|
||||
}
|
||||
|
||||
function repeat(thing, times) {
|
||||
const arr = [];
|
||||
for (let i = 0; i < times; i++) {
|
||||
arr.push(thing);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
function indent(str, spaces) {
|
||||
const lines = (str || '').split('\n');
|
||||
const newArr = [];
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
newArr.push(repeat(' ', spaces).join('') + lines[i]);
|
||||
}
|
||||
return newArr.join('\n');
|
||||
}
|
||||
|
||||
function specFailureDetails(result, failedSpecNumber) {
|
||||
printNewline();
|
||||
print(failedSpecNumber + ') ');
|
||||
print(result.fullName);
|
||||
printFailedExpectations(result);
|
||||
|
||||
if (result.debugLogs) {
|
||||
printNewline();
|
||||
print(indent('Debug logs:', 2));
|
||||
printNewline();
|
||||
|
||||
for (const entry of result.debugLogs) {
|
||||
print(indent(`${entry.timestamp}ms: ${entry.message}`, 4));
|
||||
printNewline();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function suiteFailureDetails(result) {
|
||||
printNewline();
|
||||
print('Suite error: ' + result.fullName);
|
||||
printFailedExpectations(result);
|
||||
}
|
||||
|
||||
function printFailedExpectations(result) {
|
||||
for (let i = 0; i < result.failedExpectations.length; i++) {
|
||||
const failedExpectation = result.failedExpectations[i];
|
||||
printNewline();
|
||||
print(indent('Message:', 2));
|
||||
printNewline();
|
||||
print(colored('red', indent(failedExpectation.message, 4)));
|
||||
printNewline();
|
||||
print(indent('Stack:', 2));
|
||||
printNewline();
|
||||
print(indent(stackFilter(failedExpectation.stack), 4));
|
||||
}
|
||||
|
||||
// When failSpecWithNoExpectations = true and a spec fails because of no expectations found,
|
||||
// jasmine-core reports it as a failure with no message.
|
||||
//
|
||||
// Therefore we assume that when there are no failed or passed expectations,
|
||||
// the failure was because of our failSpecWithNoExpectations setting.
|
||||
//
|
||||
// Same logic is used by jasmine.HtmlReporter, see https://github.com/jasmine/jasmine/blob/main/src/html/HtmlReporter.js
|
||||
if (result.failedExpectations.length === 0 &&
|
||||
result.passedExpectations.length === 0) {
|
||||
printNewline();
|
||||
print(indent('Message:', 2));
|
||||
printNewline();
|
||||
print(colored('red', indent('Spec has no expectations', 4)));
|
||||
}
|
||||
|
||||
printNewline();
|
||||
}
|
||||
|
||||
function pendingSpecDetails(result, pendingSpecNumber) {
|
||||
printNewline();
|
||||
printNewline();
|
||||
print(pendingSpecNumber + ') ');
|
||||
print(result.fullName);
|
||||
printNewline();
|
||||
let pendingReason = "No reason given";
|
||||
if (result.pendingReason && result.pendingReason !== '') {
|
||||
pendingReason = result.pendingReason;
|
||||
}
|
||||
print(indent(colored('yellow', pendingReason), 2));
|
||||
printNewline();
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"name": "jasmine",
|
||||
"description": "CLI for Jasmine, a simple JavaScript testing framework for browsers and Node",
|
||||
"homepage": "http://jasmine.github.io/",
|
||||
"keywords": [
|
||||
"test",
|
||||
"testing",
|
||||
"jasmine",
|
||||
"tdd",
|
||||
"bdd"
|
||||
],
|
||||
"license": "MIT",
|
||||
"version": "4.4.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/jasmine/jasmine-npm"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "node ./bin/jasmine.js",
|
||||
"posttest": "eslint \"bin/**/*.js\" \"lib/**/*.js\" \"spec/**/*.js\""
|
||||
},
|
||||
"exports": "./lib/jasmine.js",
|
||||
"files": [
|
||||
"bin",
|
||||
"lib",
|
||||
"MIT.LICENSE",
|
||||
"package.json",
|
||||
"README.md"
|
||||
],
|
||||
"dependencies": {
|
||||
"glob": "^7.1.6",
|
||||
"jasmine-core": "^4.4.0"
|
||||
},
|
||||
"bin": "./bin/jasmine.js",
|
||||
"main": "./lib/jasmine.js",
|
||||
"devDependencies": {
|
||||
"eslint": "^6.8.0",
|
||||
"grunt": "^1.0.4",
|
||||
"grunt-cli": "^1.3.2",
|
||||
"shelljs": "^0.8.3",
|
||||
"slash": "^3.0.0",
|
||||
"temp": "^0.9.4"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 11
|
||||
},
|
||||
"rules": {
|
||||
"no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
"args": "none"
|
||||
}
|
||||
],
|
||||
"block-spacing": "error",
|
||||
"func-call-spacing": [
|
||||
"error",
|
||||
"never"
|
||||
],
|
||||
"key-spacing": "error",
|
||||
"no-tabs": "error",
|
||||
"no-whitespace-before-property": "error",
|
||||
"semi": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"space-before-blocks": "error"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user