'use strict';

/*!

* mocha
* Copyright(c) 2011 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/

var escapeRe = require('escape-string-regexp'); var path = require('path'); 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 createStatsCollector = require('./stats-collector'); var createInvalidReporterError = errors.createInvalidReporterError; var createInvalidInterfaceError = errors.createInvalidInterfaceError; 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;

exports = module.exports = Mocha;

/**

* To require local UIs and reporters when running in node.
*/

if (!process.browser) {

var cwd = process.cwd();
module.paths.push(cwd, path.join(cwd, 'node_modules'));

}

/**

* Expose internals.
*/

/**

* @public
* @class utils
* @memberof Mocha
*/

exports.utils = utils; exports.interfaces = require('./interfaces'); /**

* @public
* @memberof Mocha
*/

exports.reporters = builtinReporters; exports.Runnable = require('./runnable'); exports.Context = require('./context'); /**

*
* @memberof Mocha
*/

exports.Runner = require('./runner'); exports.Suite = Suite; exports.Hook = require('./hook'); exports.Test = require('./test');

/**

* Constructs a new Mocha instance with `options`.
*
* @public
* @class Mocha
* @param {Object} [options] - Settings object.
* @param {boolean} [options.allowUncaught] - Propagate uncaught errors?
* @param {boolean} [options.asyncOnly] - Force `done` callback or promise?
* @param {boolean} [options.bail] - Bail after first test failure?
* @param {boolean} [options.checkLeaks] - If true, check leaks.
* @param {boolean} [options.delay] - Delay root suite execution?
* @param {boolean} [options.enableTimeouts] - Enable timeouts?
* @param {string} [options.fgrep] - Test filter given string.
* @param {boolean} [options.forbidOnly] - Tests marked `only` fail the suite?
* @param {boolean} [options.forbidPending] - Pending tests fail the suite?
* @param {boolean} [options.fullStackTrace] - Full stacktrace upon failure?
* @param {string[]} [options.global] - Variables expected in global scope.
* @param {RegExp|string} [options.grep] - Test filter given regular expression.
* @param {boolean} [options.growl] - Enable desktop notifications?
* @param {boolean} [options.hideDiff] - Suppress diffs from failures?
* @param {boolean} [options.ignoreLeaks] - Ignore global leaks?
* @param {boolean} [options.invert] - Invert test filter matches?
* @param {boolean} [options.noHighlighting] - Disable syntax highlighting?
* @param {string} [options.reporter] - Reporter name.
* @param {Object} [options.reporterOption] - Reporter settings object.
* @param {number} [options.retries] - Number of times to retry failed tests.
* @param {number} [options.slow] - Slow threshold value.
* @param {number|string} [options.timeout] - Timeout threshold value.
* @param {string} [options.ui] - Interface name.
* @param {boolean} [options.color] - Color TTY output from reporter?
* @param {boolean} [options.useInlineDiffs] - Use inline diffs?
*/

function Mocha(options) {

options = utils.assign({}, mocharc, options || {});
this.files = [];
this.options = options;
// root suite
this.suite = new exports.Suite('', new exports.Context(), true);

if ('useColors' in options) {
  utils.deprecate(
    'useColors is DEPRECATED and will be removed from a future version of Mocha. Instead, use the "color" option'
  );
  options.color = 'color' in options ? options.color : options.useColors;
}

// Globals are passed in as options.global, with options.globals for backward compatibility.
options.globals = options.global || options.globals || [];
delete options.global;

this.grep(options.grep)
  .fgrep(options.fgrep)
  .ui(options.ui)
  .bail(options.bail)
  .reporter(options.reporter, options.reporterOptions)
  .useColors(options.color)
  .slow(options.slow)
  .useInlineDiffs(options.inlineDiffs)
  .globals(options.globals);

if ('enableTimeouts' in options) {
  utils.deprecate(
    'enableTimeouts is DEPRECATED and will be removed from a future version of Mocha. Instead, use "timeout: false" to disable timeouts.'
  );
  if (options.enableTimeouts === false) {
    this.timeout(0);
  }
}

// this guard exists because Suite#timeout does not consider `undefined` to be valid input
if (typeof options.timeout !== 'undefined') {
  this.timeout(options.timeout === false ? 0 : options.timeout);
}

if ('retries' in options) {
  this.retries(options.retries);
}

if ('diff' in options) {
  this.hideDiff(!options.diff);
}

[
  'allowUncaught',
  'asyncOnly',
  'checkLeaks',
  'delay',
  'forbidOnly',
  'forbidPending',
  'fullTrace',
  'growl',
  'invert'
].forEach(function(opt) {
  if (options[opt]) {
    this[opt]();
  }
}, this);

}

