$
This commit is contained in:
88
node_modules/mocha/lib/cli/cli.js
generated
vendored
Normal file
88
node_modules/mocha/lib/cli/cli.js
generated
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Contains CLI entry point and public API for programmatic usage in Node.js.
|
||||
* - Option parsing is handled by {@link https://npm.im/yargs yargs}.
|
||||
* - If executed via `node`, this module will run {@linkcode module:lib/cli.main main()}.
|
||||
* @public
|
||||
* @module lib/cli
|
||||
*/
|
||||
|
||||
const debug = require('debug')('mocha:cli:cli');
|
||||
const symbols = require('log-symbols');
|
||||
const yargs = require('yargs/yargs');
|
||||
const path = require('path');
|
||||
const {
|
||||
loadRc,
|
||||
loadPkgRc,
|
||||
loadOptions,
|
||||
YARGS_PARSER_CONFIG
|
||||
} = require('./options');
|
||||
const lookupFiles = require('./lookup-files');
|
||||
const commands = require('./commands');
|
||||
const ansi = require('ansi-colors');
|
||||
const {repository, homepage, version, gitter} = require('../../package.json');
|
||||
const {cwd} = require('../utils');
|
||||
|
||||
/**
|
||||
* - Accepts an `Array` of arguments
|
||||
* - Modifies {@link https://nodejs.org/api/modules.html#modules_module_paths Node.js' search path} for easy loading of consumer modules
|
||||
* - Sets {@linkcode https://nodejs.org/api/errors.html#errors_error_stacktracelimit Error.stackTraceLimit} to `Infinity`
|
||||
* @public
|
||||
* @summary Mocha's main command-line entry-point.
|
||||
* @param {string[]} argv - Array of arguments to parse, or by default the lovely `process.argv.slice(2)`
|
||||
*/
|
||||
exports.main = (argv = process.argv.slice(2)) => {
|
||||
debug('entered main with raw args', argv);
|
||||
// ensure we can require() from current working directory
|
||||
if (typeof module.paths !== 'undefined') {
|
||||
module.paths.push(cwd(), path.resolve('node_modules'));
|
||||
}
|
||||
|
||||
Error.stackTraceLimit = Infinity; // configurable via --stack-trace-limit?
|
||||
|
||||
var args = loadOptions(argv);
|
||||
|
||||
yargs()
|
||||
.scriptName('mocha')
|
||||
.command(commands.run)
|
||||
.command(commands.init)
|
||||
.updateStrings({
|
||||
'Positionals:': 'Positional Arguments',
|
||||
'Options:': 'Other Options',
|
||||
'Commands:': 'Commands'
|
||||
})
|
||||
.fail((msg, err, yargs) => {
|
||||
debug('caught error sometime before command handler: %O', err);
|
||||
yargs.showHelp();
|
||||
console.error(`\n${symbols.error} ${ansi.red('ERROR:')} ${msg}`);
|
||||
process.exitCode = 1;
|
||||
})
|
||||
.help('help', 'Show usage information & exit')
|
||||
.alias('help', 'h')
|
||||
.version('version', 'Show version number & exit', version)
|
||||
.alias('version', 'V')
|
||||
.wrap(process.stdout.columns ? Math.min(process.stdout.columns, 80) : 80)
|
||||
.epilog(
|
||||
`Mocha Resources
|
||||
Chat: ${ansi.magenta(gitter)}
|
||||
GitHub: ${ansi.blue(repository.url)}
|
||||
Docs: ${ansi.yellow(homepage)}
|
||||
`
|
||||
)
|
||||
.parserConfiguration(YARGS_PARSER_CONFIG)
|
||||
.config(args)
|
||||
.parse(args._);
|
||||
};
|
||||
|
||||
exports.lookupFiles = lookupFiles;
|
||||
exports.loadOptions = loadOptions;
|
||||
exports.loadPkgRc = loadPkgRc;
|
||||
exports.loadRc = loadRc;
|
||||
|
||||
// allow direct execution
|
||||
if (require.main === module) {
|
||||
exports.main();
|
||||
}
|
92
node_modules/mocha/lib/cli/collect-files.js
generated
vendored
Normal file
92
node_modules/mocha/lib/cli/collect-files.js
generated
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const ansi = require('ansi-colors');
|
||||
const debug = require('debug')('mocha:cli:run:helpers');
|
||||
const minimatch = require('minimatch');
|
||||
const {NO_FILES_MATCH_PATTERN} = require('../errors').constants;
|
||||
const lookupFiles = require('./lookup-files');
|
||||
const {castArray} = require('../utils');
|
||||
|
||||
/**
|
||||
* Exports a function that collects test files from CLI parameters.
|
||||
* @see module:lib/cli/run-helpers
|
||||
* @see module:lib/cli/watch-run
|
||||
* @module
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* Smash together an array of test files in the correct order
|
||||
* @param {FileCollectionOptions} [opts] - Options
|
||||
* @returns {string[]} List of files to test
|
||||
* @private
|
||||
*/
|
||||
module.exports = ({
|
||||
ignore,
|
||||
extension,
|
||||
file: fileArgs,
|
||||
recursive,
|
||||
sort,
|
||||
spec
|
||||
} = {}) => {
|
||||
const unmatched = [];
|
||||
const specFiles = spec.reduce((specFiles, arg) => {
|
||||
try {
|
||||
const moreSpecFiles = castArray(lookupFiles(arg, extension, recursive))
|
||||
.filter(filename =>
|
||||
ignore.every(pattern => !minimatch(filename, pattern))
|
||||
)
|
||||
.map(filename => path.resolve(filename));
|
||||
return [...specFiles, ...moreSpecFiles];
|
||||
} catch (err) {
|
||||
if (err.code === NO_FILES_MATCH_PATTERN) {
|
||||
unmatched.push({message: err.message, pattern: err.pattern});
|
||||
return specFiles;
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
}, []);
|
||||
|
||||
// ensure we don't sort the stuff from fileArgs; order is important!
|
||||
if (sort) {
|
||||
specFiles.sort();
|
||||
}
|
||||
|
||||
// add files given through --file to be ran first
|
||||
const files = [
|
||||
...fileArgs.map(filepath => path.resolve(filepath)),
|
||||
...specFiles
|
||||
];
|
||||
debug('test files (in order): ', files);
|
||||
|
||||
if (!files.length) {
|
||||
// give full message details when only 1 file is missing
|
||||
const noneFoundMsg =
|
||||
unmatched.length === 1
|
||||
? `Error: No test files found: ${JSON.stringify(unmatched[0].pattern)}` // stringify to print escaped characters raw
|
||||
: 'Error: No test files found';
|
||||
console.error(ansi.red(noneFoundMsg));
|
||||
process.exit(1);
|
||||
} else {
|
||||
// print messages as a warning
|
||||
unmatched.forEach(warning => {
|
||||
console.warn(ansi.yellow(`Warning: ${warning.message}`));
|
||||
});
|
||||
}
|
||||
|
||||
return files;
|
||||
};
|
||||
|
||||
/**
|
||||
* An object to configure how Mocha gathers test files
|
||||
* @private
|
||||
* @typedef {Object} FileCollectionOptions
|
||||
* @property {string[]} extension - File extensions to use
|
||||
* @property {string[]} spec - Files, dirs, globs to run
|
||||
* @property {string[]} ignore - Files, dirs, globs to ignore
|
||||
* @property {string[]} file - List of additional files to include
|
||||
* @property {boolean} recursive - Find files recursively
|
||||
* @property {boolean} sort - Sort test files
|
||||
*/
|
13
node_modules/mocha/lib/cli/commands.js
generated
vendored
Normal file
13
node_modules/mocha/lib/cli/commands.js
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Exports Yargs commands
|
||||
* @see https://git.io/fpJ0G
|
||||
* @private
|
||||
* @module
|
||||
*/
|
||||
|
||||
exports.init = require('./init');
|
||||
|
||||
// default command
|
||||
exports.run = require('./run');
|
105
node_modules/mocha/lib/cli/config.js
generated
vendored
Normal file
105
node_modules/mocha/lib/cli/config.js
generated
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Responsible for loading / finding Mocha's "rc" files.
|
||||
*
|
||||
* @private
|
||||
* @module
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const debug = require('debug')('mocha:cli:config');
|
||||
const findUp = require('find-up');
|
||||
const {createUnparsableFileError} = require('../errors');
|
||||
const utils = require('../utils');
|
||||
|
||||
/**
|
||||
* These are the valid config files, in order of precedence;
|
||||
* e.g., if `.mocharc.js` is present, then `.mocharc.yaml` and the rest
|
||||
* will be ignored.
|
||||
* The user should still be able to explicitly specify a file.
|
||||
* @private
|
||||
*/
|
||||
exports.CONFIG_FILES = [
|
||||
'.mocharc.cjs',
|
||||
'.mocharc.js',
|
||||
'.mocharc.yaml',
|
||||
'.mocharc.yml',
|
||||
'.mocharc.jsonc',
|
||||
'.mocharc.json'
|
||||
];
|
||||
|
||||
const isModuleNotFoundError = err =>
|
||||
err.code !== 'MODULE_NOT_FOUND' ||
|
||||
err.message.indexOf('Cannot find module') !== -1;
|
||||
|
||||
/**
|
||||
* Parsers for various config filetypes. Each accepts a filepath and
|
||||
* returns an object (but could throw)
|
||||
*/
|
||||
const parsers = (exports.parsers = {
|
||||
yaml: filepath => require('js-yaml').load(fs.readFileSync(filepath, 'utf8')),
|
||||
js: filepath => {
|
||||
const cwdFilepath = path.resolve(filepath);
|
||||
try {
|
||||
debug('parsers: load using cwd-relative path: "%s"', cwdFilepath);
|
||||
return require(cwdFilepath);
|
||||
} catch (err) {
|
||||
if (isModuleNotFoundError(err)) {
|
||||
debug('parsers: retry load as module-relative path: "%s"', filepath);
|
||||
return require(filepath);
|
||||
} else {
|
||||
throw err; // rethrow
|
||||
}
|
||||
}
|
||||
},
|
||||
json: filepath =>
|
||||
JSON.parse(
|
||||
require('strip-json-comments')(fs.readFileSync(filepath, 'utf8'))
|
||||
)
|
||||
});
|
||||
|
||||
/**
|
||||
* Loads and parses, based on file extension, a config file.
|
||||
* "JSON" files may have comments.
|
||||
*
|
||||
* @private
|
||||
* @param {string} filepath - Config file path to load
|
||||
* @returns {Object} Parsed config object
|
||||
*/
|
||||
exports.loadConfig = filepath => {
|
||||
let config = {};
|
||||
debug('loadConfig: trying to parse config at %s', filepath);
|
||||
|
||||
const ext = path.extname(filepath);
|
||||
try {
|
||||
if (ext === '.yml' || ext === '.yaml') {
|
||||
config = parsers.yaml(filepath);
|
||||
} else if (ext === '.js' || ext === '.cjs') {
|
||||
config = parsers.js(filepath);
|
||||
} else {
|
||||
config = parsers.json(filepath);
|
||||
}
|
||||
} catch (err) {
|
||||
throw createUnparsableFileError(
|
||||
`Unable to read/parse ${filepath}: ${err}`,
|
||||
filepath
|
||||
);
|
||||
}
|
||||
return config;
|
||||
};
|
||||
|
||||
/**
|
||||
* Find ("find up") config file starting at `cwd`
|
||||
*
|
||||
* @param {string} [cwd] - Current working directory
|
||||
* @returns {string|null} Filepath to config, if found
|
||||
*/
|
||||
exports.findConfig = (cwd = utils.cwd()) => {
|
||||
const filepath = findUp.sync(exports.CONFIG_FILES, {cwd});
|
||||
if (filepath) {
|
||||
debug('findConfig: found config file %s', filepath);
|
||||
}
|
||||
return filepath;
|
||||
};
|
3
node_modules/mocha/lib/cli/index.js
generated
vendored
Normal file
3
node_modules/mocha/lib/cli/index.js
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = require('./cli');
|
36
node_modules/mocha/lib/cli/init.js
generated
vendored
Normal file
36
node_modules/mocha/lib/cli/init.js
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Command module for "init" command
|
||||
*
|
||||
* @private
|
||||
* @module
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
exports.command = 'init <path>';
|
||||
|
||||
exports.description = 'create a client-side Mocha setup at <path>';
|
||||
|
||||
exports.builder = yargs =>
|
||||
yargs.positional('path', {
|
||||
type: 'string',
|
||||
normalize: true
|
||||
});
|
||||
|
||||
exports.handler = argv => {
|
||||
const destdir = argv.path;
|
||||
const srcdir = path.join(__dirname, '..', '..');
|
||||
fs.mkdirSync(destdir, {recursive: true});
|
||||
const css = fs.readFileSync(path.join(srcdir, 'mocha.css'));
|
||||
const js = fs.readFileSync(path.join(srcdir, 'mocha.js'));
|
||||
const tmpl = fs.readFileSync(
|
||||
path.join(srcdir, 'lib', 'browser', 'template.html')
|
||||
);
|
||||
fs.writeFileSync(path.join(destdir, 'mocha.css'), css);
|
||||
fs.writeFileSync(path.join(destdir, 'mocha.js'), js);
|
||||
fs.writeFileSync(path.join(destdir, 'tests.spec.js'), '');
|
||||
fs.writeFileSync(path.join(destdir, 'index.html'), tmpl);
|
||||
};
|
151
node_modules/mocha/lib/cli/lookup-files.js
generated
vendored
Normal file
151
node_modules/mocha/lib/cli/lookup-files.js
generated
vendored
Normal file
@@ -0,0 +1,151 @@
|
||||
'use strict';
|
||||
/**
|
||||
* Contains `lookupFiles`, which takes some globs/dirs/options and returns a list of files.
|
||||
* @module
|
||||
* @private
|
||||
*/
|
||||
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var glob = require('glob');
|
||||
var {format} = require('util');
|
||||
var errors = require('../errors');
|
||||
var createNoFilesMatchPatternError = errors.createNoFilesMatchPatternError;
|
||||
var createMissingArgumentError = errors.createMissingArgumentError;
|
||||
var {sQuote, dQuote} = require('../utils');
|
||||
const debug = require('debug')('mocha:cli:lookup-files');
|
||||
|
||||
/**
|
||||
* Determines if pathname would be a "hidden" file (or directory) on UN*X.
|
||||
*
|
||||
* @description
|
||||
* On UN*X, pathnames beginning with a full stop (aka dot) are hidden during
|
||||
* typical usage. Dotfiles, plain-text configuration files, are prime examples.
|
||||
*
|
||||
* @see {@link http://xahlee.info/UnixResource_dir/writ/unix_origin_of_dot_filename.html|Origin of Dot File Names}
|
||||
*
|
||||
* @private
|
||||
* @param {string} pathname - Pathname to check for match.
|
||||
* @return {boolean} whether pathname would be considered a hidden file.
|
||||
* @example
|
||||
* isHiddenOnUnix('.profile'); // => true
|
||||
*/
|
||||
const isHiddenOnUnix = pathname => path.basename(pathname).startsWith('.');
|
||||
|
||||
/**
|
||||
* Determines if pathname has a matching file extension.
|
||||
*
|
||||
* Supports multi-part extensions.
|
||||
*
|
||||
* @private
|
||||
* @param {string} pathname - Pathname to check for match.
|
||||
* @param {string[]} exts - List of file extensions, w/-or-w/o leading period
|
||||
* @return {boolean} `true` if file extension matches.
|
||||
* @example
|
||||
* hasMatchingExtname('foo.html', ['js', 'css']); // false
|
||||
* hasMatchingExtname('foo.js', ['.js']); // true
|
||||
* hasMatchingExtname('foo.js', ['js']); // ture
|
||||
*/
|
||||
const hasMatchingExtname = (pathname, exts = []) =>
|
||||
exts
|
||||
.map(ext => (ext.startsWith('.') ? ext : `.${ext}`))
|
||||
.some(ext => pathname.endsWith(ext));
|
||||
|
||||
/**
|
||||
* Lookup file names at the given `path`.
|
||||
*
|
||||
* @description
|
||||
* Filenames are returned in _traversal_ order by the OS/filesystem.
|
||||
* **Make no assumption that the names will be sorted in any fashion.**
|
||||
*
|
||||
* @public
|
||||
* @alias module:lib/cli.lookupFiles
|
||||
* @param {string} filepath - Base path to start searching from.
|
||||
* @param {string[]} [extensions=[]] - File extensions to look for.
|
||||
* @param {boolean} [recursive=false] - Whether to recurse into subdirectories.
|
||||
* @return {string[]} An array of paths.
|
||||
* @throws {Error} if no files match pattern.
|
||||
* @throws {TypeError} if `filepath` is directory and `extensions` not provided.
|
||||
*/
|
||||
module.exports = function lookupFiles(
|
||||
filepath,
|
||||
extensions = [],
|
||||
recursive = false
|
||||
) {
|
||||
const files = [];
|
||||
let stat;
|
||||
|
||||
if (!fs.existsSync(filepath)) {
|
||||
let pattern;
|
||||
if (glob.hasMagic(filepath)) {
|
||||
// Handle glob as is without extensions
|
||||
pattern = filepath;
|
||||
} else {
|
||||
// glob pattern e.g. 'filepath+(.js|.ts)'
|
||||
const strExtensions = extensions
|
||||
.map(ext => (ext.startsWith('.') ? ext : `.${ext}`))
|
||||
.join('|');
|
||||
pattern = `${filepath}+(${strExtensions})`;
|
||||
debug('looking for files using glob pattern: %s', pattern);
|
||||
}
|
||||
files.push(...glob.sync(pattern, {nodir: true}));
|
||||
if (!files.length) {
|
||||
throw createNoFilesMatchPatternError(
|
||||
'Cannot find any files matching pattern ' + dQuote(filepath),
|
||||
filepath
|
||||
);
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
// Handle file
|
||||
try {
|
||||
stat = fs.statSync(filepath);
|
||||
if (stat.isFile()) {
|
||||
return filepath;
|
||||
}
|
||||
} catch (err) {
|
||||
// ignore error
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle directory
|
||||
fs.readdirSync(filepath).forEach(dirent => {
|
||||
const pathname = path.join(filepath, dirent);
|
||||
let stat;
|
||||
|
||||
try {
|
||||
stat = fs.statSync(pathname);
|
||||
if (stat.isDirectory()) {
|
||||
if (recursive) {
|
||||
files.push(...lookupFiles(pathname, extensions, recursive));
|
||||
}
|
||||
return;
|
||||
}
|
||||
} catch (ignored) {
|
||||
return;
|
||||
}
|
||||
if (!extensions.length) {
|
||||
throw createMissingArgumentError(
|
||||
format(
|
||||
'Argument %s required when argument %s is a directory',
|
||||
sQuote('extensions'),
|
||||
sQuote('filepath')
|
||||
),
|
||||
'extensions',
|
||||
'array'
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
!stat.isFile() ||
|
||||
!hasMatchingExtname(pathname, extensions) ||
|
||||
isHiddenOnUnix(pathname)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
files.push(pathname);
|
||||
});
|
||||
|
||||
return files;
|
||||
};
|
88
node_modules/mocha/lib/cli/node-flags.js
generated
vendored
Normal file
88
node_modules/mocha/lib/cli/node-flags.js
generated
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Some settings and code related to Mocha's handling of Node.js/V8 flags.
|
||||
* @private
|
||||
* @module
|
||||
*/
|
||||
|
||||
const nodeFlags = process.allowedNodeEnvironmentFlags;
|
||||
const {isMochaFlag} = require('./run-option-metadata');
|
||||
const unparse = require('yargs-unparser');
|
||||
|
||||
/**
|
||||
* These flags are considered "debug" flags.
|
||||
* @see {@link impliesNoTimeouts}
|
||||
* @private
|
||||
*/
|
||||
const debugFlags = new Set(['inspect', 'inspect-brk']);
|
||||
|
||||
/**
|
||||
* Mocha has historical support for various `node` and V8 flags which might not
|
||||
* appear in `process.allowedNodeEnvironmentFlags`.
|
||||
* These include:
|
||||
* - `--preserve-symlinks`
|
||||
* - `--harmony-*`
|
||||
* - `--gc-global`
|
||||
* - `--trace-*`
|
||||
* - `--es-staging`
|
||||
* - `--use-strict`
|
||||
* - `--v8-*` (but *not* `--v8-options`)
|
||||
* @summary Whether or not to pass a flag along to the `node` executable.
|
||||
* @param {string} flag - Flag to test
|
||||
* @param {boolean} [bareword=true] - If `false`, we expect `flag` to have one or two leading dashes.
|
||||
* @returns {boolean} If the flag is considered a "Node" flag.
|
||||
* @private
|
||||
*/
|
||||
exports.isNodeFlag = (flag, bareword = true) => {
|
||||
if (!bareword) {
|
||||
// check if the flag begins with dashes; if not, not a node flag.
|
||||
if (!/^--?/.test(flag)) {
|
||||
return false;
|
||||
}
|
||||
// strip the leading dashes to match against subsequent checks
|
||||
flag = flag.replace(/^--?/, '');
|
||||
}
|
||||
return (
|
||||
// check actual node flags from `process.allowedNodeEnvironmentFlags`,
|
||||
// then historical support for various V8 and non-`NODE_OPTIONS` flags
|
||||
// and also any V8 flags with `--v8-` prefix
|
||||
(!isMochaFlag(flag) && nodeFlags && nodeFlags.has(flag)) ||
|
||||
debugFlags.has(flag) ||
|
||||
/(?:preserve-symlinks(?:-main)?|harmony(?:[_-]|$)|(?:trace[_-].+$)|gc(?:[_-]global)?$|es[_-]staging$|use[_-]strict$|v8[_-](?!options).+?$)/.test(
|
||||
flag
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns `true` if the flag is a "debug-like" flag. These require timeouts
|
||||
* to be suppressed, or pausing the debugger on breakpoints will cause test failures.
|
||||
* @param {string} flag - Flag to test
|
||||
* @returns {boolean}
|
||||
* @private
|
||||
*/
|
||||
exports.impliesNoTimeouts = flag => debugFlags.has(flag);
|
||||
|
||||
/**
|
||||
* All non-strictly-boolean arguments to node--those with values--must specify those values using `=`, e.g., `--inspect=0.0.0.0`.
|
||||
* Unparse these arguments using `yargs-unparser` (which would result in `--inspect 0.0.0.0`), then supply `=` where we have values.
|
||||
* Apparently --require in Node.js v8 does NOT want `=`.
|
||||
* There's probably an easier or more robust way to do this; fixes welcome
|
||||
* @param {Object} opts - Arguments object
|
||||
* @returns {string[]} Unparsed arguments using `=` to specify values
|
||||
* @private
|
||||
*/
|
||||
exports.unparseNodeFlags = opts => {
|
||||
var args = unparse(opts);
|
||||
return args.length
|
||||
? args
|
||||
.join(' ')
|
||||
.split(/\b/)
|
||||
.map((arg, index, args) =>
|
||||
arg === ' ' && args[index - 1] !== 'require' ? '=' : arg
|
||||
)
|
||||
.join('')
|
||||
.split(' ')
|
||||
: [];
|
||||
};
|
70
node_modules/mocha/lib/cli/one-and-dones.js
generated
vendored
Normal file
70
node_modules/mocha/lib/cli/one-and-dones.js
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Contains "command" code for "one-and-dones"--options passed
|
||||
* to Mocha which cause it to just dump some info and exit.
|
||||
* See {@link module:lib/cli/one-and-dones.ONE_AND_DONE_ARGS ONE_AND_DONE_ARGS} for more info.
|
||||
* @module
|
||||
* @private
|
||||
*/
|
||||
|
||||
const align = require('wide-align');
|
||||
const Mocha = require('../mocha');
|
||||
|
||||
/**
|
||||
* Dumps a sorted list of the enumerable, lower-case keys of some object
|
||||
* to `STDOUT`.
|
||||
* @param {Object} obj - Object, ostensibly having some enumerable keys
|
||||
* @ignore
|
||||
* @private
|
||||
*/
|
||||
const showKeys = obj => {
|
||||
console.log();
|
||||
const keys = Object.keys(obj);
|
||||
const maxKeyLength = keys.reduce((max, key) => Math.max(max, key.length), 0);
|
||||
keys
|
||||
.filter(
|
||||
key => /^[a-z]/.test(key) && !obj[key].browserOnly && !obj[key].abstract
|
||||
)
|
||||
.sort()
|
||||
.forEach(key => {
|
||||
const description = obj[key].description;
|
||||
console.log(
|
||||
` ${align.left(key, maxKeyLength + 1)}${
|
||||
description ? `- ${description}` : ''
|
||||
}`
|
||||
);
|
||||
});
|
||||
console.log();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handlers for one-and-done options
|
||||
* @namespace
|
||||
* @private
|
||||
*/
|
||||
exports.ONE_AND_DONES = {
|
||||
/**
|
||||
* Dump list of built-in interfaces
|
||||
* @private
|
||||
*/
|
||||
'list-interfaces': () => {
|
||||
showKeys(Mocha.interfaces);
|
||||
},
|
||||
/**
|
||||
* Dump list of built-in reporters
|
||||
* @private
|
||||
*/
|
||||
'list-reporters': () => {
|
||||
showKeys(Mocha.reporters);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A Set of all one-and-done options
|
||||
* @type Set<string>
|
||||
* @private
|
||||
*/
|
||||
exports.ONE_AND_DONE_ARGS = new Set(
|
||||
['help', 'h', 'version', 'V'].concat(Object.keys(exports.ONE_AND_DONES))
|
||||
);
|
261
node_modules/mocha/lib/cli/options.js
generated
vendored
Normal file
261
node_modules/mocha/lib/cli/options.js
generated
vendored
Normal file
@@ -0,0 +1,261 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Main entry point for handling filesystem-based configuration,
|
||||
* whether that's a config file or `package.json` or whatever.
|
||||
* @module lib/cli/options
|
||||
* @private
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const ansi = require('ansi-colors');
|
||||
const yargsParser = require('yargs-parser');
|
||||
const {types, aliases} = require('./run-option-metadata');
|
||||
const {ONE_AND_DONE_ARGS} = require('./one-and-dones');
|
||||
const mocharc = require('../mocharc.json');
|
||||
const {list} = require('./run-helpers');
|
||||
const {loadConfig, findConfig} = require('./config');
|
||||
const findUp = require('find-up');
|
||||
const debug = require('debug')('mocha:cli:options');
|
||||
const {isNodeFlag} = require('./node-flags');
|
||||
const {createUnparsableFileError} = require('../errors');
|
||||
|
||||
/**
|
||||
* The `yargs-parser` namespace
|
||||
* @external yargsParser
|
||||
* @see {@link https://npm.im/yargs-parser}
|
||||
*/
|
||||
|
||||
/**
|
||||
* An object returned by a configured `yargs-parser` representing arguments
|
||||
* @memberof external:yargsParser
|
||||
* @interface Arguments
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base yargs parser configuration
|
||||
* @private
|
||||
*/
|
||||
const YARGS_PARSER_CONFIG = {
|
||||
'combine-arrays': true,
|
||||
'short-option-groups': false,
|
||||
'dot-notation': false,
|
||||
'strip-aliased': true
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the config pulled from the `yargs` property of Mocha's
|
||||
* `package.json`, but it also disables camel case expansion as to
|
||||
* avoid outputting non-canonical keynames, as we need to do some
|
||||
* lookups.
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
const configuration = Object.assign({}, YARGS_PARSER_CONFIG, {
|
||||
'camel-case-expansion': false
|
||||
});
|
||||
|
||||
/**
|
||||
* This is a really fancy way to:
|
||||
* - `array`-type options: ensure unique values and evtl. split comma-delimited lists
|
||||
* - `boolean`/`number`/`string`- options: use last element when given multiple times
|
||||
* This is passed as the `coerce` option to `yargs-parser`
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
const globOptions = ['spec', 'ignore'];
|
||||
const coerceOpts = Object.assign(
|
||||
types.array.reduce(
|
||||
(acc, arg) =>
|
||||
Object.assign(acc, {
|
||||
[arg]: v => Array.from(new Set(globOptions.includes(arg) ? v : list(v)))
|
||||
}),
|
||||
{}
|
||||
),
|
||||
types.boolean
|
||||
.concat(types.string, types.number)
|
||||
.reduce(
|
||||
(acc, arg) =>
|
||||
Object.assign(acc, {[arg]: v => (Array.isArray(v) ? v.pop() : v)}),
|
||||
{}
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
* We do not have a case when multiple arguments are ever allowed after a flag
|
||||
* (e.g., `--foo bar baz quux`), so we fix the number of arguments to 1 across
|
||||
* the board of non-boolean options.
|
||||
* This is passed as the `narg` option to `yargs-parser`
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
const nargOpts = types.array
|
||||
.concat(types.string, types.number)
|
||||
.reduce((acc, arg) => Object.assign(acc, {[arg]: 1}), {});
|
||||
|
||||
/**
|
||||
* Wrapper around `yargs-parser` which applies our settings
|
||||
* @param {string|string[]} args - Arguments to parse
|
||||
* @param {Object} defaultValues - Default values of mocharc.json
|
||||
* @param {...Object} configObjects - `configObjects` for yargs-parser
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
const parse = (args = [], defaultValues = {}, ...configObjects) => {
|
||||
// save node-specific args for special handling.
|
||||
// 1. when these args have a "=" they should be considered to have values
|
||||
// 2. if they don't, they just boolean flags
|
||||
// 3. to avoid explicitly defining the set of them, we tell yargs-parser they
|
||||
// are ALL boolean flags.
|
||||
// 4. we can then reapply the values after yargs-parser is done.
|
||||
const nodeArgs = (Array.isArray(args) ? args : args.split(' ')).reduce(
|
||||
(acc, arg) => {
|
||||
const pair = arg.split('=');
|
||||
let flag = pair[0];
|
||||
if (isNodeFlag(flag, false)) {
|
||||
flag = flag.replace(/^--?/, '');
|
||||
return arg.includes('=')
|
||||
? acc.concat([[flag, pair[1]]])
|
||||
: acc.concat([[flag, true]]);
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const result = yargsParser.detailed(args, {
|
||||
configuration,
|
||||
configObjects,
|
||||
default: defaultValues,
|
||||
coerce: coerceOpts,
|
||||
narg: nargOpts,
|
||||
alias: aliases,
|
||||
string: types.string,
|
||||
array: types.array,
|
||||
number: types.number,
|
||||
boolean: types.boolean.concat(nodeArgs.map(pair => pair[0]))
|
||||
});
|
||||
if (result.error) {
|
||||
console.error(ansi.red(`Error: ${result.error.message}`));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// reapply "=" arg values from above
|
||||
nodeArgs.forEach(([key, value]) => {
|
||||
result.argv[key] = value;
|
||||
});
|
||||
|
||||
return result.argv;
|
||||
};
|
||||
|
||||
/**
|
||||
* Given path to config file in `args.config`, attempt to load & parse config file.
|
||||
* @param {Object} [args] - Arguments object
|
||||
* @param {string|boolean} [args.config] - Path to config file or `false` to skip
|
||||
* @public
|
||||
* @alias module:lib/cli.loadRc
|
||||
* @returns {external:yargsParser.Arguments|void} Parsed config, or nothing if `args.config` is `false`
|
||||
*/
|
||||
const loadRc = (args = {}) => {
|
||||
if (args.config !== false) {
|
||||
const config = args.config || findConfig();
|
||||
return config ? loadConfig(config) : {};
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.loadRc = loadRc;
|
||||
|
||||
/**
|
||||
* Given path to `package.json` in `args.package`, attempt to load config from `mocha` prop.
|
||||
* @param {Object} [args] - Arguments object
|
||||
* @param {string|boolean} [args.config] - Path to `package.json` or `false` to skip
|
||||
* @public
|
||||
* @alias module:lib/cli.loadPkgRc
|
||||
* @returns {external:yargsParser.Arguments|void} Parsed config, or nothing if `args.package` is `false`
|
||||
*/
|
||||
const loadPkgRc = (args = {}) => {
|
||||
let result;
|
||||
if (args.package === false) {
|
||||
return result;
|
||||
}
|
||||
result = {};
|
||||
const filepath = args.package || findUp.sync(mocharc.package);
|
||||
if (filepath) {
|
||||
try {
|
||||
const pkg = JSON.parse(fs.readFileSync(filepath, 'utf8'));
|
||||
if (pkg.mocha) {
|
||||
debug('`mocha` prop of package.json parsed: %O', pkg.mocha);
|
||||
result = pkg.mocha;
|
||||
} else {
|
||||
debug('no config found in %s', filepath);
|
||||
}
|
||||
} catch (err) {
|
||||
if (args.package) {
|
||||
throw createUnparsableFileError(
|
||||
`Unable to read/parse ${filepath}: ${err}`,
|
||||
filepath
|
||||
);
|
||||
}
|
||||
debug('failed to read default package.json at %s; ignoring', filepath);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
module.exports.loadPkgRc = loadPkgRc;
|
||||
|
||||
/**
|
||||
* Priority list:
|
||||
*
|
||||
* 1. Command-line args
|
||||
* 2. RC file (`.mocharc.c?js`, `.mocharc.ya?ml`, `mocharc.json`)
|
||||
* 3. `mocha` prop of `package.json`
|
||||
* 4. default configuration (`lib/mocharc.json`)
|
||||
*
|
||||
* If a {@link module:lib/cli/one-and-dones.ONE_AND_DONE_ARGS "one-and-done" option} is present in the `argv` array, no external config files will be read.
|
||||
* @summary Parses options read from `.mocharc.*` and `package.json`.
|
||||
* @param {string|string[]} [argv] - Arguments to parse
|
||||
* @public
|
||||
* @alias module:lib/cli.loadOptions
|
||||
* @returns {external:yargsParser.Arguments} Parsed args from everything
|
||||
*/
|
||||
const loadOptions = (argv = []) => {
|
||||
let args = parse(argv);
|
||||
// short-circuit: look for a flag that would abort loading of options
|
||||
if (
|
||||
Array.from(ONE_AND_DONE_ARGS).reduce(
|
||||
(acc, arg) => acc || arg in args,
|
||||
false
|
||||
)
|
||||
) {
|
||||
return args;
|
||||
}
|
||||
|
||||
const rcConfig = loadRc(args);
|
||||
const pkgConfig = loadPkgRc(args);
|
||||
|
||||
if (rcConfig) {
|
||||
args.config = false;
|
||||
args._ = args._.concat(rcConfig._ || []);
|
||||
}
|
||||
if (pkgConfig) {
|
||||
args.package = false;
|
||||
args._ = args._.concat(pkgConfig._ || []);
|
||||
}
|
||||
|
||||
args = parse(args._, mocharc, args, rcConfig || {}, pkgConfig || {});
|
||||
|
||||
// recombine positional arguments and "spec"
|
||||
if (args.spec) {
|
||||
args._ = args._.concat(args.spec);
|
||||
delete args.spec;
|
||||
}
|
||||
|
||||
// make unique
|
||||
args._ = Array.from(new Set(args._));
|
||||
|
||||
return args;
|
||||
};
|
||||
|
||||
module.exports.loadOptions = loadOptions;
|
||||
module.exports.YARGS_PARSER_CONFIG = YARGS_PARSER_CONFIG;
|
243
node_modules/mocha/lib/cli/run-helpers.js
generated
vendored
Normal file
243
node_modules/mocha/lib/cli/run-helpers.js
generated
vendored
Normal file
@@ -0,0 +1,243 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Helper scripts for the `run` command
|
||||
* @see module:lib/cli/run
|
||||
* @module
|
||||
* @private
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const debug = require('debug')('mocha:cli:run:helpers');
|
||||
const {watchRun, watchParallelRun} = require('./watch-run');
|
||||
const collectFiles = require('./collect-files');
|
||||
const {format} = require('util');
|
||||
const {createInvalidLegacyPluginError} = require('../errors');
|
||||
const {requireOrImport} = require('../esm-utils');
|
||||
const PluginLoader = require('../plugin-loader');
|
||||
|
||||
/**
|
||||
* Exits Mocha when tests + code under test has finished execution (default)
|
||||
* @param {number} code - Exit code; typically # of failures
|
||||
* @ignore
|
||||
* @private
|
||||
*/
|
||||
const exitMochaLater = code => {
|
||||
process.on('exit', () => {
|
||||
process.exitCode = Math.min(code, 255);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Exits Mocha when Mocha itself has finished execution, regardless of
|
||||
* what the tests or code under test is doing.
|
||||
* @param {number} code - Exit code; typically # of failures
|
||||
* @ignore
|
||||
* @private
|
||||
*/
|
||||
const exitMocha = code => {
|
||||
const clampedCode = Math.min(code, 255);
|
||||
let draining = 0;
|
||||
|
||||
// Eagerly set the process's exit code in case stream.write doesn't
|
||||
// execute its callback before the process terminates.
|
||||
process.exitCode = clampedCode;
|
||||
|
||||
// flush output for Node.js Windows pipe bug
|
||||
// https://github.com/joyent/node/issues/6247 is just one bug example
|
||||
// https://github.com/visionmedia/mocha/issues/333 has a good discussion
|
||||
const done = () => {
|
||||
if (!draining--) {
|
||||
process.exit(clampedCode);
|
||||
}
|
||||
};
|
||||
|
||||
const streams = [process.stdout, process.stderr];
|
||||
|
||||
streams.forEach(stream => {
|
||||
// submit empty write request and wait for completion
|
||||
draining += 1;
|
||||
stream.write('', done);
|
||||
});
|
||||
|
||||
done();
|
||||
};
|
||||
|
||||
/**
|
||||
* Coerce a comma-delimited string (or array thereof) into a flattened array of
|
||||
* strings
|
||||
* @param {string|string[]} str - Value to coerce
|
||||
* @returns {string[]} Array of strings
|
||||
* @private
|
||||
*/
|
||||
exports.list = str =>
|
||||
Array.isArray(str) ? exports.list(str.join(',')) : str.split(/ *, */);
|
||||
|
||||
/**
|
||||
* `require()` the modules as required by `--require <require>`.
|
||||
*
|
||||
* Returns array of `mochaHooks` exports, if any.
|
||||
* @param {string[]} requires - Modules to require
|
||||
* @returns {Promise<object>} Plugin implementations
|
||||
* @private
|
||||
*/
|
||||
exports.handleRequires = async (requires = [], {ignoredPlugins = []} = {}) => {
|
||||
const pluginLoader = PluginLoader.create({ignore: ignoredPlugins});
|
||||
for await (const mod of requires) {
|
||||
let modpath = mod;
|
||||
// this is relative to cwd
|
||||
if (fs.existsSync(mod) || fs.existsSync(`${mod}.js`)) {
|
||||
modpath = path.resolve(mod);
|
||||
debug('resolved required file %s to %s', mod, modpath);
|
||||
}
|
||||
const requiredModule = await requireOrImport(modpath);
|
||||
if (requiredModule && typeof requiredModule === 'object') {
|
||||
if (pluginLoader.load(requiredModule)) {
|
||||
debug('found one or more plugin implementations in %s', modpath);
|
||||
}
|
||||
}
|
||||
debug('loaded required module "%s"', mod);
|
||||
}
|
||||
const plugins = await pluginLoader.finalize();
|
||||
if (Object.keys(plugins).length) {
|
||||
debug('finalized plugin implementations: %O', plugins);
|
||||
}
|
||||
return plugins;
|
||||
};
|
||||
|
||||
/**
|
||||
* Collect and load test files, then run mocha instance.
|
||||
* @param {Mocha} mocha - Mocha instance
|
||||
* @param {Options} [opts] - Command line options
|
||||
* @param {boolean} [opts.exit] - Whether or not to force-exit after tests are complete
|
||||
* @param {Object} fileCollectParams - Parameters that control test
|
||||
* file collection. See `lib/cli/collect-files.js`.
|
||||
* @returns {Promise<Runner>}
|
||||
* @private
|
||||
*/
|
||||
const singleRun = async (mocha, {exit}, fileCollectParams) => {
|
||||
const files = collectFiles(fileCollectParams);
|
||||
debug('single run with %d file(s)', files.length);
|
||||
mocha.files = files;
|
||||
|
||||
// handles ESM modules
|
||||
await mocha.loadFilesAsync();
|
||||
return mocha.run(exit ? exitMocha : exitMochaLater);
|
||||
};
|
||||
|
||||
/**
|
||||
* Collect files and run tests (using `BufferedRunner`).
|
||||
*
|
||||
* This is `async` for consistency.
|
||||
*
|
||||
* @param {Mocha} mocha - Mocha instance
|
||||
* @param {Options} options - Command line options
|
||||
* @param {Object} fileCollectParams - Parameters that control test
|
||||
* file collection. See `lib/cli/collect-files.js`.
|
||||
* @returns {Promise<BufferedRunner>}
|
||||
* @ignore
|
||||
* @private
|
||||
*/
|
||||
const parallelRun = async (mocha, options, fileCollectParams) => {
|
||||
const files = collectFiles(fileCollectParams);
|
||||
debug('executing %d test file(s) in parallel mode', files.length);
|
||||
mocha.files = files;
|
||||
|
||||
// note that we DO NOT load any files here; this is handled by the worker
|
||||
return mocha.run(options.exit ? exitMocha : exitMochaLater);
|
||||
};
|
||||
|
||||
/**
|
||||
* Actually run tests. Delegates to one of four different functions:
|
||||
* - `singleRun`: run tests in serial & exit
|
||||
* - `watchRun`: run tests in serial, rerunning as files change
|
||||
* - `parallelRun`: run tests in parallel & exit
|
||||
* - `watchParallelRun`: run tests in parallel, rerunning as files change
|
||||
* @param {Mocha} mocha - Mocha instance
|
||||
* @param {Options} opts - Command line options
|
||||
* @private
|
||||
* @returns {Promise<Runner>}
|
||||
*/
|
||||
exports.runMocha = async (mocha, options) => {
|
||||
const {
|
||||
watch = false,
|
||||
extension = [],
|
||||
ignore = [],
|
||||
file = [],
|
||||
parallel = false,
|
||||
recursive = false,
|
||||
sort = false,
|
||||
spec = []
|
||||
} = options;
|
||||
|
||||
const fileCollectParams = {
|
||||
ignore,
|
||||
extension,
|
||||
file,
|
||||
recursive,
|
||||
sort,
|
||||
spec
|
||||
};
|
||||
|
||||
let run;
|
||||
if (watch) {
|
||||
run = parallel ? watchParallelRun : watchRun;
|
||||
} else {
|
||||
run = parallel ? parallelRun : singleRun;
|
||||
}
|
||||
|
||||
return run(mocha, options, fileCollectParams);
|
||||
};
|
||||
|
||||
/**
|
||||
* Used for `--reporter` and `--ui`. Ensures there's only one, and asserts that
|
||||
* it actually exists. This must be run _after_ requires are processed (see
|
||||
* {@link handleRequires}), as it'll prevent interfaces from loading otherwise.
|
||||
* @param {Object} opts - Options object
|
||||
* @param {"reporter"|"interface"} pluginType - Type of plugin.
|
||||
* @param {Object} [map] - An object perhaps having key `key`. Used as a cache
|
||||
* of sorts; `Mocha.reporters` is one, where each key corresponds to a reporter
|
||||
* name
|
||||
* @private
|
||||
*/
|
||||
exports.validateLegacyPlugin = (opts, pluginType, map = {}) => {
|
||||
/**
|
||||
* This should be a unique identifier; either a string (present in `map`),
|
||||
* or a resolvable (via `require.resolve`) module ID/path.
|
||||
* @type {string}
|
||||
*/
|
||||
const pluginId = opts[pluginType];
|
||||
|
||||
if (Array.isArray(pluginId)) {
|
||||
throw createInvalidLegacyPluginError(
|
||||
`"--${pluginType}" can only be specified once`,
|
||||
pluginType
|
||||
);
|
||||
}
|
||||
|
||||
const createUnknownError = err =>
|
||||
createInvalidLegacyPluginError(
|
||||
format('Could not load %s "%s":\n\n %O', pluginType, pluginId, err),
|
||||
pluginType,
|
||||
pluginId
|
||||
);
|
||||
|
||||
// if this exists, then it's already loaded, so nothing more to do.
|
||||
if (!map[pluginId]) {
|
||||
try {
|
||||
opts[pluginType] = require(pluginId);
|
||||
} catch (err) {
|
||||
if (err.code === 'MODULE_NOT_FOUND') {
|
||||
// Try to load reporters from a path (absolute or relative)
|
||||
try {
|
||||
opts[pluginType] = require(path.resolve(pluginId));
|
||||
} catch (err) {
|
||||
throw createUnknownError(err);
|
||||
}
|
||||
} else {
|
||||
throw createUnknownError(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
113
node_modules/mocha/lib/cli/run-option-metadata.js
generated
vendored
Normal file
113
node_modules/mocha/lib/cli/run-option-metadata.js
generated
vendored
Normal file
@@ -0,0 +1,113 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Metadata about various options of the `run` command
|
||||
* @see module:lib/cli/run
|
||||
* @module
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* Dictionary of yargs option types to list of options having said type
|
||||
* @type {{string:string[]}}
|
||||
* @private
|
||||
*/
|
||||
const TYPES = (exports.types = {
|
||||
array: [
|
||||
'extension',
|
||||
'file',
|
||||
'global',
|
||||
'ignore',
|
||||
'reporter-option',
|
||||
'require',
|
||||
'spec',
|
||||
'watch-files',
|
||||
'watch-ignore'
|
||||
],
|
||||
boolean: [
|
||||
'allow-uncaught',
|
||||
'async-only',
|
||||
'bail',
|
||||
'check-leaks',
|
||||
'color',
|
||||
'delay',
|
||||
'diff',
|
||||
'exit',
|
||||
'forbid-only',
|
||||
'forbid-pending',
|
||||
'full-trace',
|
||||
'growl',
|
||||
'inline-diffs',
|
||||
'invert',
|
||||
'list-interfaces',
|
||||
'list-reporters',
|
||||
'no-colors',
|
||||
'parallel',
|
||||
'recursive',
|
||||
'sort',
|
||||
'watch'
|
||||
],
|
||||
number: ['retries', 'jobs'],
|
||||
string: [
|
||||
'config',
|
||||
'fgrep',
|
||||
'grep',
|
||||
'package',
|
||||
'reporter',
|
||||
'ui',
|
||||
'slow',
|
||||
'timeout'
|
||||
]
|
||||
});
|
||||
|
||||
/**
|
||||
* Option aliases keyed by canonical option name.
|
||||
* Arrays used to reduce
|
||||
* @type {{string:string[]}}
|
||||
* @private
|
||||
*/
|
||||
exports.aliases = {
|
||||
'async-only': ['A'],
|
||||
bail: ['b'],
|
||||
color: ['c', 'colors'],
|
||||
fgrep: ['f'],
|
||||
global: ['globals'],
|
||||
grep: ['g'],
|
||||
growl: ['G'],
|
||||
ignore: ['exclude'],
|
||||
invert: ['i'],
|
||||
jobs: ['j'],
|
||||
'no-colors': ['C'],
|
||||
parallel: ['p'],
|
||||
reporter: ['R'],
|
||||
'reporter-option': ['reporter-options', 'O'],
|
||||
require: ['r'],
|
||||
slow: ['s'],
|
||||
sort: ['S'],
|
||||
timeout: ['t', 'timeouts'],
|
||||
ui: ['u'],
|
||||
watch: ['w']
|
||||
};
|
||||
|
||||
const ALL_MOCHA_FLAGS = Object.keys(TYPES).reduce((acc, key) => {
|
||||
// gets all flags from each of the fields in `types`, adds those,
|
||||
// then adds aliases of each flag (if any)
|
||||
TYPES[key].forEach(flag => {
|
||||
acc.add(flag);
|
||||
const aliases = exports.aliases[flag] || [];
|
||||
aliases.forEach(alias => {
|
||||
acc.add(alias);
|
||||
});
|
||||
});
|
||||
return acc;
|
||||
}, new Set());
|
||||
|
||||
/**
|
||||
* Returns `true` if the provided `flag` is known to Mocha.
|
||||
* @param {string} flag - Flag to check
|
||||
* @returns {boolean} If `true`, this is a Mocha flag
|
||||
* @private
|
||||
*/
|
||||
exports.isMochaFlag = flag => {
|
||||
return ALL_MOCHA_FLAGS.has(flag.replace(/^--?/, ''));
|
||||
};
|
367
node_modules/mocha/lib/cli/run.js
generated
vendored
Normal file
367
node_modules/mocha/lib/cli/run.js
generated
vendored
Normal file
@@ -0,0 +1,367 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Definition for Mocha's default ("run tests") command
|
||||
*
|
||||
* @module
|
||||
* @private
|
||||
*/
|
||||
|
||||
const symbols = require('log-symbols');
|
||||
const ansi = require('ansi-colors');
|
||||
const Mocha = require('../mocha');
|
||||
const {
|
||||
createUnsupportedError,
|
||||
createInvalidArgumentValueError,
|
||||
createMissingArgumentError
|
||||
} = require('../errors');
|
||||
|
||||
const {
|
||||
list,
|
||||
handleRequires,
|
||||
validateLegacyPlugin,
|
||||
runMocha
|
||||
} = require('./run-helpers');
|
||||
const {ONE_AND_DONES, ONE_AND_DONE_ARGS} = require('./one-and-dones');
|
||||
const debug = require('debug')('mocha:cli:run');
|
||||
const defaults = require('../mocharc');
|
||||
const {types, aliases} = require('./run-option-metadata');
|
||||
|
||||
/**
|
||||
* Logical option groups
|
||||
* @constant
|
||||
*/
|
||||
const GROUPS = {
|
||||
FILES: 'File Handling',
|
||||
FILTERS: 'Test Filters',
|
||||
NODEJS: 'Node.js & V8',
|
||||
OUTPUT: 'Reporting & Output',
|
||||
RULES: 'Rules & Behavior',
|
||||
CONFIG: 'Configuration'
|
||||
};
|
||||
|
||||
exports.command = ['$0 [spec..]', 'inspect'];
|
||||
|
||||
exports.describe = 'Run tests with Mocha';
|
||||
|
||||
exports.builder = yargs =>
|
||||
yargs
|
||||
.options({
|
||||
'allow-uncaught': {
|
||||
description: 'Allow uncaught errors to propagate',
|
||||
group: GROUPS.RULES
|
||||
},
|
||||
'async-only': {
|
||||
description:
|
||||
'Require all tests to use a callback (async) or return a Promise',
|
||||
group: GROUPS.RULES
|
||||
},
|
||||
bail: {
|
||||
description: 'Abort ("bail") after first test failure',
|
||||
group: GROUPS.RULES
|
||||
},
|
||||
'check-leaks': {
|
||||
description: 'Check for global variable leaks',
|
||||
group: GROUPS.RULES
|
||||
},
|
||||
color: {
|
||||
description: 'Force-enable color output',
|
||||
group: GROUPS.OUTPUT
|
||||
},
|
||||
config: {
|
||||
config: true,
|
||||
defaultDescription: '(nearest rc file)',
|
||||
description: 'Path to config file',
|
||||
group: GROUPS.CONFIG
|
||||
},
|
||||
delay: {
|
||||
description: 'Delay initial execution of root suite',
|
||||
group: GROUPS.RULES
|
||||
},
|
||||
diff: {
|
||||
default: true,
|
||||
description: 'Show diff on failure',
|
||||
group: GROUPS.OUTPUT
|
||||
},
|
||||
exit: {
|
||||
description: 'Force Mocha to quit after tests complete',
|
||||
group: GROUPS.RULES
|
||||
},
|
||||
extension: {
|
||||
default: defaults.extension,
|
||||
description: 'File extension(s) to load',
|
||||
group: GROUPS.FILES,
|
||||
requiresArg: true,
|
||||
coerce: list
|
||||
},
|
||||
fgrep: {
|
||||
conflicts: 'grep',
|
||||
description: 'Only run tests containing this string',
|
||||
group: GROUPS.FILTERS,
|
||||
requiresArg: true
|
||||
},
|
||||
file: {
|
||||
defaultDescription: '(none)',
|
||||
description:
|
||||
'Specify file(s) to be loaded prior to root suite execution',
|
||||
group: GROUPS.FILES,
|
||||
normalize: true,
|
||||
requiresArg: true
|
||||
},
|
||||
'forbid-only': {
|
||||
description: 'Fail if exclusive test(s) encountered',
|
||||
group: GROUPS.RULES
|
||||
},
|
||||
'forbid-pending': {
|
||||
description: 'Fail if pending test(s) encountered',
|
||||
group: GROUPS.RULES
|
||||
},
|
||||
'full-trace': {
|
||||
description: 'Display full stack traces',
|
||||
group: GROUPS.OUTPUT
|
||||
},
|
||||
global: {
|
||||
coerce: list,
|
||||
description: 'List of allowed global variables',
|
||||
group: GROUPS.RULES,
|
||||
requiresArg: true
|
||||
},
|
||||
grep: {
|
||||
coerce: value => (!value ? null : value),
|
||||
conflicts: 'fgrep',
|
||||
description: 'Only run tests matching this string or regexp',
|
||||
group: GROUPS.FILTERS,
|
||||
requiresArg: true
|
||||
},
|
||||
growl: {
|
||||
description: 'Enable Growl notifications',
|
||||
group: GROUPS.OUTPUT
|
||||
},
|
||||
ignore: {
|
||||
defaultDescription: '(none)',
|
||||
description: 'Ignore file(s) or glob pattern(s)',
|
||||
group: GROUPS.FILES,
|
||||
requiresArg: true
|
||||
},
|
||||
'inline-diffs': {
|
||||
description:
|
||||
'Display actual/expected differences inline within each string',
|
||||
group: GROUPS.OUTPUT
|
||||
},
|
||||
invert: {
|
||||
description: 'Inverts --grep and --fgrep matches',
|
||||
group: GROUPS.FILTERS
|
||||
},
|
||||
jobs: {
|
||||
description:
|
||||
'Number of concurrent jobs for --parallel; use 1 to run in serial',
|
||||
defaultDescription: '(number of CPU cores - 1)',
|
||||
requiresArg: true,
|
||||
group: GROUPS.RULES
|
||||
},
|
||||
'list-interfaces': {
|
||||
conflicts: Array.from(ONE_AND_DONE_ARGS),
|
||||
description: 'List built-in user interfaces & exit'
|
||||
},
|
||||
'list-reporters': {
|
||||
conflicts: Array.from(ONE_AND_DONE_ARGS),
|
||||
description: 'List built-in reporters & exit'
|
||||
},
|
||||
'no-colors': {
|
||||
description: 'Force-disable color output',
|
||||
group: GROUPS.OUTPUT,
|
||||
hidden: true
|
||||
},
|
||||
package: {
|
||||
description: 'Path to package.json for config',
|
||||
group: GROUPS.CONFIG,
|
||||
normalize: true,
|
||||
requiresArg: true
|
||||
},
|
||||
parallel: {
|
||||
description: 'Run tests in parallel',
|
||||
group: GROUPS.RULES
|
||||
},
|
||||
recursive: {
|
||||
description: 'Look for tests in subdirectories',
|
||||
group: GROUPS.FILES
|
||||
},
|
||||
reporter: {
|
||||
default: defaults.reporter,
|
||||
description: 'Specify reporter to use',
|
||||
group: GROUPS.OUTPUT,
|
||||
requiresArg: true
|
||||
},
|
||||
'reporter-option': {
|
||||
coerce: opts =>
|
||||
list(opts).reduce((acc, opt) => {
|
||||
const pair = opt.split('=');
|
||||
|
||||
if (pair.length > 2 || !pair.length) {
|
||||
throw createInvalidArgumentValueError(
|
||||
`invalid reporter option '${opt}'`,
|
||||
'--reporter-option',
|
||||
opt,
|
||||
'expected "key=value" format'
|
||||
);
|
||||
}
|
||||
|
||||
acc[pair[0]] = pair.length === 2 ? pair[1] : true;
|
||||
return acc;
|
||||
}, {}),
|
||||
description: 'Reporter-specific options (<k=v,[k1=v1,..]>)',
|
||||
group: GROUPS.OUTPUT,
|
||||
requiresArg: true
|
||||
},
|
||||
require: {
|
||||
defaultDescription: '(none)',
|
||||
description: 'Require module',
|
||||
group: GROUPS.FILES,
|
||||
requiresArg: true
|
||||
},
|
||||
retries: {
|
||||
description: 'Retry failed tests this many times',
|
||||
group: GROUPS.RULES
|
||||
},
|
||||
slow: {
|
||||
default: defaults.slow,
|
||||
description: 'Specify "slow" test threshold (in milliseconds)',
|
||||
group: GROUPS.RULES
|
||||
},
|
||||
sort: {
|
||||
description: 'Sort test files',
|
||||
group: GROUPS.FILES
|
||||
},
|
||||
timeout: {
|
||||
default: defaults.timeout,
|
||||
description: 'Specify test timeout threshold (in milliseconds)',
|
||||
group: GROUPS.RULES
|
||||
},
|
||||
ui: {
|
||||
default: defaults.ui,
|
||||
description: 'Specify user interface',
|
||||
group: GROUPS.RULES,
|
||||
requiresArg: true
|
||||
},
|
||||
watch: {
|
||||
description: 'Watch files in the current working directory for changes',
|
||||
group: GROUPS.FILES
|
||||
},
|
||||
'watch-files': {
|
||||
description: 'List of paths or globs to watch',
|
||||
group: GROUPS.FILES,
|
||||
requiresArg: true,
|
||||
coerce: list
|
||||
},
|
||||
'watch-ignore': {
|
||||
description: 'List of paths or globs to exclude from watching',
|
||||
group: GROUPS.FILES,
|
||||
requiresArg: true,
|
||||
coerce: list,
|
||||
default: defaults['watch-ignore']
|
||||
}
|
||||
})
|
||||
.positional('spec', {
|
||||
default: ['test'],
|
||||
description: 'One or more files, directories, or globs to test',
|
||||
type: 'array'
|
||||
})
|
||||
.check(argv => {
|
||||
// "one-and-dones"; let yargs handle help and version
|
||||
Object.keys(ONE_AND_DONES).forEach(opt => {
|
||||
if (argv[opt]) {
|
||||
ONE_AND_DONES[opt].call(null, yargs);
|
||||
process.exit();
|
||||
}
|
||||
});
|
||||
|
||||
// yargs.implies() isn't flexible enough to handle this
|
||||
if (argv.invert && !('fgrep' in argv || 'grep' in argv)) {
|
||||
throw createMissingArgumentError(
|
||||
'"--invert" requires one of "--fgrep <str>" or "--grep <regexp>"',
|
||||
'--fgrep|--grep',
|
||||
'string|regexp'
|
||||
);
|
||||
}
|
||||
|
||||
if (argv.parallel) {
|
||||
// yargs.conflicts() can't deal with `--file foo.js --no-parallel`, either
|
||||
if (argv.file) {
|
||||
throw createUnsupportedError(
|
||||
'--parallel runs test files in a non-deterministic order, and is mutually exclusive with --file'
|
||||
);
|
||||
}
|
||||
|
||||
// or this
|
||||
if (argv.sort) {
|
||||
throw createUnsupportedError(
|
||||
'--parallel runs test files in a non-deterministic order, and is mutually exclusive with --sort'
|
||||
);
|
||||
}
|
||||
|
||||
if (argv.reporter === 'progress') {
|
||||
throw createUnsupportedError(
|
||||
'--reporter=progress is mutually exclusive with --parallel'
|
||||
);
|
||||
}
|
||||
|
||||
if (argv.reporter === 'markdown') {
|
||||
throw createUnsupportedError(
|
||||
'--reporter=markdown is mutually exclusive with --parallel'
|
||||
);
|
||||
}
|
||||
|
||||
if (argv.reporter === 'json-stream') {
|
||||
throw createUnsupportedError(
|
||||
'--reporter=json-stream is mutually exclusive with --parallel'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (argv.compilers) {
|
||||
throw createUnsupportedError(
|
||||
`--compilers is DEPRECATED and no longer supported.
|
||||
See https://git.io/vdcSr for migration information.`
|
||||
);
|
||||
}
|
||||
|
||||
if (argv.opts) {
|
||||
throw createUnsupportedError(
|
||||
`--opts: configuring Mocha via 'mocha.opts' is DEPRECATED and no longer supported.
|
||||
Please use a configuration file instead.`
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.middleware(async (argv, yargs) => {
|
||||
// currently a failing middleware does not work nicely with yargs' `fail()`.
|
||||
try {
|
||||
// load requires first, because it can impact "plugin" validation
|
||||
const plugins = await handleRequires(argv.require);
|
||||
validateLegacyPlugin(argv, 'reporter', Mocha.reporters);
|
||||
validateLegacyPlugin(argv, 'ui', Mocha.interfaces);
|
||||
Object.assign(argv, plugins);
|
||||
} catch (err) {
|
||||
// this could be a bad --require, bad reporter, ui, etc.
|
||||
console.error(`\n${symbols.error} ${ansi.red('ERROR:')}`, err);
|
||||
yargs.exit(1);
|
||||
}
|
||||
})
|
||||
.array(types.array)
|
||||
.boolean(types.boolean)
|
||||
.string(types.string)
|
||||
.number(types.number)
|
||||
.alias(aliases);
|
||||
|
||||
exports.handler = async function(argv) {
|
||||
debug('post-yargs config', argv);
|
||||
const mocha = new Mocha(argv);
|
||||
|
||||
try {
|
||||
await runMocha(mocha, argv);
|
||||
} catch (err) {
|
||||
console.error('\n' + (err.stack || `Error: ${err.message || err}`));
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
380
node_modules/mocha/lib/cli/watch-run.js
generated
vendored
Normal file
380
node_modules/mocha/lib/cli/watch-run.js
generated
vendored
Normal file
@@ -0,0 +1,380 @@
|
||||
'use strict';
|
||||
|
||||
const logSymbols = require('log-symbols');
|
||||
const debug = require('debug')('mocha:cli:watch');
|
||||
const path = require('path');
|
||||
const chokidar = require('chokidar');
|
||||
const Context = require('../context');
|
||||
const collectFiles = require('./collect-files');
|
||||
|
||||
/**
|
||||
* Exports the `watchRun` function that runs mocha in "watch" mode.
|
||||
* @see module:lib/cli/run-helpers
|
||||
* @module
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* Run Mocha in parallel "watch" mode
|
||||
* @param {Mocha} mocha - Mocha instance
|
||||
* @param {Object} opts - Options
|
||||
* @param {string[]} [opts.watchFiles] - List of paths and patterns to
|
||||
* watch. If not provided all files with an extension included in
|
||||
* `fileCollectionParams.extension` are watched. See first argument of
|
||||
* `chokidar.watch`.
|
||||
* @param {string[]} opts.watchIgnore - List of paths and patterns to
|
||||
* exclude from watching. See `ignored` option of `chokidar`.
|
||||
* @param {FileCollectionOptions} fileCollectParams - Parameters that control test
|
||||
* @private
|
||||
*/
|
||||
exports.watchParallelRun = (
|
||||
mocha,
|
||||
{watchFiles, watchIgnore},
|
||||
fileCollectParams
|
||||
) => {
|
||||
debug('creating parallel watcher');
|
||||
|
||||
return createWatcher(mocha, {
|
||||
watchFiles,
|
||||
watchIgnore,
|
||||
beforeRun({mocha}) {
|
||||
// I don't know why we're cloning the root suite.
|
||||
const rootSuite = mocha.suite.clone();
|
||||
|
||||
// ensure we aren't leaking event listeners
|
||||
mocha.dispose();
|
||||
|
||||
// this `require` is needed because the require cache has been cleared. the dynamic
|
||||
// exports set via the below call to `mocha.ui()` won't work properly if a
|
||||
// test depends on this module (see `required-tokens.spec.js`).
|
||||
const Mocha = require('../mocha');
|
||||
|
||||
// ... and now that we've gotten a new module, we need to use it again due
|
||||
// to `mocha.ui()` call
|
||||
const newMocha = new Mocha(mocha.options);
|
||||
// don't know why this is needed
|
||||
newMocha.suite = rootSuite;
|
||||
// nor this
|
||||
newMocha.suite.ctx = new Context();
|
||||
|
||||
// reset the list of files
|
||||
newMocha.files = collectFiles(fileCollectParams);
|
||||
|
||||
// because we've swapped out the root suite (see the `run` inner function
|
||||
// in `createRerunner`), we need to call `mocha.ui()` again to set up the context/globals.
|
||||
newMocha.ui(newMocha.options.ui);
|
||||
|
||||
// we need to call `newMocha.rootHooks` to set up rootHooks for the new
|
||||
// suite
|
||||
newMocha.rootHooks(newMocha.options.rootHooks);
|
||||
|
||||
// in parallel mode, the main Mocha process doesn't actually load the
|
||||
// files. this flag prevents `mocha.run()` from autoloading.
|
||||
newMocha.lazyLoadFiles(true);
|
||||
return newMocha;
|
||||
},
|
||||
fileCollectParams
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Run Mocha in "watch" mode
|
||||
* @param {Mocha} mocha - Mocha instance
|
||||
* @param {Object} opts - Options
|
||||
* @param {string[]} [opts.watchFiles] - List of paths and patterns to
|
||||
* watch. If not provided all files with an extension included in
|
||||
* `fileCollectionParams.extension` are watched. See first argument of
|
||||
* `chokidar.watch`.
|
||||
* @param {string[]} opts.watchIgnore - List of paths and patterns to
|
||||
* exclude from watching. See `ignored` option of `chokidar`.
|
||||
* @param {FileCollectionOptions} fileCollectParams - Parameters that control test
|
||||
* file collection. See `lib/cli/collect-files.js`.
|
||||
* @private
|
||||
*/
|
||||
exports.watchRun = (mocha, {watchFiles, watchIgnore}, fileCollectParams) => {
|
||||
debug('creating serial watcher');
|
||||
|
||||
return createWatcher(mocha, {
|
||||
watchFiles,
|
||||
watchIgnore,
|
||||
beforeRun({mocha}) {
|
||||
mocha.unloadFiles();
|
||||
|
||||
// I don't know why we're cloning the root suite.
|
||||
const rootSuite = mocha.suite.clone();
|
||||
|
||||
// ensure we aren't leaking event listeners
|
||||
mocha.dispose();
|
||||
|
||||
// this `require` is needed because the require cache has been cleared. the dynamic
|
||||
// exports set via the below call to `mocha.ui()` won't work properly if a
|
||||
// test depends on this module (see `required-tokens.spec.js`).
|
||||
const Mocha = require('../mocha');
|
||||
|
||||
// ... and now that we've gotten a new module, we need to use it again due
|
||||
// to `mocha.ui()` call
|
||||
const newMocha = new Mocha(mocha.options);
|
||||
// don't know why this is needed
|
||||
newMocha.suite = rootSuite;
|
||||
// nor this
|
||||
newMocha.suite.ctx = new Context();
|
||||
|
||||
// reset the list of files
|
||||
newMocha.files = collectFiles(fileCollectParams);
|
||||
|
||||
// because we've swapped out the root suite (see the `run` inner function
|
||||
// in `createRerunner`), we need to call `mocha.ui()` again to set up the context/globals.
|
||||
newMocha.ui(newMocha.options.ui);
|
||||
|
||||
// we need to call `newMocha.rootHooks` to set up rootHooks for the new
|
||||
// suite
|
||||
newMocha.rootHooks(newMocha.options.rootHooks);
|
||||
|
||||
return newMocha;
|
||||
},
|
||||
fileCollectParams
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Bootstraps a chokidar watcher. Handles keyboard input & signals
|
||||
* @param {Mocha} mocha - Mocha instance
|
||||
* @param {Object} opts
|
||||
* @param {BeforeWatchRun} [opts.beforeRun] - Function to call before
|
||||
* `mocha.run()`
|
||||
* @param {string[]} [opts.watchFiles] - List of paths and patterns to watch. If
|
||||
* not provided all files with an extension included in
|
||||
* `fileCollectionParams.extension` are watched. See first argument of
|
||||
* `chokidar.watch`.
|
||||
* @param {string[]} [opts.watchIgnore] - List of paths and patterns to exclude
|
||||
* from watching. See `ignored` option of `chokidar`.
|
||||
* @param {FileCollectionOptions} opts.fileCollectParams - List of extensions to watch if `opts.watchFiles` is not given.
|
||||
* @returns {FSWatcher}
|
||||
* @ignore
|
||||
* @private
|
||||
*/
|
||||
const createWatcher = (
|
||||
mocha,
|
||||
{watchFiles, watchIgnore, beforeRun, fileCollectParams}
|
||||
) => {
|
||||
if (!watchFiles) {
|
||||
watchFiles = fileCollectParams.extension.map(ext => `**/*.${ext}`);
|
||||
}
|
||||
|
||||
debug('ignoring files matching: %s', watchIgnore);
|
||||
let globalFixtureContext;
|
||||
|
||||
// we handle global fixtures manually
|
||||
mocha.enableGlobalSetup(false).enableGlobalTeardown(false);
|
||||
|
||||
const watcher = chokidar.watch(watchFiles, {
|
||||
ignored: watchIgnore,
|
||||
ignoreInitial: true
|
||||
});
|
||||
|
||||
const rerunner = createRerunner(mocha, watcher, {
|
||||
beforeRun
|
||||
});
|
||||
|
||||
watcher.on('ready', async () => {
|
||||
if (!globalFixtureContext) {
|
||||
debug('triggering global setup');
|
||||
globalFixtureContext = await mocha.runGlobalSetup();
|
||||
}
|
||||
rerunner.run();
|
||||
});
|
||||
|
||||
watcher.on('all', () => {
|
||||
rerunner.scheduleRun();
|
||||
});
|
||||
|
||||
hideCursor();
|
||||
process.on('exit', () => {
|
||||
showCursor();
|
||||
});
|
||||
|
||||
// this is for testing.
|
||||
// win32 cannot gracefully shutdown via a signal from a parent
|
||||
// process; a `SIGINT` from a parent will cause the process
|
||||
// to immediately exit. during normal course of operation, a user
|
||||
// will type Ctrl-C and the listener will be invoked, but this
|
||||
// is not possible in automated testing.
|
||||
// there may be another way to solve this, but it too will be a hack.
|
||||
// for our watch tests on win32 we must _fork_ mocha with an IPC channel
|
||||
if (process.connected) {
|
||||
process.on('message', msg => {
|
||||
if (msg === 'SIGINT') {
|
||||
process.emit('SIGINT');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let exiting = false;
|
||||
process.on('SIGINT', async () => {
|
||||
showCursor();
|
||||
console.error(`${logSymbols.warning} [mocha] cleaning up, please wait...`);
|
||||
if (!exiting) {
|
||||
exiting = true;
|
||||
if (mocha.hasGlobalTeardownFixtures()) {
|
||||
debug('running global teardown');
|
||||
try {
|
||||
await mocha.runGlobalTeardown(globalFixtureContext);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
process.exit(130);
|
||||
}
|
||||
});
|
||||
|
||||
// Keyboard shortcut for restarting when "rs\n" is typed (ala Nodemon)
|
||||
process.stdin.resume();
|
||||
process.stdin.setEncoding('utf8');
|
||||
process.stdin.on('data', data => {
|
||||
const str = data
|
||||
.toString()
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
if (str === 'rs') rerunner.scheduleRun();
|
||||
});
|
||||
|
||||
return watcher;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create an object that allows you to rerun tests on the mocha instance.
|
||||
*
|
||||
* @param {Mocha} mocha - Mocha instance
|
||||
* @param {FSWatcher} watcher - chokidar `FSWatcher` instance
|
||||
* @param {Object} [opts] - Options!
|
||||
* @param {BeforeWatchRun} [opts.beforeRun] - Function to call before `mocha.run()`
|
||||
* @returns {Rerunner}
|
||||
* @ignore
|
||||
* @private
|
||||
*/
|
||||
const createRerunner = (mocha, watcher, {beforeRun} = {}) => {
|
||||
// Set to a `Runner` when mocha is running. Set to `null` when mocha is not
|
||||
// running.
|
||||
let runner = null;
|
||||
|
||||
// true if a file has changed during a test run
|
||||
let rerunScheduled = false;
|
||||
|
||||
const run = () => {
|
||||
try {
|
||||
mocha = beforeRun ? beforeRun({mocha, watcher}) || mocha : mocha;
|
||||
runner = mocha.run(() => {
|
||||
debug('finished watch run');
|
||||
runner = null;
|
||||
blastCache(watcher);
|
||||
if (rerunScheduled) {
|
||||
rerun();
|
||||
} else {
|
||||
console.error(`${logSymbols.info} [mocha] waiting for changes...`);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e.stack);
|
||||
}
|
||||
};
|
||||
|
||||
const scheduleRun = () => {
|
||||
if (rerunScheduled) {
|
||||
return;
|
||||
}
|
||||
|
||||
rerunScheduled = true;
|
||||
if (runner) {
|
||||
runner.abort();
|
||||
} else {
|
||||
rerun();
|
||||
}
|
||||
};
|
||||
|
||||
const rerun = () => {
|
||||
rerunScheduled = false;
|
||||
eraseLine();
|
||||
run();
|
||||
};
|
||||
|
||||
return {
|
||||
scheduleRun,
|
||||
run
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the list of absolute paths watched by a chokidar watcher.
|
||||
*
|
||||
* @param watcher - Instance of a chokidar watcher
|
||||
* @return {string[]} - List of absolute paths
|
||||
* @ignore
|
||||
* @private
|
||||
*/
|
||||
const getWatchedFiles = watcher => {
|
||||
const watchedDirs = watcher.getWatched();
|
||||
return Object.keys(watchedDirs).reduce(
|
||||
(acc, dir) => [
|
||||
...acc,
|
||||
...watchedDirs[dir].map(file => path.join(dir, file))
|
||||
],
|
||||
[]
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Hide the cursor.
|
||||
* @ignore
|
||||
* @private
|
||||
*/
|
||||
const hideCursor = () => {
|
||||
process.stdout.write('\u001b[?25l');
|
||||
};
|
||||
|
||||
/**
|
||||
* Show the cursor.
|
||||
* @ignore
|
||||
* @private
|
||||
*/
|
||||
const showCursor = () => {
|
||||
process.stdout.write('\u001b[?25h');
|
||||
};
|
||||
|
||||
/**
|
||||
* Erases the line on stdout
|
||||
* @private
|
||||
*/
|
||||
const eraseLine = () => {
|
||||
process.stdout.write('\u001b[2K');
|
||||
};
|
||||
|
||||
/**
|
||||
* Blast all of the watched files out of `require.cache`
|
||||
* @param {FSWatcher} watcher - chokidar FSWatcher
|
||||
* @ignore
|
||||
* @private
|
||||
*/
|
||||
const blastCache = watcher => {
|
||||
const files = getWatchedFiles(watcher);
|
||||
files.forEach(file => {
|
||||
delete require.cache[file];
|
||||
});
|
||||
debug('deleted %d file(s) from the require cache', files.length);
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback to be run before `mocha.run()` is called.
|
||||
* Optionally, it can return a new `Mocha` instance.
|
||||
* @callback BeforeWatchRun
|
||||
* @private
|
||||
* @param {{mocha: Mocha, watcher: FSWatcher}} options
|
||||
* @returns {Mocha}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Object containing run control methods
|
||||
* @typedef {Object} Rerunner
|
||||
* @private
|
||||
* @property {Function} run - Calls `mocha.run()`
|
||||
* @property {Function} scheduleRun - Schedules another call to `run`
|
||||
*/
|
Reference in New Issue
Block a user