solved task 2

This commit is contained in:
schrom01
2022-10-20 10:56:45 +02:00
parent b5dfdc4cdd
commit 749fe3a3fb
97 changed files with 19785 additions and 0 deletions
+20
View File
@@ -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
View File
@@ -0,0 +1,67 @@
[![Build Status](https://circleci.com/gh/jasmine/jasmine-npm.svg?style=shield)](https://circleci.com/gh/jasmine/jasmine-npm)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fjasmine%2Fjasmine-npm.svg?type=shield)](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
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fjasmine%2Fjasmine-npm.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fjasmine%2Fjasmine-npm?ref=badge_large)
+11
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,13 @@
{
"spec_dir": "spec",
"spec_files": [
"**/*[sS]pec.?(m)js"
],
"helpers": [
"helpers/**/*.?(m)js"
],
"env": {
"stopSpecOnExpectationFailure": false,
"random": true
}
}
+15
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 its 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
View File
@@ -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
View File
@@ -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"
}
}
}