/**

* Enables or disables bailing on the first failure.
*
* @public
* @see {@link https://mochajs.org/#-b---bail|CLI option}
* @param {boolean} [bail=true] - Whether to bail on first error.
* @returns {Mocha} this
* @chainable
*/

Mocha.prototype.bail = function(bail) {

if (!arguments.length) {
  bail = true;
}
this.suite.bail(bail);
return this;

};

/**

* @summary
* Adds `file` to be loaded for execution.
*
* @description
* Useful for generic setup code that must be included within test suite.
*
* @public
* @see {@link https://mochajs.org/#--file-file|CLI option}
* @param {string} file - Pathname of file to be loaded.
* @returns {Mocha} this
* @chainable
*/

Mocha.prototype.addFile = function(file) {

this.files.push(file);
return this;

};

/**

* Sets reporter to `reporter`, defaults to "spec".
*
* @public
* @see {@link https://mochajs.org/#-r---reporter-name|CLI option}
* @see {@link https://mochajs.org/#reporters|Reporters}
* @param {String|Function} reporter - Reporter name or constructor.
* @param {Object} [reporterOptions] - Options used to configure the reporter.
* @returns {Mocha} this
* @chainable
* @throws {Error} if requested reporter cannot be loaded
* @example
*
* // Use XUnit reporter and direct its output to file
* mocha.reporter('xunit', { output: '/path/to/testspec.xunit.xml' });
*/

Mocha.prototype.reporter = function(reporter, reporterOptions) {

if (typeof reporter === 'function') {
  this._reporter = reporter;
} else {
  reporter = reporter || 'spec';
  var _reporter;
  // Try to load a built-in reporter.
  if (builtinReporters[reporter]) {
    _reporter = builtinReporters[reporter];
  }
  // Try to load reporters from process.cwd() and node_modules
  if (!_reporter) {
    try {
      _reporter = require(reporter);
    } catch (err) {
      if (
        err.code !== 'MODULE_NOT_FOUND' ||
        err.message.indexOf('Cannot find module') !== -1
      ) {
        // Try to load reporters from a path (absolute or relative)
        try {
          _reporter = require(path.resolve(process.cwd(), reporter));
        } catch (_err) {
          _err.code !== 'MODULE_NOT_FOUND' ||
          _err.message.indexOf('Cannot find module') !== -1
            ? console.warn(sQuote(reporter) + ' reporter not found')
            : console.warn(
                sQuote(reporter) +
                  ' reporter blew up with error:\n' +
                  err.stack
              );
        }
      } else {
        console.warn(
          sQuote(reporter) + ' reporter blew up with error:\n' + err.stack
        );
      }
    }
  }
  if (!_reporter) {
    throw createInvalidReporterError(
      'invalid reporter ' + sQuote(reporter),
      reporter
    );
  }
  this._reporter = _reporter;
}
this.options.reporterOptions = reporterOptions;
return this;

};

/**

* Sets test UI `name`, defaults to "bdd".
*
* @public
* @see {@link https://mochajs.org/#-u---ui-name|CLI option}
* @see {@link https://mochajs.org/#interfaces|Interface DSLs}
* @param {string|Function} [ui=bdd] - Interface name or class.
* @returns {Mocha} this
* @chainable
* @throws {Error} if requested interface cannot be loaded
*/

Mocha.prototype.ui = function(ui) {

var bindInterface;
if (typeof ui === 'function') {
  bindInterface = ui;
} else {
  ui = ui || 'bdd';
  bindInterface = exports.interfaces[ui];
  if (!bindInterface) {
    try {
      bindInterface = require(ui);
    } catch (err) {
      throw createInvalidInterfaceError(
        'invalid interface ' + sQuote(ui),
        ui
      );
    }
  }
}
bindInterface(this.suite);

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;
});

