Comparing version 7.2.0 to 10.2.0
@@ -12,2 +12,4 @@ 'use strict'; | ||
var parseQuery = require('./lib/browser/parse-query'); | ||
var highlightTags = require('./lib/browser/highlight-tags'); | ||
var Mocha = require('./lib/mocha'); | ||
@@ -42,3 +44,3 @@ | ||
process.removeListener = function(e, fn) { | ||
process.removeListener = function (e, fn) { | ||
if (e === 'uncaughtException') { | ||
@@ -48,3 +50,3 @@ if (originalOnerrorHandler) { | ||
} else { | ||
global.onerror = function() {}; | ||
global.onerror = function () {}; | ||
} | ||
@@ -62,3 +64,3 @@ var i = uncaughtExceptionHandlers.indexOf(fn); | ||
process.listenerCount = function(name) { | ||
process.listenerCount = function (name) { | ||
if (name === 'uncaughtException') { | ||
@@ -74,5 +76,5 @@ return uncaughtExceptionHandlers.length; | ||
process.on = function(e, fn) { | ||
process.on = function (e, fn) { | ||
if (e === 'uncaughtException') { | ||
global.onerror = function(err, url, line) { | ||
global.onerror = function (err, url, line) { | ||
fn(new Error(err + ' (' + url + ':' + line + ')')); | ||
@@ -85,2 +87,9 @@ return !mocha.options.allowUncaught; | ||
process.listeners = function (e) { | ||
if (e === 'uncaughtException') { | ||
return uncaughtExceptionHandlers; | ||
} | ||
return []; | ||
}; | ||
// The BDD UI is registered by default, but no UI will be functional in the | ||
@@ -110,3 +119,3 @@ // browser without an explicit call to the overridden `mocha.ui` (see below). | ||
Mocha.Runner.immediately = function(callback) { | ||
Mocha.Runner.immediately = function (callback) { | ||
immediateQueue.push(callback); | ||
@@ -123,4 +132,4 @@ if (!immediateTimeout) { | ||
*/ | ||
mocha.throwError = function(err) { | ||
uncaughtExceptionHandlers.forEach(function(fn) { | ||
mocha.throwError = function (err) { | ||
uncaughtExceptionHandlers.forEach(function (fn) { | ||
fn(err); | ||
@@ -136,3 +145,3 @@ }); | ||
mocha.ui = function(ui) { | ||
mocha.ui = function (ui) { | ||
Mocha.prototype.ui.call(this, ui); | ||
@@ -147,11 +156,19 @@ this.suite.emit('pre-require', global, null, this); | ||
mocha.setup = function(opts) { | ||
mocha.setup = function (opts) { | ||
if (typeof opts === 'string') { | ||
opts = {ui: opts}; | ||
} | ||
for (var opt in opts) { | ||
if (Object.prototype.hasOwnProperty.call(opts, opt)) { | ||
this[opt](opts[opt]); | ||
} | ||
if (opts.delay === true) { | ||
this.delay(); | ||
} | ||
var self = this; | ||
Object.keys(opts) | ||
.filter(function (opt) { | ||
return opt !== 'delay'; | ||
}) | ||
.forEach(function (opt) { | ||
if (Object.prototype.hasOwnProperty.call(opts, opt)) { | ||
self[opt](opts[opt]); | ||
} | ||
}); | ||
return this; | ||
@@ -164,7 +181,7 @@ }; | ||
mocha.run = function(fn) { | ||
mocha.run = function (fn) { | ||
var options = mocha.options; | ||
mocha.globals('location'); | ||
var query = Mocha.utils.parseQuery(global.location.search || ''); | ||
var query = parseQuery(global.location.search || ''); | ||
if (query.grep) { | ||
@@ -180,3 +197,3 @@ mocha.grep(query.grep); | ||
return Mocha.prototype.run.call(mocha, function(err) { | ||
return Mocha.prototype.run.call(mocha, function (err) { | ||
// The DOM Document is not available in Web Workers. | ||
@@ -189,3 +206,3 @@ var document = global.document; | ||
) { | ||
Mocha.utils.highlightTags('code'); | ||
highlightTags('code'); | ||
} | ||
@@ -208,9 +225,16 @@ if (fn) { | ||
*/ | ||
global.Mocha = Mocha; | ||
global.mocha = mocha; | ||
// this allows test/acceptance/required-tokens.js to pass; thus, | ||
// you can now do `const describe = require('mocha').describe` in a | ||
// browser context (assuming browserification). should fix #880 | ||
module.exports = global; | ||
// for bundlers: enable `import {describe, it} from 'mocha'` | ||
// `bdd` interface only | ||
// prettier-ignore | ||
[ | ||
'describe', 'context', 'it', 'specify', | ||
'xdescribe', 'xcontext', 'xit', 'xspecify', | ||
'before', 'beforeEach', 'afterEach', 'after' | ||
].forEach(function(key) { | ||
mocha[key] = global[key]; | ||
}); | ||
module.exports = mocha; |
'use strict'; | ||
/** | ||
@module browser/Progress | ||
*/ | ||
/** | ||
* Expose `Progress`. | ||
@@ -26,3 +30,3 @@ */ | ||
*/ | ||
Progress.prototype.size = function(size) { | ||
Progress.prototype.size = function (size) { | ||
this._size = size; | ||
@@ -39,3 +43,3 @@ return this; | ||
*/ | ||
Progress.prototype.text = function(text) { | ||
Progress.prototype.text = function (text) { | ||
this._text = text; | ||
@@ -52,3 +56,3 @@ return this; | ||
*/ | ||
Progress.prototype.fontSize = function(size) { | ||
Progress.prototype.fontSize = function (size) { | ||
this._fontSize = size; | ||
@@ -64,3 +68,3 @@ return this; | ||
*/ | ||
Progress.prototype.font = function(family) { | ||
Progress.prototype.font = function (family) { | ||
this._font = family; | ||
@@ -76,3 +80,3 @@ return this; | ||
*/ | ||
Progress.prototype.update = function(n) { | ||
Progress.prototype.update = function (n) { | ||
this.percent = n; | ||
@@ -88,4 +92,18 @@ return this; | ||
*/ | ||
Progress.prototype.draw = function(ctx) { | ||
Progress.prototype.draw = function (ctx) { | ||
try { | ||
var darkMatcher = window.matchMedia('(prefers-color-scheme: dark)'); | ||
var isDarkMode = !!darkMatcher.matches; | ||
var lightColors = { | ||
outerCircle: '#9f9f9f', | ||
innerCircle: '#eee', | ||
text: '#000' | ||
}; | ||
var darkColors = { | ||
outerCircle: '#888', | ||
innerCircle: '#444', | ||
text: '#fff' | ||
}; | ||
var colors = isDarkMode ? darkColors : lightColors; | ||
var percent = Math.min(this.percent, 100); | ||
@@ -105,3 +123,3 @@ var size = this._size; | ||
// outer circle | ||
ctx.strokeStyle = '#9f9f9f'; | ||
ctx.strokeStyle = colors.outerCircle; | ||
ctx.beginPath(); | ||
@@ -112,3 +130,3 @@ ctx.arc(x, y, rad, 0, angle, false); | ||
// inner circle | ||
ctx.strokeStyle = '#eee'; | ||
ctx.strokeStyle = colors.innerCircle; | ||
ctx.beginPath(); | ||
@@ -122,2 +140,3 @@ ctx.arc(x, y, rad - 1, 0, angle, true); | ||
ctx.fillStyle = colors.text; | ||
ctx.fillText(text, x - w / 2 + 1, y + fontSize / 2 - 1); | ||
@@ -124,0 +143,0 @@ } catch (ignore) { |
@@ -6,8 +6,7 @@ #!/usr/bin/env node | ||
/** | ||
* This is where we finally parse and handle arguments passed to the `mocha` executable. | ||
* Option parsing is handled by {@link https://npm.im/yargs yargs}. | ||
* If executed via `node`, this module will run {@linkcode module:lib/cli/cli.main main()}. | ||
* | ||
* @private | ||
* @module | ||
* 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 | ||
*/ | ||
@@ -19,3 +18,9 @@ | ||
const path = require('path'); | ||
const {loadOptions, YARGS_PARSER_CONFIG} = require('./options'); | ||
const { | ||
loadRc, | ||
loadPkgRc, | ||
loadOptions, | ||
YARGS_PARSER_CONFIG | ||
} = require('./options'); | ||
const lookupFiles = require('./lookup-files'); | ||
const commands = require('./commands'); | ||
@@ -30,6 +35,8 @@ const ansi = require('ansi-colors'); | ||
* - Sets {@linkcode https://nodejs.org/api/errors.html#errors_error_stacktracelimit Error.stackTraceLimit} to `Infinity` | ||
* @summary Mocha's main entry point from the command-line. | ||
* @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)` | ||
* @param {object} [mochaArgs] - Object of already parsed Mocha arguments (by bin/mocha) | ||
*/ | ||
exports.main = (argv = process.argv.slice(2)) => { | ||
exports.main = (argv = process.argv.slice(2), mochaArgs) => { | ||
debug('entered main with raw args', argv); | ||
@@ -43,3 +50,3 @@ // ensure we can require() from current working directory | ||
var args = loadOptions(argv); | ||
var args = mochaArgs || loadOptions(argv); | ||
@@ -56,6 +63,6 @@ yargs() | ||
.fail((msg, err, yargs) => { | ||
debug(err); | ||
debug('caught error sometime before command handler: %O', err); | ||
yargs.showHelp(); | ||
console.error(`\n${symbols.error} ${ansi.red('ERROR:')} ${msg}`); | ||
process.exit(1); | ||
process.exitCode = 1; | ||
}) | ||
@@ -79,2 +86,7 @@ .help('help', 'Show usage information & exit') | ||
exports.lookupFiles = lookupFiles; | ||
exports.loadOptions = loadOptions; | ||
exports.loadPkgRc = loadPkgRc; | ||
exports.loadRc = loadRc; | ||
// allow direct execution | ||
@@ -81,0 +93,0 @@ if (require.main === module) { |
@@ -7,4 +7,5 @@ 'use strict'; | ||
const minimatch = require('minimatch'); | ||
const utils = require('../utils'); | ||
const {NO_FILES_MATCH_PATTERN} = require('../errors').constants; | ||
const lookupFiles = require('./lookup-files'); | ||
const {castArray} = require('../utils'); | ||
@@ -21,23 +22,27 @@ /** | ||
* Smash together an array of test files in the correct order | ||
* @param {Object} opts - Options | ||
* @param {string[]} opts.extension - File extensions to use | ||
* @param {string[]} opts.spec - Files, dirs, globs to run | ||
* @param {string[]} opts.ignore - Files, dirs, globs to ignore | ||
* @param {string[]} opts.file - List of additional files to include | ||
* @param {boolean} opts.recursive - Find files recursively | ||
* @param {boolean} opts.sort - Sort test files | ||
* @param {FileCollectionOptions} [opts] - Options | ||
* @returns {string[]} List of files to test | ||
* @private | ||
*/ | ||
module.exports = ({ignore, extension, file, recursive, sort, spec} = {}) => { | ||
let files = []; | ||
module.exports = ({ | ||
ignore, | ||
extension, | ||
file: fileArgs, | ||
recursive, | ||
sort, | ||
spec | ||
} = {}) => { | ||
const unmatched = []; | ||
spec.forEach(arg => { | ||
let newFiles; | ||
const specFiles = spec.reduce((specFiles, arg) => { | ||
try { | ||
newFiles = utils.lookupFiles(arg, extension, recursive); | ||
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; | ||
return specFiles; | ||
} | ||
@@ -47,26 +52,15 @@ | ||
} | ||
}, []); | ||
if (typeof newFiles !== 'undefined') { | ||
if (typeof newFiles === 'string') { | ||
newFiles = [newFiles]; | ||
} | ||
newFiles = newFiles.filter(fileName => | ||
ignore.every(pattern => !minimatch(fileName, pattern)) | ||
); | ||
} | ||
files = files.concat(newFiles); | ||
}); | ||
const fileArgs = file.map(filepath => path.resolve(filepath)); | ||
files = files.map(filepath => path.resolve(filepath)); | ||
// ensure we don't sort the stuff from fileArgs; order is important! | ||
if (sort) { | ||
files.sort(); | ||
specFiles.sort(); | ||
} | ||
// add files given through --file to be ran first | ||
files = fileArgs.concat(files); | ||
debug('files (in order): ', files); | ||
const files = [ | ||
...fileArgs.map(filepath => path.resolve(filepath)), | ||
...specFiles | ||
]; | ||
debug('test files (in order): ', files); | ||
@@ -82,3 +76,3 @@ if (!files.length) { | ||
} else { | ||
// print messages as an warning | ||
// print messages as a warning | ||
unmatched.forEach(warning => { | ||
@@ -91,1 +85,13 @@ console.warn(ansi.yellow(`Warning: ${warning.message}`)); | ||
}; | ||
/** | ||
* 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 | ||
*/ |
@@ -5,3 +5,3 @@ 'use strict'; | ||
* Exports Yargs commands | ||
* @see https://git.io/fpJ0G | ||
* @see https://github.com/yargs/yargs/blob/main/docs/advanced.md | ||
* @private | ||
@@ -8,0 +8,0 @@ * @module |
@@ -5,3 +5,2 @@ 'use strict'; | ||
* Responsible for loading / finding Mocha's "rc" files. | ||
* This doesn't have anything to do with `mocha.opts`. | ||
* | ||
@@ -16,2 +15,3 @@ * @private | ||
const findUp = require('find-up'); | ||
const {createUnparsableFileError} = require('../errors'); | ||
const utils = require('../utils'); | ||
@@ -35,6 +35,2 @@ | ||
const isModuleNotFoundError = err => | ||
err.code !== 'MODULE_NOT_FOUND' || | ||
err.message.indexOf('Cannot find module') !== -1; | ||
/** | ||
@@ -45,16 +41,14 @@ * Parsers for various config filetypes. Each accepts a filepath and | ||
const parsers = (exports.parsers = { | ||
yaml: filepath => | ||
require('js-yaml').safeLoad(fs.readFileSync(filepath, 'utf8')), | ||
yaml: filepath => require('js-yaml').load(fs.readFileSync(filepath, 'utf8')), | ||
js: filepath => { | ||
const cwdFilepath = path.resolve(filepath); | ||
let cwdFilepath; | ||
try { | ||
debug('parsers: load using cwd-relative path: "%s"', cwdFilepath); | ||
debug('parsers: load cwd-relative path: "%s"', path.resolve(filepath)); | ||
cwdFilepath = require.resolve(path.resolve(filepath)); // evtl. throws | ||
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 | ||
} | ||
if (cwdFilepath) throw err; | ||
debug('parsers: retry load as module-relative path: "%s"', filepath); | ||
return require(filepath); | ||
} | ||
@@ -90,3 +84,6 @@ }, | ||
} catch (err) { | ||
throw new Error(`failed to parse config "${filepath}": ${err}`); | ||
throw createUnparsableFileError( | ||
`Unable to read/parse ${filepath}: ${err}`, | ||
filepath | ||
); | ||
} | ||
@@ -93,0 +90,0 @@ return config; |
'use strict'; | ||
/** | ||
* Just exports {@link module:lib/cli/cli} for convenience. | ||
* @private | ||
* @module lib/cli | ||
* @exports module:lib/cli/cli | ||
*/ | ||
module.exports = require('./cli'); |
@@ -12,3 +12,2 @@ 'use strict'; | ||
const path = require('path'); | ||
const mkdirp = require('mkdirp'); | ||
@@ -28,3 +27,3 @@ exports.command = 'init <path>'; | ||
const srcdir = path.join(__dirname, '..', '..'); | ||
mkdirp.sync(destdir); | ||
fs.mkdirSync(destdir, {recursive: true}); | ||
const css = fs.readFileSync(path.join(srcdir, 'mocha.css')); | ||
@@ -31,0 +30,0 @@ const js = fs.readFileSync(path.join(srcdir, 'mocha.js')); |
@@ -9,3 +9,4 @@ 'use strict'; | ||
const nodeFlags = require('node-environment-flags'); | ||
const nodeFlags = process.allowedNodeEnvironmentFlags; | ||
const {isMochaFlag} = require('./run-option-metadata'); | ||
const unparse = require('yargs-unparser'); | ||
@@ -47,12 +48,10 @@ | ||
return ( | ||
// treat --require/-r as Mocha flag even though it's also a node flag | ||
!(flag === 'require' || flag === 'r') && | ||
// 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 | ||
(nodeFlags.has(flag) || | ||
debugFlags.has(flag) || | ||
/(?:preserve-symlinks(?:-main)?|harmony(?:[_-]|$)|(?:trace[_-].+$)|gc(?:[_-]global)?$|es[_-]staging$|use[_-]strict$|v8[_-](?!options).+?$)/.test( | ||
flag | ||
)) | ||
(!isMochaFlag(flag) && nodeFlags && nodeFlags.has(flag)) || | ||
debugFlags.has(flag) || | ||
/(?:preserve-symlinks(?:-main)?|harmony(?:[_-]|$)|(?:trace[_-].+$)|gc[_-]global$|es[_-]staging$|use[_-]strict$|v8[_-](?!options).+?$)/.test( | ||
flag | ||
) | ||
); | ||
@@ -73,3 +72,2 @@ }; | ||
* 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 | ||
@@ -86,5 +84,3 @@ * @param {Object} opts - Arguments object | ||
.split(/\b/) | ||
.map((arg, index, args) => | ||
arg === ' ' && args[index - 1] !== 'require' ? '=' : arg | ||
) | ||
.map(arg => (arg === ' ' ? '=' : arg)) | ||
.join('') | ||
@@ -91,0 +87,0 @@ .split(' ') |
@@ -11,3 +11,2 @@ 'use strict'; | ||
const align = require('wide-align'); | ||
const Mocha = require('../mocha'); | ||
@@ -34,3 +33,3 @@ | ||
console.log( | ||
` ${align.left(key, maxKeyLength + 1)}${ | ||
` ${key.padEnd(maxKeyLength + 1)}${ | ||
description ? `- ${description}` : '' | ||
@@ -37,0 +36,0 @@ }` |
@@ -5,4 +5,5 @@ 'use strict'; | ||
* Main entry point for handling filesystem-based configuration, | ||
* whether that's `mocha.opts` or a config file or `package.json` or whatever. | ||
* @module | ||
* whether that's a config file or `package.json` or whatever. | ||
* @module lib/cli/options | ||
* @private | ||
*/ | ||
@@ -19,5 +20,5 @@ | ||
const findUp = require('find-up'); | ||
const {deprecate} = require('../utils'); | ||
const debug = require('debug')('mocha:cli:options'); | ||
const {isNodeFlag} = require('./node-flags'); | ||
const {createUnparsableFileError} = require('../errors'); | ||
@@ -43,3 +44,4 @@ /** | ||
'short-option-groups': false, | ||
'dot-notation': false | ||
'dot-notation': false, | ||
'strip-aliased': true | ||
}; | ||
@@ -61,4 +63,4 @@ | ||
* This is a really fancy way to: | ||
* - ensure unique values for `array`-type options | ||
* - use its array's last element for `boolean`/`number`/`string`- options given multiple times | ||
* - `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` | ||
@@ -68,6 +70,9 @@ * @private | ||
*/ | ||
const globOptions = ['spec', 'ignore']; | ||
const coerceOpts = Object.assign( | ||
types.array.reduce( | ||
(acc, arg) => | ||
Object.assign(acc, {[arg]: v => Array.from(new Set(list(v)))}), | ||
Object.assign(acc, { | ||
[arg]: v => Array.from(new Set(globOptions.includes(arg) ? v : list(v))) | ||
}), | ||
{} | ||
@@ -152,67 +157,2 @@ ), | ||
/** | ||
* - Replaces comments with empty strings | ||
* - Replaces escaped spaces (e.g., 'xxx\ yyy') with HTML space | ||
* - Splits on whitespace, creating array of substrings | ||
* - Filters empty string elements from array | ||
* - Replaces any HTML space with space | ||
* @summary Parses options read from run-control file. | ||
* @private | ||
* @param {string} content - Content read from run-control file. | ||
* @returns {string[]} cmdline options (and associated arguments) | ||
* @ignore | ||
*/ | ||
const parseMochaOpts = content => | ||
content | ||
.replace(/^#.*$/gm, '') | ||
.replace(/\\\s/g, '%20') | ||
.split(/\s/) | ||
.filter(Boolean) | ||
.map(value => value.replace(/%20/g, ' ')); | ||
/** | ||
* Given filepath in `args.opts`, attempt to load and parse a `mocha.opts` file. | ||
* @param {Object} [args] - Arguments object | ||
* @param {string|boolean} [args.opts] - Filepath to mocha.opts; defaults to whatever's in `mocharc.opts`, or `false` to skip | ||
* @returns {external:yargsParser.Arguments|void} If read, object containing parsed arguments | ||
* @memberof module:lib/cli/options | ||
* @see {@link /#mochaopts|mocha.opts} | ||
* @public | ||
*/ | ||
const loadMochaOpts = (args = {}) => { | ||
let result; | ||
let filepath = args.opts; | ||
// /dev/null is backwards compat | ||
if (filepath === false || filepath === '/dev/null') { | ||
return result; | ||
} | ||
filepath = filepath || mocharc.opts; | ||
result = {}; | ||
let mochaOpts; | ||
try { | ||
mochaOpts = fs.readFileSync(filepath, 'utf8'); | ||
debug(`read ${filepath}`); | ||
} catch (err) { | ||
if (args.opts) { | ||
throw new Error(`Unable to read ${filepath}: ${err}`); | ||
} | ||
// ignore otherwise. we tried | ||
debug(`No mocha.opts found at ${filepath}`); | ||
} | ||
// real args should override `mocha.opts` which should override defaults. | ||
// if there's an exception to catch here, I'm not sure what it is. | ||
// by attaching the `no-opts` arg, we avoid re-parsing of `mocha.opts`. | ||
if (mochaOpts) { | ||
deprecate( | ||
'Configuration via mocha.opts is DEPRECATED and will be removed from a future version of Mocha. Use RC files or package.json instead.' | ||
); | ||
result = parse(parseMochaOpts(mochaOpts)); | ||
debug(`${filepath} parsed succesfully`); | ||
} | ||
return result; | ||
}; | ||
module.exports.loadMochaOpts = loadMochaOpts; | ||
/** | ||
* Given path to config file in `args.config`, attempt to load & parse config file. | ||
@@ -222,3 +162,3 @@ * @param {Object} [args] - Arguments object | ||
* @public | ||
* @memberof module:lib/cli/options | ||
* @alias module:lib/cli.loadRc | ||
* @returns {external:yargsParser.Arguments|void} Parsed config, or nothing if `args.config` is `false` | ||
@@ -240,3 +180,3 @@ */ | ||
* @public | ||
* @memberof module:lib/cli/options | ||
* @alias module:lib/cli.loadPkgRc | ||
* @returns {external:yargsParser.Arguments|void} Parsed config, or nothing if `args.package` is `false` | ||
@@ -262,3 +202,6 @@ */ | ||
if (args.package) { | ||
throw new Error(`Unable to read/parse ${filepath}: ${err}`); | ||
throw createUnparsableFileError( | ||
`Unable to read/parse ${filepath}: ${err}`, | ||
filepath | ||
); | ||
} | ||
@@ -279,10 +222,9 @@ debug('failed to read default package.json at %s; ignoring', filepath); | ||
* 3. `mocha` prop of `package.json` | ||
* 4. `mocha.opts` | ||
* 5. default configuration (`lib/mocharc.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 `mocha.opts`, `.mocharc.*` and `package.json`. | ||
* @summary Parses options read from `.mocharc.*` and `package.json`. | ||
* @param {string|string[]} [argv] - Arguments to parse | ||
* @public | ||
* @memberof module:lib/cli/options | ||
* @alias module:lib/cli.loadOptions | ||
* @returns {external:yargsParser.Arguments} Parsed args from everything | ||
@@ -292,3 +234,3 @@ */ | ||
let args = parse(argv); | ||
// short-circuit: look for a flag that would abort loading of mocha.opts | ||
// short-circuit: look for a flag that would abort loading of options | ||
if ( | ||
@@ -305,3 +247,2 @@ Array.from(ONE_AND_DONE_ARGS).reduce( | ||
const pkgConfig = loadPkgRc(args); | ||
const optsConfig = loadMochaOpts(args); | ||
@@ -316,15 +257,4 @@ if (rcConfig) { | ||
} | ||
if (optsConfig) { | ||
args.opts = false; | ||
args._ = args._.concat(optsConfig._ || []); | ||
} | ||
args = parse( | ||
args._, | ||
mocharc, | ||
args, | ||
rcConfig || {}, | ||
pkgConfig || {}, | ||
optsConfig || {} | ||
); | ||
args = parse(args._, mocharc, args, rcConfig || {}, pkgConfig || {}); | ||
@@ -331,0 +261,0 @@ // recombine positional arguments and "spec" |
@@ -13,7 +13,8 @@ 'use strict'; | ||
const debug = require('debug')('mocha:cli:run:helpers'); | ||
const watchRun = require('./watch-run'); | ||
const {watchRun, watchParallelRun} = require('./watch-run'); | ||
const collectFiles = require('./collect-files'); | ||
const {type} = require('../utils'); | ||
const {format} = require('util'); | ||
const {createInvalidPluginError, createUnsupportedError} = require('../errors'); | ||
const {createInvalidLegacyPluginError} = require('../errors'); | ||
const {requireOrImport} = require('../nodejs/esm-utils'); | ||
const PluginLoader = require('../plugin-loader'); | ||
@@ -82,7 +83,8 @@ /** | ||
* @param {string[]} requires - Modules to require | ||
* @returns {Promise<MochaRootHookObject|MochaRootHookFunction>} Any root hooks | ||
* @returns {Promise<object>} Plugin implementations | ||
* @private | ||
*/ | ||
exports.handleRequires = async (requires = []) => | ||
requires.reduce((acc, mod) => { | ||
exports.handleRequires = async (requires = [], {ignoredPlugins = []} = {}) => { | ||
const pluginLoader = PluginLoader.create({ignore: ignoredPlugins}); | ||
for await (const mod of requires) { | ||
let modpath = mod; | ||
@@ -94,41 +96,15 @@ // this is relative to cwd | ||
} | ||
const requiredModule = require(modpath); | ||
if (type(requiredModule) === 'object' && requiredModule.mochaHooks) { | ||
const mochaHooksType = type(requiredModule.mochaHooks); | ||
if (/function$/.test(mochaHooksType) || mochaHooksType === 'object') { | ||
debug('found root hooks in required file %s', mod); | ||
acc.push(requiredModule.mochaHooks); | ||
} else { | ||
throw createUnsupportedError( | ||
'mochaHooks must be an object or a function returning (or fulfilling with) an object' | ||
); | ||
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); | ||
return acc; | ||
}, []); | ||
/** | ||
* Loads root hooks as exported via `mochaHooks` from required files. | ||
* These can be sync/async functions returning objects, or just objects. | ||
* Flattens to a single object. | ||
* @param {Array<MochaRootHookObject|MochaRootHookFunction>} rootHooks - Array of root hooks | ||
* @private | ||
* @returns {MochaRootHookObject} | ||
*/ | ||
exports.loadRootHooks = async rootHooks => { | ||
const rootHookObjects = await Promise.all( | ||
rootHooks.map(async hook => (/function$/.test(type(hook)) ? hook() : hook)) | ||
); | ||
return rootHookObjects.reduce( | ||
(acc, hook) => { | ||
acc.beforeAll = acc.beforeAll.concat(hook.beforeAll || []); | ||
acc.beforeEach = acc.beforeEach.concat(hook.beforeEach || []); | ||
acc.afterAll = acc.afterAll.concat(hook.afterAll || []); | ||
acc.afterEach = acc.afterEach.concat(hook.afterEach || []); | ||
return acc; | ||
}, | ||
{beforeAll: [], beforeEach: [], afterAll: [], afterEach: []} | ||
); | ||
} | ||
const plugins = await pluginLoader.finalize(); | ||
if (Object.keys(plugins).length) { | ||
debug('finalized plugin implementations: %O', plugins); | ||
} | ||
return plugins; | ||
}; | ||
@@ -157,8 +133,34 @@ | ||
/** | ||
* Actually run tests | ||
* Collect files and run tests (using `BufferedRunner`). | ||
* | ||
* This is `async` for consistency. | ||
* | ||
* @param {Mocha} mocha - Mocha instance | ||
* @param {Object} opts - Command line options | ||
* @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 | ||
* @returns {Promise} | ||
*/ | ||
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) => { | ||
@@ -168,10 +170,8 @@ const { | ||
extension = [], | ||
exit = false, | ||
ignore = [], | ||
file = [], | ||
parallel = false, | ||
recursive = false, | ||
sort = false, | ||
spec = [], | ||
watchFiles, | ||
watchIgnore | ||
spec = [] | ||
} = options; | ||
@@ -188,7 +188,10 @@ | ||
let run; | ||
if (watch) { | ||
watchRun(mocha, {watchFiles, watchIgnore}, fileCollectParams); | ||
run = parallel ? watchParallelRun : watchRun; | ||
} else { | ||
await singleRun(mocha, {exit}, fileCollectParams); | ||
run = parallel ? parallelRun : singleRun; | ||
} | ||
return run(mocha, options, fileCollectParams); | ||
}; | ||
@@ -201,9 +204,9 @@ | ||
* @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 | ||
* @param {"reporter"|"ui"} pluginType - Type of plugin. | ||
* @param {Object} [map] - Used as a cache of sorts; | ||
* `Mocha.reporters` where each key corresponds to a reporter name, | ||
* `Mocha.interfaces` where each key corresponds to an interface name. | ||
* @private | ||
*/ | ||
exports.validatePlugin = (opts, pluginType, map = {}) => { | ||
exports.validateLegacyPlugin = (opts, pluginType, map = {}) => { | ||
/** | ||
@@ -217,3 +220,3 @@ * This should be a unique identifier; either a string (present in `map`), | ||
if (Array.isArray(pluginId)) { | ||
throw createInvalidPluginError( | ||
throw createInvalidLegacyPluginError( | ||
`"--${pluginType}" can only be specified once`, | ||
@@ -224,4 +227,4 @@ pluginType | ||
const unknownError = err => | ||
createInvalidPluginError( | ||
const createUnknownError = err => | ||
createInvalidLegacyPluginError( | ||
format('Could not load %s "%s":\n\n %O', pluginType, pluginId, err), | ||
@@ -234,14 +237,14 @@ pluginType, | ||
if (!map[pluginId]) { | ||
let foundId; | ||
try { | ||
opts[pluginType] = require(pluginId); | ||
foundId = require.resolve(pluginId); | ||
map[pluginId] = require(foundId); | ||
} 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 unknownError(err); | ||
} | ||
} else { | ||
throw unknownError(err); | ||
if (foundId) throw createUnknownError(err); | ||
// Try to load reporters from a cwd-relative path | ||
try { | ||
map[pluginId] = require(path.resolve(pluginId)); | ||
} catch (e) { | ||
throw createUnknownError(e); | ||
} | ||
@@ -248,0 +251,0 @@ } |
@@ -15,3 +15,3 @@ 'use strict'; | ||
*/ | ||
exports.types = { | ||
const TYPES = (exports.types = { | ||
array: [ | ||
@@ -22,2 +22,3 @@ 'extension', | ||
'ignore', | ||
'node-option', | ||
'reporter-option', | ||
@@ -37,7 +38,8 @@ 'require', | ||
'diff', | ||
'dry-run', | ||
'exit', | ||
'fail-zero', | ||
'forbid-only', | ||
'forbid-pending', | ||
'full-trace', | ||
'growl', | ||
'inline-diffs', | ||
@@ -48,2 +50,3 @@ 'invert', | ||
'no-colors', | ||
'parallel', | ||
'recursive', | ||
@@ -53,3 +56,3 @@ 'sort', | ||
], | ||
number: ['retries'], | ||
number: ['retries', 'jobs'], | ||
string: [ | ||
@@ -59,3 +62,2 @@ 'config', | ||
'grep', | ||
'opts', | ||
'package', | ||
@@ -67,3 +69,3 @@ 'reporter', | ||
] | ||
}; | ||
}); | ||
@@ -83,6 +85,8 @@ /** | ||
grep: ['g'], | ||
growl: ['G'], | ||
ignore: ['exclude'], | ||
invert: ['i'], | ||
jobs: ['j'], | ||
'no-colors': ['C'], | ||
'node-option': ['n'], | ||
parallel: ['p'], | ||
reporter: ['R'], | ||
@@ -97,1 +101,24 @@ 'reporter-option': ['reporter-options', 'O'], | ||
}; | ||
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(/^--?/, '')); | ||
}; |
@@ -10,2 +10,4 @@ 'use strict'; | ||
const symbols = require('log-symbols'); | ||
const ansi = require('ansi-colors'); | ||
const Mocha = require('../mocha'); | ||
@@ -21,4 +23,3 @@ const { | ||
handleRequires, | ||
validatePlugin, | ||
loadRootHooks, | ||
validateLegacyPlugin, | ||
runMocha | ||
@@ -87,2 +88,6 @@ } = require('./run-helpers'); | ||
}, | ||
'dry-run': { | ||
description: 'Report tests without executing them', | ||
group: GROUPS.RULES | ||
}, | ||
exit: { | ||
@@ -99,2 +104,6 @@ description: 'Force Mocha to quit after tests complete', | ||
}, | ||
'fail-zero': { | ||
description: 'Fail test run if no test(s) encountered', | ||
group: GROUPS.RULES | ||
}, | ||
fgrep: { | ||
@@ -139,6 +148,2 @@ conflicts: 'grep', | ||
}, | ||
growl: { | ||
description: 'Enable Growl notifications', | ||
group: GROUPS.OUTPUT | ||
}, | ||
ignore: { | ||
@@ -159,2 +164,9 @@ defaultDescription: '(none)', | ||
}, | ||
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': { | ||
@@ -173,8 +185,5 @@ conflicts: Array.from(ONE_AND_DONE_ARGS), | ||
}, | ||
opts: { | ||
default: defaults.opts, | ||
description: 'Path to `mocha.opts` (DEPRECATED)', | ||
group: GROUPS.CONFIG, | ||
normalize: true, | ||
requiresArg: true | ||
'node-option': { | ||
description: 'Node or V8 option (no leading "--")', | ||
group: GROUPS.CONFIG | ||
}, | ||
@@ -187,2 +196,6 @@ package: { | ||
}, | ||
parallel: { | ||
description: 'Run tests in parallel', | ||
group: GROUPS.RULES | ||
}, | ||
recursive: { | ||
@@ -290,6 +303,40 @@ description: 'Look for tests in subdirectories', | ||
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.` | ||
See https://github.com/mochajs/mocha/wiki/compilers-deprecation for migration information.` | ||
); | ||
@@ -307,10 +354,14 @@ } | ||
}) | ||
.middleware(async argv => { | ||
// load requires first, because it can impact "plugin" validation | ||
const rawRootHooks = await handleRequires(argv.require); | ||
validatePlugin(argv, 'reporter', Mocha.reporters); | ||
validatePlugin(argv, 'ui', Mocha.interfaces); | ||
if (rawRootHooks.length) { | ||
argv.rootHooks = await loadRootHooks(rawRootHooks); | ||
.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); | ||
} | ||
@@ -324,3 +375,3 @@ }) | ||
exports.handler = async function(argv) { | ||
exports.handler = async function (argv) { | ||
debug('post-yargs config', argv); | ||
@@ -327,0 +378,0 @@ const mocha = new Mocha(argv); |
'use strict'; | ||
const logSymbols = require('log-symbols'); | ||
const debug = require('debug')('mocha:cli:watch'); | ||
const path = require('path'); | ||
@@ -16,2 +18,65 @@ const chokidar = require('chokidar'); | ||
/** | ||
* 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. | ||
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 | ||
@@ -22,13 +87,76 @@ * @param {Mocha} mocha - Mocha instance | ||
* watch. If not provided all files with an extension included in | ||
* `fileColletionParams.extension` are watched. See first argument of | ||
* `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 {Object} fileCollectParams - Parameters that control test | ||
* @param {FileCollectionOptions} fileCollectParams - Parameters that control test | ||
* file collection. See `lib/cli/collect-files.js`. | ||
* @param {string[]} fileCollectParams.extension - List of extensions | ||
* to watch if `opts.watchFiles` is not given. | ||
* @private | ||
*/ | ||
module.exports = (mocha, {watchFiles, watchIgnore}, fileCollectParams) => { | ||
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. | ||
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) { | ||
@@ -38,2 +166,8 @@ 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, { | ||
@@ -44,10 +178,11 @@ ignored: watchIgnore, | ||
const rerunner = createRerunner(mocha, () => { | ||
getWatchedFiles(watcher).forEach(file => { | ||
delete require.cache[file]; | ||
}); | ||
mocha.files = collectFiles(fileCollectParams); | ||
const rerunner = createRerunner(mocha, watcher, { | ||
beforeRun | ||
}); | ||
watcher.on('ready', () => { | ||
watcher.on('ready', async () => { | ||
if (!globalFixtureContext) { | ||
debug('triggering global setup'); | ||
globalFixtureContext = await mocha.runGlobalSetup(); | ||
} | ||
rerunner.run(); | ||
@@ -60,3 +195,2 @@ }); | ||
console.log(); | ||
hideCursor(); | ||
@@ -66,6 +200,35 @@ process.on('exit', () => { | ||
}); | ||
process.on('SIGINT', () => { | ||
// 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.log('\n'); | ||
process.exit(128 + 2); | ||
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); | ||
} | ||
}); | ||
@@ -77,19 +240,21 @@ | ||
process.stdin.on('data', data => { | ||
const str = data | ||
.toString() | ||
.trim() | ||
.toLowerCase(); | ||
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. `beforeRun` is called everytime before `mocha.run()` is | ||
* called. | ||
* Create an object that allows you to rerun tests on the mocha instance. | ||
* | ||
* @param {Mocha} mocha - Mocha instance | ||
* @param {function} beforeRun - Called just before `mocha.run()` | ||
* @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, beforeRun) => { | ||
const createRerunner = (mocha, watcher, {beforeRun} = {}) => { | ||
// Set to a `Runner` when mocha is running. Set to `null` when mocha is not | ||
@@ -99,2 +264,3 @@ // running. | ||
// true if a file has changed during a test run | ||
let rerunScheduled = false; | ||
@@ -104,12 +270,15 @@ | ||
try { | ||
beforeRun(); | ||
resetMocha(mocha); | ||
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.log(e.stack); | ||
console.error(e.stack); | ||
} | ||
@@ -148,31 +317,17 @@ }; | ||
* @return {string[]} - List of absolute paths | ||
* @ignore | ||
* @private | ||
*/ | ||
const getWatchedFiles = watcher => { | ||
const watchedDirs = watcher.getWatched(); | ||
let watchedFiles = []; | ||
Object.keys(watchedDirs).forEach(dir => { | ||
watchedFiles = watchedFiles.concat( | ||
watchedDirs[dir].map(file => path.join(dir, file)) | ||
); | ||
}); | ||
return watchedFiles; | ||
return Object.keys(watchedDirs).reduce( | ||
(acc, dir) => [ | ||
...acc, | ||
...watchedDirs[dir].map(file => path.join(dir, file)) | ||
], | ||
[] | ||
); | ||
}; | ||
/** | ||
* Reset the internal state of the mocha instance so that tests can be rerun. | ||
* | ||
* @param {Mocha} mocha - Mocha instance | ||
* @private | ||
*/ | ||
const resetMocha = mocha => { | ||
mocha.unloadFiles(); | ||
mocha.suite = mocha.suite.clone(); | ||
mocha.suite.ctx = new Context(); | ||
// Registers a callback on `mocha.suite` that wires new context to the DSL | ||
// (e.g. `describe`) that is exposed as globals when the test files are | ||
// reloaded. | ||
mocha.ui(mocha.options.ui); | ||
}; | ||
/** | ||
* Hide the cursor. | ||
@@ -202,1 +357,32 @@ * @ignore | ||
}; | ||
/** | ||
* 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` | ||
*/ |
@@ -25,3 +25,3 @@ 'use strict'; | ||
*/ | ||
Context.prototype.runnable = function(runnable) { | ||
Context.prototype.runnable = function (runnable) { | ||
if (!arguments.length) { | ||
@@ -41,3 +41,3 @@ return this._runnable; | ||
*/ | ||
Context.prototype.timeout = function(ms) { | ||
Context.prototype.timeout = function (ms) { | ||
if (!arguments.length) { | ||
@@ -51,17 +51,2 @@ return this.runnable().timeout(); | ||
/** | ||
* Set test timeout `enabled`. | ||
* | ||
* @private | ||
* @param {boolean} enabled | ||
* @return {Context} self | ||
*/ | ||
Context.prototype.enableTimeouts = function(enabled) { | ||
if (!arguments.length) { | ||
return this.runnable().enableTimeouts(); | ||
} | ||
this.runnable().enableTimeouts(enabled); | ||
return this; | ||
}; | ||
/** | ||
* Set or get test slowness threshold `ms`. | ||
@@ -73,3 +58,3 @@ * | ||
*/ | ||
Context.prototype.slow = function(ms) { | ||
Context.prototype.slow = function (ms) { | ||
if (!arguments.length) { | ||
@@ -88,3 +73,3 @@ return this.runnable().slow(); | ||
*/ | ||
Context.prototype.skip = function() { | ||
Context.prototype.skip = function () { | ||
this.runnable().skip(); | ||
@@ -100,3 +85,3 @@ }; | ||
*/ | ||
Context.prototype.retries = function(n) { | ||
Context.prototype.retries = function (n) { | ||
if (!arguments.length) { | ||
@@ -103,0 +88,0 @@ return this.runnable().retries(); |
'use strict'; | ||
var format = require('util').format; | ||
const {format} = require('util'); | ||
/** | ||
* Factory functions to create throwable error objects | ||
* @module Errors | ||
* Contains error codes, factory functions to create throwable error objects, | ||
* and warning/deprecation functions. | ||
* @module | ||
*/ | ||
/** | ||
* When Mocha throw exceptions (or otherwise errors), it attempts to assign a | ||
* `code` property to the `Error` object, for easier handling. These are the | ||
* potential values of `code`. | ||
* process.emitWarning or a polyfill | ||
* @see https://nodejs.org/api/process.html#process_process_emitwarning_warning_options | ||
* @ignore | ||
*/ | ||
const emitWarning = (msg, type) => { | ||
if (process.emitWarning) { | ||
process.emitWarning(msg, type); | ||
} else { | ||
/* istanbul ignore next */ | ||
process.nextTick(function () { | ||
console.warn(type + ': ' + msg); | ||
}); | ||
} | ||
}; | ||
/** | ||
* Show a deprecation warning. Each distinct message is only displayed once. | ||
* Ignores empty messages. | ||
* | ||
* @param {string} [msg] - Warning to print | ||
* @private | ||
*/ | ||
const deprecate = msg => { | ||
msg = String(msg); | ||
if (msg && !deprecate.cache[msg]) { | ||
deprecate.cache[msg] = true; | ||
emitWarning(msg, 'DeprecationWarning'); | ||
} | ||
}; | ||
deprecate.cache = {}; | ||
/** | ||
* Show a generic warning. | ||
* Ignores empty messages. | ||
* | ||
* @param {string} [msg] - Warning to print | ||
* @private | ||
*/ | ||
const warn = msg => { | ||
if (msg) { | ||
emitWarning(msg); | ||
} | ||
}; | ||
/** | ||
* When Mocha throws exceptions (or rejects `Promise`s), it attempts to assign a `code` property to the `Error` object, for easier handling. These are the potential values of `code`. | ||
* @public | ||
* @namespace | ||
* @memberof module:lib/errors | ||
*/ | ||
var constants = { | ||
/** | ||
* An unrecoverable error. | ||
* @constant | ||
* @default | ||
*/ | ||
@@ -23,2 +72,4 @@ FATAL: 'ERR_MOCHA_FATAL', | ||
* The type of an argument to a function call is invalid | ||
* @constant | ||
* @default | ||
*/ | ||
@@ -29,2 +80,4 @@ INVALID_ARG_TYPE: 'ERR_MOCHA_INVALID_ARG_TYPE', | ||
* The value of an argument to a function call is invalid | ||
* @constant | ||
* @default | ||
*/ | ||
@@ -35,2 +88,4 @@ INVALID_ARG_VALUE: 'ERR_MOCHA_INVALID_ARG_VALUE', | ||
* Something was thrown, but it wasn't an `Error` | ||
* @constant | ||
* @default | ||
*/ | ||
@@ -41,2 +96,4 @@ INVALID_EXCEPTION: 'ERR_MOCHA_INVALID_EXCEPTION', | ||
* An interface (e.g., `Mocha.interfaces`) is unknown or invalid | ||
* @constant | ||
* @default | ||
*/ | ||
@@ -47,2 +104,4 @@ INVALID_INTERFACE: 'ERR_MOCHA_INVALID_INTERFACE', | ||
* A reporter (.e.g, `Mocha.reporters`) is unknown or invalid | ||
* @constant | ||
* @default | ||
*/ | ||
@@ -53,2 +112,4 @@ INVALID_REPORTER: 'ERR_MOCHA_INVALID_REPORTER', | ||
* `done()` was called twice in a `Test` or `Hook` callback | ||
* @constant | ||
* @default | ||
*/ | ||
@@ -59,2 +120,4 @@ MULTIPLE_DONE: 'ERR_MOCHA_MULTIPLE_DONE', | ||
* No files matched the pattern provided by the user | ||
* @constant | ||
* @default | ||
*/ | ||
@@ -65,2 +128,4 @@ NO_FILES_MATCH_PATTERN: 'ERR_MOCHA_NO_FILES_MATCH_PATTERN', | ||
* Known, but unsupported behavior of some kind | ||
* @constant | ||
* @default | ||
*/ | ||
@@ -70,3 +135,5 @@ UNSUPPORTED: 'ERR_MOCHA_UNSUPPORTED', | ||
/** | ||
* Invalid state transition occuring in `Mocha` instance | ||
* Invalid state transition occurring in `Mocha` instance | ||
* @constant | ||
* @default | ||
*/ | ||
@@ -76,11 +143,55 @@ INSTANCE_ALREADY_RUNNING: 'ERR_MOCHA_INSTANCE_ALREADY_RUNNING', | ||
/** | ||
* Invalid state transition occuring in `Mocha` instance | ||
* Invalid state transition occurring in `Mocha` instance | ||
* @constant | ||
* @default | ||
*/ | ||
INSTANCE_ALREADY_DISPOSED: 'ERR_MOCHA_INSTANCE_ALREADY_DISPOSED' | ||
INSTANCE_ALREADY_DISPOSED: 'ERR_MOCHA_INSTANCE_ALREADY_DISPOSED', | ||
/** | ||
* Use of `only()` w/ `--forbid-only` results in this error. | ||
* @constant | ||
* @default | ||
*/ | ||
FORBIDDEN_EXCLUSIVITY: 'ERR_MOCHA_FORBIDDEN_EXCLUSIVITY', | ||
/** | ||
* To be thrown when a user-defined plugin implementation (e.g., `mochaHooks`) is invalid | ||
* @constant | ||
* @default | ||
*/ | ||
INVALID_PLUGIN_IMPLEMENTATION: 'ERR_MOCHA_INVALID_PLUGIN_IMPLEMENTATION', | ||
/** | ||
* To be thrown when a builtin or third-party plugin definition (the _definition_ of `mochaHooks`) is invalid | ||
* @constant | ||
* @default | ||
*/ | ||
INVALID_PLUGIN_DEFINITION: 'ERR_MOCHA_INVALID_PLUGIN_DEFINITION', | ||
/** | ||
* When a runnable exceeds its allowed run time. | ||
* @constant | ||
* @default | ||
*/ | ||
TIMEOUT: 'ERR_MOCHA_TIMEOUT', | ||
/** | ||
* Input file is not able to be parsed | ||
* @constant | ||
* @default | ||
*/ | ||
UNPARSABLE_FILE: 'ERR_MOCHA_UNPARSABLE_FILE' | ||
}; | ||
/** | ||
* A set containing all string values of all Mocha error constants, for use by {@link isMochaError}. | ||
* @private | ||
*/ | ||
const MOCHA_ERRORS = new Set(Object.values(constants)); | ||
/** | ||
* Creates an error object to be thrown when no files to be tested could be found using specified pattern. | ||
* | ||
* @public | ||
* @static | ||
* @param {string} message - Error message to be displayed. | ||
@@ -116,2 +227,3 @@ * @param {string} pattern - User-specified argument value. | ||
* @public | ||
* @static | ||
* @param {string} message - Error message to be displayed. | ||
@@ -132,2 +244,3 @@ * @param {string} ui - User-specified interface value. | ||
* @public | ||
* @static | ||
* @param {string} message - Error message to be displayed. | ||
@@ -146,2 +259,3 @@ * @returns {Error} instance detailing the error condition | ||
* @public | ||
* @static | ||
* @param {string} message - Error message to be displayed. | ||
@@ -160,2 +274,3 @@ * @param {string} argument - Argument name. | ||
* @public | ||
* @static | ||
* @param {string} message - Error message to be displayed. | ||
@@ -179,2 +294,3 @@ * @param {string} argument - Argument name. | ||
* @public | ||
* @static | ||
* @param {string} message - Error message to be displayed. | ||
@@ -199,2 +315,3 @@ * @param {string} argument - Argument name. | ||
* @public | ||
* @static | ||
* @param {string} message - Error message to be displayed. | ||
@@ -215,2 +332,3 @@ * @returns {Error} instance detailing the error condition | ||
* @public | ||
* @static | ||
* @param {string} message - Error message to be displayed. | ||
@@ -230,13 +348,14 @@ * @returns {Error} instance detailing the error condition | ||
* @param {string} message - Error message | ||
* @param {"reporter"|"interface"} pluginType - Plugin type. Future: expand as needed | ||
* @param {"reporter"|"ui"} pluginType - Plugin type. Future: expand as needed | ||
* @param {string} [pluginId] - Name/path of plugin, if any | ||
* @throws When `pluginType` is not known | ||
* @public | ||
* @static | ||
* @returns {Error} | ||
*/ | ||
function createInvalidPluginError(message, pluginType, pluginId) { | ||
function createInvalidLegacyPluginError(message, pluginType, pluginId) { | ||
switch (pluginType) { | ||
case 'reporter': | ||
return createInvalidReporterError(message, pluginId); | ||
case 'interface': | ||
case 'ui': | ||
return createInvalidInterfaceError(message, pluginId); | ||
@@ -249,2 +368,18 @@ default: | ||
/** | ||
* **DEPRECATED**. Use {@link createInvalidLegacyPluginError} instead Dynamically creates a plugin-type-specific error based on plugin type | ||
* @deprecated | ||
* @param {string} message - Error message | ||
* @param {"reporter"|"interface"} pluginType - Plugin type. Future: expand as needed | ||
* @param {string} [pluginId] - Name/path of plugin, if any | ||
* @throws When `pluginType` is not known | ||
* @public | ||
* @static | ||
* @returns {Error} | ||
*/ | ||
function createInvalidPluginError(...args) { | ||
deprecate('Use createInvalidLegacyPluginError() instead'); | ||
return createInvalidLegacyPluginError(...args); | ||
} | ||
/** | ||
* Creates an error object to be thrown when a mocha object's `run` method is executed while it is already disposed. | ||
@@ -254,2 +389,3 @@ * @param {string} message The error message to be displayed. | ||
* @param {Mocha} instance the mocha instance that throw this error | ||
* @static | ||
*/ | ||
@@ -271,2 +407,4 @@ function createMochaInstanceAlreadyDisposedError( | ||
* @param {string} message The error message to be displayed. | ||
* @static | ||
* @public | ||
*/ | ||
@@ -280,3 +418,3 @@ function createMochaInstanceAlreadyRunningError(message, instance) { | ||
/* | ||
/** | ||
* Creates an error object to be thrown when done() is called multiple times in a test | ||
@@ -288,2 +426,3 @@ * | ||
* @returns {Error} instance detailing the error condition | ||
* @static | ||
*/ | ||
@@ -319,17 +458,130 @@ function createMultipleDoneError(runnable, originalErr) { | ||
/** | ||
* Creates an error object to be thrown when `.only()` is used with | ||
* `--forbid-only`. | ||
* @static | ||
* @public | ||
* @param {Mocha} mocha - Mocha instance | ||
* @returns {Error} Error with code {@link constants.FORBIDDEN_EXCLUSIVITY} | ||
*/ | ||
function createForbiddenExclusivityError(mocha) { | ||
var err = new Error( | ||
mocha.isWorker | ||
? '`.only` is not supported in parallel mode' | ||
: '`.only` forbidden by --forbid-only' | ||
); | ||
err.code = constants.FORBIDDEN_EXCLUSIVITY; | ||
return err; | ||
} | ||
/** | ||
* Creates an error object to be thrown when a plugin definition is invalid | ||
* @static | ||
* @param {string} msg - Error message | ||
* @param {PluginDefinition} [pluginDef] - Problematic plugin definition | ||
* @public | ||
* @returns {Error} Error with code {@link constants.INVALID_PLUGIN_DEFINITION} | ||
*/ | ||
function createInvalidPluginDefinitionError(msg, pluginDef) { | ||
const err = new Error(msg); | ||
err.code = constants.INVALID_PLUGIN_DEFINITION; | ||
err.pluginDef = pluginDef; | ||
return err; | ||
} | ||
/** | ||
* Creates an error object to be thrown when a plugin implementation (user code) is invalid | ||
* @static | ||
* @param {string} msg - Error message | ||
* @param {Object} [opts] - Plugin definition and user-supplied implementation | ||
* @param {PluginDefinition} [opts.pluginDef] - Plugin Definition | ||
* @param {*} [opts.pluginImpl] - Plugin Implementation (user-supplied) | ||
* @public | ||
* @returns {Error} Error with code {@link constants.INVALID_PLUGIN_DEFINITION} | ||
*/ | ||
function createInvalidPluginImplementationError( | ||
msg, | ||
{pluginDef, pluginImpl} = {} | ||
) { | ||
const err = new Error(msg); | ||
err.code = constants.INVALID_PLUGIN_IMPLEMENTATION; | ||
err.pluginDef = pluginDef; | ||
err.pluginImpl = pluginImpl; | ||
return err; | ||
} | ||
/** | ||
* Creates an error object to be thrown when a runnable exceeds its allowed run time. | ||
* @static | ||
* @param {string} msg - Error message | ||
* @param {number} [timeout] - Timeout in ms | ||
* @param {string} [file] - File, if given | ||
* @returns {MochaTimeoutError} | ||
*/ | ||
function createTimeoutError(msg, timeout, file) { | ||
const err = new Error(msg); | ||
err.code = constants.TIMEOUT; | ||
err.timeout = timeout; | ||
err.file = file; | ||
return err; | ||
} | ||
/** | ||
* Creates an error object to be thrown when file is unparsable | ||
* @public | ||
* @static | ||
* @param {string} message - Error message to be displayed. | ||
* @param {string} filename - File name | ||
* @returns {Error} Error with code {@link constants.UNPARSABLE_FILE} | ||
*/ | ||
function createUnparsableFileError(message, filename) { | ||
var err = new Error(message); | ||
err.code = constants.UNPARSABLE_FILE; | ||
return err; | ||
} | ||
/** | ||
* Returns `true` if an error came out of Mocha. | ||
* _Can suffer from false negatives, but not false positives._ | ||
* @static | ||
* @public | ||
* @param {*} err - Error, or anything | ||
* @returns {boolean} | ||
*/ | ||
const isMochaError = err => | ||
Boolean(err && typeof err === 'object' && MOCHA_ERRORS.has(err.code)); | ||
module.exports = { | ||
createInvalidArgumentTypeError: createInvalidArgumentTypeError, | ||
createInvalidArgumentValueError: createInvalidArgumentValueError, | ||
createInvalidExceptionError: createInvalidExceptionError, | ||
createInvalidInterfaceError: createInvalidInterfaceError, | ||
createInvalidReporterError: createInvalidReporterError, | ||
createMissingArgumentError: createMissingArgumentError, | ||
createNoFilesMatchPatternError: createNoFilesMatchPatternError, | ||
createUnsupportedError: createUnsupportedError, | ||
createInvalidPluginError: createInvalidPluginError, | ||
createMochaInstanceAlreadyDisposedError: createMochaInstanceAlreadyDisposedError, | ||
createMochaInstanceAlreadyRunningError: createMochaInstanceAlreadyRunningError, | ||
createFatalError: createFatalError, | ||
createMultipleDoneError: createMultipleDoneError, | ||
constants: constants | ||
constants, | ||
createFatalError, | ||
createForbiddenExclusivityError, | ||
createInvalidArgumentTypeError, | ||
createInvalidArgumentValueError, | ||
createInvalidExceptionError, | ||
createInvalidInterfaceError, | ||
createInvalidLegacyPluginError, | ||
createInvalidPluginDefinitionError, | ||
createInvalidPluginError, | ||
createInvalidPluginImplementationError, | ||
createInvalidReporterError, | ||
createMissingArgumentError, | ||
createMochaInstanceAlreadyDisposedError, | ||
createMochaInstanceAlreadyRunningError, | ||
createMultipleDoneError, | ||
createNoFilesMatchPatternError, | ||
createTimeoutError, | ||
createUnparsableFileError, | ||
createUnsupportedError, | ||
deprecate, | ||
isMochaError, | ||
warn | ||
}; | ||
/** | ||
* The error thrown when a Runnable times out | ||
* @memberof module:lib/errors | ||
* @typedef {Error} MochaTimeoutError | ||
* @property {constants.TIMEOUT} code - Error code | ||
* @property {number?} timeout Timeout in ms | ||
* @property {string?} file Filepath, if given | ||
*/ |
'use strict'; | ||
var Runnable = require('./runnable'); | ||
var inherits = require('./utils').inherits; | ||
const {inherits, constants} = require('./utils'); | ||
const {MOCHA_ID_PROP_NAME} = constants; | ||
@@ -33,3 +34,3 @@ /** | ||
*/ | ||
Hook.prototype.reset = function() { | ||
Hook.prototype.reset = function () { | ||
Runnable.prototype.reset.call(this); | ||
@@ -47,3 +48,3 @@ delete this._error; | ||
*/ | ||
Hook.prototype.error = function(err) { | ||
Hook.prototype.error = function (err) { | ||
if (!arguments.length) { | ||
@@ -57,1 +58,35 @@ err = this._error; | ||
}; | ||
/** | ||
* Returns an object suitable for IPC. | ||
* Functions are represented by keys beginning with `$$`. | ||
* @private | ||
* @returns {Object} | ||
*/ | ||
Hook.prototype.serialize = function serialize() { | ||
return { | ||
$$currentRetry: this.currentRetry(), | ||
$$fullTitle: this.fullTitle(), | ||
$$isPending: Boolean(this.isPending()), | ||
$$titlePath: this.titlePath(), | ||
ctx: | ||
this.ctx && this.ctx.currentTest | ||
? { | ||
currentTest: { | ||
title: this.ctx.currentTest.title, | ||
[MOCHA_ID_PROP_NAME]: this.ctx.currentTest.id | ||
} | ||
} | ||
: {}, | ||
duration: this.duration, | ||
file: this.file, | ||
parent: { | ||
$$fullTitle: this.parent.fullTitle(), | ||
[MOCHA_ID_PROP_NAME]: this.parent.id | ||
}, | ||
state: this.state, | ||
title: this.title, | ||
type: this.type, | ||
[MOCHA_ID_PROP_NAME]: this.id | ||
}; | ||
}; |
'use strict'; | ||
var Test = require('../test'); | ||
var EVENT_FILE_PRE_REQUIRE = require('../suite').constants | ||
.EVENT_FILE_PRE_REQUIRE; | ||
var EVENT_FILE_PRE_REQUIRE = | ||
require('../suite').constants.EVENT_FILE_PRE_REQUIRE; | ||
@@ -27,3 +27,3 @@ /** | ||
suite.on(EVENT_FILE_PRE_REQUIRE, function(context, file, mocha) { | ||
suite.on(EVENT_FILE_PRE_REQUIRE, function (context, file, mocha) { | ||
var common = require('./common')(suites, context, mocha); | ||
@@ -42,7 +42,7 @@ | ||
context.describe = context.context = function(title, fn) { | ||
context.describe = context.context = function (title, fn) { | ||
return common.suite.create({ | ||
title: title, | ||
file: file, | ||
fn: fn | ||
title, | ||
file, | ||
fn | ||
}); | ||
@@ -55,12 +55,12 @@ }; | ||
context.xdescribe = context.xcontext = context.describe.skip = function( | ||
title, | ||
fn | ||
) { | ||
return common.suite.skip({ | ||
title: title, | ||
file: file, | ||
fn: fn | ||
}); | ||
}; | ||
context.xdescribe = | ||
context.xcontext = | ||
context.describe.skip = | ||
function (title, fn) { | ||
return common.suite.skip({ | ||
title, | ||
file, | ||
fn | ||
}); | ||
}; | ||
@@ -71,7 +71,7 @@ /** | ||
context.describe.only = function(title, fn) { | ||
context.describe.only = function (title, fn) { | ||
return common.suite.only({ | ||
title: title, | ||
file: file, | ||
fn: fn | ||
title, | ||
file, | ||
fn | ||
}); | ||
@@ -86,3 +86,3 @@ }; | ||
context.it = context.specify = function(title, fn) { | ||
context.it = context.specify = function (title, fn) { | ||
var suite = suites[0]; | ||
@@ -102,3 +102,3 @@ if (suite.isPending()) { | ||
context.it.only = function(title, fn) { | ||
context.it.only = function (title, fn) { | ||
return common.test.only(mocha, context.it(title, fn)); | ||
@@ -111,12 +111,8 @@ }; | ||
context.xit = context.xspecify = context.it.skip = function(title) { | ||
return context.it(title); | ||
}; | ||
/** | ||
* Number of attempts to retry. | ||
*/ | ||
context.it.retries = function(n) { | ||
context.retries(n); | ||
}; | ||
context.xit = | ||
context.xspecify = | ||
context.it.skip = | ||
function (title) { | ||
return context.it(title); | ||
}; | ||
}); | ||
@@ -123,0 +119,0 @@ }; |
'use strict'; | ||
/** | ||
@module interfaces/common | ||
*/ | ||
var Suite = require('../suite'); | ||
@@ -7,2 +11,3 @@ var errors = require('../errors'); | ||
var createUnsupportedError = errors.createUnsupportedError; | ||
var createForbiddenExclusivityError = errors.createForbiddenExclusivityError; | ||
@@ -12,2 +17,3 @@ /** | ||
* | ||
* @private | ||
* @param {Suite[]} suites | ||
@@ -18,3 +24,3 @@ * @param {Context} context | ||
*/ | ||
module.exports = function(suites, context, mocha) { | ||
module.exports = function (suites, context, mocha) { | ||
/** | ||
@@ -56,3 +62,3 @@ * Check if the suite should be tested. | ||
*/ | ||
before: function(name, fn) { | ||
before: function (name, fn) { | ||
suites[0].beforeAll(name, fn); | ||
@@ -67,3 +73,3 @@ }, | ||
*/ | ||
after: function(name, fn) { | ||
after: function (name, fn) { | ||
suites[0].afterAll(name, fn); | ||
@@ -78,3 +84,3 @@ }, | ||
*/ | ||
beforeEach: function(name, fn) { | ||
beforeEach: function (name, fn) { | ||
suites[0].beforeEach(name, fn); | ||
@@ -89,3 +95,3 @@ }, | ||
*/ | ||
afterEach: function(name, fn) { | ||
afterEach: function (name, fn) { | ||
suites[0].afterEach(name, fn); | ||
@@ -103,2 +109,5 @@ }, | ||
only: function only(opts) { | ||
if (mocha.options.forbidOnly) { | ||
throw createForbiddenExclusivityError(mocha); | ||
} | ||
opts.isOnly = true; | ||
@@ -137,12 +146,10 @@ return this.create(opts); | ||
if (opts.isOnly) { | ||
if (mocha.options.forbidOnly && shouldBeTested(suite)) { | ||
throw createUnsupportedError('`.only` forbidden'); | ||
} | ||
suite.parent.appendOnlySuite(suite); | ||
suite.markOnly(); | ||
} | ||
if (suite.pending) { | ||
if (mocha.options.forbidPending && shouldBeTested(suite)) { | ||
throw createUnsupportedError('Pending test forbidden'); | ||
} | ||
if ( | ||
suite.pending && | ||
mocha.options.forbidPending && | ||
shouldBeTested(suite) | ||
) { | ||
throw createUnsupportedError('Pending test forbidden'); | ||
} | ||
@@ -177,5 +184,6 @@ if (typeof opts.fn === 'function') { | ||
*/ | ||
only: function(mocha, test) { | ||
if (mocha.options.forbidOnly) | ||
throw createUnsupportedError('`.only` forbidden'); | ||
only: function (mocha, test) { | ||
if (mocha.options.forbidOnly) { | ||
throw createForbiddenExclusivityError(mocha); | ||
} | ||
test.markOnly(); | ||
@@ -190,13 +198,4 @@ return test; | ||
*/ | ||
skip: function(title) { | ||
skip: function (title) { | ||
context.test(title); | ||
}, | ||
/** | ||
* Number of retry attempts | ||
* | ||
* @param {number} n | ||
*/ | ||
retries: function(n) { | ||
context.retries(n); | ||
} | ||
@@ -203,0 +202,0 @@ } |
@@ -22,3 +22,3 @@ 'use strict'; | ||
*/ | ||
module.exports = function(suite) { | ||
module.exports = function (suite) { | ||
var suites = [suite]; | ||
@@ -25,0 +25,0 @@ |
'use strict'; | ||
var Test = require('../test'); | ||
var EVENT_FILE_PRE_REQUIRE = require('../suite').constants | ||
.EVENT_FILE_PRE_REQUIRE; | ||
var EVENT_FILE_PRE_REQUIRE = | ||
require('../suite').constants.EVENT_FILE_PRE_REQUIRE; | ||
@@ -35,3 +35,3 @@ /** | ||
suite.on(EVENT_FILE_PRE_REQUIRE, function(context, file, mocha) { | ||
suite.on(EVENT_FILE_PRE_REQUIRE, function (context, file, mocha) { | ||
var common = require('./common')(suites, context, mocha); | ||
@@ -48,3 +48,3 @@ | ||
context.suite = function(title) { | ||
context.suite = function (title) { | ||
if (suites.length > 1) { | ||
@@ -54,4 +54,4 @@ suites.shift(); | ||
return common.suite.create({ | ||
title: title, | ||
file: file, | ||
title, | ||
file, | ||
fn: false | ||
@@ -65,3 +65,3 @@ }); | ||
context.suite.only = function(title) { | ||
context.suite.only = function (title) { | ||
if (suites.length > 1) { | ||
@@ -71,4 +71,4 @@ suites.shift(); | ||
return common.suite.only({ | ||
title: title, | ||
file: file, | ||
title, | ||
file, | ||
fn: false | ||
@@ -84,3 +84,3 @@ }); | ||
context.test = function(title, fn) { | ||
context.test = function (title, fn) { | ||
var test = new Test(title, fn); | ||
@@ -96,3 +96,3 @@ test.file = file; | ||
context.test.only = function(title, fn) { | ||
context.test.only = function (title, fn) { | ||
return common.test.only(mocha, context.test(title, fn)); | ||
@@ -102,3 +102,2 @@ }; | ||
context.test.skip = common.test.skip; | ||
context.test.retries = common.test.retries; | ||
}); | ||
@@ -105,0 +104,0 @@ }; |
'use strict'; | ||
var Test = require('../test'); | ||
var EVENT_FILE_PRE_REQUIRE = require('../suite').constants | ||
.EVENT_FILE_PRE_REQUIRE; | ||
var EVENT_FILE_PRE_REQUIRE = | ||
require('../suite').constants.EVENT_FILE_PRE_REQUIRE; | ||
@@ -32,6 +32,6 @@ /** | ||
*/ | ||
module.exports = function(suite) { | ||
module.exports = function (suite) { | ||
var suites = [suite]; | ||
suite.on(EVENT_FILE_PRE_REQUIRE, function(context, file, mocha) { | ||
suite.on(EVENT_FILE_PRE_REQUIRE, function (context, file, mocha) { | ||
var common = require('./common')(suites, context, mocha); | ||
@@ -49,7 +49,7 @@ | ||
*/ | ||
context.suite = function(title, fn) { | ||
context.suite = function (title, fn) { | ||
return common.suite.create({ | ||
title: title, | ||
file: file, | ||
fn: fn | ||
title, | ||
file, | ||
fn | ||
}); | ||
@@ -61,7 +61,7 @@ }; | ||
*/ | ||
context.suite.skip = function(title, fn) { | ||
context.suite.skip = function (title, fn) { | ||
return common.suite.skip({ | ||
title: title, | ||
file: file, | ||
fn: fn | ||
title, | ||
file, | ||
fn | ||
}); | ||
@@ -73,7 +73,7 @@ }; | ||
*/ | ||
context.suite.only = function(title, fn) { | ||
context.suite.only = function (title, fn) { | ||
return common.suite.only({ | ||
title: title, | ||
file: file, | ||
fn: fn | ||
title, | ||
file, | ||
fn | ||
}); | ||
@@ -86,3 +86,3 @@ }; | ||
*/ | ||
context.test = function(title, fn) { | ||
context.test = function (title, fn) { | ||
var suite = suites[0]; | ||
@@ -102,3 +102,3 @@ if (suite.isPending()) { | ||
context.test.only = function(title, fn) { | ||
context.test.only = function (title, fn) { | ||
return common.test.only(mocha, context.test(title, fn)); | ||
@@ -108,3 +108,2 @@ }; | ||
context.test.skip = common.test.skip; | ||
context.test.retries = common.test.retries; | ||
}); | ||
@@ -111,0 +110,0 @@ }; |
818
lib/mocha.js
@@ -12,19 +12,17 @@ 'use strict'; | ||
var builtinReporters = require('./reporters'); | ||
var growl = require('./growl'); | ||
var utils = require('./utils'); | ||
var mocharc = require('./mocharc.json'); | ||
var errors = require('./errors'); | ||
var Suite = require('./suite'); | ||
var esmUtils = utils.supportsEsModules() ? require('./esm-utils') : undefined; | ||
var esmUtils = require('./nodejs/esm-utils'); | ||
var createStatsCollector = require('./stats-collector'); | ||
var createInvalidReporterError = errors.createInvalidReporterError; | ||
var createInvalidInterfaceError = errors.createInvalidInterfaceError; | ||
var createMochaInstanceAlreadyDisposedError = | ||
errors.createMochaInstanceAlreadyDisposedError; | ||
var createMochaInstanceAlreadyRunningError = | ||
errors.createMochaInstanceAlreadyRunningError; | ||
var EVENT_FILE_PRE_REQUIRE = Suite.constants.EVENT_FILE_PRE_REQUIRE; | ||
var EVENT_FILE_POST_REQUIRE = Suite.constants.EVENT_FILE_POST_REQUIRE; | ||
var EVENT_FILE_REQUIRE = Suite.constants.EVENT_FILE_REQUIRE; | ||
var sQuote = utils.sQuote; | ||
const { | ||
createInvalidReporterError, | ||
createInvalidInterfaceError, | ||
createMochaInstanceAlreadyDisposedError, | ||
createMochaInstanceAlreadyRunningError, | ||
createUnsupportedError | ||
} = require('./errors'); | ||
const {EVENT_FILE_PRE_REQUIRE, EVENT_FILE_POST_REQUIRE, EVENT_FILE_REQUIRE} = | ||
Suite.constants; | ||
var debug = require('debug')('mocha:mocha'); | ||
@@ -36,2 +34,3 @@ exports = module.exports = Mocha; | ||
* These are the states it can be in. | ||
* @private | ||
*/ | ||
@@ -41,2 +40,3 @@ var mochaStates = utils.defineConstants({ | ||
* Initial state of the mocha instance | ||
* @private | ||
*/ | ||
@@ -46,2 +46,3 @@ INIT: 'init', | ||
* Mocha instance is running tests | ||
* @private | ||
*/ | ||
@@ -52,2 +53,3 @@ RUNNING: 'running', | ||
* You can reset this state by unloading the test files. | ||
* @private | ||
*/ | ||
@@ -57,2 +59,3 @@ REFERENCES_CLEANED: 'referencesCleaned', | ||
* Mocha instance is disposed and can no longer be used. | ||
* @private | ||
*/ | ||
@@ -66,3 +69,3 @@ DISPOSED: 'disposed' | ||
if (!process.browser && typeof module.paths !== 'undefined') { | ||
if (!utils.isBrowser() && typeof module.paths !== 'undefined') { | ||
var cwd = utils.cwd(); | ||
@@ -74,9 +77,5 @@ module.paths.push(cwd, path.join(cwd, 'node_modules')); | ||
* Expose internals. | ||
* @private | ||
*/ | ||
/** | ||
* @public | ||
* @class utils | ||
* @memberof Mocha | ||
*/ | ||
exports.utils = utils; | ||
@@ -100,2 +99,57 @@ exports.interfaces = require('./interfaces'); | ||
let currentContext; | ||
exports.afterEach = function (...args) { | ||
return (currentContext.afterEach || currentContext.teardown).apply( | ||
this, | ||
args | ||
); | ||
}; | ||
exports.after = function (...args) { | ||
return (currentContext.after || currentContext.suiteTeardown).apply( | ||
this, | ||
args | ||
); | ||
}; | ||
exports.beforeEach = function (...args) { | ||
return (currentContext.beforeEach || currentContext.setup).apply(this, args); | ||
}; | ||
exports.before = function (...args) { | ||
return (currentContext.before || currentContext.suiteSetup).apply(this, args); | ||
}; | ||
exports.describe = function (...args) { | ||
return (currentContext.describe || currentContext.suite).apply(this, args); | ||
}; | ||
exports.describe.only = function (...args) { | ||
return (currentContext.describe || currentContext.suite).only.apply( | ||
this, | ||
args | ||
); | ||
}; | ||
exports.describe.skip = function (...args) { | ||
return (currentContext.describe || currentContext.suite).skip.apply( | ||
this, | ||
args | ||
); | ||
}; | ||
exports.it = function (...args) { | ||
return (currentContext.it || currentContext.test).apply(this, args); | ||
}; | ||
exports.it.only = function (...args) { | ||
return (currentContext.it || currentContext.test).only.apply(this, args); | ||
}; | ||
exports.it.skip = function (...args) { | ||
return (currentContext.it || currentContext.test).skip.apply(this, args); | ||
}; | ||
exports.xdescribe = exports.describe.skip; | ||
exports.xit = exports.it.skip; | ||
exports.setup = exports.beforeEach; | ||
exports.suiteSetup = exports.before; | ||
exports.suiteTeardown = exports.after; | ||
exports.suite = exports.describe; | ||
exports.teardown = exports.afterEach; | ||
exports.test = exports.it; | ||
exports.run = function (...args) { | ||
return currentContext.run.apply(this, args); | ||
}; | ||
/** | ||
@@ -114,2 +168,4 @@ * Constructs a new Mocha instance with `options`. | ||
* @param {boolean} [options.diff] - Show diff on failure? | ||
* @param {boolean} [options.dryRun] - Report tests without running them? | ||
* @param {boolean} [options.failZero] - Fail test run if zero tests? | ||
* @param {string} [options.fgrep] - Test filter given string. | ||
@@ -121,3 +177,2 @@ * @param {boolean} [options.forbidOnly] - Tests marked `only` fail the suite? | ||
* @param {RegExp|string} [options.grep] - Test filter given regular expression. | ||
* @param {boolean} [options.growl] - Enable desktop notifications? | ||
* @param {boolean} [options.inlineDiffs] - Display inline diffs? | ||
@@ -132,7 +187,10 @@ * @param {boolean} [options.invert] - Invert test filter matches? | ||
* @param {string} [options.ui] - Interface name. | ||
* @param {MochaRootHookObject} [options.rootHooks] - Hooks to bootstrap the root | ||
* suite with | ||
* @param {boolean} [options.parallel] - Run jobs in parallel. | ||
* @param {number} [options.jobs] - Max number of worker processes for parallel runs. | ||
* @param {MochaRootHookObject} [options.rootHooks] - Hooks to bootstrap the root suite with. | ||
* @param {string[]} [options.require] - Pathname of `rootHooks` plugin for parallel runs. | ||
* @param {boolean} [options.isWorker] - Should be `true` if `Mocha` process is running in a worker process. | ||
*/ | ||
function Mocha(options) { | ||
options = utils.assign({}, mocharc, options || {}); | ||
function Mocha(options = {}) { | ||
options = {...mocharc, ...options}; | ||
this.files = []; | ||
@@ -143,2 +201,3 @@ this.options = options; | ||
this._cleanReferencesAfterRun = true; | ||
this._state = mochaStates.INIT; | ||
@@ -150,3 +209,3 @@ this.grep(options.grep) | ||
options.reporter, | ||
options.reporterOption || options.reporterOptions // reporterOptions was previously the only way to specify options to reporter | ||
options.reporterOption || options.reporterOptions // for backwards compatibility | ||
) | ||
@@ -173,9 +232,10 @@ .slow(options.slow) | ||
'diff', | ||
'dryRun', | ||
'failZero', | ||
'forbidOnly', | ||
'forbidPending', | ||
'fullTrace', | ||
'growl', | ||
'inlineDiffs', | ||
'invert' | ||
].forEach(function(opt) { | ||
].forEach(function (opt) { | ||
if (options[opt]) { | ||
@@ -189,2 +249,40 @@ this[opt](); | ||
} | ||
/** | ||
* The class which we'll instantiate in {@link Mocha#run}. Defaults to | ||
* {@link Runner} in serial mode; changes in parallel mode. | ||
* @memberof Mocha | ||
* @private | ||
*/ | ||
this._runnerClass = exports.Runner; | ||
/** | ||
* Whether or not to call {@link Mocha#loadFiles} implicitly when calling | ||
* {@link Mocha#run}. If this is `true`, then it's up to the consumer to call | ||
* {@link Mocha#loadFiles} _or_ {@link Mocha#loadFilesAsync}. | ||
* @private | ||
* @memberof Mocha | ||
*/ | ||
this._lazyLoadFiles = false; | ||
/** | ||
* It's useful for a Mocha instance to know if it's running in a worker process. | ||
* We could derive this via other means, but it's helpful to have a flag to refer to. | ||
* @memberof Mocha | ||
* @private | ||
*/ | ||
this.isWorker = Boolean(options.isWorker); | ||
this.globalSetup(options.globalSetup) | ||
.globalTeardown(options.globalTeardown) | ||
.enableGlobalSetup(options.enableGlobalSetup) | ||
.enableGlobalTeardown(options.enableGlobalTeardown); | ||
if ( | ||
options.parallel && | ||
(typeof options.jobs === 'undefined' || options.jobs > 1) | ||
) { | ||
debug('attempting to enable parallel mode'); | ||
this.parallelMode(true); | ||
} | ||
} | ||
@@ -201,3 +299,3 @@ | ||
*/ | ||
Mocha.prototype.bail = function(bail) { | ||
Mocha.prototype.bail = function (bail) { | ||
this.suite.bail(bail !== false); | ||
@@ -220,3 +318,3 @@ return this; | ||
*/ | ||
Mocha.prototype.addFile = function(file) { | ||
Mocha.prototype.addFile = function (file) { | ||
this.files.push(file); | ||
@@ -232,3 +330,3 @@ return this; | ||
* @see [Reporters](../#reporters) | ||
* @param {String|Function} reporter - Reporter name or constructor. | ||
* @param {String|Function} reporterName - Reporter name or constructor. | ||
* @param {Object} [reporterOptions] - Options used to configure the reporter. | ||
@@ -243,51 +341,34 @@ * @returns {Mocha} this | ||
*/ | ||
Mocha.prototype.reporter = function(reporter, reporterOptions) { | ||
if (typeof reporter === 'function') { | ||
this._reporter = reporter; | ||
Mocha.prototype.reporter = function (reporterName, reporterOptions) { | ||
if (typeof reporterName === 'function') { | ||
this._reporter = reporterName; | ||
} else { | ||
reporter = reporter || 'spec'; | ||
var _reporter; | ||
reporterName = reporterName || 'spec'; | ||
var reporter; | ||
// Try to load a built-in reporter. | ||
if (builtinReporters[reporter]) { | ||
_reporter = builtinReporters[reporter]; | ||
if (builtinReporters[reporterName]) { | ||
reporter = builtinReporters[reporterName]; | ||
} | ||
// Try to load reporters from process.cwd() and node_modules | ||
if (!_reporter) { | ||
if (!reporter) { | ||
let foundReporter; | ||
try { | ||
_reporter = require(reporter); | ||
foundReporter = require.resolve(reporterName); | ||
reporter = require(foundReporter); | ||
} catch (err) { | ||
if ( | ||
err.code === 'MODULE_NOT_FOUND' || | ||
err.message.indexOf('Cannot find module') >= 0 | ||
) { | ||
// Try to load reporters from a path (absolute or relative) | ||
try { | ||
_reporter = require(path.resolve(utils.cwd(), reporter)); | ||
} catch (_err) { | ||
_err.code === 'MODULE_NOT_FOUND' || | ||
_err.message.indexOf('Cannot find module') >= 0 | ||
? utils.warn(sQuote(reporter) + ' reporter not found') | ||
: utils.warn( | ||
sQuote(reporter) + | ||
' reporter blew up with error:\n' + | ||
err.stack | ||
); | ||
} | ||
} else { | ||
utils.warn( | ||
sQuote(reporter) + ' reporter blew up with error:\n' + err.stack | ||
); | ||
if (foundReporter) { | ||
throw createInvalidReporterError(err.message, foundReporter); | ||
} | ||
// Try to load reporters from a cwd-relative path | ||
try { | ||
reporter = require(path.resolve(reporterName)); | ||
} catch (e) { | ||
throw createInvalidReporterError(e.message, reporterName); | ||
} | ||
} | ||
} | ||
if (!_reporter) { | ||
throw createInvalidReporterError( | ||
'invalid reporter ' + sQuote(reporter), | ||
reporter | ||
); | ||
} | ||
this._reporter = _reporter; | ||
this._reporter = reporter; | ||
} | ||
this.options.reporterOption = reporterOptions; | ||
// alias option name is used in public reporters xunit/tap/progress | ||
// alias option name is used in built-in reporters xunit/tap/progress | ||
this.options.reporterOptions = reporterOptions; | ||
@@ -308,3 +389,3 @@ return this; | ||
*/ | ||
Mocha.prototype.ui = function(ui) { | ||
Mocha.prototype.ui = function (ui) { | ||
var bindInterface; | ||
@@ -320,6 +401,3 @@ if (typeof ui === 'function') { | ||
} catch (err) { | ||
throw createInvalidInterfaceError( | ||
'invalid interface ' + sQuote(ui), | ||
ui | ||
); | ||
throw createInvalidInterfaceError(`invalid interface '${ui}'`, ui); | ||
} | ||
@@ -330,17 +408,4 @@ } | ||
this.suite.on(EVENT_FILE_PRE_REQUIRE, function(context) { | ||
exports.afterEach = context.afterEach || context.teardown; | ||
exports.after = context.after || context.suiteTeardown; | ||
exports.beforeEach = context.beforeEach || context.setup; | ||
exports.before = context.before || context.suiteSetup; | ||
exports.describe = context.describe || context.suite; | ||
exports.it = context.it || context.test; | ||
exports.xit = context.xit || (context.test && context.test.skip); | ||
exports.setup = context.setup || context.beforeEach; | ||
exports.suiteSetup = context.suiteSetup || context.before; | ||
exports.suiteTeardown = context.suiteTeardown || context.after; | ||
exports.suite = context.suite || context.describe; | ||
exports.teardown = context.teardown || context.afterEach; | ||
exports.test = context.test || context.it; | ||
exports.run = context.run; | ||
this.suite.on(EVENT_FILE_PRE_REQUIRE, function (context) { | ||
currentContext = context; | ||
}); | ||
@@ -366,6 +431,6 @@ | ||
*/ | ||
Mocha.prototype.loadFiles = function(fn) { | ||
Mocha.prototype.loadFiles = function (fn) { | ||
var self = this; | ||
var suite = this.suite; | ||
this.files.forEach(function(file) { | ||
this.files.forEach(function (file) { | ||
file = path.resolve(file); | ||
@@ -391,2 +456,4 @@ suite.emit(EVENT_FILE_PRE_REQUIRE, global, file, self); | ||
* @see {@link Mocha#unloadFiles} | ||
* @param {Object} [options] - Settings object. | ||
* @param {Function} [options.esmDecorator] - Function invoked on esm module name right before importing it. By default will passthrough as is. | ||
* @returns {Promise} | ||
@@ -400,22 +467,17 @@ * @example | ||
*/ | ||
Mocha.prototype.loadFilesAsync = function() { | ||
Mocha.prototype.loadFilesAsync = function ({esmDecorator} = {}) { | ||
var self = this; | ||
var suite = this.suite; | ||
this.loadAsync = true; | ||
this.lazyLoadFiles(true); | ||
if (!esmUtils) { | ||
return new Promise(function(resolve) { | ||
self.loadFiles(resolve); | ||
}); | ||
} | ||
return esmUtils.loadFilesAsync( | ||
this.files, | ||
function(file) { | ||
function (file) { | ||
suite.emit(EVENT_FILE_PRE_REQUIRE, global, file, self); | ||
}, | ||
function(file, resultModule) { | ||
function (file, resultModule) { | ||
suite.emit(EVENT_FILE_REQUIRE, resultModule, file, self); | ||
suite.emit(EVENT_FILE_POST_REQUIRE, global, file, self); | ||
} | ||
}, | ||
esmDecorator | ||
); | ||
@@ -432,4 +494,9 @@ }; | ||
*/ | ||
Mocha.unloadFile = function(file) { | ||
delete require.cache[require.resolve(file)]; | ||
Mocha.unloadFile = function (file) { | ||
if (utils.isBrowser()) { | ||
throw createUnsupportedError( | ||
'unloadFile() is only supported in a Node.js environment' | ||
); | ||
} | ||
return require('./nodejs/file-unloader').unloadFile(file); | ||
}; | ||
@@ -452,3 +519,3 @@ | ||
*/ | ||
Mocha.prototype.unloadFiles = function() { | ||
Mocha.prototype.unloadFiles = function () { | ||
if (this._state === mochaStates.DISPOSED) { | ||
@@ -462,3 +529,3 @@ throw createMochaInstanceAlreadyDisposedError( | ||
this.files.forEach(function(file) { | ||
this.files.forEach(function (file) { | ||
Mocha.unloadFile(file); | ||
@@ -483,3 +550,3 @@ }); | ||
*/ | ||
Mocha.prototype.fgrep = function(str) { | ||
Mocha.prototype.fgrep = function (str) { | ||
if (!str) { | ||
@@ -525,6 +592,6 @@ return this; | ||
*/ | ||
Mocha.prototype.grep = function(re) { | ||
Mocha.prototype.grep = function (re) { | ||
if (utils.isString(re)) { | ||
// extract args if it's regex-like, i.e: [string, pattern, flag] | ||
var arg = re.match(/^\/(.*)\/(g|i|)$|.*/); | ||
var arg = re.match(/^\/(.*)\/([gimy]{0,4})$|.*/); | ||
this.options.grep = new RegExp(arg[1] || arg[0], arg[2]); | ||
@@ -549,3 +616,3 @@ } else { | ||
*/ | ||
Mocha.prototype.invert = function() { | ||
Mocha.prototype.invert = function () { | ||
this.options.invert = true; | ||
@@ -556,20 +623,2 @@ return this; | ||
/** | ||
* Enables or disables ignoring global leaks. | ||
* | ||
* @deprecated since v7.0.0 | ||
* @public | ||
* @see {@link Mocha#checkLeaks} | ||
* @param {boolean} [ignoreLeaks=false] - Whether to ignore global leaks. | ||
* @return {Mocha} this | ||
* @chainable | ||
*/ | ||
Mocha.prototype.ignoreLeaks = function(ignoreLeaks) { | ||
utils.deprecate( | ||
'"ignoreLeaks()" is DEPRECATED, please use "checkLeaks()" instead.' | ||
); | ||
this.options.checkLeaks = !ignoreLeaks; | ||
return this; | ||
}; | ||
/** | ||
* Enables or disables checking for global variables leaked while running tests. | ||
@@ -583,3 +632,3 @@ * | ||
*/ | ||
Mocha.prototype.checkLeaks = function(checkLeaks) { | ||
Mocha.prototype.checkLeaks = function (checkLeaks) { | ||
this.options.checkLeaks = checkLeaks !== false; | ||
@@ -599,3 +648,3 @@ return this; | ||
*/ | ||
Mocha.prototype.cleanReferencesAfterRun = function(cleanReferencesAfterRun) { | ||
Mocha.prototype.cleanReferencesAfterRun = function (cleanReferencesAfterRun) { | ||
this._cleanReferencesAfterRun = cleanReferencesAfterRun !== false; | ||
@@ -610,3 +659,3 @@ return this; | ||
*/ | ||
Mocha.prototype.dispose = function() { | ||
Mocha.prototype.dispose = function () { | ||
if (this._state === mochaStates.RUNNING) { | ||
@@ -632,3 +681,3 @@ throw createMochaInstanceAlreadyRunningError( | ||
*/ | ||
Mocha.prototype.fullTrace = function(fullTrace) { | ||
Mocha.prototype.fullTrace = function (fullTrace) { | ||
this.options.fullTrace = fullTrace !== false; | ||
@@ -639,45 +688,2 @@ return this; | ||
/** | ||
* Enables desktop notification support if prerequisite software installed. | ||
* | ||
* @public | ||
* @see [CLI option](../#-growl-g) | ||
* @return {Mocha} this | ||
* @chainable | ||
*/ | ||
Mocha.prototype.growl = function() { | ||
this.options.growl = this.isGrowlCapable(); | ||
if (!this.options.growl) { | ||
var detail = process.browser | ||
? 'notification support not available in this browser...' | ||
: 'notification support prerequisites not installed...'; | ||
console.error(detail + ' cannot enable!'); | ||
} | ||
return this; | ||
}; | ||
/** | ||
* @summary | ||
* Determines if Growl support seems likely. | ||
* | ||
* @description | ||
* <strong>Not available when run in browser.</strong> | ||
* | ||
* @private | ||
* @see {@link Growl#isCapable} | ||
* @see {@link Mocha#growl} | ||
* @return {boolean} whether Growl support can be expected | ||
*/ | ||
Mocha.prototype.isGrowlCapable = growl.isCapable; | ||
/** | ||
* Implements desktop notifications using a pseudo-reporter. | ||
* | ||
* @private | ||
* @see {@link Mocha#growl} | ||
* @see {@link Growl#notify} | ||
* @param {Runner} runner - Runner instance. | ||
*/ | ||
Mocha.prototype._growl = growl.notify; | ||
/** | ||
* Specifies whitelist of variable names to be expected in global scope. | ||
@@ -696,7 +702,7 @@ * | ||
*/ | ||
Mocha.prototype.global = function(global) { | ||
Mocha.prototype.global = function (global) { | ||
this.options.global = (this.options.global || []) | ||
.concat(global) | ||
.filter(Boolean) | ||
.filter(function(elt, idx, arr) { | ||
.filter(function (elt, idx, arr) { | ||
return arr.indexOf(elt) === idx; | ||
@@ -706,3 +712,3 @@ }); | ||
}; | ||
// for backwards compability, 'globals' is an alias of 'global' | ||
// for backwards compatibility, 'globals' is an alias of 'global' | ||
Mocha.prototype.globals = Mocha.prototype.global; | ||
@@ -713,21 +719,3 @@ | ||
* | ||
* @deprecated since v7.0.0 | ||
* @public | ||
* @see {@link Mocha#color} | ||
* @param {boolean} colors - Whether to enable color output. | ||
* @return {Mocha} this | ||
* @chainable | ||
*/ | ||
Mocha.prototype.useColors = function(colors) { | ||
utils.deprecate('"useColors()" is DEPRECATED, please use "color()" instead.'); | ||
if (colors !== undefined) { | ||
this.options.color = colors; | ||
} | ||
return this; | ||
}; | ||
/** | ||
* Enables or disables TTY color output by screen-oriented reporters. | ||
* | ||
* @public | ||
* @see [CLI option](../#-color-c-colors) | ||
@@ -738,3 +726,3 @@ * @param {boolean} [color=true] - Whether to enable color output. | ||
*/ | ||
Mocha.prototype.color = function(color) { | ||
Mocha.prototype.color = function (color) { | ||
this.options.color = color !== false; | ||
@@ -745,21 +733,2 @@ return this; | ||
/** | ||
* Determines if reporter should use inline diffs (rather than +/-) | ||
* in test failure output. | ||
* | ||
* @deprecated since v7.0.0 | ||
* @public | ||
* @see {@link Mocha#inlineDiffs} | ||
* @param {boolean} [inlineDiffs=false] - Whether to use inline diffs. | ||
* @return {Mocha} this | ||
* @chainable | ||
*/ | ||
Mocha.prototype.useInlineDiffs = function(inlineDiffs) { | ||
utils.deprecate( | ||
'"useInlineDiffs()" is DEPRECATED, please use "inlineDiffs()" instead.' | ||
); | ||
this.options.inlineDiffs = inlineDiffs !== undefined && inlineDiffs; | ||
return this; | ||
}; | ||
/** | ||
* Enables or disables reporter to use inline diffs (rather than +/-) | ||
@@ -774,3 +743,3 @@ * in test failure output. | ||
*/ | ||
Mocha.prototype.inlineDiffs = function(inlineDiffs) { | ||
Mocha.prototype.inlineDiffs = function (inlineDiffs) { | ||
this.options.inlineDiffs = inlineDiffs !== false; | ||
@@ -781,18 +750,2 @@ return this; | ||
/** | ||
* Determines if reporter should include diffs in test failure output. | ||
* | ||
* @deprecated since v7.0.0 | ||
* @public | ||
* @see {@link Mocha#diff} | ||
* @param {boolean} [hideDiff=false] - Whether to hide diffs. | ||
* @return {Mocha} this | ||
* @chainable | ||
*/ | ||
Mocha.prototype.hideDiff = function(hideDiff) { | ||
utils.deprecate('"hideDiff()" is DEPRECATED, please use "diff()" instead.'); | ||
this.options.diff = !(hideDiff === true); | ||
return this; | ||
}; | ||
/** | ||
* Enables or disables reporter to include diff in test failure output. | ||
@@ -806,3 +759,3 @@ * | ||
*/ | ||
Mocha.prototype.diff = function(diff) { | ||
Mocha.prototype.diff = function (diff) { | ||
this.options.diff = diff !== false; | ||
@@ -823,3 +776,2 @@ return this; | ||
* @see [Timeouts](../#timeouts) | ||
* @see {@link Mocha#enableTimeouts} | ||
* @param {number|string} msecs - Timeout threshold value. | ||
@@ -837,3 +789,3 @@ * @return {Mocha} this | ||
*/ | ||
Mocha.prototype.timeout = function(msecs) { | ||
Mocha.prototype.timeout = function (msecs) { | ||
this.suite.timeout(msecs); | ||
@@ -857,4 +809,4 @@ return this; | ||
*/ | ||
Mocha.prototype.retries = function(n) { | ||
this.suite.retries(n); | ||
Mocha.prototype.retries = function (retry) { | ||
this.suite.retries(retry); | ||
return this; | ||
@@ -880,3 +832,3 @@ }; | ||
*/ | ||
Mocha.prototype.slow = function(msecs) { | ||
Mocha.prototype.slow = function (msecs) { | ||
this.suite.slow(msecs); | ||
@@ -887,18 +839,2 @@ return this; | ||
/** | ||
* Enables or disables timeouts. | ||
* | ||
* @public | ||
* @see [CLI option](../#-timeout-ms-t-ms) | ||
* @param {boolean} enableTimeouts - Whether to enable timeouts. | ||
* @return {Mocha} this | ||
* @chainable | ||
*/ | ||
Mocha.prototype.enableTimeouts = function(enableTimeouts) { | ||
this.suite.enableTimeouts( | ||
arguments.length && enableTimeouts !== undefined ? enableTimeouts : true | ||
); | ||
return this; | ||
}; | ||
/** | ||
* Forces all tests to either accept a `done` callback or return a promise. | ||
@@ -908,7 +844,7 @@ * | ||
* @see [CLI option](../#-async-only-a) | ||
* @param {boolean} [asyncOnly=true] - Wether to force `done` callback or promise. | ||
* @param {boolean} [asyncOnly=true] - Whether to force `done` callback or promise. | ||
* @return {Mocha} this | ||
* @chainable | ||
*/ | ||
Mocha.prototype.asyncOnly = function(asyncOnly) { | ||
Mocha.prototype.asyncOnly = function (asyncOnly) { | ||
this.options.asyncOnly = asyncOnly !== false; | ||
@@ -925,3 +861,3 @@ return this; | ||
*/ | ||
Mocha.prototype.noHighlighting = function() { | ||
Mocha.prototype.noHighlighting = function () { | ||
this.options.noHighlighting = true; | ||
@@ -940,3 +876,3 @@ return this; | ||
*/ | ||
Mocha.prototype.allowUncaught = function(allowUncaught) { | ||
Mocha.prototype.allowUncaught = function (allowUncaught) { | ||
this.options.allowUncaught = allowUncaught !== false; | ||
@@ -951,3 +887,3 @@ return this; | ||
* @description | ||
* Used to perform asynch operations before any suites are run. | ||
* Used to perform async operations before any suites are run. | ||
* | ||
@@ -965,2 +901,30 @@ * @public | ||
/** | ||
* Enables or disables running tests in dry-run mode. | ||
* | ||
* @public | ||
* @see [CLI option](../#-dry-run) | ||
* @param {boolean} [dryRun=true] - Whether to activate dry-run mode. | ||
* @return {Mocha} this | ||
* @chainable | ||
*/ | ||
Mocha.prototype.dryRun = function (dryRun) { | ||
this.options.dryRun = dryRun !== false; | ||
return this; | ||
}; | ||
/** | ||
* Fails test run if no tests encountered with exit-code 1. | ||
* | ||
* @public | ||
* @see [CLI option](../#-fail-zero) | ||
* @param {boolean} [failZero=true] - Whether to fail test run. | ||
* @return {Mocha} this | ||
* @chainable | ||
*/ | ||
Mocha.prototype.failZero = function (failZero) { | ||
this.options.failZero = failZero !== false; | ||
return this; | ||
}; | ||
/** | ||
* Causes tests marked `only` to fail the suite. | ||
@@ -974,3 +938,3 @@ * | ||
*/ | ||
Mocha.prototype.forbidOnly = function(forbidOnly) { | ||
Mocha.prototype.forbidOnly = function (forbidOnly) { | ||
this.options.forbidOnly = forbidOnly !== false; | ||
@@ -989,3 +953,3 @@ return this; | ||
*/ | ||
Mocha.prototype.forbidPending = function(forbidPending) { | ||
Mocha.prototype.forbidPending = function (forbidPending) { | ||
this.options.forbidPending = forbidPending !== false; | ||
@@ -997,4 +961,5 @@ return this; | ||
* Throws an error if mocha is in the wrong state to be able to transition to a "running" state. | ||
* @private | ||
*/ | ||
Mocha.prototype._guardRunningStateTransition = function() { | ||
Mocha.prototype._guardRunningStateTransition = function () { | ||
if (this._state === mochaStates.RUNNING) { | ||
@@ -1035,2 +1000,3 @@ throw createMochaInstanceAlreadyRunningError( | ||
* | ||
* @private | ||
* @callback DoneCB | ||
@@ -1058,3 +1024,3 @@ * @param {number} failures - Number of failures that occurred. | ||
*/ | ||
Mocha.prototype.run = function(fn) { | ||
Mocha.prototype.run = function (fn) { | ||
this._guardRunningStateTransition(); | ||
@@ -1066,12 +1032,13 @@ this._state = mochaStates.RUNNING; | ||
} | ||
if (this.files.length && !this.loadAsync) { | ||
if (this.files.length && !this._lazyLoadFiles) { | ||
this.loadFiles(); | ||
} | ||
var self = this; | ||
var suite = this.suite; | ||
var options = this.options; | ||
options.files = this.files; | ||
var runner = new exports.Runner(suite, { | ||
const runner = new this._runnerClass(suite, { | ||
cleanReferencesAfterRun: this._cleanReferencesAfterRun, | ||
delay: options.delay, | ||
cleanReferencesAfterRun: this._cleanReferencesAfterRun | ||
dryRun: options.dryRun, | ||
failZero: options.failZero | ||
}); | ||
@@ -1092,5 +1059,2 @@ createStatsCollector(runner); | ||
} | ||
if (options.growl) { | ||
this._growl(runner); | ||
} | ||
if (options.color !== undefined) { | ||
@@ -1102,11 +1066,9 @@ exports.reporters.Base.useColors = options.color; | ||
function done(failures) { | ||
self._previousRunner = runner; | ||
if (self._cleanReferencesAfterRun) { | ||
self._state = mochaStates.REFERENCES_CLEANED; | ||
} else { | ||
self._state = mochaStates.INIT; | ||
} | ||
const done = failures => { | ||
this._previousRunner = runner; | ||
this._state = this._cleanReferencesAfterRun | ||
? mochaStates.REFERENCES_CLEANED | ||
: mochaStates.INIT; | ||
fn = fn || utils.noop; | ||
if (reporter.done) { | ||
if (typeof reporter.done === 'function') { | ||
reporter.done(failures, fn); | ||
@@ -1116,5 +1078,27 @@ } else { | ||
} | ||
} | ||
}; | ||
return runner.run(done); | ||
const runAsync = async runner => { | ||
const context = | ||
this.options.enableGlobalSetup && this.hasGlobalSetupFixtures() | ||
? await this.runGlobalSetup(runner) | ||
: {}; | ||
const failureCount = await runner.runAsync({ | ||
files: this.files, | ||
options | ||
}); | ||
if (this.options.enableGlobalTeardown && this.hasGlobalTeardownFixtures()) { | ||
await this.runGlobalTeardown(runner, {context}); | ||
} | ||
return failureCount; | ||
}; | ||
// no "catch" here is intentional. errors coming out of | ||
// Runner#run are considered uncaught/unhandled and caught | ||
// by the `process` event listeners. | ||
// also: returning anything other than `runner` would be a breaking | ||
// change | ||
runAsync(runner).then(done); | ||
return runner; | ||
}; | ||
@@ -1127,22 +1111,84 @@ | ||
*/ | ||
Mocha.prototype.rootHooks = function rootHooks(hooks) { | ||
if (utils.type(hooks) === 'object') { | ||
var beforeAll = [].concat(hooks.beforeAll || []); | ||
var beforeEach = [].concat(hooks.beforeEach || []); | ||
var afterAll = [].concat(hooks.afterAll || []); | ||
var afterEach = [].concat(hooks.afterEach || []); | ||
var rootSuite = this.suite; | ||
beforeAll.forEach(function(hook) { | ||
rootSuite.beforeAll(hook); | ||
}); | ||
beforeEach.forEach(function(hook) { | ||
rootSuite.beforeEach(hook); | ||
}); | ||
afterAll.forEach(function(hook) { | ||
rootSuite.afterAll(hook); | ||
}); | ||
afterEach.forEach(function(hook) { | ||
rootSuite.afterEach(hook); | ||
}); | ||
Mocha.prototype.rootHooks = function rootHooks({ | ||
beforeAll = [], | ||
beforeEach = [], | ||
afterAll = [], | ||
afterEach = [] | ||
} = {}) { | ||
beforeAll = utils.castArray(beforeAll); | ||
beforeEach = utils.castArray(beforeEach); | ||
afterAll = utils.castArray(afterAll); | ||
afterEach = utils.castArray(afterEach); | ||
beforeAll.forEach(hook => { | ||
this.suite.beforeAll(hook); | ||
}); | ||
beforeEach.forEach(hook => { | ||
this.suite.beforeEach(hook); | ||
}); | ||
afterAll.forEach(hook => { | ||
this.suite.afterAll(hook); | ||
}); | ||
afterEach.forEach(hook => { | ||
this.suite.afterEach(hook); | ||
}); | ||
return this; | ||
}; | ||
/** | ||
* Toggles parallel mode. | ||
* | ||
* Must be run before calling {@link Mocha#run}. Changes the `Runner` class to | ||
* use; also enables lazy file loading if not already done so. | ||
* | ||
* Warning: when passed `false` and lazy loading has been enabled _via any means_ (including calling `parallelMode(true)`), this method will _not_ disable lazy loading. Lazy loading is a prerequisite for parallel | ||
* mode, but parallel mode is _not_ a prerequisite for lazy loading! | ||
* @param {boolean} [enable] - If `true`, enable; otherwise disable. | ||
* @throws If run in browser | ||
* @throws If Mocha not in `INIT` state | ||
* @returns {Mocha} | ||
* @chainable | ||
* @public | ||
*/ | ||
Mocha.prototype.parallelMode = function parallelMode(enable = true) { | ||
if (utils.isBrowser()) { | ||
throw createUnsupportedError('parallel mode is only supported in Node.js'); | ||
} | ||
const parallel = Boolean(enable); | ||
if ( | ||
parallel === this.options.parallel && | ||
this._lazyLoadFiles && | ||
this._runnerClass !== exports.Runner | ||
) { | ||
return this; | ||
} | ||
if (this._state !== mochaStates.INIT) { | ||
throw createUnsupportedError( | ||
'cannot change parallel mode after having called run()' | ||
); | ||
} | ||
this.options.parallel = parallel; | ||
// swap Runner class | ||
this._runnerClass = parallel | ||
? require('./nodejs/parallel-buffered-runner') | ||
: exports.Runner; | ||
// lazyLoadFiles may have been set `true` otherwise (for ESM loading), | ||
// so keep `true` if so. | ||
return this.lazyLoadFiles(this._lazyLoadFiles || parallel); | ||
}; | ||
/** | ||
* Disables implicit call to {@link Mocha#loadFiles} in {@link Mocha#run}. This | ||
* setting is used by watch mode, parallel mode, and for loading ESM files. | ||
* @todo This should throw if we've already loaded files; such behavior | ||
* necessitates adding a new state. | ||
* @param {boolean} [enable] - If `true`, disable eager loading of files in | ||
* {@link Mocha#run} | ||
* @chainable | ||
* @public | ||
*/ | ||
Mocha.prototype.lazyLoadFiles = function lazyLoadFiles(enable) { | ||
this._lazyLoadFiles = enable === true; | ||
debug('set lazy load to %s', enable); | ||
return this; | ||
@@ -1152,2 +1198,140 @@ }; | ||
/** | ||
* Configures one or more global setup fixtures. | ||
* | ||
* If given no parameters, _unsets_ any previously-set fixtures. | ||
* @chainable | ||
* @public | ||
* @param {MochaGlobalFixture|MochaGlobalFixture[]} [setupFns] - Global setup fixture(s) | ||
* @returns {Mocha} | ||
*/ | ||
Mocha.prototype.globalSetup = function globalSetup(setupFns = []) { | ||
setupFns = utils.castArray(setupFns); | ||
this.options.globalSetup = setupFns; | ||
debug('configured %d global setup functions', setupFns.length); | ||
return this; | ||
}; | ||
/** | ||
* Configures one or more global teardown fixtures. | ||
* | ||
* If given no parameters, _unsets_ any previously-set fixtures. | ||
* @chainable | ||
* @public | ||
* @param {MochaGlobalFixture|MochaGlobalFixture[]} [teardownFns] - Global teardown fixture(s) | ||
* @returns {Mocha} | ||
*/ | ||
Mocha.prototype.globalTeardown = function globalTeardown(teardownFns = []) { | ||
teardownFns = utils.castArray(teardownFns); | ||
this.options.globalTeardown = teardownFns; | ||
debug('configured %d global teardown functions', teardownFns.length); | ||
return this; | ||
}; | ||
/** | ||
* Run any global setup fixtures sequentially, if any. | ||
* | ||
* This is _automatically called_ by {@link Mocha#run} _unless_ the `runGlobalSetup` option is `false`; see {@link Mocha#enableGlobalSetup}. | ||
* | ||
* The context object this function resolves with should be consumed by {@link Mocha#runGlobalTeardown}. | ||
* @param {object} [context] - Context object if already have one | ||
* @public | ||
* @returns {Promise<object>} Context object | ||
*/ | ||
Mocha.prototype.runGlobalSetup = async function runGlobalSetup(context = {}) { | ||
const {globalSetup} = this.options; | ||
if (globalSetup && globalSetup.length) { | ||
debug('run(): global setup starting'); | ||
await this._runGlobalFixtures(globalSetup, context); | ||
debug('run(): global setup complete'); | ||
} | ||
return context; | ||
}; | ||
/** | ||
* Run any global teardown fixtures sequentially, if any. | ||
* | ||
* This is _automatically called_ by {@link Mocha#run} _unless_ the `runGlobalTeardown` option is `false`; see {@link Mocha#enableGlobalTeardown}. | ||
* | ||
* Should be called with context object returned by {@link Mocha#runGlobalSetup}, if applicable. | ||
* @param {object} [context] - Context object if already have one | ||
* @public | ||
* @returns {Promise<object>} Context object | ||
*/ | ||
Mocha.prototype.runGlobalTeardown = async function runGlobalTeardown( | ||
context = {} | ||
) { | ||
const {globalTeardown} = this.options; | ||
if (globalTeardown && globalTeardown.length) { | ||
debug('run(): global teardown starting'); | ||
await this._runGlobalFixtures(globalTeardown, context); | ||
} | ||
debug('run(): global teardown complete'); | ||
return context; | ||
}; | ||
/** | ||
* Run global fixtures sequentially with context `context` | ||
* @private | ||
* @param {MochaGlobalFixture[]} [fixtureFns] - Fixtures to run | ||
* @param {object} [context] - context object | ||
* @returns {Promise<object>} context object | ||
*/ | ||
Mocha.prototype._runGlobalFixtures = async function _runGlobalFixtures( | ||
fixtureFns = [], | ||
context = {} | ||
) { | ||
for await (const fixtureFn of fixtureFns) { | ||
await fixtureFn.call(context); | ||
} | ||
return context; | ||
}; | ||
/** | ||
* Toggle execution of any global setup fixture(s) | ||
* | ||
* @chainable | ||
* @public | ||
* @param {boolean } [enabled=true] - If `false`, do not run global setup fixture | ||
* @returns {Mocha} | ||
*/ | ||
Mocha.prototype.enableGlobalSetup = function enableGlobalSetup(enabled = true) { | ||
this.options.enableGlobalSetup = Boolean(enabled); | ||
return this; | ||
}; | ||
/** | ||
* Toggle execution of any global teardown fixture(s) | ||
* | ||
* @chainable | ||
* @public | ||
* @param {boolean } [enabled=true] - If `false`, do not run global teardown fixture | ||
* @returns {Mocha} | ||
*/ | ||
Mocha.prototype.enableGlobalTeardown = function enableGlobalTeardown( | ||
enabled = true | ||
) { | ||
this.options.enableGlobalTeardown = Boolean(enabled); | ||
return this; | ||
}; | ||
/** | ||
* Returns `true` if one or more global setup fixtures have been supplied. | ||
* @public | ||
* @returns {boolean} | ||
*/ | ||
Mocha.prototype.hasGlobalSetupFixtures = function hasGlobalSetupFixtures() { | ||
return Boolean(this.options.globalSetup.length); | ||
}; | ||
/** | ||
* Returns `true` if one or more global teardown fixtures have been supplied. | ||
* @public | ||
* @returns {boolean} | ||
*/ | ||
Mocha.prototype.hasGlobalTeardownFixtures = | ||
function hasGlobalTeardownFixtures() { | ||
return Boolean(this.options.globalTeardown.length); | ||
}; | ||
/** | ||
* An alternative way to define root hooks that works with parallel runs. | ||
@@ -1163,4 +1347,38 @@ * @typedef {Object} MochaRootHookObject | ||
* An function that returns a {@link MochaRootHookObject}, either sync or async. | ||
* @callback MochaRootHookFunction | ||
@callback MochaRootHookFunction | ||
* @returns {MochaRootHookObject|Promise<MochaRootHookObject>} | ||
*/ | ||
/** | ||
* A function that's invoked _once_ which is either sync or async. | ||
* Can be a "teardown" or "setup". These will all share the same context. | ||
* @callback MochaGlobalFixture | ||
* @returns {void|Promise<void>} | ||
*/ | ||
/** | ||
* An object making up all necessary parts of a plugin loader and aggregator | ||
* @typedef {Object} PluginDefinition | ||
* @property {string} exportName - Named export to use | ||
* @property {string} [optionName] - Option name for Mocha constructor (use `exportName` if omitted) | ||
* @property {PluginValidator} [validate] - Validator function | ||
* @property {PluginFinalizer} [finalize] - Finalizer/aggregator function | ||
*/ | ||
/** | ||
* A (sync) function to assert a user-supplied plugin implementation is valid. | ||
* | ||
* Defined in a {@link PluginDefinition}. | ||
* @callback PluginValidator | ||
* @param {*} value - Value to check | ||
* @this {PluginDefinition} | ||
* @returns {void} | ||
*/ | ||
/** | ||
* A function to finalize plugins impls of a particular ilk | ||
* @callback PluginFinalizer | ||
* @param {Array<*>} impls - User-supplied implementations | ||
* @returns {Promise<*>|*} | ||
*/ |
{ | ||
"diff": true, | ||
"extension": ["js", "cjs", "mjs"], | ||
"opts": "./test/mocha.opts", | ||
"package": "./package.json", | ||
@@ -6,0 +5,0 @@ "reporter": "spec", |
'use strict'; | ||
/** | ||
@module Pending | ||
*/ | ||
module.exports = Pending; | ||
@@ -4,0 +8,0 @@ |
@@ -9,7 +9,7 @@ 'use strict'; | ||
var tty = require('tty'); | ||
var diff = require('diff'); | ||
var milliseconds = require('ms'); | ||
var utils = require('../utils'); | ||
var supportsColor = process.browser ? null : require('supports-color'); | ||
var supportsColor = require('supports-color'); | ||
var symbols = require('log-symbols'); | ||
var constants = require('../runner').constants; | ||
@@ -19,2 +19,12 @@ var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; | ||
const isBrowser = utils.isBrowser(); | ||
function getBrowserWindowSize() { | ||
if ('innerHeight' in global) { | ||
return [global.innerHeight, global.innerWidth]; | ||
} | ||
// In a Web Worker, the DOM Window is not available. | ||
return [640, 480]; | ||
} | ||
/** | ||
@@ -30,3 +40,3 @@ * Expose `Base`. | ||
var isatty = process.stdout.isTTY && process.stderr.isTTY; | ||
var isatty = isBrowser || (process.stdout.isTTY && process.stderr.isTTY); | ||
@@ -43,3 +53,3 @@ /** | ||
exports.useColors = | ||
!process.browser && | ||
!isBrowser && | ||
(supportsColor.stdout || process.env.MOCHA_COLORS !== undefined); | ||
@@ -54,2 +64,7 @@ | ||
/** | ||
* Truncate diffs longer than this value to avoid slow performance | ||
*/ | ||
exports.maxDiffSize = 8192; | ||
/** | ||
* Default color map. | ||
@@ -77,3 +92,5 @@ */ | ||
'diff added': 32, | ||
'diff removed': 31 | ||
'diff removed': 31, | ||
'diff added inline': '30;42', | ||
'diff removed inline': '30;41' | ||
}; | ||
@@ -86,5 +103,5 @@ | ||
exports.symbols = { | ||
ok: '✓', | ||
err: '✖', | ||
dot: '․', | ||
ok: symbols.success, | ||
err: symbols.error, | ||
dot: '.', | ||
comma: ',', | ||
@@ -94,9 +111,2 @@ bang: '!' | ||
// With node.js on Windows: use symbols available in terminal default fonts | ||
if (process.platform === 'win32') { | ||
exports.symbols.ok = '\u221A'; | ||
exports.symbols.err = '\u00D7'; | ||
exports.symbols.dot = '.'; | ||
} | ||
/** | ||
@@ -113,3 +123,3 @@ * Color `str` with the given `type`, | ||
*/ | ||
var color = (exports.color = function(type, str) { | ||
var color = (exports.color = function (type, str) { | ||
if (!exports.useColors) { | ||
@@ -130,5 +140,7 @@ return String(str); | ||
if (isatty) { | ||
exports.window.width = process.stdout.getWindowSize | ||
? process.stdout.getWindowSize(1)[0] | ||
: tty.getWindowSize()[1]; | ||
if (isBrowser) { | ||
exports.window.width = getBrowserWindowSize()[1]; | ||
} else { | ||
exports.window.width = process.stdout.getWindowSize(1)[0]; | ||
} | ||
} | ||
@@ -141,19 +153,19 @@ | ||
exports.cursor = { | ||
hide: function() { | ||
hide: function () { | ||
isatty && process.stdout.write('\u001b[?25l'); | ||
}, | ||
show: function() { | ||
show: function () { | ||
isatty && process.stdout.write('\u001b[?25h'); | ||
}, | ||
deleteLine: function() { | ||
deleteLine: function () { | ||
isatty && process.stdout.write('\u001b[2K'); | ||
}, | ||
beginningOfLine: function() { | ||
beginningOfLine: function () { | ||
isatty && process.stdout.write('\u001b[0G'); | ||
}, | ||
CR: function() { | ||
CR: function () { | ||
if (isatty) { | ||
@@ -168,3 +180,3 @@ exports.cursor.deleteLine(); | ||
var showDiff = (exports.showDiff = function(err) { | ||
var showDiff = (exports.showDiff = function (err) { | ||
return ( | ||
@@ -196,7 +208,19 @@ err && | ||
*/ | ||
var generateDiff = (exports.generateDiff = function(actual, expected) { | ||
var generateDiff = (exports.generateDiff = function (actual, expected) { | ||
try { | ||
return exports.inlineDiffs | ||
var maxLen = exports.maxDiffSize; | ||
var skipped = 0; | ||
if (maxLen > 0) { | ||
skipped = Math.max(actual.length - maxLen, expected.length - maxLen); | ||
actual = actual.slice(0, maxLen); | ||
expected = expected.slice(0, maxLen); | ||
} | ||
let result = exports.inlineDiffs | ||
? inlineDiff(actual, expected) | ||
: unifiedDiff(actual, expected); | ||
if (skipped > 0) { | ||
result = `${result}\n [mocha] output truncated to ${maxLen} characters, see "maxDiffSize" reporter-option\n`; | ||
} | ||
return result; | ||
} catch (err) { | ||
@@ -222,6 +246,6 @@ var msg = | ||
*/ | ||
exports.list = function(failures) { | ||
exports.list = function (failures) { | ||
var multipleErr, multipleTest; | ||
Base.consoleLog(); | ||
failures.forEach(function(test, i) { | ||
failures.forEach(function (test, i) { | ||
// format | ||
@@ -246,6 +270,6 @@ var fmt = | ||
var message; | ||
if (err.message && typeof err.message.toString === 'function') { | ||
if (typeof err.inspect === 'function') { | ||
message = err.inspect() + ''; | ||
} else if (err.message && typeof err.message.toString === 'function') { | ||
message = err.message + ''; | ||
} else if (typeof err.inspect === 'function') { | ||
message = err.inspect() + ''; | ||
} else { | ||
@@ -286,3 +310,3 @@ message = ''; | ||
var testTitle = ''; | ||
test.titlePath().forEach(function(str, index) { | ||
test.titlePath().forEach(function (str, index) { | ||
if (index !== 0) { | ||
@@ -323,3 +347,9 @@ testTitle += '\n '; | ||
runner.on(EVENT_TEST_PASS, function(test) { | ||
var maxDiffSizeOpt = | ||
this.options.reporterOption && this.options.reporterOption.maxDiffSize; | ||
if (maxDiffSizeOpt !== undefined && !isNaN(Number(maxDiffSizeOpt))) { | ||
exports.maxDiffSize = Number(maxDiffSizeOpt); | ||
} | ||
runner.on(EVENT_TEST_PASS, function (test) { | ||
if (test.duration > test.slow()) { | ||
@@ -334,3 +364,3 @@ test.speed = 'slow'; | ||
runner.on(EVENT_TEST_FAIL, function(test, err) { | ||
runner.on(EVENT_TEST_FAIL, function (test, err) { | ||
if (showDiff(err)) { | ||
@@ -355,3 +385,3 @@ stringifyDiffObjs(err); | ||
*/ | ||
Base.prototype.epilogue = function() { | ||
Base.prototype.epilogue = function () { | ||
var stats = this.stats; | ||
@@ -419,3 +449,3 @@ var fmt; | ||
msg = lines | ||
.map(function(str, i) { | ||
.map(function (str, i) { | ||
return pad(++i, width) + ' |' + ' ' + str; | ||
@@ -429,5 +459,5 @@ }) | ||
'\n' + | ||
color('diff removed', 'actual') + | ||
color('diff removed inline', 'actual') + | ||
' ' + | ||
color('diff added', 'expected') + | ||
color('diff added inline', 'expected') + | ||
'\n\n' + | ||
@@ -478,6 +508,3 @@ msg + | ||
'\n\n' + | ||
lines | ||
.map(cleanUp) | ||
.filter(notBlank) | ||
.join('\n') | ||
lines.map(cleanUp).filter(notBlank).join('\n') | ||
); | ||
@@ -497,8 +524,8 @@ } | ||
.diffWordsWithSpace(actual, expected) | ||
.map(function(str) { | ||
.map(function (str) { | ||
if (str.added) { | ||
return colorLines('diff added', str.value); | ||
return colorLines('diff added inline', str.value); | ||
} | ||
if (str.removed) { | ||
return colorLines('diff removed', str.value); | ||
return colorLines('diff removed inline', str.value); | ||
} | ||
@@ -521,3 +548,3 @@ return str.value; | ||
.split('\n') | ||
.map(function(str) { | ||
.map(function (str) { | ||
return color(name, str); | ||
@@ -524,0 +551,0 @@ }) |
@@ -42,3 +42,3 @@ 'use strict'; | ||
runner.on(EVENT_SUITE_BEGIN, function(suite) { | ||
runner.on(EVENT_SUITE_BEGIN, function (suite) { | ||
if (suite.root) { | ||
@@ -54,3 +54,3 @@ return; | ||
runner.on(EVENT_SUITE_END, function(suite) { | ||
runner.on(EVENT_SUITE_END, function (suite) { | ||
if (suite.root) { | ||
@@ -65,3 +65,3 @@ return; | ||
runner.on(EVENT_TEST_PASS, function(test) { | ||
runner.on(EVENT_TEST_PASS, function (test) { | ||
Base.consoleLog('%s <dt>%s</dt>', indent(), utils.escape(test.title)); | ||
@@ -73,3 +73,3 @@ Base.consoleLog('%s <dt>%s</dt>', indent(), utils.escape(test.file)); | ||
runner.on(EVENT_TEST_FAIL, function(test, err) { | ||
runner.on(EVENT_TEST_FAIL, function (test, err) { | ||
Base.consoleLog( | ||
@@ -76,0 +76,0 @@ '%s <dt class="error">%s</dt>', |
@@ -41,7 +41,7 @@ 'use strict'; | ||
runner.on(EVENT_RUN_BEGIN, function() { | ||
runner.on(EVENT_RUN_BEGIN, function () { | ||
process.stdout.write('\n'); | ||
}); | ||
runner.on(EVENT_TEST_PENDING, function() { | ||
runner.on(EVENT_TEST_PENDING, function () { | ||
if (++n % width === 0) { | ||
@@ -53,3 +53,3 @@ process.stdout.write('\n '); | ||
runner.on(EVENT_TEST_PASS, function(test) { | ||
runner.on(EVENT_TEST_PASS, function (test) { | ||
if (++n % width === 0) { | ||
@@ -65,3 +65,3 @@ process.stdout.write('\n '); | ||
runner.on(EVENT_TEST_FAIL, function() { | ||
runner.on(EVENT_TEST_FAIL, function () { | ||
if (++n % width === 0) { | ||
@@ -73,3 +73,3 @@ process.stdout.write('\n '); | ||
runner.once(EVENT_RUN_END, function() { | ||
runner.once(EVENT_RUN_END, function () { | ||
process.stdout.write('\n'); | ||
@@ -76,0 +76,0 @@ self.epilogue(); |
@@ -94,3 +94,3 @@ 'use strict'; | ||
// pass toggle | ||
on(passesLink, 'click', function(evt) { | ||
on(passesLink, 'click', function (evt) { | ||
evt.preventDefault(); | ||
@@ -106,3 +106,3 @@ unhide(); | ||
// failure toggle | ||
on(failuresLink, 'click', function(evt) { | ||
on(failuresLink, 'click', function (evt) { | ||
evt.preventDefault(); | ||
@@ -124,3 +124,3 @@ unhide(); | ||
runner.on(EVENT_SUITE_BEGIN, function(suite) { | ||
runner.on(EVENT_SUITE_BEGIN, function (suite) { | ||
if (suite.root) { | ||
@@ -144,3 +144,3 @@ return; | ||
runner.on(EVENT_SUITE_END, function(suite) { | ||
runner.on(EVENT_SUITE_END, function (suite) { | ||
if (suite.root) { | ||
@@ -153,3 +153,3 @@ updateStats(); | ||
runner.on(EVENT_TEST_PASS, function(test) { | ||
runner.on(EVENT_TEST_PASS, function (test) { | ||
var url = self.testURL(test); | ||
@@ -167,3 +167,3 @@ var markup = | ||
runner.on(EVENT_TEST_FAIL, function(test) { | ||
runner.on(EVENT_TEST_FAIL, function (test) { | ||
var el = fragment( | ||
@@ -190,3 +190,3 @@ '<li class="test fail"><h2>%e <a href="%e" class="replay">' + | ||
} else { | ||
stackString = test.err.stack.substr( | ||
stackString = test.err.stack.slice( | ||
test.err.message.length + indexOfMessage | ||
@@ -225,3 +225,3 @@ ); | ||
runner.on(EVENT_TEST_PENDING, function(test) { | ||
runner.on(EVENT_TEST_PENDING, function (test) { | ||
var el = fragment( | ||
@@ -284,3 +284,3 @@ '<li class="test pass pending"><h2>%e</h2></li>', | ||
*/ | ||
HTML.prototype.suiteURL = function(suite) { | ||
HTML.prototype.suiteURL = function (suite) { | ||
return makeUrl(suite.fullTitle()); | ||
@@ -294,3 +294,3 @@ }; | ||
*/ | ||
HTML.prototype.testURL = function(test) { | ||
HTML.prototype.testURL = function (test) { | ||
return makeUrl(test.fullTitle()); | ||
@@ -305,6 +305,6 @@ }; | ||
*/ | ||
HTML.prototype.addCodeToggle = function(el, contents) { | ||
HTML.prototype.addCodeToggle = function (el, contents) { | ||
var h2 = el.getElementsByTagName('h2')[0]; | ||
on(h2, 'click', function() { | ||
on(h2, 'click', function () { | ||
pre.style.display = pre.style.display === 'none' ? 'block' : 'none'; | ||
@@ -337,3 +337,3 @@ }); | ||
div.innerHTML = html.replace(/%([se])/g, function(_, type) { | ||
div.innerHTML = html.replace(/%([se])/g, function (_, type) { | ||
switch (type) { | ||
@@ -340,0 +340,0 @@ case 's': |
@@ -38,11 +38,11 @@ 'use strict'; | ||
runner.once(EVENT_RUN_BEGIN, function() { | ||
writeEvent(['start', {total: total}]); | ||
runner.once(EVENT_RUN_BEGIN, function () { | ||
writeEvent(['start', {total}]); | ||
}); | ||
runner.on(EVENT_TEST_PASS, function(test) { | ||
runner.on(EVENT_TEST_PASS, function (test) { | ||
writeEvent(['pass', clean(test)]); | ||
}); | ||
runner.on(EVENT_TEST_FAIL, function(test, err) { | ||
runner.on(EVENT_TEST_FAIL, function (test, err) { | ||
test = clean(test); | ||
@@ -54,3 +54,3 @@ test.err = err.message; | ||
runner.once(EVENT_RUN_END, function() { | ||
runner.once(EVENT_RUN_END, function () { | ||
writeEvent(['end', self.stats]); | ||
@@ -89,3 +89,4 @@ }); | ||
duration: test.duration, | ||
currentRetry: test.currentRetry() | ||
currentRetry: test.currentRetry(), | ||
speed: test.speed | ||
}; | ||
@@ -92,0 +93,0 @@ } |
@@ -10,8 +10,12 @@ 'use strict'; | ||
var Base = require('./base'); | ||
var fs = require('fs'); | ||
var path = require('path'); | ||
const createUnsupportedError = require('../errors').createUnsupportedError; | ||
const utils = require('../utils'); | ||
var constants = require('../runner').constants; | ||
var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; | ||
var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING; | ||
var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; | ||
var EVENT_TEST_END = constants.EVENT_TEST_END; | ||
var EVENT_RUN_END = constants.EVENT_RUN_END; | ||
var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING; | ||
@@ -34,3 +38,3 @@ /** | ||
*/ | ||
function JSONReporter(runner, options) { | ||
function JSONReporter(runner, options = {}) { | ||
Base.call(this, runner, options); | ||
@@ -43,20 +47,28 @@ | ||
var passes = []; | ||
var output; | ||
runner.on(EVENT_TEST_END, function(test) { | ||
if (options.reporterOption && options.reporterOption.output) { | ||
if (utils.isBrowser()) { | ||
throw createUnsupportedError('file output not supported in browser'); | ||
} | ||
output = options.reporterOption.output; | ||
} | ||
runner.on(EVENT_TEST_END, function (test) { | ||
tests.push(test); | ||
}); | ||
runner.on(EVENT_TEST_PASS, function(test) { | ||
runner.on(EVENT_TEST_PASS, function (test) { | ||
passes.push(test); | ||
}); | ||
runner.on(EVENT_TEST_FAIL, function(test) { | ||
runner.on(EVENT_TEST_FAIL, function (test) { | ||
failures.push(test); | ||
}); | ||
runner.on(EVENT_TEST_PENDING, function(test) { | ||
runner.on(EVENT_TEST_PENDING, function (test) { | ||
pending.push(test); | ||
}); | ||
runner.once(EVENT_RUN_END, function() { | ||
runner.once(EVENT_RUN_END, function () { | ||
var obj = { | ||
@@ -72,3 +84,16 @@ stats: self.stats, | ||
process.stdout.write(JSON.stringify(obj, null, 2)); | ||
var json = JSON.stringify(obj, null, 2); | ||
if (output) { | ||
try { | ||
fs.mkdirSync(path.dirname(output), {recursive: true}); | ||
fs.writeFileSync(output, json); | ||
} catch (err) { | ||
console.error( | ||
`${Base.symbols.err} [mocha] writing output to "${output}" failed: ${err.message}\n` | ||
); | ||
process.stdout.write(json); | ||
} | ||
} else { | ||
process.stdout.write(json); | ||
} | ||
}); | ||
@@ -97,2 +122,3 @@ } | ||
currentRetry: test.currentRetry(), | ||
speed: test.speed, | ||
err: cleanCycles(err) | ||
@@ -112,3 +138,3 @@ }; | ||
return JSON.parse( | ||
JSON.stringify(obj, function(key, value) { | ||
JSON.stringify(obj, function (key, value) { | ||
if (typeof value === 'object' && value !== null) { | ||
@@ -136,3 +162,3 @@ if (cache.indexOf(value) !== -1) { | ||
var res = {}; | ||
Object.getOwnPropertyNames(err).forEach(function(key) { | ||
Object.getOwnPropertyNames(err).forEach(function (key) { | ||
res[key] = err[key]; | ||
@@ -139,0 +165,0 @@ }, err); |
@@ -59,7 +59,8 @@ 'use strict'; | ||
var width = (Base.window.width * 0.75) | 0; | ||
var total = runner.total; | ||
var stream = process.stdout; | ||
var plane = color('plane', '✈'); | ||
var crashed = -1; | ||
var n = 0; | ||
var total = 0; | ||
@@ -71,3 +72,3 @@ function runway() { | ||
runner.on(EVENT_RUN_BEGIN, function() { | ||
runner.on(EVENT_RUN_BEGIN, function () { | ||
stream.write('\n\n\n '); | ||
@@ -77,6 +78,5 @@ cursor.hide(); | ||
runner.on(EVENT_TEST_END, function(test) { | ||
runner.on(EVENT_TEST_END, function (test) { | ||
// check if the plane crashed | ||
var col = crashed === -1 ? ((width * ++n) / total) | 0 : crashed; | ||
var col = crashed === -1 ? ((width * ++n) / ++total) | 0 : crashed; | ||
// show the crash | ||
@@ -99,3 +99,3 @@ if (test.state === STATE_FAILED) { | ||
runner.once(EVENT_RUN_END, function() { | ||
runner.once(EVENT_RUN_END, function () { | ||
cursor.show(); | ||
@@ -107,5 +107,5 @@ process.stdout.write('\n'); | ||
// if cursor is hidden when we ctrl-C, then it will remain hidden unless... | ||
process.once('SIGINT', function() { | ||
process.once('SIGINT', function () { | ||
cursor.show(); | ||
process.nextTick(function() { | ||
process.nextTick(function () { | ||
process.kill(process.pid, 'SIGINT'); | ||
@@ -112,0 +112,0 @@ }); |
@@ -43,11 +43,11 @@ 'use strict'; | ||
runner.on(EVENT_RUN_BEGIN, function() { | ||
runner.on(EVENT_RUN_BEGIN, function () { | ||
Base.consoleLog(); | ||
}); | ||
runner.on(EVENT_TEST_BEGIN, function(test) { | ||
runner.on(EVENT_TEST_BEGIN, function (test) { | ||
process.stdout.write(color('pass', ' ' + test.fullTitle() + ': ')); | ||
}); | ||
runner.on(EVENT_TEST_PENDING, function(test) { | ||
runner.on(EVENT_TEST_PENDING, function (test) { | ||
var fmt = color('checkmark', ' -') + color('pending', ' %s'); | ||
@@ -57,3 +57,3 @@ Base.consoleLog(fmt, test.fullTitle()); | ||
runner.on(EVENT_TEST_PASS, function(test) { | ||
runner.on(EVENT_TEST_PASS, function (test) { | ||
var fmt = | ||
@@ -67,3 +67,3 @@ color('checkmark', ' ' + Base.symbols.ok) + | ||
runner.on(EVENT_TEST_FAIL, function(test) { | ||
runner.on(EVENT_TEST_FAIL, function (test) { | ||
cursor.CR(); | ||
@@ -70,0 +70,0 @@ Base.consoleLog(color('fail', ' %d) %s'), ++n, test.fullTitle()); |
@@ -53,4 +53,4 @@ 'use strict'; | ||
obj = obj[key] = obj[key] || {suite: suite}; | ||
suite.suites.forEach(function(suite) { | ||
obj = obj[key] = obj[key] || {suite}; | ||
suite.suites.forEach(function (suite) { | ||
mapTOC(suite, obj); | ||
@@ -87,3 +87,3 @@ }); | ||
runner.on(EVENT_SUITE_BEGIN, function(suite) { | ||
runner.on(EVENT_SUITE_BEGIN, function (suite) { | ||
++level; | ||
@@ -95,7 +95,7 @@ var slug = utils.slug(suite.fullTitle()); | ||
runner.on(EVENT_SUITE_END, function() { | ||
runner.on(EVENT_SUITE_END, function () { | ||
--level; | ||
}); | ||
runner.on(EVENT_TEST_PASS, function(test) { | ||
runner.on(EVENT_TEST_PASS, function (test) { | ||
var code = utils.clean(test.body); | ||
@@ -108,3 +108,3 @@ buf += test.title + '.\n'; | ||
runner.once(EVENT_RUN_END, function() { | ||
runner.once(EVENT_RUN_END, function () { | ||
process.stdout.write('# TOC\n'); | ||
@@ -111,0 +111,0 @@ process.stdout.write(generateTOC(runner.suite)); |
@@ -37,3 +37,3 @@ 'use strict'; | ||
runner.on(EVENT_RUN_BEGIN, function() { | ||
runner.on(EVENT_RUN_BEGIN, function () { | ||
// clear screen | ||
@@ -40,0 +40,0 @@ process.stdout.write('\u001b[2J'); |
@@ -49,3 +49,3 @@ 'use strict'; | ||
runner.on(EVENT_RUN_BEGIN, function() { | ||
runner.on(EVENT_RUN_BEGIN, function () { | ||
Base.cursor.hide(); | ||
@@ -55,15 +55,15 @@ self.draw(); | ||
runner.on(EVENT_TEST_PENDING, function() { | ||
runner.on(EVENT_TEST_PENDING, function () { | ||
self.draw(); | ||
}); | ||
runner.on(EVENT_TEST_PASS, function() { | ||
runner.on(EVENT_TEST_PASS, function () { | ||
self.draw(); | ||
}); | ||
runner.on(EVENT_TEST_FAIL, function() { | ||
runner.on(EVENT_TEST_FAIL, function () { | ||
self.draw(); | ||
}); | ||
runner.once(EVENT_RUN_END, function() { | ||
runner.once(EVENT_RUN_END, function () { | ||
Base.cursor.show(); | ||
@@ -88,3 +88,3 @@ for (var i = 0; i < self.numberOfLines; i++) { | ||
NyanCat.prototype.draw = function() { | ||
NyanCat.prototype.draw = function () { | ||
this.appendRainbow(); | ||
@@ -104,3 +104,3 @@ this.drawScoreboard(); | ||
NyanCat.prototype.drawScoreboard = function() { | ||
NyanCat.prototype.drawScoreboard = function () { | ||
var stats = this.stats; | ||
@@ -128,3 +128,3 @@ | ||
NyanCat.prototype.appendRainbow = function() { | ||
NyanCat.prototype.appendRainbow = function () { | ||
var segment = this.tick ? '_' : '-'; | ||
@@ -148,6 +148,6 @@ var rainbowified = this.rainbowify(segment); | ||
NyanCat.prototype.drawRainbow = function() { | ||
NyanCat.prototype.drawRainbow = function () { | ||
var self = this; | ||
this.trajectories.forEach(function(line) { | ||
this.trajectories.forEach(function (line) { | ||
write('\u001b[' + self.scoreboardWidth + 'C'); | ||
@@ -166,3 +166,3 @@ write(line.join('')); | ||
*/ | ||
NyanCat.prototype.drawNyanCat = function() { | ||
NyanCat.prototype.drawNyanCat = function () { | ||
var self = this; | ||
@@ -203,3 +203,3 @@ var startWidth = this.scoreboardWidth + this.trajectories[0].length; | ||
NyanCat.prototype.face = function() { | ||
NyanCat.prototype.face = function () { | ||
var stats = this.stats; | ||
@@ -223,3 +223,3 @@ if (stats.failures) { | ||
NyanCat.prototype.cursorUp = function(n) { | ||
NyanCat.prototype.cursorUp = function (n) { | ||
write('\u001b[' + n + 'A'); | ||
@@ -235,3 +235,3 @@ }; | ||
NyanCat.prototype.cursorDown = function(n) { | ||
NyanCat.prototype.cursorDown = function (n) { | ||
write('\u001b[' + n + 'B'); | ||
@@ -246,3 +246,3 @@ }; | ||
*/ | ||
NyanCat.prototype.generateColors = function() { | ||
NyanCat.prototype.generateColors = function () { | ||
var colors = []; | ||
@@ -269,3 +269,3 @@ | ||
*/ | ||
NyanCat.prototype.rainbowify = function(str) { | ||
NyanCat.prototype.rainbowify = function (str) { | ||
if (!Base.useColors) { | ||
@@ -272,0 +272,0 @@ return str; |
@@ -60,3 +60,3 @@ 'use strict'; | ||
// tests started | ||
runner.on(EVENT_RUN_BEGIN, function() { | ||
runner.on(EVENT_RUN_BEGIN, function () { | ||
process.stdout.write('\n'); | ||
@@ -67,3 +67,3 @@ cursor.hide(); | ||
// tests complete | ||
runner.on(EVENT_TEST_END, function() { | ||
runner.on(EVENT_TEST_END, function () { | ||
complete++; | ||
@@ -94,3 +94,3 @@ | ||
// and the failures if any | ||
runner.once(EVENT_RUN_END, function() { | ||
runner.once(EVENT_RUN_END, function () { | ||
cursor.show(); | ||
@@ -97,0 +97,0 @@ process.stdout.write('\n'); |
@@ -48,7 +48,7 @@ 'use strict'; | ||
runner.on(EVENT_RUN_BEGIN, function() { | ||
runner.on(EVENT_RUN_BEGIN, function () { | ||
Base.consoleLog(); | ||
}); | ||
runner.on(EVENT_SUITE_BEGIN, function(suite) { | ||
runner.on(EVENT_SUITE_BEGIN, function (suite) { | ||
++indents; | ||
@@ -58,3 +58,3 @@ Base.consoleLog(color('suite', '%s%s'), indent(), suite.title); | ||
runner.on(EVENT_SUITE_END, function() { | ||
runner.on(EVENT_SUITE_END, function () { | ||
--indents; | ||
@@ -66,3 +66,3 @@ if (indents === 1) { | ||
runner.on(EVENT_TEST_PENDING, function(test) { | ||
runner.on(EVENT_TEST_PENDING, function (test) { | ||
var fmt = indent() + color('pending', ' - %s'); | ||
@@ -72,3 +72,3 @@ Base.consoleLog(fmt, test.title); | ||
runner.on(EVENT_TEST_PASS, function(test) { | ||
runner.on(EVENT_TEST_PASS, function (test) { | ||
var fmt; | ||
@@ -91,3 +91,3 @@ if (test.speed === 'fast') { | ||
runner.on(EVENT_TEST_FAIL, function(test) { | ||
runner.on(EVENT_TEST_FAIL, function (test) { | ||
Base.consoleLog(indent() + color('fail', ' %d) %s'), ++n, test.title); | ||
@@ -94,0 +94,0 @@ }); |
@@ -52,25 +52,23 @@ 'use strict'; | ||
runner.once(EVENT_RUN_BEGIN, function() { | ||
var ntests = runner.grepTotal(runner.suite); | ||
runner.once(EVENT_RUN_BEGIN, function () { | ||
self._producer.writeVersion(); | ||
self._producer.writePlan(ntests); | ||
}); | ||
runner.on(EVENT_TEST_END, function() { | ||
runner.on(EVENT_TEST_END, function () { | ||
++n; | ||
}); | ||
runner.on(EVENT_TEST_PENDING, function(test) { | ||
runner.on(EVENT_TEST_PENDING, function (test) { | ||
self._producer.writePending(n, test); | ||
}); | ||
runner.on(EVENT_TEST_PASS, function(test) { | ||
runner.on(EVENT_TEST_PASS, function (test) { | ||
self._producer.writePass(n, test); | ||
}); | ||
runner.on(EVENT_TEST_FAIL, function(test, err) { | ||
runner.on(EVENT_TEST_FAIL, function (test, err) { | ||
self._producer.writeFail(n, test, err); | ||
}); | ||
runner.once(EVENT_RUN_END, function() { | ||
runner.once(EVENT_RUN_END, function () { | ||
self._producer.writeEpilogue(runner.stats); | ||
@@ -119,4 +117,4 @@ }); | ||
var producers = { | ||
'12': new TAP12Producer(), | ||
'13': new TAP13Producer() | ||
12: new TAP12Producer(), | ||
13: new TAP13Producer() | ||
}; | ||
@@ -151,3 +149,3 @@ var producer = producers[tapVersion]; | ||
*/ | ||
TAPProducer.prototype.writeVersion = function() {}; | ||
TAPProducer.prototype.writeVersion = function () {}; | ||
@@ -160,3 +158,3 @@ /** | ||
*/ | ||
TAPProducer.prototype.writePlan = function(ntests) { | ||
TAPProducer.prototype.writePlan = function (ntests) { | ||
println('%d..%d', 1, ntests); | ||
@@ -172,3 +170,3 @@ }; | ||
*/ | ||
TAPProducer.prototype.writePass = function(n, test) { | ||
TAPProducer.prototype.writePass = function (n, test) { | ||
println('ok %d %s', n, title(test)); | ||
@@ -184,3 +182,3 @@ }; | ||
*/ | ||
TAPProducer.prototype.writePending = function(n, test) { | ||
TAPProducer.prototype.writePending = function (n, test) { | ||
println('ok %d %s # SKIP -', n, title(test)); | ||
@@ -197,3 +195,3 @@ }; | ||
*/ | ||
TAPProducer.prototype.writeFail = function(n, test, err) { | ||
TAPProducer.prototype.writeFail = function (n, test, err) { | ||
println('not ok %d %s', n, title(test)); | ||
@@ -208,3 +206,3 @@ }; | ||
*/ | ||
TAPProducer.prototype.writeEpilogue = function(stats) { | ||
TAPProducer.prototype.writeEpilogue = function (stats) { | ||
// :TBD: Why is this not counting pending tests? | ||
@@ -215,2 +213,3 @@ println('# tests ' + (stats.passes + stats.failures)); | ||
println('# fail ' + stats.failures); | ||
this.writePlan(stats.passes + stats.failures + stats.pending); | ||
}; | ||
@@ -235,3 +234,3 @@ | ||
*/ | ||
this.writeFail = function(n, test, err) { | ||
this.writeFail = function (n, test, err) { | ||
TAPProducer.prototype.writeFail.call(this, n, test, err); | ||
@@ -269,3 +268,3 @@ if (err.message) { | ||
*/ | ||
this.writeVersion = function() { | ||
this.writeVersion = function () { | ||
println('TAP version 13'); | ||
@@ -278,3 +277,3 @@ }; | ||
*/ | ||
this.writeFail = function(n, test, err) { | ||
this.writeFail = function (n, test, err) { | ||
TAPProducer.prototype.writeFail.call(this, n, test, err); | ||
@@ -281,0 +280,0 @@ var emitYamlBlock = err.message != null || err.stack != null; |
@@ -12,3 +12,2 @@ 'use strict'; | ||
var fs = require('fs'); | ||
var mkdirp = require('mkdirp'); | ||
var path = require('path'); | ||
@@ -66,3 +65,5 @@ var errors = require('../errors'); | ||
mkdirp.sync(path.dirname(options.reporterOptions.output)); | ||
fs.mkdirSync(path.dirname(options.reporterOptions.output), { | ||
recursive: true | ||
}); | ||
self.fileStream = fs.createWriteStream(options.reporterOptions.output); | ||
@@ -78,15 +79,15 @@ } | ||
runner.on(EVENT_TEST_PENDING, function(test) { | ||
runner.on(EVENT_TEST_PENDING, function (test) { | ||
tests.push(test); | ||
}); | ||
runner.on(EVENT_TEST_PASS, function(test) { | ||
runner.on(EVENT_TEST_PASS, function (test) { | ||
tests.push(test); | ||
}); | ||
runner.on(EVENT_TEST_FAIL, function(test) { | ||
runner.on(EVENT_TEST_FAIL, function (test) { | ||
tests.push(test); | ||
}); | ||
runner.once(EVENT_RUN_END, function() { | ||
runner.once(EVENT_RUN_END, function () { | ||
self.write( | ||
@@ -108,3 +109,3 @@ tag( | ||
tests.forEach(function(t) { | ||
tests.forEach(function (t) { | ||
self.test(t); | ||
@@ -128,5 +129,5 @@ }); | ||
*/ | ||
XUnit.prototype.done = function(failures, fn) { | ||
XUnit.prototype.done = function (failures, fn) { | ||
if (this.fileStream) { | ||
this.fileStream.end(function() { | ||
this.fileStream.end(function () { | ||
fn(failures); | ||
@@ -144,3 +145,3 @@ }); | ||
*/ | ||
XUnit.prototype.write = function(line) { | ||
XUnit.prototype.write = function (line) { | ||
if (this.fileStream) { | ||
@@ -160,3 +161,3 @@ this.fileStream.write(line + '\n'); | ||
*/ | ||
XUnit.prototype.test = function(test) { | ||
XUnit.prototype.test = function (test) { | ||
Base.useColors = false; | ||
@@ -163,0 +164,0 @@ |
@@ -8,8 +8,11 @@ 'use strict'; | ||
var utils = require('./utils'); | ||
var errors = require('./errors'); | ||
var createInvalidExceptionError = errors.createInvalidExceptionError; | ||
var createMultipleDoneError = errors.createMultipleDoneError; | ||
const { | ||
createInvalidExceptionError, | ||
createMultipleDoneError, | ||
createTimeoutError | ||
} = require('./errors'); | ||
/** | ||
* Save timer references to avoid Sinon interfering (see GH-237). | ||
* @private | ||
*/ | ||
@@ -40,4 +43,9 @@ var Date = global.Date; | ||
this._slow = 75; | ||
this._enableTimeouts = true; | ||
this._retries = -1; | ||
utils.assignNewMochaID(this); | ||
Object.defineProperty(this, 'id', { | ||
get() { | ||
return utils.getMochaID(this); | ||
} | ||
}); | ||
this.reset(); | ||
@@ -54,3 +62,3 @@ } | ||
*/ | ||
Runnable.prototype.reset = function() { | ||
Runnable.prototype.reset = function () { | ||
this.timedOut = false; | ||
@@ -84,3 +92,3 @@ this._currentRetry = 0; | ||
*/ | ||
Runnable.prototype.timeout = function(ms) { | ||
Runnable.prototype.timeout = function (ms) { | ||
if (!arguments.length) { | ||
@@ -100,6 +108,8 @@ return this._timeout; | ||
if (ms === range[0] || ms === range[1]) { | ||
this._enableTimeouts = false; | ||
this._timeout = 0; | ||
} else { | ||
this._timeout = ms; | ||
} | ||
debug('timeout %d', ms); | ||
this._timeout = ms; | ||
debug('timeout %d', this._timeout); | ||
if (this.timer) { | ||
@@ -118,3 +128,3 @@ this.resetTimeout(); | ||
*/ | ||
Runnable.prototype.slow = function(ms) { | ||
Runnable.prototype.slow = function (ms) { | ||
if (!arguments.length || typeof ms === 'undefined') { | ||
@@ -132,18 +142,2 @@ return this._slow; | ||
/** | ||
* Set and get whether timeout is `enabled`. | ||
* | ||
* @private | ||
* @param {boolean} enabled | ||
* @return {Runnable|boolean} enabled or Runnable instance. | ||
*/ | ||
Runnable.prototype.enableTimeouts = function(enabled) { | ||
if (!arguments.length) { | ||
return this._enableTimeouts; | ||
} | ||
debug('enableTimeouts %s', enabled); | ||
this._enableTimeouts = enabled; | ||
return this; | ||
}; | ||
/** | ||
* Halt and mark as pending. | ||
@@ -154,3 +148,3 @@ * | ||
*/ | ||
Runnable.prototype.skip = function() { | ||
Runnable.prototype.skip = function () { | ||
this.pending = true; | ||
@@ -165,3 +159,3 @@ throw new Pending('sync skip; aborting execution'); | ||
*/ | ||
Runnable.prototype.isPending = function() { | ||
Runnable.prototype.isPending = function () { | ||
return this.pending || (this.parent && this.parent.isPending()); | ||
@@ -175,3 +169,3 @@ }; | ||
*/ | ||
Runnable.prototype.isFailed = function() { | ||
Runnable.prototype.isFailed = function () { | ||
return !this.isPending() && this.state === constants.STATE_FAILED; | ||
@@ -185,3 +179,3 @@ }; | ||
*/ | ||
Runnable.prototype.isPassed = function() { | ||
Runnable.prototype.isPassed = function () { | ||
return !this.isPending() && this.state === constants.STATE_PASSED; | ||
@@ -195,3 +189,3 @@ }; | ||
*/ | ||
Runnable.prototype.retries = function(n) { | ||
Runnable.prototype.retries = function (n) { | ||
if (!arguments.length) { | ||
@@ -208,3 +202,3 @@ return this._retries; | ||
*/ | ||
Runnable.prototype.currentRetry = function(n) { | ||
Runnable.prototype.currentRetry = function (n) { | ||
if (!arguments.length) { | ||
@@ -224,3 +218,3 @@ return this._currentRetry; | ||
*/ | ||
Runnable.prototype.fullTitle = function() { | ||
Runnable.prototype.fullTitle = function () { | ||
return this.titlePath().join(' '); | ||
@@ -236,3 +230,3 @@ }; | ||
*/ | ||
Runnable.prototype.titlePath = function() { | ||
Runnable.prototype.titlePath = function () { | ||
return this.parent.titlePath().concat([this.title]); | ||
@@ -246,3 +240,3 @@ }; | ||
*/ | ||
Runnable.prototype.clearTimeout = function() { | ||
Runnable.prototype.clearTimeout = function () { | ||
clearTimeout(this.timer); | ||
@@ -256,12 +250,12 @@ }; | ||
*/ | ||
Runnable.prototype.resetTimeout = function() { | ||
Runnable.prototype.resetTimeout = function () { | ||
var self = this; | ||
var ms = this.timeout() || 1e9; | ||
var ms = this.timeout(); | ||
if (!this._enableTimeouts) { | ||
if (ms === 0) { | ||
return; | ||
} | ||
this.clearTimeout(); | ||
this.timer = setTimeout(function() { | ||
if (!self._enableTimeouts) { | ||
this.timer = setTimeout(function () { | ||
if (self.timeout() === 0) { | ||
return; | ||
@@ -280,3 +274,3 @@ } | ||
*/ | ||
Runnable.prototype.globals = function(globals) { | ||
Runnable.prototype.globals = function (globals) { | ||
if (!arguments.length) { | ||
@@ -294,3 +288,3 @@ return this._allowedGlobals; | ||
*/ | ||
Runnable.prototype.run = function(fn) { | ||
Runnable.prototype.run = function (fn) { | ||
var self = this; | ||
@@ -302,2 +296,4 @@ var start = new Date(); | ||
if (this.isPending()) return fn(); | ||
// Sometimes the ctx exists, but it is not runnable | ||
@@ -331,3 +327,3 @@ if (ctx && ctx.runnable) { | ||
finished = true; | ||
if (!err && self.duration > ms && self._enableTimeouts) { | ||
if (!err && self.duration > ms && ms > 0) { | ||
err = self._timeoutError(ms); | ||
@@ -379,7 +375,3 @@ } | ||
try { | ||
if (this.isPending()) { | ||
done(); | ||
} else { | ||
callFn(this.fn); | ||
} | ||
callFn(this.fn); | ||
} catch (err) { | ||
@@ -400,3 +392,3 @@ errorWasHandled = true; | ||
result.then( | ||
function() { | ||
function () { | ||
done(); | ||
@@ -407,3 +399,3 @@ // Return null so libraries like bluebird do not warn about | ||
}, | ||
function(reason) { | ||
function (reason) { | ||
done(reason || new Error('Promise rejected with no or falsy reason')); | ||
@@ -426,3 +418,3 @@ } | ||
function callFnAsync(fn) { | ||
var result = fn.call(ctx, function(err) { | ||
var result = fn.call(ctx, function (err) { | ||
if (err instanceof Error || toString.call(err) === '[object Error]') { | ||
@@ -459,11 +451,8 @@ return done(err); | ||
*/ | ||
Runnable.prototype._timeoutError = function(ms) { | ||
var msg = | ||
'Timeout of ' + | ||
ms + | ||
'ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.'; | ||
Runnable.prototype._timeoutError = function (ms) { | ||
let msg = `Timeout of ${ms}ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.`; | ||
if (this.file) { | ||
msg += ' (' + this.file + ')'; | ||
} | ||
return new Error(msg); | ||
return createTimeoutError(msg, ms, this.file); | ||
}; | ||
@@ -489,3 +478,7 @@ | ||
*/ | ||
STATE_PASSED: 'passed' | ||
STATE_PASSED: 'passed', | ||
/** | ||
* Value of `state` prop when a `Runnable` has been skipped by user | ||
*/ | ||
STATE_PENDING: 'pending' | ||
} | ||
@@ -500,3 +493,3 @@ ); | ||
*/ | ||
Runnable.toValueOrError = function(value) { | ||
Runnable.toValueOrError = function (value) { | ||
return ( | ||
@@ -503,0 +496,0 @@ value || |
@@ -5,8 +5,7 @@ 'use strict'; | ||
* Module dependencies. | ||
* @private | ||
*/ | ||
var util = require('util'); | ||
var EventEmitter = require('events').EventEmitter; | ||
var Pending = require('./pending'); | ||
var utils = require('./utils'); | ||
var inherits = utils.inherits; | ||
var debug = require('debug')('mocha:runner'); | ||
@@ -22,14 +21,17 @@ var Runnable = require('./runnable'); | ||
var STATE_PASSED = Runnable.constants.STATE_PASSED; | ||
var dQuote = utils.dQuote; | ||
var sQuote = utils.sQuote; | ||
var STATE_PENDING = Runnable.constants.STATE_PENDING; | ||
var stackFilter = utils.stackTraceFilter(); | ||
var stringify = utils.stringify; | ||
var type = utils.type; | ||
var errors = require('./errors'); | ||
var createInvalidExceptionError = errors.createInvalidExceptionError; | ||
var createUnsupportedError = errors.createUnsupportedError; | ||
var createFatalError = errors.createFatalError; | ||
const { | ||
createInvalidExceptionError, | ||
createUnsupportedError, | ||
createFatalError, | ||
isMochaError, | ||
constants: errorConstants | ||
} = require('./errors'); | ||
/** | ||
* Non-enumerable globals. | ||
* @private | ||
* @readonly | ||
@@ -130,50 +132,71 @@ */ | ||
module.exports = Runner; | ||
class Runner extends EventEmitter { | ||
/** | ||
* Initialize a `Runner` at the Root {@link Suite}, which represents a hierarchy of {@link Suite|Suites} and {@link Test|Tests}. | ||
* | ||
* @extends external:EventEmitter | ||
* @public | ||
* @class | ||
* @param {Suite} suite - Root suite | ||
* @param {Object} [opts] - Settings object | ||
* @param {boolean} [opts.cleanReferencesAfterRun] - Whether to clean references to test fns and hooks when a suite is done. | ||
* @param {boolean} [opts.delay] - Whether to delay execution of root suite until ready. | ||
* @param {boolean} [opts.dryRun] - Whether to report tests without running them. | ||
* @param {boolean} [opts.failZero] - Whether to fail test run if zero tests encountered. | ||
*/ | ||
constructor(suite, opts = {}) { | ||
super(); | ||
/** | ||
* Initialize a `Runner` at the Root {@link Suite}, which represents a hierarchy of {@link Suite|Suites} and {@link Test|Tests}. | ||
* | ||
* @extends external:EventEmitter | ||
* @public | ||
* @class | ||
* @param {Suite} suite - Root suite | ||
* @param {Object|boolean} [opts] - Options. If `boolean`, whether or not to delay execution of root suite until ready (for backwards compatibility). | ||
* @param {boolean} [opts.delay] - Whether to delay execution of root suite until ready. | ||
* @param {boolean} [opts.cleanReferencesAfterRun] - Whether to clean references to test fns and hooks when a suite is done. | ||
*/ | ||
function Runner(suite, opts) { | ||
if (opts === undefined) { | ||
opts = {}; | ||
var self = this; | ||
this._globals = []; | ||
this._abort = false; | ||
this.suite = suite; | ||
this._opts = opts; | ||
this.state = constants.STATE_IDLE; | ||
this.total = suite.total(); | ||
this.failures = 0; | ||
/** | ||
* @type {Map<EventEmitter,Map<string,Set<EventListener>>>} | ||
*/ | ||
this._eventListeners = new Map(); | ||
this.on(constants.EVENT_TEST_END, function (test) { | ||
if (test.type === 'test' && test.retriedTest() && test.parent) { | ||
var idx = | ||
test.parent.tests && test.parent.tests.indexOf(test.retriedTest()); | ||
if (idx > -1) test.parent.tests[idx] = test; | ||
} | ||
self.checkGlobals(test); | ||
}); | ||
this.on(constants.EVENT_HOOK_END, function (hook) { | ||
self.checkGlobals(hook); | ||
}); | ||
this._defaultGrep = /.*/; | ||
this.grep(this._defaultGrep); | ||
this.globals(this.globalProps()); | ||
this.uncaught = this._uncaught.bind(this); | ||
this.unhandled = (reason, promise) => { | ||
if (isMochaError(reason)) { | ||
debug( | ||
'trapped unhandled rejection coming out of Mocha; forwarding to uncaught handler:', | ||
reason | ||
); | ||
this.uncaught(reason); | ||
} else { | ||
debug( | ||
'trapped unhandled rejection from (probably) user code; re-emitting on process' | ||
); | ||
this._removeEventListener( | ||
process, | ||
'unhandledRejection', | ||
this.unhandled | ||
); | ||
try { | ||
process.emit('unhandledRejection', reason, promise); | ||
} finally { | ||
this._addEventListener(process, 'unhandledRejection', this.unhandled); | ||
} | ||
} | ||
}; | ||
} | ||
if (typeof opts === 'boolean') { | ||
this._delay = opts; | ||
opts = {}; | ||
} else { | ||
this._delay = opts.delay; | ||
} | ||
var self = this; | ||
this._globals = []; | ||
this._abort = false; | ||
this.suite = suite; | ||
this._opts = opts; | ||
this.state = constants.STATE_IDLE; | ||
this.total = suite.total(); | ||
this.failures = 0; | ||
this._eventListeners = []; | ||
this.on(constants.EVENT_TEST_END, function(test) { | ||
if (test.type === 'test' && test.retriedTest() && test.parent) { | ||
var idx = | ||
test.parent.tests && test.parent.tests.indexOf(test.retriedTest()); | ||
if (idx > -1) test.parent.tests[idx] = test; | ||
} | ||
self.checkGlobals(test); | ||
}); | ||
this.on(constants.EVENT_HOOK_END, function(hook) { | ||
self.checkGlobals(hook); | ||
}); | ||
this._defaultGrep = /.*/; | ||
this.grep(this._defaultGrep); | ||
this.globals(this.globalProps()); | ||
this.uncaught = this._uncaught.bind(this); | ||
} | ||
@@ -190,7 +213,2 @@ | ||
/** | ||
* Inherit from `EventEmitter.prototype`. | ||
*/ | ||
inherits(Runner, EventEmitter); | ||
/** | ||
* Replacement for `target.on(eventName, listener)` that does bookkeeping to remove them when this runner instance is disposed. | ||
@@ -200,6 +218,32 @@ * @param {EventEmitter} target - The `EventEmitter` | ||
* @param {string} fn - Listener function | ||
* @private | ||
*/ | ||
Runner.prototype._addEventListener = function(target, eventName, listener) { | ||
Runner.prototype._addEventListener = function (target, eventName, listener) { | ||
debug( | ||
'_addEventListener(): adding for event %s; %d current listeners', | ||
eventName, | ||
target.listenerCount(eventName) | ||
); | ||
/* istanbul ignore next */ | ||
if ( | ||
this._eventListeners.has(target) && | ||
this._eventListeners.get(target).has(eventName) && | ||
this._eventListeners.get(target).get(eventName).has(listener) | ||
) { | ||
debug( | ||
'warning: tried to attach duplicate event listener for %s', | ||
eventName | ||
); | ||
return; | ||
} | ||
target.on(eventName, listener); | ||
this._eventListeners.push([target, eventName, listener]); | ||
const targetListeners = this._eventListeners.has(target) | ||
? this._eventListeners.get(target) | ||
: new Map(); | ||
const targetEventListeners = targetListeners.has(eventName) | ||
? targetListeners.get(eventName) | ||
: new Set(); | ||
targetEventListeners.add(listener); | ||
targetListeners.set(eventName, targetEventListeners); | ||
this._eventListeners.set(target, targetListeners); | ||
}; | ||
@@ -210,22 +254,24 @@ | ||
* @param {EventEmitter} target - The `EventEmitter` | ||
* @param {string} eventName - The event anme | ||
* @param {string} eventName - The event name | ||
* @param {function} listener - Listener function | ||
* @private | ||
*/ | ||
Runner.prototype._removeEventListener = function(target, eventName, listener) { | ||
var eventListenerIndex = -1; | ||
for (var i = 0; i < this._eventListeners.length; i++) { | ||
var eventListenerDescriptor = this._eventListeners[i]; | ||
if ( | ||
eventListenerDescriptor[0] === target && | ||
eventListenerDescriptor[1] === eventName && | ||
eventListenerDescriptor[2] === listener | ||
) { | ||
eventListenerIndex = i; | ||
break; | ||
Runner.prototype._removeEventListener = function (target, eventName, listener) { | ||
target.removeListener(eventName, listener); | ||
if (this._eventListeners.has(target)) { | ||
const targetListeners = this._eventListeners.get(target); | ||
if (targetListeners.has(eventName)) { | ||
const targetEventListeners = targetListeners.get(eventName); | ||
targetEventListeners.delete(listener); | ||
if (!targetEventListeners.size) { | ||
targetListeners.delete(eventName); | ||
} | ||
} | ||
if (!targetListeners.size) { | ||
this._eventListeners.delete(target); | ||
} | ||
} else { | ||
debug('trying to remove listener for untracked object %s', target); | ||
} | ||
if (eventListenerIndex !== -1) { | ||
var removedListener = this._eventListeners.splice(eventListenerIndex, 1)[0]; | ||
removedListener[0].removeListener(removedListener[1], removedListener[2]); | ||
} | ||
}; | ||
@@ -237,10 +283,12 @@ | ||
*/ | ||
Runner.prototype.dispose = function() { | ||
Runner.prototype.dispose = function () { | ||
this.removeAllListeners(); | ||
this._eventListeners.forEach(function(eventListenerDescriptor) { | ||
eventListenerDescriptor[0].removeListener( | ||
eventListenerDescriptor[1], | ||
eventListenerDescriptor[2] | ||
); | ||
this._eventListeners.forEach((targetListeners, target) => { | ||
targetListeners.forEach((targetEventListeners, eventName) => { | ||
targetEventListeners.forEach(listener => { | ||
target.removeListener(eventName, listener); | ||
}); | ||
}); | ||
}); | ||
this._eventListeners.clear(); | ||
}; | ||
@@ -258,3 +306,3 @@ | ||
*/ | ||
Runner.prototype.grep = function(re, invert) { | ||
Runner.prototype.grep = function (re, invert) { | ||
debug('grep(): setting to %s', re); | ||
@@ -276,7 +324,7 @@ this._grep = re; | ||
*/ | ||
Runner.prototype.grepTotal = function(suite) { | ||
Runner.prototype.grepTotal = function (suite) { | ||
var self = this; | ||
var total = 0; | ||
suite.eachTest(function(test) { | ||
suite.eachTest(function (test) { | ||
var match = self._grep.test(test.fullTitle()); | ||
@@ -300,3 +348,3 @@ if (self._invert) { | ||
*/ | ||
Runner.prototype.globalProps = function() { | ||
Runner.prototype.globalProps = function () { | ||
var props = Object.keys(global); | ||
@@ -323,3 +371,3 @@ | ||
*/ | ||
Runner.prototype.globals = function(arr) { | ||
Runner.prototype.globals = function (arr) { | ||
if (!arguments.length) { | ||
@@ -338,3 +386,3 @@ return this._globals; | ||
*/ | ||
Runner.prototype.checkGlobals = function(test) { | ||
Runner.prototype.checkGlobals = function (test) { | ||
if (!this.checkLeaks) { | ||
@@ -361,5 +409,4 @@ return; | ||
if (leaks.length) { | ||
var msg = 'global leak(s) detected: %s'; | ||
var error = new Error(util.format(msg, leaks.map(sQuote).join(', '))); | ||
this.fail(test, error); | ||
var msg = `global leak(s) detected: ${leaks.map(e => `'${e}'`).join(', ')}`; | ||
this.fail(test, new Error(msg)); | ||
} | ||
@@ -371,12 +418,27 @@ }; | ||
* | ||
* If `test` is a hook, failures work in the following pattern: | ||
* - If bail, run corresponding `after each` and `after` hooks, | ||
* then exit | ||
* - Failed `before` hook skips all tests in a suite and subsuites, | ||
* but jumps to corresponding `after` hook | ||
* - Failed `before each` hook skips remaining tests in a | ||
* suite and jumps to corresponding `after each` hook, | ||
* which is run only once | ||
* - Failed `after` hook does not alter execution order | ||
* - Failed `after each` hook skips remaining tests in a | ||
* suite and subsuites, but executes other `after each` | ||
* hooks | ||
* | ||
* @private | ||
* @param {Test} test | ||
* @param {Runnable} test | ||
* @param {Error} err | ||
* @param {boolean} [force=false] - Whether to fail a pending test. | ||
*/ | ||
Runner.prototype.fail = function(test, err) { | ||
if (test.isPending()) { | ||
Runner.prototype.fail = function (test, err, force) { | ||
force = force === true; | ||
if (test.isPending() && !force) { | ||
return; | ||
} | ||
if (this.state === constants.STATE_STOPPED) { | ||
if (err.code === errors.constants.MULTIPLE_DONE) { | ||
if (err.code === errorConstants.MULTIPLE_DONE) { | ||
throw err; | ||
@@ -409,40 +471,2 @@ } | ||
/** | ||
* Fail the given `hook` with `err`. | ||
* | ||
* Hook failures work in the following pattern: | ||
* - If bail, run corresponding `after each` and `after` hooks, | ||
* then exit | ||
* - Failed `before` hook skips all tests in a suite and subsuites, | ||
* but jumps to corresponding `after` hook | ||
* - Failed `before each` hook skips remaining tests in a | ||
* suite and jumps to corresponding `after each` hook, | ||
* which is run only once | ||
* - Failed `after` hook does not alter execution order | ||
* - Failed `after each` hook skips remaining tests in a | ||
* suite and subsuites, but executes other `after each` | ||
* hooks | ||
* | ||
* @private | ||
* @param {Hook} hook | ||
* @param {Error} err | ||
*/ | ||
Runner.prototype.failHook = function(hook, err) { | ||
hook.originalTitle = hook.originalTitle || hook.title; | ||
if (hook.ctx && hook.ctx.currentTest) { | ||
hook.title = | ||
hook.originalTitle + ' for ' + dQuote(hook.ctx.currentTest.title); | ||
} else { | ||
var parentTitle; | ||
if (hook.parent.title) { | ||
parentTitle = hook.parent.title; | ||
} else { | ||
parentTitle = hook.parent.root ? '{root}' : ''; | ||
} | ||
hook.title = hook.originalTitle + ' in ' + dQuote(parentTitle); | ||
} | ||
this.fail(hook, err); | ||
}; | ||
/** | ||
* Run hook `name` callbacks and then invoke `fn()`. | ||
@@ -455,3 +479,5 @@ * | ||
Runner.prototype.hook = function(name, fn) { | ||
Runner.prototype.hook = function (name, fn) { | ||
if (this._opts.dryRun) return fn(); | ||
var suite = this.suite; | ||
@@ -476,2 +502,4 @@ var hooks = suite.getHooks(name); | ||
setHookTitle(hook); | ||
hook.allowUncaught = self.allowUncaught; | ||
@@ -482,8 +510,8 @@ | ||
if (!hook.listeners('error').length) { | ||
self._addEventListener(hook, 'error', function(err) { | ||
self.failHook(hook, err); | ||
self._addEventListener(hook, 'error', function (err) { | ||
self.fail(hook, err); | ||
}); | ||
} | ||
hook.run(function(err) { | ||
hook.run(function cbHookRun(err) { | ||
var testError = hook.error(); | ||
@@ -508,16 +536,17 @@ if (testError) { | ||
} else if (name === HOOK_TYPE_BEFORE_ALL) { | ||
suite.tests.forEach(function(test) { | ||
suite.tests.forEach(function (test) { | ||
test.pending = true; | ||
}); | ||
suite.suites.forEach(function(suite) { | ||
suite.suites.forEach(function (suite) { | ||
suite.pending = true; | ||
}); | ||
hooks = []; | ||
} else { | ||
hook.pending = false; | ||
var errForbid = createUnsupportedError('`this.skip` forbidden'); | ||
self.failHook(hook, errForbid); | ||
self.fail(hook, errForbid); | ||
return fn(errForbid); | ||
} | ||
} else if (err) { | ||
self.failHook(hook, err); | ||
self.fail(hook, err); | ||
// stop executing hooks, notify callee of hook err | ||
@@ -528,7 +557,23 @@ return fn(err); | ||
delete hook.ctx.currentTest; | ||
setHookTitle(hook); | ||
next(++i); | ||
}); | ||
function setHookTitle(hook) { | ||
hook.originalTitle = hook.originalTitle || hook.title; | ||
if (hook.ctx && hook.ctx.currentTest) { | ||
hook.title = `${hook.originalTitle} for "${hook.ctx.currentTest.title}"`; | ||
} else { | ||
var parentTitle; | ||
if (hook.parent.title) { | ||
parentTitle = hook.parent.title; | ||
} else { | ||
parentTitle = hook.parent.root ? '{root}' : ''; | ||
} | ||
hook.title = `${hook.originalTitle} in "${parentTitle}"`; | ||
} | ||
} | ||
} | ||
Runner.immediately(function() { | ||
Runner.immediately(function () { | ||
next(0); | ||
@@ -547,3 +592,3 @@ }); | ||
*/ | ||
Runner.prototype.hooks = function(name, suites, fn) { | ||
Runner.prototype.hooks = function (name, suites, fn) { | ||
var self = this; | ||
@@ -560,3 +605,3 @@ var orig = this.suite; | ||
self.hook(name, function(err) { | ||
self.hook(name, function (err) { | ||
if (err) { | ||
@@ -576,3 +621,3 @@ var errSuite = self.suite; | ||
/** | ||
* Run hooks from the top level down. | ||
* Run 'afterEach' hooks from bottom up. | ||
* | ||
@@ -583,3 +628,3 @@ * @param {String} name | ||
*/ | ||
Runner.prototype.hookUp = function(name, fn) { | ||
Runner.prototype.hookUp = function (name, fn) { | ||
var suites = [this.suite].concat(this.parents()).reverse(); | ||
@@ -590,3 +635,3 @@ this.hooks(name, suites, fn); | ||
/** | ||
* Run hooks from the bottom up. | ||
* Run 'beforeEach' hooks from top level down. | ||
* | ||
@@ -597,3 +642,3 @@ * @param {String} name | ||
*/ | ||
Runner.prototype.hookDown = function(name, fn) { | ||
Runner.prototype.hookDown = function (name, fn) { | ||
var suites = [this.suite].concat(this.parents()); | ||
@@ -610,3 +655,3 @@ this.hooks(name, suites, fn); | ||
*/ | ||
Runner.prototype.parents = function() { | ||
Runner.prototype.parents = function () { | ||
var suite = this.suite; | ||
@@ -627,3 +672,5 @@ var suites = []; | ||
*/ | ||
Runner.prototype.runTest = function(fn) { | ||
Runner.prototype.runTest = function (fn) { | ||
if (this._opts.dryRun) return Runner.immediately(fn); | ||
var self = this; | ||
@@ -639,3 +686,3 @@ var test = this.test; | ||
} | ||
this._addEventListener(test, 'error', function(err) { | ||
this._addEventListener(test, 'error', function (err) { | ||
self.fail(test, err); | ||
@@ -661,3 +708,3 @@ }); | ||
*/ | ||
Runner.prototype.runTests = function(suite, fn) { | ||
Runner.prototype.runTests = function (suite, fn) { | ||
var self = this; | ||
@@ -676,4 +723,3 @@ var tests = suite.tests.slice(); | ||
if (self.suite) { | ||
// call hookUp afterEach | ||
self.hookUp(HOOK_TYPE_AFTER_EACH, function(err2, errSuite2) { | ||
self.hookUp(HOOK_TYPE_AFTER_EACH, function (err2, errSuite2) { | ||
self.suite = orig; | ||
@@ -741,6 +787,5 @@ // some hooks may fail even now | ||
if (self.forbidPending) { | ||
test.isPending = alwaysFalse; | ||
self.fail(test, new Error('Pending test forbidden')); | ||
delete test.isPending; | ||
self.fail(test, new Error('Pending test forbidden'), true); | ||
} else { | ||
test.state = STATE_PENDING; | ||
self.emit(constants.EVENT_TEST_PENDING, test); | ||
@@ -754,10 +799,9 @@ } | ||
self.emit(constants.EVENT_TEST_BEGIN, (self.test = test)); | ||
self.hookDown(HOOK_TYPE_BEFORE_EACH, function(err, errSuite) { | ||
self.hookDown(HOOK_TYPE_BEFORE_EACH, function (err, errSuite) { | ||
// conditional skip within beforeEach | ||
if (test.isPending()) { | ||
if (self.forbidPending) { | ||
test.isPending = alwaysFalse; | ||
self.fail(test, new Error('Pending test forbidden')); | ||
delete test.isPending; | ||
self.fail(test, new Error('Pending test forbidden'), true); | ||
} else { | ||
test.state = STATE_PENDING; | ||
self.emit(constants.EVENT_TEST_PENDING, test); | ||
@@ -769,3 +813,3 @@ } | ||
self.suite = errSuite || self.suite; | ||
return self.hookUp(HOOK_TYPE_AFTER_EACH, function(e, eSuite) { | ||
return self.hookUp(HOOK_TYPE_AFTER_EACH, function (e, eSuite) { | ||
self.suite = origSuite; | ||
@@ -779,3 +823,3 @@ next(e, eSuite); | ||
self.currentRunnable = self.test; | ||
self.runTest(function(err) { | ||
self.runTest(function (err) { | ||
test = self.test; | ||
@@ -785,6 +829,5 @@ // conditional skip within it | ||
if (self.forbidPending) { | ||
test.isPending = alwaysFalse; | ||
self.fail(test, new Error('Pending test forbidden')); | ||
delete test.isPending; | ||
self.fail(test, new Error('Pending test forbidden'), true); | ||
} else { | ||
test.state = STATE_PENDING; | ||
self.emit(constants.EVENT_TEST_PENDING, test); | ||
@@ -826,6 +869,2 @@ } | ||
function alwaysFalse() { | ||
return false; | ||
} | ||
/** | ||
@@ -838,3 +877,3 @@ * Run the given `suite` and invoke the callback `fn()` when complete. | ||
*/ | ||
Runner.prototype.runSuite = function(suite, fn) { | ||
Runner.prototype.runSuite = function (suite, fn) { | ||
var i = 0; | ||
@@ -879,3 +918,3 @@ var self = this; | ||
if (self._grep !== self._defaultGrep) { | ||
Runner.immediately(function() { | ||
Runner.immediately(function () { | ||
self.runSuite(curr, next); | ||
@@ -895,3 +934,3 @@ }); | ||
self.hook(HOOK_TYPE_AFTER_ALL, function() { | ||
self.hook(HOOK_TYPE_AFTER_ALL, function () { | ||
self.emit(constants.EVENT_SUITE_END, suite); | ||
@@ -904,3 +943,3 @@ fn(errSuite); | ||
this.hook(HOOK_TYPE_BEFORE_ALL, function(err) { | ||
this.hook(HOOK_TYPE_BEFORE_ALL, function (err) { | ||
if (err) { | ||
@@ -929,3 +968,3 @@ return done(); | ||
*/ | ||
Runner.prototype._uncaught = function(err) { | ||
Runner.prototype._uncaught = function (err) { | ||
// this is defensive to prevent future developers from mis-calling this function. | ||
@@ -946,3 +985,3 @@ // it's more likely that it'd be called with the incorrect context--say, the global | ||
// browser does not exit script when throwing in global.onerror() | ||
if (this.allowUncaught && !process.browser) { | ||
if (this.allowUncaught && !utils.isBrowser()) { | ||
debug('uncaught(): bubbling exception due to --allow-uncaught'); | ||
@@ -1003,5 +1042,3 @@ throw err; | ||
// report 'pending test' retrospectively as failed | ||
runnable.isPending = alwaysFalse; | ||
this.fail(runnable, err); | ||
delete runnable.isPending; | ||
this.fail(runnable, err, true); | ||
return; | ||
@@ -1028,12 +1065,31 @@ } | ||
* @memberof Runner | ||
* @param {Function} fn | ||
* @return {Runner} Runner instance. | ||
* @param {Function} fn - Callback when finished | ||
* @param {Object} [opts] - For subclasses | ||
* @param {string[]} opts.files - Files to run | ||
* @param {Options} opts.options - command-line options | ||
* @returns {Runner} Runner instance. | ||
*/ | ||
Runner.prototype.run = function(fn) { | ||
var self = this; | ||
Runner.prototype.run = function (fn, opts = {}) { | ||
var rootSuite = this.suite; | ||
var options = opts.options || {}; | ||
fn = fn || function() {}; | ||
debug('run(): got options: %O', options); | ||
fn = fn || function () {}; | ||
function start() { | ||
const end = () => { | ||
if (!this.total && this._opts.failZero) this.failures = 1; | ||
debug('run(): root suite completed; emitting %s', constants.EVENT_RUN_END); | ||
this.emit(constants.EVENT_RUN_END); | ||
}; | ||
const begin = () => { | ||
debug('run(): emitting %s', constants.EVENT_RUN_BEGIN); | ||
this.emit(constants.EVENT_RUN_BEGIN); | ||
debug('run(): emitted %s', constants.EVENT_RUN_BEGIN); | ||
this.runSuite(rootSuite, end); | ||
}; | ||
const prepare = () => { | ||
debug('run(): starting'); | ||
@@ -1045,24 +1101,14 @@ // If there is an `only` filter | ||
} | ||
self.state = constants.STATE_RUNNING; | ||
if (self._delay) { | ||
self.emit(constants.EVENT_DELAY_END); | ||
this.state = constants.STATE_RUNNING; | ||
if (this._opts.delay) { | ||
this.emit(constants.EVENT_DELAY_END); | ||
debug('run(): "delay" ended'); | ||
} | ||
debug('run(): emitting %s', constants.EVENT_RUN_BEGIN); | ||
self.emit(constants.EVENT_RUN_BEGIN); | ||
debug('run(): emitted %s', constants.EVENT_RUN_BEGIN); | ||
self.runSuite(rootSuite, function() { | ||
debug( | ||
'run(): root suite completed; emitting %s', | ||
constants.EVENT_RUN_END | ||
); | ||
self.emit(constants.EVENT_RUN_END); | ||
debug('run(): emitted %s', constants.EVENT_RUN_END); | ||
}); | ||
} | ||
return begin(); | ||
}; | ||
// references cleanup to avoid memory leaks | ||
if (this._opts.cleanReferencesAfterRun) { | ||
this.on(constants.EVENT_SUITE_END, function(suite) { | ||
this.on(constants.EVENT_SUITE_END, suite => { | ||
suite.cleanReferences(); | ||
@@ -1073,22 +1119,21 @@ }); | ||
// callback | ||
this.on(constants.EVENT_RUN_END, function() { | ||
self.state = constants.STATE_STOPPED; | ||
debug(constants.EVENT_RUN_END); | ||
this.on(constants.EVENT_RUN_END, function () { | ||
this.state = constants.STATE_STOPPED; | ||
debug('run(): emitted %s', constants.EVENT_RUN_END); | ||
fn(self.failures); | ||
fn(this.failures); | ||
}); | ||
self._removeEventListener(process, 'uncaughtException', self.uncaught); | ||
self._addEventListener(process, 'uncaughtException', self.uncaught); | ||
this._removeEventListener(process, 'uncaughtException', this.uncaught); | ||
this._removeEventListener(process, 'unhandledRejection', this.unhandled); | ||
this._addEventListener(process, 'uncaughtException', this.uncaught); | ||
this._addEventListener(process, 'unhandledRejection', this.unhandled); | ||
if (this._delay) { | ||
if (this._opts.delay) { | ||
// for reporters, I guess. | ||
// might be nice to debounce some dots while we wait. | ||
this.emit(constants.EVENT_DELAY_BEGIN, rootSuite); | ||
rootSuite.once(EVENT_ROOT_SUITE_RUN, start); | ||
rootSuite.once(EVENT_ROOT_SUITE_RUN, prepare); | ||
debug('run(): waiting for green light due to --delay'); | ||
} else { | ||
Runner.immediately(function() { | ||
start(); | ||
}); | ||
Runner.immediately(prepare); | ||
} | ||
@@ -1100,2 +1145,43 @@ | ||
/** | ||
* Toggle partial object linking behavior; used for building object references from | ||
* unique ID's. Does nothing in serial mode, because the object references already exist. | ||
* Subclasses can implement this (e.g., `ParallelBufferedRunner`) | ||
* @abstract | ||
* @param {boolean} [value] - If `true`, enable partial object linking, otherwise disable | ||
* @returns {Runner} | ||
* @chainable | ||
* @public | ||
* @example | ||
* // this reporter needs proper object references when run in parallel mode | ||
* class MyReporter() { | ||
* constructor(runner) { | ||
* this.runner.linkPartialObjects(true) | ||
* .on(EVENT_SUITE_BEGIN, suite => { | ||
// this Suite may be the same object... | ||
* }) | ||
* .on(EVENT_TEST_BEGIN, test => { | ||
* // ...as the `test.parent` property | ||
* }); | ||
* } | ||
* } | ||
*/ | ||
Runner.prototype.linkPartialObjects = function (value) { | ||
return this; | ||
}; | ||
/* | ||
* Like {@link Runner#run}, but does not accept a callback and returns a `Promise` instead of a `Runner`. | ||
* This function cannot reject; an `unhandledRejection` event will bubble up to the `process` object instead. | ||
* @public | ||
* @memberof Runner | ||
* @param {Object} [opts] - Options for {@link Runner#run} | ||
* @returns {Promise<number>} Failure count | ||
*/ | ||
Runner.prototype.runAsync = async function runAsync(opts = {}) { | ||
return new Promise(resolve => { | ||
this.run(resolve, opts); | ||
}); | ||
}; | ||
/** | ||
* Cleanly abort execution. | ||
@@ -1107,3 +1193,3 @@ * | ||
*/ | ||
Runner.prototype.abort = function() { | ||
Runner.prototype.abort = function () { | ||
debug('abort(): aborting'); | ||
@@ -1116,2 +1202,27 @@ this._abort = true; | ||
/** | ||
* Returns `true` if Mocha is running in parallel mode. For reporters. | ||
* | ||
* Subclasses should return an appropriate value. | ||
* @public | ||
* @returns {false} | ||
*/ | ||
Runner.prototype.isParallelMode = function isParallelMode() { | ||
return false; | ||
}; | ||
/** | ||
* Configures an alternate reporter for worker processes to use. Subclasses | ||
* using worker processes should implement this. | ||
* @public | ||
* @param {string} path - Absolute path to alternate reporter for worker processes to use | ||
* @returns {Runner} | ||
* @throws When in serial mode | ||
* @chainable | ||
* @abstract | ||
*/ | ||
Runner.prototype.workerReporter = function () { | ||
throw createUnsupportedError('workerReporter() not supported in serial mode'); | ||
}; | ||
/** | ||
* Filter leaks with the given globals flagged as `ok`. | ||
@@ -1125,3 +1236,3 @@ * | ||
function filterLeaks(ok, globals) { | ||
return globals.filter(function(key) { | ||
return globals.filter(function (key) { | ||
// Firefox and Chrome exposes iframes as index inside the window object | ||
@@ -1150,3 +1261,3 @@ if (/^\d+/.test(key)) { | ||
var matched = ok.filter(function(ok) { | ||
var matched = ok.filter(function (ok) { | ||
if (~ok.indexOf('*')) { | ||
@@ -1183,3 +1294,5 @@ return key.indexOf(ok.split('*')[0]) === 0; | ||
return new Error( | ||
'the ' + type(err) + ' ' + stringify(err) + ' was thrown, throw an Error :)' | ||
`the ${utils.canonicalType(err)} ${stringify( | ||
err | ||
)} was thrown, throw an Error :)` | ||
); | ||
@@ -1195,1 +1308,3 @@ } | ||
*/ | ||
module.exports = Runner; |
@@ -59,21 +59,21 @@ 'use strict'; | ||
runner.once(EVENT_RUN_BEGIN, function() { | ||
runner.once(EVENT_RUN_BEGIN, function () { | ||
stats.start = new Date(); | ||
}); | ||
runner.on(EVENT_SUITE_BEGIN, function(suite) { | ||
runner.on(EVENT_SUITE_BEGIN, function (suite) { | ||
suite.root || stats.suites++; | ||
}); | ||
runner.on(EVENT_TEST_PASS, function() { | ||
runner.on(EVENT_TEST_PASS, function () { | ||
stats.passes++; | ||
}); | ||
runner.on(EVENT_TEST_FAIL, function() { | ||
runner.on(EVENT_TEST_FAIL, function () { | ||
stats.failures++; | ||
}); | ||
runner.on(EVENT_TEST_PENDING, function() { | ||
runner.on(EVENT_TEST_PENDING, function () { | ||
stats.pending++; | ||
}); | ||
runner.on(EVENT_TEST_END, function() { | ||
runner.on(EVENT_TEST_END, function () { | ||
stats.tests++; | ||
}); | ||
runner.once(EVENT_RUN_END, function() { | ||
runner.once(EVENT_RUN_END, function () { | ||
stats.end = new Date(); | ||
@@ -80,0 +80,0 @@ stats.duration = stats.end - stats.start; |
206
lib/suite.js
@@ -5,12 +5,21 @@ 'use strict'; | ||
* Module dependencies. | ||
* @private | ||
*/ | ||
var EventEmitter = require('events').EventEmitter; | ||
var Hook = require('./hook'); | ||
var utils = require('./utils'); | ||
var inherits = utils.inherits; | ||
var debug = require('debug')('mocha:suite'); | ||
var milliseconds = require('ms'); | ||
var errors = require('./errors'); | ||
var createInvalidArgumentTypeError = errors.createInvalidArgumentTypeError; | ||
const {EventEmitter} = require('events'); | ||
const Hook = require('./hook'); | ||
var { | ||
assignNewMochaID, | ||
clamp, | ||
constants: utilsConstants, | ||
defineConstants, | ||
getMochaID, | ||
inherits, | ||
isString | ||
} = require('./utils'); | ||
const debug = require('debug')('mocha:suite'); | ||
const milliseconds = require('ms'); | ||
const errors = require('./errors'); | ||
const {MOCHA_ID_PROP_NAME} = utilsConstants; | ||
/** | ||
@@ -30,3 +39,3 @@ * Expose `Suite`. | ||
*/ | ||
Suite.create = function(parent, title) { | ||
Suite.create = function (parent, title) { | ||
var suite = new Suite(title, parent.ctx); | ||
@@ -51,4 +60,4 @@ suite.parent = parent; | ||
function Suite(title, parentContext, isRoot) { | ||
if (!utils.isString(title)) { | ||
throw createInvalidArgumentTypeError( | ||
if (!isString(title)) { | ||
throw errors.createInvalidArgumentTypeError( | ||
'Suite argument "title" must be a string. Received type "' + | ||
@@ -75,3 +84,2 @@ typeof title + | ||
this._timeout = 2000; | ||
this._enableTimeouts = true; | ||
this._slow = 75; | ||
@@ -81,13 +89,11 @@ this._bail = false; | ||
this._onlySuites = []; | ||
this.reset(); | ||
assignNewMochaID(this); | ||
this.on('newListener', function(event) { | ||
if (deprecatedEvents[event]) { | ||
utils.deprecate( | ||
'Event "' + | ||
event + | ||
'" is deprecated. Please let the Mocha team know about your use case: https://git.io/v6Lwm' | ||
); | ||
Object.defineProperty(this, 'id', { | ||
get() { | ||
return getMochaID(this); | ||
} | ||
}); | ||
this.reset(); | ||
} | ||
@@ -103,3 +109,3 @@ | ||
*/ | ||
Suite.prototype.reset = function() { | ||
Suite.prototype.reset = function () { | ||
this.delayed = false; | ||
@@ -123,3 +129,3 @@ function doReset(thingToReset) { | ||
*/ | ||
Suite.prototype.clone = function() { | ||
Suite.prototype.clone = function () { | ||
var suite = new Suite(this.title); | ||
@@ -131,3 +137,2 @@ debug('clone'); | ||
suite.retries(this.retries()); | ||
suite.enableTimeouts(this.enableTimeouts()); | ||
suite.slow(this.slow()); | ||
@@ -146,12 +151,15 @@ suite.bail(this.bail()); | ||
*/ | ||
Suite.prototype.timeout = function(ms) { | ||
Suite.prototype.timeout = function (ms) { | ||
if (!arguments.length) { | ||
return this._timeout; | ||
} | ||
if (ms.toString() === '0') { | ||
this._enableTimeouts = false; | ||
} | ||
if (typeof ms === 'string') { | ||
ms = milliseconds(ms); | ||
} | ||
// Clamp to range | ||
var INT_MAX = Math.pow(2, 31) - 1; | ||
var range = [0, INT_MAX]; | ||
ms = clamp(ms, range); | ||
debug('timeout %d', ms); | ||
@@ -169,3 +177,3 @@ this._timeout = parseInt(ms, 10); | ||
*/ | ||
Suite.prototype.retries = function(n) { | ||
Suite.prototype.retries = function (n) { | ||
if (!arguments.length) { | ||
@@ -180,18 +188,2 @@ return this._retries; | ||
/** | ||
* Set or get timeout to `enabled`. | ||
* | ||
* @private | ||
* @param {boolean} enabled | ||
* @return {Suite|boolean} self or enabled | ||
*/ | ||
Suite.prototype.enableTimeouts = function(enabled) { | ||
if (!arguments.length) { | ||
return this._enableTimeouts; | ||
} | ||
debug('enableTimeouts %s', enabled); | ||
this._enableTimeouts = enabled; | ||
return this; | ||
}; | ||
/** | ||
* Set or get slow `ms` or short-hand such as "2s". | ||
@@ -203,3 +195,3 @@ * | ||
*/ | ||
Suite.prototype.slow = function(ms) { | ||
Suite.prototype.slow = function (ms) { | ||
if (!arguments.length) { | ||
@@ -223,3 +215,3 @@ return this._slow; | ||
*/ | ||
Suite.prototype.bail = function(bail) { | ||
Suite.prototype.bail = function (bail) { | ||
if (!arguments.length) { | ||
@@ -238,3 +230,3 @@ return this._bail; | ||
*/ | ||
Suite.prototype.isPending = function() { | ||
Suite.prototype.isPending = function () { | ||
return this.pending || (this.parent && this.parent.isPending()); | ||
@@ -250,3 +242,3 @@ }; | ||
*/ | ||
Suite.prototype._createHook = function(title, fn) { | ||
Suite.prototype._createHook = function (title, fn) { | ||
var hook = new Hook(title, fn); | ||
@@ -256,3 +248,2 @@ hook.parent = this; | ||
hook.retries(this.retries()); | ||
hook.enableTimeouts(this.enableTimeouts()); | ||
hook.slow(this.slow()); | ||
@@ -272,3 +263,3 @@ hook.ctx = this.ctx; | ||
*/ | ||
Suite.prototype.beforeAll = function(title, fn) { | ||
Suite.prototype.beforeAll = function (title, fn) { | ||
if (this.isPending()) { | ||
@@ -297,3 +288,3 @@ return this; | ||
*/ | ||
Suite.prototype.afterAll = function(title, fn) { | ||
Suite.prototype.afterAll = function (title, fn) { | ||
if (this.isPending()) { | ||
@@ -322,3 +313,3 @@ return this; | ||
*/ | ||
Suite.prototype.beforeEach = function(title, fn) { | ||
Suite.prototype.beforeEach = function (title, fn) { | ||
if (this.isPending()) { | ||
@@ -347,3 +338,3 @@ return this; | ||
*/ | ||
Suite.prototype.afterEach = function(title, fn) { | ||
Suite.prototype.afterEach = function (title, fn) { | ||
if (this.isPending()) { | ||
@@ -371,3 +362,3 @@ return this; | ||
*/ | ||
Suite.prototype.addSuite = function(suite) { | ||
Suite.prototype.addSuite = function (suite) { | ||
suite.parent = this; | ||
@@ -377,3 +368,2 @@ suite.root = false; | ||
suite.retries(this.retries()); | ||
suite.enableTimeouts(this.enableTimeouts()); | ||
suite.slow(this.slow()); | ||
@@ -393,7 +383,6 @@ suite.bail(this.bail()); | ||
*/ | ||
Suite.prototype.addTest = function(test) { | ||
Suite.prototype.addTest = function (test) { | ||
test.parent = this; | ||
test.timeout(this.timeout()); | ||
test.retries(this.retries()); | ||
test.enableTimeouts(this.enableTimeouts()); | ||
test.slow(this.slow()); | ||
@@ -414,3 +403,3 @@ test.ctx = this.ctx; | ||
*/ | ||
Suite.prototype.fullTitle = function() { | ||
Suite.prototype.fullTitle = function () { | ||
return this.titlePath().join(' '); | ||
@@ -427,3 +416,3 @@ }; | ||
*/ | ||
Suite.prototype.titlePath = function() { | ||
Suite.prototype.titlePath = function () { | ||
var result = []; | ||
@@ -446,5 +435,5 @@ if (this.parent) { | ||
*/ | ||
Suite.prototype.total = function() { | ||
Suite.prototype.total = function () { | ||
return ( | ||
this.suites.reduce(function(sum, suite) { | ||
this.suites.reduce(function (sum, suite) { | ||
return sum + suite.total(); | ||
@@ -463,5 +452,5 @@ }, 0) + this.tests.length | ||
*/ | ||
Suite.prototype.eachTest = function(fn) { | ||
Suite.prototype.eachTest = function (fn) { | ||
this.tests.forEach(fn); | ||
this.suites.forEach(function(suite) { | ||
this.suites.forEach(function (suite) { | ||
suite.eachTest(fn); | ||
@@ -492,3 +481,3 @@ }); | ||
this._onlySuites.length > 0 || | ||
this.suites.some(function(suite) { | ||
this.suites.some(function (suite) { | ||
return suite.hasOnly(); | ||
@@ -513,3 +502,3 @@ }) | ||
this.tests = []; | ||
this._onlySuites.forEach(function(onlySuite) { | ||
this._onlySuites.forEach(function (onlySuite) { | ||
// If there are other `only` tests/suites nested in the current `only` suite, then filter that `only` suite. | ||
@@ -523,3 +512,3 @@ // Otherwise, all of the tests on this `only` suite should be run, so don't filter it. | ||
var onlySuites = this._onlySuites; | ||
this.suites = this.suites.filter(function(childSuite) { | ||
this.suites = this.suites.filter(function (childSuite) { | ||
return onlySuites.indexOf(childSuite) !== -1 || childSuite.filterOnly(); | ||
@@ -538,3 +527,3 @@ }); | ||
*/ | ||
Suite.prototype.appendOnlySuite = function(suite) { | ||
Suite.prototype.appendOnlySuite = function (suite) { | ||
this._onlySuites.push(suite); | ||
@@ -544,2 +533,11 @@ }; | ||
/** | ||
* Marks a suite to be `only`. | ||
* | ||
* @private | ||
*/ | ||
Suite.prototype.markOnly = function () { | ||
this.parent && this.parent.appendOnlySuite(this); | ||
}; | ||
/** | ||
* Adds a test to the list of tests marked `only`. | ||
@@ -550,3 +548,3 @@ * | ||
*/ | ||
Suite.prototype.appendOnlyTest = function(test) { | ||
Suite.prototype.appendOnlyTest = function (test) { | ||
this._onlyTests.push(test); | ||
@@ -566,4 +564,4 @@ }; | ||
*/ | ||
Suite.prototype.dispose = function() { | ||
this.suites.forEach(function(suite) { | ||
Suite.prototype.dispose = function () { | ||
this.suites.forEach(function (suite) { | ||
suite.dispose(); | ||
@@ -612,3 +610,21 @@ }); | ||
var constants = utils.defineConstants( | ||
/** | ||
* Returns an object suitable for IPC. | ||
* Functions are represented by keys beginning with `$$`. | ||
* @private | ||
* @returns {Object} | ||
*/ | ||
Suite.prototype.serialize = function serialize() { | ||
return { | ||
_bail: this._bail, | ||
$$fullTitle: this.fullTitle(), | ||
$$isPending: Boolean(this.isPending()), | ||
root: this.root, | ||
title: this.title, | ||
[MOCHA_ID_PROP_NAME]: this.id, | ||
parent: this.parent ? {[MOCHA_ID_PROP_NAME]: this.parent.id} : null | ||
}; | ||
}; | ||
var constants = defineConstants( | ||
/** | ||
@@ -625,3 +641,3 @@ * {@link Suite}-related constants. | ||
/** | ||
* Event emitted after a test file has been loaded Not emitted in browser. | ||
* Event emitted after a test file has been loaded. Not emitted in browser. | ||
*/ | ||
@@ -638,3 +654,3 @@ EVENT_FILE_POST_REQUIRE: 'post-require', | ||
/** | ||
* Event emitted when `global.run()` is called (use with `delay` option) | ||
* Event emitted when `global.run()` is called (use with `delay` option). | ||
*/ | ||
@@ -644,43 +660,41 @@ EVENT_ROOT_SUITE_RUN: 'run', | ||
/** | ||
* Namespace for collection of a `Suite`'s "after all" hooks | ||
* Namespace for collection of a `Suite`'s "after all" hooks. | ||
*/ | ||
HOOK_TYPE_AFTER_ALL: 'afterAll', | ||
/** | ||
* Namespace for collection of a `Suite`'s "after each" hooks | ||
* Namespace for collection of a `Suite`'s "after each" hooks. | ||
*/ | ||
HOOK_TYPE_AFTER_EACH: 'afterEach', | ||
/** | ||
* Namespace for collection of a `Suite`'s "before all" hooks | ||
* Namespace for collection of a `Suite`'s "before all" hooks. | ||
*/ | ||
HOOK_TYPE_BEFORE_ALL: 'beforeAll', | ||
/** | ||
* Namespace for collection of a `Suite`'s "before all" hooks | ||
* Namespace for collection of a `Suite`'s "before each" hooks. | ||
*/ | ||
HOOK_TYPE_BEFORE_EACH: 'beforeEach', | ||
// the following events are all deprecated | ||
/** | ||
* Emitted after an "after all" `Hook` has been added to a `Suite`. Deprecated | ||
* Emitted after a child `Suite` has been added to a `Suite`. | ||
*/ | ||
EVENT_SUITE_ADD_SUITE: 'suite', | ||
/** | ||
* Emitted after an "after all" `Hook` has been added to a `Suite`. | ||
*/ | ||
EVENT_SUITE_ADD_HOOK_AFTER_ALL: 'afterAll', | ||
/** | ||
* Emitted after an "after each" `Hook` has been added to a `Suite` Deprecated | ||
* Emitted after an "after each" `Hook` has been added to a `Suite`. | ||
*/ | ||
EVENT_SUITE_ADD_HOOK_AFTER_EACH: 'afterEach', | ||
/** | ||
* Emitted after an "before all" `Hook` has been added to a `Suite` Deprecated | ||
* Emitted after an "before all" `Hook` has been added to a `Suite`. | ||
*/ | ||
EVENT_SUITE_ADD_HOOK_BEFORE_ALL: 'beforeAll', | ||
/** | ||
* Emitted after an "before each" `Hook` has been added to a `Suite` Deprecated | ||
* Emitted after an "before each" `Hook` has been added to a `Suite`. | ||
*/ | ||
EVENT_SUITE_ADD_HOOK_BEFORE_EACH: 'beforeEach', | ||
/** | ||
* Emitted after a child `Suite` has been added to a `Suite`. Deprecated | ||
* Emitted after a `Test` has been added to a `Suite`. | ||
*/ | ||
EVENT_SUITE_ADD_SUITE: 'suite', | ||
/** | ||
* Emitted after a `Test` has been added to a `Suite`. Deprecated | ||
*/ | ||
EVENT_SUITE_ADD_TEST: 'test' | ||
@@ -690,18 +704,2 @@ } | ||
/** | ||
* @summary There are no known use cases for these events. | ||
* @desc This is a `Set`-like object having all keys being the constant's string value and the value being `true`. | ||
* @todo Remove eventually | ||
* @type {Object<string,boolean>} | ||
* @ignore | ||
*/ | ||
var deprecatedEvents = Object.keys(constants) | ||
.filter(function(constant) { | ||
return constant.substring(0, 15) === 'EVENT_SUITE_ADD'; | ||
}) | ||
.reduce(function(acc, constant) { | ||
acc[constants[constant]] = true; | ||
return acc; | ||
}, utils.createMap()); | ||
Suite.constants = constants; |
@@ -8,2 +8,4 @@ 'use strict'; | ||
const {MOCHA_ID_PROP_NAME} = utils.constants; | ||
module.exports = Test; | ||
@@ -43,3 +45,3 @@ | ||
*/ | ||
Test.prototype.reset = function() { | ||
Test.prototype.reset = function () { | ||
Runnable.prototype.reset.call(this); | ||
@@ -55,3 +57,3 @@ this.pending = !this.fn; | ||
*/ | ||
Test.prototype.retriedTest = function(n) { | ||
Test.prototype.retriedTest = function (n) { | ||
if (!arguments.length) { | ||
@@ -68,11 +70,10 @@ return this._retriedTest; | ||
*/ | ||
Test.prototype.markOnly = function() { | ||
Test.prototype.markOnly = function () { | ||
this.parent.appendOnlyTest(this); | ||
}; | ||
Test.prototype.clone = function() { | ||
Test.prototype.clone = function () { | ||
var test = new Test(this.title, this.fn); | ||
test.timeout(this.timeout()); | ||
test.slow(this.slow()); | ||
test.enableTimeouts(this.enableTimeouts()); | ||
test.retries(this.retries()); | ||
@@ -87,1 +88,31 @@ test.currentRetry(this.currentRetry()); | ||
}; | ||
/** | ||
* Returns an minimal object suitable for transmission over IPC. | ||
* Functions are represented by keys beginning with `$$`. | ||
* @private | ||
* @returns {Object} | ||
*/ | ||
Test.prototype.serialize = function serialize() { | ||
return { | ||
$$currentRetry: this._currentRetry, | ||
$$fullTitle: this.fullTitle(), | ||
$$isPending: Boolean(this.pending), | ||
$$retriedTest: this._retriedTest || null, | ||
$$slow: this._slow, | ||
$$titlePath: this.titlePath(), | ||
body: this.body, | ||
duration: this.duration, | ||
err: this.err, | ||
parent: { | ||
$$fullTitle: this.parent.fullTitle(), | ||
[MOCHA_ID_PROP_NAME]: this.parent.id | ||
}, | ||
speed: this.speed, | ||
state: this.state, | ||
title: this.title, | ||
type: this.type, | ||
file: this.file, | ||
[MOCHA_ID_PROP_NAME]: this.id | ||
}; | ||
}; |
498
lib/utils.js
@@ -12,12 +12,8 @@ 'use strict'; | ||
var fs = require('fs'); | ||
const {nanoid} = require('nanoid/non-secure'); | ||
var path = require('path'); | ||
var util = require('util'); | ||
var glob = require('glob'); | ||
var he = require('he'); | ||
var errors = require('./errors'); | ||
var createNoFilesMatchPatternError = errors.createNoFilesMatchPatternError; | ||
var createMissingArgumentError = errors.createMissingArgumentError; | ||
var assign = (exports.assign = require('object.assign').getPolyfill()); | ||
const MOCHA_ID_PROP_NAME = '__mocha_id__'; | ||
@@ -42,3 +38,3 @@ /** | ||
*/ | ||
exports.escape = function(html) { | ||
exports.escape = function (html) { | ||
return he.encode(String(html), {useNamedReferences: false}); | ||
@@ -54,3 +50,3 @@ }; | ||
*/ | ||
exports.isString = function(obj) { | ||
exports.isString = function (obj) { | ||
return typeof obj === 'string'; | ||
@@ -66,3 +62,3 @@ }; | ||
*/ | ||
exports.slug = function(str) { | ||
exports.slug = function (str) { | ||
return str | ||
@@ -81,3 +77,3 @@ .toLowerCase() | ||
*/ | ||
exports.clean = function(str) { | ||
exports.clean = function (str) { | ||
str = str | ||
@@ -88,3 +84,3 @@ .replace(/\r\n?|[\n\u2028\u2029]/g, '\n') | ||
.replace( | ||
/^function(?:\s*|\s+[^(]*)\([^)]*\)\s*\{((?:.|\n)*?)\s*\}$|^\([^)]*\)\s*=>\s*(?:\{((?:.|\n)*?)\s*\}|((?:.|\n)*))$/, | ||
/^function(?:\s*|\s[^(]*)\([^)]*\)\s*\{((?:.|\n)*?)\}$|^\([^)]*\)\s*=>\s*(?:\{((?:.|\n)*?)\}|((?:.|\n)*))$/, | ||
'$1$2$3' | ||
@@ -106,63 +102,2 @@ ); | ||
/** | ||
* Parse the given `qs`. | ||
* | ||
* @private | ||
* @param {string} qs | ||
* @return {Object} | ||
*/ | ||
exports.parseQuery = function(qs) { | ||
return qs | ||
.replace('?', '') | ||
.split('&') | ||
.reduce(function(obj, pair) { | ||
var i = pair.indexOf('='); | ||
var key = pair.slice(0, i); | ||
var val = pair.slice(++i); | ||
// Due to how the URLSearchParams API treats spaces | ||
obj[key] = decodeURIComponent(val.replace(/\+/g, '%20')); | ||
return obj; | ||
}, {}); | ||
}; | ||
/** | ||
* Highlight the given string of `js`. | ||
* | ||
* @private | ||
* @param {string} js | ||
* @return {string} | ||
*/ | ||
function highlight(js) { | ||
return js | ||
.replace(/</g, '<') | ||
.replace(/>/g, '>') | ||
.replace(/\/\/(.*)/gm, '<span class="comment">//$1</span>') | ||
.replace(/('.*?')/gm, '<span class="string">$1</span>') | ||
.replace(/(\d+\.\d+)/gm, '<span class="number">$1</span>') | ||
.replace(/(\d+)/gm, '<span class="number">$1</span>') | ||
.replace( | ||
/\bnew[ \t]+(\w+)/gm, | ||
'<span class="keyword">new</span> <span class="init">$1</span>' | ||
) | ||
.replace( | ||
/\b(function|new|throw|return|var|if|else)\b/gm, | ||
'<span class="keyword">$1</span>' | ||
); | ||
} | ||
/** | ||
* Highlight the contents of tag `name`. | ||
* | ||
* @private | ||
* @param {string} name | ||
*/ | ||
exports.highlightTags = function(name) { | ||
var code = document.getElementById('mocha').getElementsByTagName(name); | ||
for (var i = 0, len = code.length; i < len; ++i) { | ||
code[i].innerHTML = highlight(code[i].innerHTML); | ||
} | ||
}; | ||
/** | ||
* If a value could have properties, and has none, this function is called, | ||
@@ -203,15 +138,17 @@ * which returns a string representation of the empty value. | ||
* @example | ||
* type({}) // 'object' | ||
* type([]) // 'array' | ||
* type(1) // 'number' | ||
* type(false) // 'boolean' | ||
* type(Infinity) // 'number' | ||
* type(null) // 'null' | ||
* type(new Date()) // 'date' | ||
* type(/foo/) // 'regexp' | ||
* type('type') // 'string' | ||
* type(global) // 'global' | ||
* type(new String('foo') // 'object' | ||
* canonicalType({}) // 'object' | ||
* canonicalType([]) // 'array' | ||
* canonicalType(1) // 'number' | ||
* canonicalType(false) // 'boolean' | ||
* canonicalType(Infinity) // 'number' | ||
* canonicalType(null) // 'null' | ||
* canonicalType(new Date()) // 'date' | ||
* canonicalType(/foo/) // 'regexp' | ||
* canonicalType('type') // 'string' | ||
* canonicalType(global) // 'global' | ||
* canonicalType(new String('foo') // 'object' | ||
* canonicalType(async function() {}) // 'asyncfunction' | ||
* canonicalType(await import(name)) // 'module' | ||
*/ | ||
var type = (exports.type = function type(value) { | ||
var canonicalType = (exports.canonicalType = function canonicalType(value) { | ||
if (value === undefined) { | ||
@@ -231,2 +168,43 @@ return 'undefined'; | ||
/** | ||
* | ||
* Returns a general type or data structure of a variable | ||
* @private | ||
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures | ||
* @param {*} value The value to test. | ||
* @returns {string} One of undefined, boolean, number, string, bigint, symbol, object | ||
* @example | ||
* type({}) // 'object' | ||
* type([]) // 'array' | ||
* type(1) // 'number' | ||
* type(false) // 'boolean' | ||
* type(Infinity) // 'number' | ||
* type(null) // 'null' | ||
* type(new Date()) // 'object' | ||
* type(/foo/) // 'object' | ||
* type('type') // 'string' | ||
* type(global) // 'object' | ||
* type(new String('foo') // 'string' | ||
*/ | ||
exports.type = function type(value) { | ||
// Null is special | ||
if (value === null) return 'null'; | ||
const primitives = new Set([ | ||
'undefined', | ||
'boolean', | ||
'number', | ||
'string', | ||
'bigint', | ||
'symbol' | ||
]); | ||
const _type = typeof value; | ||
if (_type === 'function') return _type; | ||
if (primitives.has(_type)) return _type; | ||
if (value instanceof String) return 'string'; | ||
if (value instanceof Error) return 'error'; | ||
if (Array.isArray(value)) return 'array'; | ||
return _type; | ||
}; | ||
/** | ||
* Stringify `value`. Different behavior depending on type of value: | ||
@@ -246,4 +224,4 @@ * | ||
*/ | ||
exports.stringify = function(value) { | ||
var typeHint = type(value); | ||
exports.stringify = function (value) { | ||
var typeHint = canonicalType(value); | ||
@@ -263,3 +241,3 @@ if (!~['object', 'array', 'function'].indexOf(typeHint)) { | ||
if (typeHint === 'string' && typeof value === 'object') { | ||
value = value.split('').reduce(function(acc, char, idx) { | ||
value = value.split('').reduce(function (acc, char, idx) { | ||
acc[idx] = char; | ||
@@ -315,3 +293,3 @@ return acc; | ||
function _stringify(val) { | ||
switch (type(val)) { | ||
switch (canonicalType(val)) { | ||
case 'null': | ||
@@ -334,2 +312,5 @@ case 'undefined': | ||
break; | ||
case 'bigint': | ||
val = val.toString() + 'n'; | ||
break; | ||
case 'date': | ||
@@ -398,3 +379,3 @@ var sDate = isNaN(val.getTime()) ? val.toString() : val.toISOString(); | ||
/* eslint-enable no-unused-vars */ | ||
typeHint = typeHint || type(value); | ||
typeHint = typeHint || canonicalType(value); | ||
function withStack(value, fn) { | ||
@@ -419,4 +400,4 @@ stack.push(value); | ||
case 'array': | ||
withStack(value, function() { | ||
canonicalizedObj = value.map(function(item) { | ||
withStack(value, function () { | ||
canonicalizedObj = value.map(function (item) { | ||
return exports.canonicalize(item, stack); | ||
@@ -427,3 +408,3 @@ }); | ||
case 'function': | ||
/* eslint-disable guard-for-in */ | ||
/* eslint-disable-next-line no-unused-vars, no-unreachable-loop */ | ||
for (prop in value) { | ||
@@ -441,6 +422,6 @@ canonicalizedObj = {}; | ||
canonicalizedObj = canonicalizedObj || {}; | ||
withStack(value, function() { | ||
withStack(value, function () { | ||
Object.keys(value) | ||
.sort() | ||
.forEach(function(key) { | ||
.forEach(function (key) { | ||
canonicalizedObj[key] = exports.canonicalize(value[key], stack); | ||
@@ -465,181 +446,2 @@ }); | ||
/** | ||
* Determines if pathname has a matching file extension. | ||
* | ||
* @private | ||
* @param {string} pathname - Pathname to check for match. | ||
* @param {string[]} exts - List of file extensions (sans period). | ||
* @return {boolean} whether file extension matches. | ||
* @example | ||
* hasMatchingExtname('foo.html', ['js', 'css']); // => false | ||
*/ | ||
function hasMatchingExtname(pathname, exts) { | ||
var suffix = path.extname(pathname).slice(1); | ||
return exts.some(function(element) { | ||
return suffix === element; | ||
}); | ||
} | ||
/** | ||
* 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 | ||
*/ | ||
function isHiddenOnUnix(pathname) { | ||
return path.basename(pathname)[0] === '.'; | ||
} | ||
/** | ||
* 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 | ||
* @memberof Mocha.utils | ||
* @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. | ||
*/ | ||
exports.lookupFiles = function lookupFiles(filepath, extensions, recursive) { | ||
extensions = extensions || []; | ||
recursive = recursive || false; | ||
var files = []; | ||
var stat; | ||
if (!fs.existsSync(filepath)) { | ||
var pattern; | ||
if (glob.hasMagic(filepath)) { | ||
// Handle glob as is without extensions | ||
pattern = filepath; | ||
} else { | ||
// glob pattern e.g. 'filepath+(.js|.ts)' | ||
var strExtensions = extensions | ||
.map(function(v) { | ||
return '.' + v; | ||
}) | ||
.join('|'); | ||
pattern = filepath + '+(' + strExtensions + ')'; | ||
} | ||
files = glob.sync(pattern, {nodir: true}); | ||
if (!files.length) { | ||
throw createNoFilesMatchPatternError( | ||
'Cannot find any files matching pattern ' + exports.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(function(dirent) { | ||
var pathname = path.join(filepath, dirent); | ||
var stat; | ||
try { | ||
stat = fs.statSync(pathname); | ||
if (stat.isDirectory()) { | ||
if (recursive) { | ||
files = files.concat(lookupFiles(pathname, extensions, recursive)); | ||
} | ||
return; | ||
} | ||
} catch (err) { | ||
// ignore error | ||
return; | ||
} | ||
if (!extensions.length) { | ||
throw createMissingArgumentError( | ||
util.format( | ||
'Argument %s required when argument %s is a directory', | ||
exports.sQuote('extensions'), | ||
exports.sQuote('filepath') | ||
), | ||
'extensions', | ||
'array' | ||
); | ||
} | ||
if ( | ||
!stat.isFile() || | ||
!hasMatchingExtname(pathname, extensions) || | ||
isHiddenOnUnix(pathname) | ||
) { | ||
return; | ||
} | ||
files.push(pathname); | ||
}); | ||
return files; | ||
}; | ||
/** | ||
* process.emitWarning or a polyfill | ||
* @see https://nodejs.org/api/process.html#process_process_emitwarning_warning_options | ||
* @ignore | ||
*/ | ||
function emitWarning(msg, type) { | ||
if (process.emitWarning) { | ||
process.emitWarning(msg, type); | ||
} else { | ||
process.nextTick(function() { | ||
console.warn(type + ': ' + msg); | ||
}); | ||
} | ||
} | ||
/** | ||
* Show a deprecation warning. Each distinct message is only displayed once. | ||
* Ignores empty messages. | ||
* | ||
* @param {string} [msg] - Warning to print | ||
* @private | ||
*/ | ||
exports.deprecate = function deprecate(msg) { | ||
msg = String(msg); | ||
if (msg && !deprecate.cache[msg]) { | ||
deprecate.cache[msg] = true; | ||
emitWarning(msg, 'DeprecationWarning'); | ||
} | ||
}; | ||
exports.deprecate.cache = {}; | ||
/** | ||
* Show a generic warning. | ||
* Ignores empty messages. | ||
* | ||
* @param {string} [msg] - Warning to print | ||
* @private | ||
*/ | ||
exports.warn = function warn(msg) { | ||
if (msg) { | ||
emitWarning(msg); | ||
} | ||
}; | ||
/** | ||
* @summary | ||
@@ -653,3 +455,3 @@ * This Filter based on `mocha-clean` module.(see: `github.com/rstacruz/mocha-clean`) | ||
*/ | ||
exports.stackTraceFilter = function() { | ||
exports.stackTraceFilter = function () { | ||
// TODO: Replace with `process.browser` | ||
@@ -662,5 +464,4 @@ var is = typeof document === 'undefined' ? {node: true} : {browser: true}; | ||
} else { | ||
cwd = (typeof location === 'undefined' | ||
? window.location | ||
: location | ||
cwd = ( | ||
typeof location === 'undefined' ? window.location : location | ||
).href.replace(/\/[^/]*$/, '/'); | ||
@@ -689,6 +490,6 @@ slash = '/'; | ||
return function(stack) { | ||
return function (stack) { | ||
stack = stack.split('\n'); | ||
stack = stack.reduce(function(list, line) { | ||
stack = stack.reduce(function (list, line) { | ||
if (isMochaInternal(line)) { | ||
@@ -733,3 +534,3 @@ return list; | ||
* @param {number} value - Value to be clamped. | ||
* @param {numer[]} range - Two element array specifying [min, max] range. | ||
* @param {number[]} range - Two element array specifying [min, max] range. | ||
* @returns {number} clamped value | ||
@@ -742,44 +543,6 @@ */ | ||
/** | ||
* Single quote text by combining with undirectional ASCII quotation marks. | ||
* | ||
* @description | ||
* Provides a simple means of markup for quoting text to be used in output. | ||
* Use this to quote names of variables, methods, and packages. | ||
* | ||
* <samp>package 'foo' cannot be found</samp> | ||
* | ||
* @private | ||
* @param {string} str - Value to be quoted. | ||
* @returns {string} quoted value | ||
* @example | ||
* sQuote('n') // => 'n' | ||
*/ | ||
exports.sQuote = function(str) { | ||
return "'" + str + "'"; | ||
}; | ||
/** | ||
* Double quote text by combining with undirectional ASCII quotation marks. | ||
* | ||
* @description | ||
* Provides a simple means of markup for quoting text to be used in output. | ||
* Use this to quote names of datatypes, classes, pathnames, and strings. | ||
* | ||
* <samp>argument 'value' must be "string" or "number"</samp> | ||
* | ||
* @private | ||
* @param {string} str - Value to be quoted. | ||
* @returns {string} quoted value | ||
* @example | ||
* dQuote('number') // => "number" | ||
*/ | ||
exports.dQuote = function(str) { | ||
return '"' + str + '"'; | ||
}; | ||
/** | ||
* It's a noop. | ||
* @public | ||
*/ | ||
exports.noop = function() {}; | ||
exports.noop = function () {}; | ||
@@ -795,10 +558,10 @@ /** | ||
* @public | ||
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map|MDN:Map} | ||
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map#Custom_and_Null_objects|MDN:Map} | ||
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create#Custom_and_Null_objects|MDN:Object.create - Custom objects} | ||
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign|MDN:Object.assign} | ||
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Custom_and_Null_objects|MDN:Object.assign} | ||
* @param {...*} [obj] - Arguments to `Object.assign()`. | ||
* @returns {Object} An object with no prototype, having `...obj` properties | ||
*/ | ||
exports.createMap = function(obj) { | ||
return assign.apply( | ||
exports.createMap = function (obj) { | ||
return Object.assign.apply( | ||
null, | ||
@@ -821,4 +584,4 @@ [Object.create(null)].concat(Array.prototype.slice.call(arguments)) | ||
*/ | ||
exports.defineConstants = function(obj) { | ||
if (type(obj) !== 'object' || !Object.keys(obj).length) { | ||
exports.defineConstants = function (obj) { | ||
if (canonicalType(obj) !== 'object' || !Object.keys(obj).length) { | ||
throw new TypeError('Invalid argument; expected a non-empty object'); | ||
@@ -830,31 +593,74 @@ } | ||
/** | ||
* Whether current version of Node support ES modules | ||
* Returns current working directory | ||
* | ||
* @description | ||
* Versions prior to 10 did not support ES Modules, and version 10 has an old incompatibile version of ESM. | ||
* This function returns whether Node.JS has ES Module supports that is compatible with Mocha's needs, | ||
* which is version >=12.11. | ||
* Wrapper around `process.cwd()` for isolation | ||
* @private | ||
*/ | ||
exports.cwd = function cwd() { | ||
return process.cwd(); | ||
}; | ||
/** | ||
* Returns `true` if Mocha is running in a browser. | ||
* Checks for `process.browser`. | ||
* @returns {boolean} | ||
* @private | ||
*/ | ||
exports.isBrowser = function isBrowser() { | ||
return Boolean(process.browser); | ||
}; | ||
/* | ||
* Casts `value` to an array; useful for optionally accepting array parameters | ||
* | ||
* @returns {Boolean} whether the current version of Node.JS supports ES Modules in a way that is compatible with Mocha | ||
* It follows these rules, depending on `value`. If `value` is... | ||
* 1. `undefined`: return an empty Array | ||
* 2. `null`: return an array with a single `null` element | ||
* 3. Any other object: return the value of `Array.from()` _if_ the object is iterable | ||
* 4. otherwise: return an array with a single element, `value` | ||
* @param {*} value - Something to cast to an Array | ||
* @returns {Array<*>} | ||
*/ | ||
exports.supportsEsModules = function() { | ||
if (!process.browser && process.versions && process.versions.node) { | ||
var versionFields = process.versions.node.split('.'); | ||
var major = +versionFields[0]; | ||
var minor = +versionFields[1]; | ||
exports.castArray = function castArray(value) { | ||
if (value === undefined) { | ||
return []; | ||
} | ||
if (value === null) { | ||
return [null]; | ||
} | ||
if ( | ||
typeof value === 'object' && | ||
(typeof value[Symbol.iterator] === 'function' || value.length !== undefined) | ||
) { | ||
return Array.from(value); | ||
} | ||
return [value]; | ||
}; | ||
if (major >= 13 || (major === 12 && minor >= 11)) { | ||
return true; | ||
exports.constants = exports.defineConstants({ | ||
MOCHA_ID_PROP_NAME | ||
}); | ||
/** | ||
* Creates a new unique identifier | ||
* @returns {string} Unique identifier | ||
*/ | ||
exports.uniqueID = () => nanoid(); | ||
exports.assignNewMochaID = obj => { | ||
const id = exports.uniqueID(); | ||
Object.defineProperty(obj, MOCHA_ID_PROP_NAME, { | ||
get() { | ||
return id; | ||
} | ||
} | ||
}); | ||
return obj; | ||
}; | ||
/** | ||
* Returns current working directory | ||
* | ||
* Wrapper around `process.cwd()` for isolation | ||
* @private | ||
* Retrieves a Mocha ID from an object, if present. | ||
* @param {*} [obj] - Object | ||
* @returns {string|void} | ||
*/ | ||
exports.cwd = function cwd() { | ||
return process.cwd(); | ||
}; | ||
exports.getMochaID = obj => | ||
obj && typeof obj === 'object' ? obj[MOCHA_ID_PROP_NAME] : undefined; |
205
package.json
{ | ||
"name": "mocha", | ||
"version": "7.2.0", | ||
"version": "10.2.0", | ||
"type": "commonjs", | ||
"description": "simple, flexible, fun test framework", | ||
@@ -20,6 +21,2 @@ "keywords": [ | ||
], | ||
"funding": { | ||
"type": "opencollective", | ||
"url": "https://opencollective.com/mochajs" | ||
}, | ||
"author": "TJ Holowaychuk <tj@vision-media.ca>", | ||
@@ -34,2 +31,7 @@ "license": "MIT", | ||
}, | ||
"funding": { | ||
"type": "opencollective", | ||
"url": "https://opencollective.com/mochajs" | ||
}, | ||
"gitter": "https://gitter.im/mochajs/mocha", | ||
"homepage": "https://mochajs.org/", | ||
@@ -39,3 +41,3 @@ "logo": "https://cldup.com/S9uQ-cOLYz.svg", | ||
"bin": { | ||
"mocha": "./bin/mocha", | ||
"mocha": "./bin/mocha.js", | ||
"_mocha": "./bin/_mocha" | ||
@@ -48,3 +50,3 @@ }, | ||
"engines": { | ||
"node": ">= 8.10.0" | ||
"node": ">= 14.0.0" | ||
}, | ||
@@ -55,95 +57,102 @@ "scripts": { | ||
"test": "nps test", | ||
"version": "nps version" | ||
"version": "nps version", | ||
"test:smoke": "node ./bin/mocha --no-config test/smoke/smoke.spec.js" | ||
}, | ||
"dependencies": { | ||
"ansi-colors": "3.2.3", | ||
"ansi-colors": "4.1.1", | ||
"browser-stdout": "1.3.1", | ||
"chokidar": "3.3.0", | ||
"debug": "3.2.6", | ||
"diff": "3.5.0", | ||
"escape-string-regexp": "1.0.5", | ||
"find-up": "3.0.0", | ||
"glob": "7.1.3", | ||
"growl": "1.10.5", | ||
"chokidar": "3.5.3", | ||
"debug": "4.3.4", | ||
"diff": "5.0.0", | ||
"escape-string-regexp": "4.0.0", | ||
"find-up": "5.0.0", | ||
"glob": "7.2.0", | ||
"he": "1.2.0", | ||
"js-yaml": "3.13.1", | ||
"log-symbols": "3.0.0", | ||
"minimatch": "3.0.4", | ||
"mkdirp": "0.5.5", | ||
"ms": "2.1.1", | ||
"node-environment-flags": "1.0.6", | ||
"object.assign": "4.1.0", | ||
"strip-json-comments": "2.0.1", | ||
"supports-color": "6.0.0", | ||
"which": "1.3.1", | ||
"wide-align": "1.1.3", | ||
"yargs": "13.3.2", | ||
"yargs-parser": "13.1.2", | ||
"yargs-unparser": "1.6.0" | ||
"js-yaml": "4.1.0", | ||
"log-symbols": "4.1.0", | ||
"minimatch": "5.0.1", | ||
"ms": "2.1.3", | ||
"nanoid": "3.3.3", | ||
"serialize-javascript": "6.0.0", | ||
"strip-json-comments": "3.1.1", | ||
"supports-color": "8.1.1", | ||
"workerpool": "6.2.1", | ||
"yargs": "16.2.0", | ||
"yargs-parser": "20.2.4", | ||
"yargs-unparser": "2.0.0" | ||
}, | ||
"devDependencies": { | ||
"@11ty/eleventy": "^0.10.0", | ||
"@11ty/eleventy-plugin-inclusive-language": "^1.0.0", | ||
"@mocha/docdash": "^2.1.3", | ||
"assetgraph-builder": "^8.0.0", | ||
"autoprefixer": "^9.7.4", | ||
"babel-eslint": "^10.1.0", | ||
"browserify": "^16.5.0", | ||
"browserify-package-json": "^1.0.1", | ||
"chai": "^4.2.0", | ||
"coffee-script": "^1.12.7", | ||
"coveralls": "^3.0.3", | ||
"cross-env": "^5.2.0", | ||
"cross-spawn": "^6.0.5", | ||
"eslint": "^6.8.0", | ||
"eslint-config-prettier": "^6.9.0", | ||
"eslint-config-semistandard": "^15.0.0", | ||
"eslint-config-standard": "^14.1.0", | ||
"eslint-plugin-import": "^2.19.1", | ||
"eslint-plugin-node": "^11.0.0", | ||
"eslint-plugin-prettier": "^3.1.2", | ||
"eslint-plugin-promise": "^4.2.1", | ||
"eslint-plugin-standard": "^4.0.1", | ||
"fs-extra": "^8.0.1", | ||
"husky": "^4.2.3", | ||
"hyperlink": "^4.4.3", | ||
"image-size": "^0.8.3", | ||
"jsdoc": "^3.6.3", | ||
"karma": "^4.1.0", | ||
"karma-browserify": "^6.0.0", | ||
"karma-chrome-launcher": "^2.2.0", | ||
"karma-mocha": "^1.3.0", | ||
"@11ty/eleventy": "^1.0.0", | ||
"@11ty/eleventy-plugin-inclusive-language": "^1.0.3", | ||
"@babel/eslint-parser": "^7.19.1", | ||
"@mocha/docdash": "^4.0.1", | ||
"@rollup/plugin-commonjs": "^21.0.2", | ||
"@rollup/plugin-json": "^4.1.0", | ||
"@rollup/plugin-multi-entry": "^4.0.1", | ||
"@rollup/plugin-node-resolve": "^13.1.3", | ||
"assetgraph-builder": "^9.0.0", | ||
"autoprefixer": "^9.8.6", | ||
"canvas": "^2.9.0", | ||
"chai": "^4.3.4", | ||
"coffeescript": "^2.6.1", | ||
"coveralls": "^3.1.1", | ||
"cross-env": "^7.0.2", | ||
"eslint": "^8.24.0", | ||
"eslint-config-prettier": "^8.3.0", | ||
"eslint-config-semistandard": "^17.0.0", | ||
"eslint-config-standard": "^17.0.0", | ||
"eslint-plugin-import": "^2.24.2", | ||
"eslint-plugin-node": "^11.1.0", | ||
"eslint-plugin-prettier": "^4.2.1", | ||
"eslint-plugin-promise": "^6.0.1", | ||
"fail-on-errors-webpack-plugin": "^3.0.0", | ||
"fs-extra": "^10.0.0", | ||
"husky": "^4.2.5", | ||
"hyperlink": "^5.0.4", | ||
"jsdoc": "^3.6.7", | ||
"jsdoc-ts-utils": "^2.0.1", | ||
"karma": "^6.3.11", | ||
"karma-chrome-launcher": "^3.1.0", | ||
"karma-mocha": "^2.0.1", | ||
"karma-mocha-reporter": "^2.2.5", | ||
"karma-sauce-launcher": "^2.0.2", | ||
"lint-staged": "^9.5.0", | ||
"markdown-it": "^10.0.0", | ||
"markdown-it-anchor": "^5.2.5", | ||
"markdown-it-attrs": "^3.0.2", | ||
"markdown-it-emoji": "^1.4.0", | ||
"markdown-it-prism": "^2.0.5", | ||
"karma-sauce-launcher": "^4.3.6", | ||
"lint-staged": "^10.2.11", | ||
"markdown-it": "^12.3.2", | ||
"markdown-it-anchor": "^8.4.1", | ||
"markdown-it-attrs": "^4.1.3", | ||
"markdown-it-emoji": "^2.0.0", | ||
"markdown-it-prism": "^2.2.2", | ||
"markdown-toc": "^1.2.0", | ||
"markdownlint-cli": "^0.22.0", | ||
"needle": "^2.4.1", | ||
"nps": "^5.9.12", | ||
"nyc": "^15.0.0", | ||
"prettier": "^1.19.1", | ||
"remark": "^11.0.2", | ||
"remark-github": "^8.0.0", | ||
"remark-inline-links": "^3.1.3", | ||
"rewiremock": "^3.14.1", | ||
"markdownlint-cli": "^0.30.0", | ||
"needle": "^2.5.0", | ||
"nps": "^5.10.0", | ||
"nyc": "^15.1.0", | ||
"pidtree": "^0.5.0", | ||
"prettier": "^2.4.1", | ||
"remark": "^14.0.2", | ||
"remark-github": "^11.2.2", | ||
"remark-inline-links": "^6.0.1", | ||
"rewiremock": "^3.14.3", | ||
"rimraf": "^3.0.2", | ||
"sinon": "^9.0.1", | ||
"rollup": "^2.70.1", | ||
"rollup-plugin-node-globals": "^1.4.0", | ||
"rollup-plugin-polyfill-node": "^0.8.0", | ||
"rollup-plugin-visualizer": "^5.6.0", | ||
"sinon": "^9.0.3", | ||
"strip-ansi": "^6.0.0", | ||
"svgo": "^1.3.2", | ||
"through2": "^3.0.1", | ||
"to-vfile": "^6.1.0", | ||
"unexpected": "^11.13.0", | ||
"touch": "^3.1.0", | ||
"unexpected": "^11.14.0", | ||
"unexpected-eventemitter": "^2.2.0", | ||
"unexpected-map": "^2.0.0", | ||
"unexpected-set": "^3.0.0", | ||
"unexpected-sinon": "^10.11.2", | ||
"update-notifier": "^4.1.0", | ||
"uslug": "^1.0.4", | ||
"watchify": "^3.11.1" | ||
"uuid": "^8.3.0", | ||
"webpack": "^5.67.0", | ||
"webpack-cli": "^4.9.1" | ||
}, | ||
"files": [ | ||
"bin/*mocha", | ||
"assets/growl/*.png", | ||
"bin/*mocha*", | ||
"lib/**/*.{js,html,json}", | ||
@@ -153,26 +162,26 @@ "index.js", | ||
"mocha.js", | ||
"mocha.js.map", | ||
"browser-entry.js" | ||
], | ||
"browserify": { | ||
"transform": [ | ||
"./scripts/package-json-cullify" | ||
] | ||
}, | ||
"browser": { | ||
"./index.js": "./browser-entry.js", | ||
"./lib/growl.js": "./lib/browser/growl.js", | ||
"tty": "./lib/browser/tty.js", | ||
"./lib/cli/*.js": false, | ||
"chokidar": false, | ||
"fs": false, | ||
"glob": false, | ||
"path": false, | ||
"supports-color": false | ||
"supports-color": false, | ||
"./lib/nodejs/buffered-worker-pool.js": false, | ||
"./lib/nodejs/esm-utils.js": false, | ||
"./lib/nodejs/file-unloader.js": false, | ||
"./lib/nodejs/parallel-buffered-runner.js": false, | ||
"./lib/nodejs/serializer.js": false, | ||
"./lib/nodejs/worker.js": false, | ||
"./lib/nodejs/reporters/parallel-buffered.js": false, | ||
"./lib/cli/index.js": false | ||
}, | ||
"prettier": { | ||
"arrowParens": "avoid", | ||
"bracketSpacing": false, | ||
"endOfLine": "auto", | ||
"singleQuote": true, | ||
"bracketSpacing": false, | ||
"endOfLine": "auto" | ||
"trailingComma": "none" | ||
}, | ||
"gitter": "https://gitter.im/mochajs/mocha", | ||
"husky": { | ||
@@ -179,0 +188,0 @@ "hooks": { |
@@ -7,5 +7,16 @@ <p align="center"> | ||
<p align="center"><a href="http://travis-ci.org/mochajs/mocha"><img src="https://api.travis-ci.org/mochajs/mocha.svg?branch=master" alt="Build Status"></a> <a href="https://coveralls.io/github/mochajs/mocha"><img src="https://coveralls.io/repos/github/mochajs/mocha/badge.svg" alt="Coverage Status"></a> <a href="https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fmochajs%2Fmocha?ref=badge_shield"><img src="https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fmochajs%2Fmocha.svg?type=shield" alt="FOSSA Status"></a> <a href="https://gitter.im/mochajs/mocha?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"><img src="https://badges.gitter.im/Join%20Chat.svg" alt="Gitter"></a> <a href="https://github.com/mochajs/mocha#backers"><img src="https://opencollective.com/mochajs/backers/badge.svg" alt="OpenCollective"></a> <a href="https://github.com/mochajs/mocha#sponsors"><img src="https://opencollective.com/mochajs/sponsors/badge.svg" alt="OpenCollective"></a> | ||
<p align="center"> | ||
<a href="https://github.com/mochajs/mocha/actions?query=workflow%3ATests+branch%3Amaster"><img src="https://github.com/mochajs/mocha/workflows/Tests/badge.svg?branch=master" alt="GitHub Actions Build Status"></a> | ||
<a href="https://coveralls.io/github/mochajs/mocha"><img src="https://coveralls.io/repos/github/mochajs/mocha/badge.svg" alt="Coverage Status"></a> | ||
<a href="https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fmochajs%2Fmocha?ref=badge_shield"><img src="https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fmochajs%2Fmocha.svg?type=shield" alt="FOSSA Status"></a> | ||
<a href="https://gitter.im/mochajs/mocha?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"><img src="https://badges.gitter.im/Join%20Chat.svg" alt="Gitter"></a> | ||
<a href="https://github.com/mochajs/mocha#sponsors"><img src="https://opencollective.com/mochajs/tiers/sponsors/badge.svg" alt="OpenCollective"></a> | ||
<a href="https://github.com/mochajs/mocha#backers"><img src="https://opencollective.com/mochajs/tiers/backers/badge.svg" alt="OpenCollective"></a> | ||
</p> | ||
<p align="center"> | ||
<a href="https://www.npmjs.com/package/mocha"><img src="https://img.shields.io/npm/v/mocha.svg" alt="NPM Version"></a> | ||
<a href="https://github.com/mochajs/mocha"><img src="https://img.shields.io/node/v/mocha.svg" alt="Node Version"></a> | ||
</p> | ||
<p align="center"><br><img alt="Mocha Browser Support h/t SauceLabs" src="https://saucelabs.com/browser-matrix/mochajs.svg" width="354"></p> | ||
@@ -20,3 +31,2 @@ | ||
- [Gitter Chatroom](https://gitter.im/mochajs/mocha) (ask questions here!) | ||
- [Google Group](https://groups.google.com/group/mochajs) | ||
- [Issue Tracker](https://github.com/mochajs/mocha/issues) | ||
@@ -26,59 +36,14 @@ | ||
[Become a backer](https://opencollective.com/mochajs#backer) and show your support to our open source project. | ||
[Become a backer](https://opencollective.com/mochajs) and show your support to our open source project on [our site](https://mochajs.org/#backers). | ||
[![MochaJS Backer](https://opencollective.com/mochajs/backer/0/avatar)](https://opencollective.com/mochajs/backer/0/website) | ||
[![MochaJS Backer](https://opencollective.com/mochajs/backer/1/avatar)](https://opencollective.com/mochajs/backer/1/website) | ||
[![MochaJS Backer](https://opencollective.com/mochajs/backer/2/avatar)](https://opencollective.com/mochajs/backer/2/website) | ||
[![MochaJS Backer](https://opencollective.com/mochajs/backer/3/avatar)](https://opencollective.com/mochajs/backer/3/website) | ||
[![MochaJS Backer](https://opencollective.com/mochajs/backer/4/avatar)](https://opencollective.com/mochajs/backer/4/website) | ||
[![MochaJS Backer](https://opencollective.com/mochajs/backer/5/avatar)](https://opencollective.com/mochajs/backer/5/website) | ||
[![MochaJS Backer](https://opencollective.com/mochajs/backer/6/avatar)](https://opencollective.com/mochajs/backer/6/website) | ||
[![MochaJS Backer](https://opencollective.com/mochajs/backer/7/avatar)](https://opencollective.com/mochajs/backer/7/website) | ||
[![MochaJS Backer](https://opencollective.com/mochajs/backer/8/avatar)](https://opencollective.com/mochajs/backer/8/website) | ||
[![MochaJS Backer](https://opencollective.com/mochajs/backer/9/avatar)](https://opencollective.com/mochajs/backer/9/website) | ||
[![MochaJS Backer](https://opencollective.com/mochajs/backer/10/avatar)](https://opencollective.com/mochajs/backer/10/website) | ||
[![MochaJS Backer](https://opencollective.com/mochajs/backer/11/avatar)](https://opencollective.com/mochajs/backer/11/website) | ||
[![MochaJS Backer](https://opencollective.com/mochajs/backer/12/avatar)](https://opencollective.com/mochajs/backer/12/website) | ||
[![MochaJS Backer](https://opencollective.com/mochajs/backer/13/avatar)](https://opencollective.com/mochajs/backer/13/website) | ||
[![MochaJS Backer](https://opencollective.com/mochajs/backer/14/avatar)](https://opencollective.com/mochajs/backer/14/website) | ||
[![MochaJS Backer](https://opencollective.com/mochajs/backer/15/avatar)](https://opencollective.com/mochajs/backer/15/website) | ||
[![MochaJS Backer](https://opencollective.com/mochajs/backer/16/avatar)](https://opencollective.com/mochajs/backer/16/website) | ||
[![MochaJS Backer](https://opencollective.com/mochajs/backer/17/avatar)](https://opencollective.com/mochajs/backer/17/website) | ||
[![MochaJS Backer](https://opencollective.com/mochajs/backer/18/avatar)](https://opencollective.com/mochajs/backer/18/website) | ||
[![MochaJS Backer](https://opencollective.com/mochajs/backer/19/avatar)](https://opencollective.com/mochajs/backer/19/website) | ||
[![MochaJS Backer](https://opencollective.com/mochajs/backer/20/avatar)](https://opencollective.com/mochajs/backer/20/website) | ||
[![MochaJS Backer](https://opencollective.com/mochajs/backer/21/avatar)](https://opencollective.com/mochajs/backer/21/website) | ||
[![MochaJS Backer](https://opencollective.com/mochajs/backer/22/avatar)](https://opencollective.com/mochajs/backer/22/website) | ||
[![MochaJS Backer](https://opencollective.com/mochajs/backer/23/avatar)](https://opencollective.com/mochajs/backer/23/website) | ||
[![MochaJS Backer](https://opencollective.com/mochajs/backer/24/avatar)](https://opencollective.com/mochajs/backer/24/website) | ||
[![MochaJS Backer](https://opencollective.com/mochajs/backer/25/avatar)](https://opencollective.com/mochajs/backer/25/website) | ||
[![MochaJS Backer](https://opencollective.com/mochajs/backer/26/avatar)](https://opencollective.com/mochajs/backer/26/website) | ||
[![MochaJS Backer](https://opencollective.com/mochajs/backer/27/avatar)](https://opencollective.com/mochajs/backer/27/website) | ||
[![MochaJS Backer](https://opencollective.com/mochajs/backer/28/avatar)](https://opencollective.com/mochajs/backer/28/website) | ||
[![MochaJS Backer](https://opencollective.com/mochajs/backer/29/avatar)](https://opencollective.com/mochajs/backer/29/website) | ||
<a href="https://opencollective.com/mochajs"><img src="https://opencollective.com/mochajs/tiers/backers.svg?limit=30&button=false&avatarHeight=46&width=750"></a> | ||
## Sponsors | ||
Does your company use Mocha? Ask your manager or marketing team if your company would be interested in supporting our project. Support will allow the maintainers to dedicate more time for maintenance and new features for everyone. Also, your company's logo will show [on GitHub](https://github.com/mochajs/mocha#readme) and on [our site](https://mochajs.org) - who doesn't want a little extra exposure? [Here's the info](https://opencollective.com/mochajs#sponsor). | ||
Does your company use Mocha? Ask your manager or marketing team if your company would be interested in supporting our project. Support will allow the maintainers to dedicate more time for maintenance and new features for everyone. Also, your company's logo will show [on GitHub](https://github.com/mochajs/mocha#readme) and on [our site](https://mochajs.org#sponsors) - who doesn't want a little extra exposure? [Here's the info](https://opencollective.com/mochajs). | ||
[![MochaJS Sponsor](https://opencollective.com/mochajs/sponsor/0/avatar)](https://opencollective.com/mochajs/sponsor/0/website) | ||
[![MochaJS Sponsor](https://opencollective.com/mochajs/sponsor/1/avatar)](https://opencollective.com/mochajs/sponsor/1/website) | ||
[![MochaJS Sponsor](https://opencollective.com/mochajs/sponsor/2/avatar)](https://opencollective.com/mochajs/sponsor/2/website) | ||
[![MochaJS Sponsor](https://opencollective.com/mochajs/sponsor/3/avatar)](https://opencollective.com/mochajs/sponsor/3/website) | ||
[![MochaJS Sponsor](https://opencollective.com/mochajs/sponsor/4/avatar)](https://opencollective.com/mochajs/sponsor/4/website) | ||
[![MochaJS Sponsor](https://opencollective.com/mochajs/sponsor/5/avatar)](https://opencollective.com/mochajs/sponsor/5/website) | ||
[![MochaJS Sponsor](https://opencollective.com/mochajs/sponsor/6/avatar)](https://opencollective.com/mochajs/sponsor/6/website) | ||
[![MochaJS Sponsor](https://opencollective.com/mochajs/sponsor/7/avatar)](https://opencollective.com/mochajs/sponsor/7/website) | ||
[![MochaJS Sponsor](https://opencollective.com/mochajs/sponsor/8/avatar)](https://opencollective.com/mochajs/sponsor/8/website) | ||
[![MochaJS Sponsor](https://opencollective.com/mochajs/sponsor/9/avatar)](https://opencollective.com/mochajs/sponsor/9/website) | ||
[![MochaJS Sponsor](https://opencollective.com/mochajs/sponsor/10/avatar)](https://opencollective.com/mochajs/sponsor/10/website) | ||
[![MochaJS Sponsor](https://opencollective.com/mochajs/sponsor/11/avatar)](https://opencollective.com/mochajs/sponsor/11/website) | ||
[![MochaJS Sponsor](https://opencollective.com/mochajs/sponsor/12/avatar)](https://opencollective.com/mochajs/sponsor/12/website) | ||
[![MochaJS Sponsor](https://opencollective.com/mochajs/sponsor/13/avatar)](https://opencollective.com/mochajs/sponsor/13/website) | ||
[![MochaJS Sponsor](https://opencollective.com/mochajs/sponsor/14/avatar)](https://opencollective.com/mochajs/sponsor/14/website) | ||
[![MochaJS Sponsor](https://opencollective.com/mochajs/sponsor/15/avatar)](https://opencollective.com/mochajs/sponsor/15/website) | ||
[![MochaJS Sponsor](https://opencollective.com/mochajs/sponsor/16/avatar)](https://opencollective.com/mochajs/sponsor/16/website) | ||
[![MochaJS Sponsor](https://opencollective.com/mochajs/sponsor/17/avatar)](https://opencollective.com/mochajs/sponsor/17/website) | ||
[![MochaJS Sponsor](https://opencollective.com/mochajs/sponsor/18/avatar)](https://opencollective.com/mochajs/sponsor/18/website) | ||
[![MochaJS Sponsor](https://opencollective.com/mochajs/sponsor/19/avatar)](https://opencollective.com/mochajs/sponsor/19/website) | ||
[![MochaJS Sponsor](https://opencollective.com/mochajs/tiers/sponsors/0/avatar)](https://opencollective.com/mochajs/tiers/sponsors/0/website) | ||
[![MochaJS Sponsor](https://opencollective.com/mochajs/tiers/sponsors/1/avatar)](https://opencollective.com/mochajs/tiers/sponsors/1/website) | ||
[![MochaJS Sponsor](https://opencollective.com/mochajs/tiers/sponsors/2/avatar)](https://opencollective.com/mochajs/tiers/sponsors/2/website) | ||
[![MochaJS Sponsor](https://opencollective.com/mochajs/tiers/sponsors/3/avatar)](https://opencollective.com/mochajs/tiers/sponsors/3/website) | ||
@@ -89,3 +54,3 @@ ## Development | ||
- Mocha is the _most-depended-upon_ module on npm (source: [libraries.io](https://libraries.io/search?order=desc&platforms=NPM&sort=dependents_count)), and | ||
- Mocha is one of the _most-depended-upon_ modules on npm (source: [libraries.io](https://libraries.io/search?order=desc&platforms=NPM&sort=dependents_count)), and | ||
- Mocha is an _independent_ open-source project, maintained exclusively by volunteers. | ||
@@ -107,4 +72,4 @@ | ||
Copyright 2011-2020 OpenJS Foundation and contributors. Licensed [MIT](https://github.com/mochajs/mocha/blob/master/LICENSE). | ||
Copyright 2011-2022 OpenJS Foundation and contributors. Licensed [MIT](https://github.com/mochajs/mocha/blob/master/LICENSE). | ||
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fmochajs%2Fmocha.svg?type=large)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fmochajs%2Fmocha?ref=badge_large) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses eval() which is a dangerous function. This prevents the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses eval() which is a dangerous function. This prevents the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
2086789
21
70
30310
25
70
71
2
+ Addednanoid@3.3.3
+ Addedserialize-javascript@6.0.0
+ Addedworkerpool@6.2.1
+ Addedansi-colors@4.1.1(transitive)
+ Addedansi-regex@5.0.1(transitive)
+ Addedansi-styles@4.3.0(transitive)
+ Addedargparse@2.0.1(transitive)
+ Addedbrace-expansion@2.0.1(transitive)
+ Addedcamelcase@6.3.0(transitive)
+ Addedchalk@4.1.2(transitive)
+ Addedchokidar@3.5.3(transitive)
+ Addedcliui@7.0.4(transitive)
+ Addedcolor-convert@2.0.1(transitive)
+ Addedcolor-name@1.1.4(transitive)
+ Addeddebug@4.3.4(transitive)
+ Addeddecamelize@4.0.0(transitive)
+ Addeddiff@5.0.0(transitive)
+ Addedemoji-regex@8.0.0(transitive)
+ Addedescalade@3.2.0(transitive)
+ Addedescape-string-regexp@4.0.0(transitive)
+ Addedfind-up@5.0.0(transitive)
+ Addedflat@5.0.2(transitive)
+ Addedfsevents@2.3.3(transitive)
+ Addedglob@7.2.0(transitive)
+ Addedhas-flag@4.0.0(transitive)
+ Addedis-fullwidth-code-point@3.0.0(transitive)
+ Addedis-plain-obj@2.1.0(transitive)
+ Addedis-unicode-supported@0.1.0(transitive)
+ Addedjs-yaml@4.1.0(transitive)
+ Addedlocate-path@6.0.0(transitive)
+ Addedlog-symbols@4.1.0(transitive)
+ Addedminimatch@3.1.25.0.1(transitive)
+ Addedms@2.1.22.1.3(transitive)
+ Addednanoid@3.3.3(transitive)
+ Addedp-limit@3.1.0(transitive)
+ Addedp-locate@5.0.0(transitive)
+ Addedpath-exists@4.0.0(transitive)
+ Addedrandombytes@2.1.0(transitive)
+ Addedreaddirp@3.6.0(transitive)
+ Addedsafe-buffer@5.2.1(transitive)
+ Addedserialize-javascript@6.0.0(transitive)
+ Addedstring-width@4.2.3(transitive)
+ Addedstrip-ansi@6.0.1(transitive)
+ Addedstrip-json-comments@3.1.1(transitive)
+ Addedsupports-color@7.2.08.1.1(transitive)
+ Addedworkerpool@6.2.1(transitive)
+ Addedwrap-ansi@7.0.0(transitive)
+ Addedy18n@5.0.8(transitive)
+ Addedyargs@16.2.0(transitive)
+ Addedyargs-parser@20.2.4(transitive)
+ Addedyargs-unparser@2.0.0(transitive)
+ Addedyocto-queue@0.1.0(transitive)
- Removedgrowl@1.10.5
- Removedmkdirp@0.5.5
- Removednode-environment-flags@1.0.6
- Removedobject.assign@4.1.0
- Removedwhich@1.3.1
- Removedwide-align@1.1.3
- Removedansi-colors@3.2.3(transitive)
- Removedansi-regex@3.0.14.1.1(transitive)
- Removedansi-styles@3.2.1(transitive)
- Removedargparse@1.0.10(transitive)
- Removedarray-buffer-byte-length@1.0.1(transitive)
- Removedarray.prototype.reduce@1.0.7(transitive)
- Removedarraybuffer.prototype.slice@1.0.3(transitive)
- Removedavailable-typed-arrays@1.0.7(transitive)
- Removedcall-bind@1.0.7(transitive)
- Removedcamelcase@5.3.1(transitive)
- Removedchalk@2.4.2(transitive)
- Removedchokidar@3.3.0(transitive)
- Removedcliui@5.0.0(transitive)
- Removedcolor-convert@1.9.3(transitive)
- Removedcolor-name@1.1.3(transitive)
- Removeddata-view-buffer@1.0.1(transitive)
- Removeddata-view-byte-length@1.0.1(transitive)
- Removeddata-view-byte-offset@1.0.0(transitive)
- Removeddebug@3.2.6(transitive)
- Removeddecamelize@1.2.0(transitive)
- Removeddefine-data-property@1.1.4(transitive)
- Removeddefine-properties@1.2.1(transitive)
- Removeddiff@3.5.0(transitive)
- Removedemoji-regex@7.0.3(transitive)
- Removedes-abstract@1.23.3(transitive)
- Removedes-array-method-boxes-properly@1.0.0(transitive)
- Removedes-define-property@1.0.0(transitive)
- Removedes-errors@1.3.0(transitive)
- Removedes-object-atoms@1.0.0(transitive)
- Removedes-set-tostringtag@2.0.3(transitive)
- Removedes-to-primitive@1.2.1(transitive)
- Removedescape-string-regexp@1.0.5(transitive)
- Removedesprima@4.0.1(transitive)
- Removedfind-up@3.0.0(transitive)
- Removedflat@4.1.1(transitive)
- Removedfor-each@0.3.3(transitive)
- Removedfsevents@2.1.3(transitive)
- Removedfunction-bind@1.1.2(transitive)
- Removedfunction.prototype.name@1.1.6(transitive)
- Removedfunctions-have-names@1.2.3(transitive)
- Removedget-intrinsic@1.2.4(transitive)
- Removedget-symbol-description@1.0.2(transitive)
- Removedglob@7.1.3(transitive)
- Removedglobalthis@1.0.4(transitive)
- Removedgopd@1.0.1(transitive)
- Removedgrowl@1.10.5(transitive)
- Removedhas-bigints@1.0.2(transitive)
- Removedhas-flag@3.0.0(transitive)
- Removedhas-property-descriptors@1.0.2(transitive)
- Removedhas-proto@1.0.3(transitive)
- Removedhas-symbols@1.0.3(transitive)
- Removedhas-tostringtag@1.0.2(transitive)
- Removedhasown@2.0.2(transitive)
- Removedinternal-slot@1.0.7(transitive)
- Removedis-array-buffer@3.0.4(transitive)
- Removedis-bigint@1.0.4(transitive)
- Removedis-boolean-object@1.1.2(transitive)
- Removedis-buffer@2.0.5(transitive)
- Removedis-callable@1.2.7(transitive)
- Removedis-data-view@1.0.1(transitive)
- Removedis-date-object@1.0.5(transitive)
- Removedis-fullwidth-code-point@2.0.0(transitive)
- Removedis-negative-zero@2.0.3(transitive)
- Removedis-number-object@1.0.7(transitive)
- Removedis-regex@1.1.4(transitive)
- Removedis-shared-array-buffer@1.0.3(transitive)
- Removedis-string@1.0.7(transitive)
- Removedis-symbol@1.0.4(transitive)
- Removedis-typed-array@1.1.13(transitive)
- Removedis-weakref@1.0.2(transitive)
- Removedisarray@2.0.5(transitive)
- Removedisexe@2.0.0(transitive)
- Removedjs-yaml@3.13.1(transitive)
- Removedlocate-path@3.0.0(transitive)
- Removedlodash@4.17.21(transitive)
- Removedlog-symbols@3.0.0(transitive)
- Removedminimatch@3.0.4(transitive)
- Removedminimist@1.2.8(transitive)
- Removedmkdirp@0.5.5(transitive)
- Removedms@2.1.1(transitive)
- Removednode-environment-flags@1.0.6(transitive)
- Removedobject-inspect@1.13.2(transitive)
- Removedobject-keys@1.1.1(transitive)
- Removedobject.assign@4.1.04.1.5(transitive)
- Removedobject.getownpropertydescriptors@2.1.8(transitive)
- Removedp-limit@2.3.0(transitive)
- Removedp-locate@3.0.0(transitive)
- Removedp-try@2.2.0(transitive)
- Removedpath-exists@3.0.0(transitive)
- Removedpossible-typed-array-names@1.0.0(transitive)
- Removedreaddirp@3.2.0(transitive)
- Removedregexp.prototype.flags@1.5.2(transitive)
- Removedrequire-main-filename@2.0.0(transitive)
- Removedsafe-array-concat@1.1.2(transitive)
- Removedsafe-regex-test@1.0.3(transitive)
- Removedsemver@5.7.2(transitive)
- Removedset-blocking@2.0.0(transitive)
- Removedset-function-length@1.2.2(transitive)
- Removedset-function-name@2.0.2(transitive)
- Removedside-channel@1.0.6(transitive)
- Removedsprintf-js@1.0.3(transitive)
- Removedstring-width@2.1.13.1.0(transitive)
- Removedstring.prototype.trim@1.2.9(transitive)
- Removedstring.prototype.trimend@1.0.8(transitive)
- Removedstring.prototype.trimstart@1.0.8(transitive)
- Removedstrip-ansi@4.0.05.2.0(transitive)
- Removedstrip-json-comments@2.0.1(transitive)
- Removedsupports-color@5.5.06.0.0(transitive)
- Removedtyped-array-buffer@1.0.2(transitive)
- Removedtyped-array-byte-length@1.0.1(transitive)
- Removedtyped-array-byte-offset@1.0.2(transitive)
- Removedtyped-array-length@1.0.6(transitive)
- Removedunbox-primitive@1.0.2(transitive)
- Removedwhich@1.3.1(transitive)
- Removedwhich-boxed-primitive@1.0.2(transitive)
- Removedwhich-module@2.0.1(transitive)
- Removedwhich-typed-array@1.1.15(transitive)
- Removedwide-align@1.1.3(transitive)
- Removedwrap-ansi@5.1.0(transitive)
- Removedy18n@4.0.3(transitive)
- Removedyargs@13.3.2(transitive)
- Removedyargs-parser@13.1.2(transitive)
- Removedyargs-unparser@1.6.0(transitive)
Updatedansi-colors@4.1.1
Updatedchokidar@3.5.3
Updateddebug@4.3.4
Updateddiff@5.0.0
Updatedescape-string-regexp@4.0.0
Updatedfind-up@5.0.0
Updatedglob@7.2.0
Updatedjs-yaml@4.1.0
Updatedlog-symbols@4.1.0
Updatedminimatch@5.0.1
Updatedms@2.1.3
Updatedstrip-json-comments@3.1.1
Updatedsupports-color@8.1.1
Updatedyargs@16.2.0
Updatedyargs-parser@20.2.4
Updatedyargs-unparser@2.0.0