This commit is contained in:
lalBi94
2023-03-05 13:23:23 +01:00
commit 7bc56c09b5
14034 changed files with 1834369 additions and 0 deletions

88
node_modules/mocha/lib/cli/cli.js generated vendored Normal file
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,3 @@
'use strict';
module.exports = require('./cli');

36
node_modules/mocha/lib/cli/init.js generated vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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`
*/