return this;

};

/**

* Loads `files` prior to execution.
*
* @description
* The implementation relies on Node's `require` to execute
* the test interface functions and will be subject to its cache.
*
* @private
* @see {@link Mocha#addFile}
* @see {@link Mocha#run}
* @see {@link Mocha#unloadFiles}
* @param {Function} [fn] - Callback invoked upon completion.
*/

Mocha.prototype.loadFiles = function(fn) {

var self = this;
var suite = this.suite;
this.files.forEach(function(file) {
  file = path.resolve(file);
  suite.emit(EVENT_FILE_PRE_REQUIRE, global, file, self);
  suite.emit(EVENT_FILE_REQUIRE, require(file), file, self);
  suite.emit(EVENT_FILE_POST_REQUIRE, global, file, self);
});
fn && fn();

};

/**

* Removes a previously loaded file from Node's `require` cache.
*
* @private
* @static
* @see {@link Mocha#unloadFiles}
* @param {string} file - Pathname of file to be unloaded.
*/

Mocha.unloadFile = function(file) {

delete require.cache[require.resolve(file)];

};

/**

* Unloads `files` from Node's `require` cache.
*
* @description
* This allows files to be "freshly" reloaded, providing the ability
* to reuse a Mocha instance programmatically.
*
* <strong>Intended for consumers &mdash; not used internally</strong>
*
* @public
* @see {@link Mocha.unloadFile}
* @see {@link Mocha#loadFiles}
* @see {@link Mocha#run}
* @returns {Mocha} this
* @chainable
*/

Mocha.prototype.unloadFiles = function() {

this.files.forEach(Mocha.unloadFile);
return this;

};

/**

* Sets `grep` filter after escaping RegExp special characters.
*
* @public
* @see {@link Mocha#grep}
* @param {string} str - Value to be converted to a regexp.
* @returns {Mocha} this
* @chainable
* @example
*
* // Select tests whose full title begins with `"foo"` followed by a period
* mocha.fgrep('foo.');
*/

Mocha.prototype.fgrep = function(str) {

if (!str) {
  return this;
}
return this.grep(new RegExp(escapeRe(str)));

};

/**

* @summary
* Sets `grep` filter used to select specific tests for execution.
*
* @description
* If `re` is a regexp-like string, it will be converted to regexp.
* The regexp is tested against the full title of each test (i.e., the
* name of the test preceded by titles of each its ancestral suites).
* As such, using an <em>exact-match</em> fixed pattern against the
* test name itself will not yield any matches.
* <br>
* <strong>Previous filter value will be overwritten on each call!</strong>
*
* @public
* @see {@link https://mochajs.org/#-g---grep-pattern|CLI option}
* @see {@link Mocha#fgrep}
* @see {@link Mocha#invert}
* @param {RegExp|String} re - Regular expression used to select tests.
* @return {Mocha} this
* @chainable
* @example
*
* // Select tests whose full title contains `"match"`, ignoring case
* mocha.grep(/match/i);
* @example
*
* // Same as above but with regexp-like string argument
* mocha.grep('/match/i');
* @example
*
* // ## Anti-example
* // Given embedded test `it('only-this-test')`...
* mocha.grep('/^only-this-test$/');    // NO! Use `.only()` to do this!
*/

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|)$|.*/);
  this.options.grep = new RegExp(arg[1] || arg[0], arg[2]);
} else {
  this.options.grep = re;
}
return this;

};

/**

* Inverts `grep` matches.
*
* @public
* @see {@link Mocha#grep}
* @return {Mocha} this
* @chainable
* @example
*
* // Select tests whose full title does *not* contain `"match"`, ignoring case
* mocha.grep(/match/i).invert();
*/

Mocha.prototype.invert = function() {

this.options.invert = true;
return this;

};

/**

* Enables or disables ignoring global leaks.
*
* @public
* @see {@link Mocha#checkLeaks}
* @param {boolean} ignoreLeaks - Whether to ignore global leaks.
* @return {Mocha} this
* @chainable
* @example
*
* // Ignore global leaks
* mocha.ignoreLeaks(true);
*/

Mocha.prototype.ignoreLeaks = function(ignoreLeaks) {

this.options.ignoreLeaks = Boolean(ignoreLeaks);
return this;

};

/**

* Enables checking for global variables leaked while running tests.
*
* @public
* @see {@link https://mochajs.org/#--check-leaks|CLI option}
* @see {@link Mocha#ignoreLeaks}
* @return {Mocha} this
* @chainable
*/

Mocha.prototype.checkLeaks = function() {

this.options.ignoreLeaks = false;
return this;

};

/**

* Displays full stack trace upon test failure.
*
* @public
* @return {Mocha} this
* @chainable
*/

Mocha.prototype.fullTrace = function() {

this.options.fullStackTrace = true;
return this;

};

/**

* Enables desktop notification support if prerequisite software installed.
*
* @public
* @see {@link Mocha#isGrowlCapable}
* @see {@link Mocha#_growl}
* @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.
*
* @public
* @see {@link https://mochajs.org/#-global-variable-name|CLI option}
* @see {@link Mocha#checkLeaks}
* @param {String[]|String} globals - Accepted global variable name(s).
* @return {Mocha} this
* @chainable
* @example
*
* // Specify variables to be expected in global scope
* mocha.globals(['jQuery', 'MyLib']);
*/

Mocha.prototype.globals = function(globals) {

this.options.globals = this.options.globals
  .concat(globals)
  .filter(Boolean)
  .filter(function(elt, idx, arr) {
    return arr.indexOf(elt) === idx;
  });
return this;

};

/**

* Enables or disables TTY color output by screen-oriented reporters.
*
* @public
* @param {boolean} colors - Whether to enable color output.
* @return {Mocha} this
* @chainable
*/

Mocha.prototype.useColors = function(colors) {

if (colors !== undefined) {
  this.options.useColors = colors;
}
return this;

};

/**

* Determines if reporter should use inline diffs (rather than +/-)
* in test failure output.
*
* @public
* @param {boolean} inlineDiffs - Whether to use inline diffs.
* @return {Mocha} this
* @chainable
*/

Mocha.prototype.useInlineDiffs = function(inlineDiffs) {

this.options.useInlineDiffs = inlineDiffs !== undefined && inlineDiffs;
return this;

};

/**

* Determines if reporter should include diffs in test failure output.
*
* @public
* @param {boolean} hideDiff - Whether to hide diffs.
* @return {Mocha} this
* @chainable
*/

Mocha.prototype.hideDiff = function(hideDiff) {

this.options.hideDiff = hideDiff !== undefined && hideDiff;
return this;

};

/**

* @summary
* Sets timeout threshold value.
*
* @description
* A string argument can use shorthand (such as "2s") and will be converted.
* If the value is `0`, timeouts will be disabled.
*
* @public
* @see {@link https://mochajs.org/#-t---timeout-ms|CLI option}
* @see {@link https://mochajs.org/#--no-timeouts|CLI option}
* @see {@link https://mochajs.org/#timeouts|Timeouts}
* @see {@link Mocha#enableTimeouts}
* @param {number|string} msecs - Timeout threshold value.
* @return {Mocha} this
* @chainable
* @example
*
* // Sets timeout to one second
* mocha.timeout(1000);
* @example
*
* // Same as above but using string argument
* mocha.timeout('1s');
*/

Mocha.prototype.timeout = function(msecs) {

this.suite.timeout(msecs);
return this;

};

/**

* Sets the number of times to retry failed tests.
*
* @public
* @see {@link https://mochajs.org/#retry-tests|Retry Tests}
* @param {number} retry - Number of times to retry failed tests.
* @return {Mocha} this
* @chainable
* @example
*
* // Allow any failed test to retry one more time
* mocha.retries(1);
*/

Mocha.prototype.retries = function(n) {

this.suite.retries(n);
return this;

};

/**

* Sets slowness threshold value.
*
* @public
* @see {@link https://mochajs.org/#-s---slow-ms|CLI option}
* @param {number} msecs - Slowness threshold value.
* @return {Mocha} this
* @chainable
* @example
*
* // Sets "slow" threshold to half a second
* mocha.slow(500);
* @example
*
* // Same as above but using string argument
* mocha.slow('0.5s');
*/

Mocha.prototype.slow = function(msecs) {

this.suite.slow(msecs);
return this;

};

/**

* Enables or disables timeouts.
*
* @public
* @see {@link https://mochajs.org/#-t---timeout-ms|CLI option}
* @see {@link https://mochajs.org/#--no-timeouts|CLI option}
* @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.
*
* @public
* @return {Mocha} this
* @chainable
*/

Mocha.prototype.asyncOnly = function() {

this.options.asyncOnly = true;
return this;

};

/**

* Disables syntax highlighting (in browser).
*
* @public
* @return {Mocha} this
* @chainable
*/

Mocha.prototype.noHighlighting = function() {

this.options.noHighlighting = true;
return this;

};

/**

* Enables uncaught errors to propagate (in browser).
*
* @public
* @return {Mocha} this
* @chainable
*/

Mocha.prototype.allowUncaught = function() {

this.options.allowUncaught = true;
return this;

};

/**

* @summary
* Delays root suite execution.
*
* @description
* Used to perform asynch operations before any suites are run.
*
* @public
* @see {@link https://mochajs.org/#delayed-root-suite|delayed root suite}
* @returns {Mocha} this
* @chainable
*/

Mocha.prototype.delay = function delay() {

this.options.delay = true;
return this;

};

/**

* Causes tests marked `only` to fail the suite.
*
* @public
* @returns {Mocha} this
* @chainable
*/

Mocha.prototype.forbidOnly = function() {

this.options.forbidOnly = true;
return this;

};

/**

* Causes pending tests and tests marked `skip` to fail the suite.
*
* @public
* @returns {Mocha} this
* @chainable
*/

Mocha.prototype.forbidPending = function() {

this.options.forbidPending = true;
return this;

};

/**

* Mocha version as specified by "package.json".
*
* @name Mocha#version
* @type string
* @readonly
*/

Object.defineProperty(Mocha.prototype, 'version', {

value: require('../package.json').version,
configurable: false,
enumerable: true,
writable: false

});

/**

* Callback to be invoked when test execution is complete.
*
* @callback DoneCB
* @param {number} failures - Number of failures that occurred.
*/

/**

* Runs root suite and invokes `fn()` when complete.
*
* @description
* To run tests multiple times (or to run tests in files that are
* already in the `require` cache), make sure to clear them from
* the cache first!
*
* @public
* @see {@link Mocha#loadFiles}
* @see {@link Mocha#unloadFiles}
* @see {@link Runner#run}
* @param {DoneCB} [fn] - Callback invoked when test execution completed.
* @return {Runner} runner instance
*/

Mocha.prototype.run = function(fn) {

if (this.files.length) {
  this.loadFiles();
}
var suite = this.suite;
var options = this.options;
options.files = this.files;
var runner = new exports.Runner(suite, options.delay);
createStatsCollector(runner);
var reporter = new this._reporter(runner, options);
runner.ignoreLeaks = options.ignoreLeaks !== false;
runner.fullStackTrace = options.fullStackTrace;
runner.asyncOnly = options.asyncOnly;
runner.allowUncaught = options.allowUncaught;
runner.forbidOnly = options.forbidOnly;
runner.forbidPending = options.forbidPending;
if (options.grep) {
  runner.grep(options.grep, options.invert);
}
if (options.globals) {
  runner.globals(options.globals);
}
if (options.growl) {
  this._growl(runner);
}
if (options.useColors !== undefined) {
  exports.reporters.Base.useColors = options.useColors;
}
exports.reporters.Base.inlineDiffs = options.useInlineDiffs;
exports.reporters.Base.hideDiff = options.hideDiff;

function done(failures) {
  fn = fn || utils.noop;
  if (reporter.done) {
    reporter.done(failures, fn);
  } else {
    fn(failures);
  }
}

return runner.run(done);

};