'use strict';

/**

* Definition for Mocha's default ("run tests") command
*
* @module
* @private
*/

const Mocha = require('../mocha'); const {

createUnsupportedError,
createInvalidArgumentValueError,
createMissingArgumentError

} = require('../errors');

const {

list,
handleRequires,
validatePlugin,
runMocha

} = require('./run-helpers'); const {ONE_AND_DONES, ONE_AND_DONE_ARGS} = require('./one-and-dones'); const debug = require('debug')('mocha:cli:run'); const defaults = require('../mocharc'); const {types, aliases} = require('./run-option-metadata');

/**

* Logical option groups
* @constant
*/

const GROUPS = {

FILES: 'File Handling',
FILTERS: 'Test Filters',
NODEJS: 'Node.js & V8',
OUTPUT: 'Reporting & Output',
RULES: 'Rules & Behavior',
CONFIG: 'Configuration'

};

exports.command = ['$0 [spec..]', 'debug [spec..]'];

exports.describe = 'Run tests with Mocha';

exports.builder = yargs =>

yargs
  .options({
    'allow-uncaught': {
      description: 'Allow uncaught errors to propagate',
      group: GROUPS.RULES
    },
    'async-only': {
      description:
        'Require all tests to use a callback (async) or return a Promise',
      group: GROUPS.RULES
    },
    bail: {
      description: 'Abort ("bail") after first test failure',
      group: GROUPS.RULES
    },
    'check-leaks': {
      description: 'Check for global variable leaks',
      group: GROUPS.RULES
    },
    color: {
      description: 'Force-enable color output',
      group: GROUPS.OUTPUT
    },
    config: {
      config: true,
      defaultDescription: '(nearest rc file)',
      description: 'Path to config file',
      group: GROUPS.CONFIG
    },
    delay: {
      description: 'Delay initial execution of root suite',
      group: GROUPS.RULES
    },
    diff: {
      default: true,
      description: 'Show diff on failure',
      group: GROUPS.OUTPUT
    },
    exit: {
      description: 'Force Mocha to quit after tests complete',
      group: GROUPS.RULES
    },
    extension: {
      default: defaults.extension,
      defaultDescription: 'js',
      description: 'File extension(s) to load and/or watch',
      group: GROUPS.FILES,
      requiresArg: true,
      coerce: list
    },
    fgrep: {
      conflicts: 'grep',
      description: 'Only run tests containing this string',
      group: GROUPS.FILTERS,
      requiresArg: true
    },
    file: {
      defaultDescription: '(none)',
      description:
        'Specify file(s) to be loaded prior to root suite execution',
      group: GROUPS.FILES,
      normalize: true,
      requiresArg: true
    },
    'forbid-only': {
      description: 'Fail if exclusive test(s) encountered',
      group: GROUPS.RULES
    },
    'forbid-pending': {
      description: 'Fail if pending test(s) encountered',
      group: GROUPS.RULES
    },
    'full-trace': {
      description: 'Display full stack traces',
      group: GROUPS.OUTPUT
    },
    global: {
      coerce: list,
      description: 'List of allowed global variables',
      group: GROUPS.RULES,
      requiresArg: true
    },
    grep: {
      coerce: value => (!value ? null : value),
      conflicts: 'fgrep',
      description: 'Only run tests matching this string or regexp',
      group: GROUPS.FILTERS,
      requiresArg: true
    },
    growl: {
      description: 'Enable Growl notifications',
      group: GROUPS.OUTPUT
    },
    ignore: {
      defaultDescription: '(none)',
      description: 'Ignore file(s) or glob pattern(s)',
      group: GROUPS.FILES,
      requiresArg: true
    },
    'inline-diffs': {
      description:
        'Display actual/expected differences inline within each string',
      group: GROUPS.OUTPUT
    },
    interfaces: {
      conflicts: Array.from(ONE_AND_DONE_ARGS),
      description: 'List built-in user interfaces & exit'
    },
    invert: {
      description: 'Inverts --grep and --fgrep matches',
      group: GROUPS.FILTERS
    },
    'no-colors': {
      description: 'Force-disable color output',
      group: GROUPS.OUTPUT,
      hidden: true
    },
    opts: {
      default: defaults.opts,
      description: 'Path to `mocha.opts`',
      group: GROUPS.CONFIG,
      normalize: true,
      requiresArg: true
    },
    package: {
      description: 'Path to package.json for config',
      group: GROUPS.CONFIG,
      normalize: true,
      requiresArg: true
    },
    recursive: {
      description: 'Look for tests in subdirectories',
      group: GROUPS.FILES
    },
    reporter: {
      default: defaults.reporter,
      description: 'Specify reporter to use',
      group: GROUPS.OUTPUT,
      requiresArg: true
    },
    reporters: {
      conflicts: Array.from(ONE_AND_DONE_ARGS),
      description: 'List built-in reporters & exit'
    },
    'reporter-option': {
      coerce: opts =>
        list(opts).reduce((acc, opt) => {
          const pair = opt.split('=');

          if (pair.length > 2 || !pair.length) {
            throw createInvalidArgumentValueError(
              `invalid reporter option '${opt}'`,
              '--reporter-option',
              opt,
              'expected "key=value" format'
            );
          }

          acc[pair[0]] = pair.length === 2 ? pair[1] : true;
          return acc;
        }, {}),
      description: 'Reporter-specific options (<k=v,[k1=v1,..]>)',
      group: GROUPS.OUTPUT,
      requiresArg: true
    },
    require: {
      defaultDescription: '(none)',
      description: 'Require module',
      group: GROUPS.FILES,
      requiresArg: true
    },
    retries: {
      description: 'Retry failed tests this many times',
      group: GROUPS.RULES
    },
    slow: {
      default: defaults.slow,
      description: 'Specify "slow" test threshold (in milliseconds)',
      group: GROUPS.RULES
    },
    sort: {
      description: 'Sort test files',
      group: GROUPS.FILES
    },
    timeout: {
      default: defaults.timeout,
      description: 'Specify test timeout threshold (in milliseconds)',
      group: GROUPS.RULES
    },
    ui: {
      default: defaults.ui,
      description: 'Specify user interface',
      group: GROUPS.RULES,
      requiresArg: true
    },
    watch: {
      description: 'Watch files in the current working directory for changes',
      group: GROUPS.FILES
    }
  })
  .positional('spec', {
    default: ['test'],
    description: 'One or more files, directories, or globs to test',
    type: 'array'
  })
  .check(argv => {
    // "one-and-dones"; let yargs handle help and version
    Object.keys(ONE_AND_DONES).forEach(opt => {
      if (argv[opt]) {
        ONE_AND_DONES[opt].call(null, yargs);
        process.exit();
      }
    });

    // yargs.implies() isn't flexible enough to handle this
    if (argv.invert && !('fgrep' in argv || 'grep' in argv)) {
      throw createMissingArgumentError(
        '"--invert" requires one of "--fgrep <str>" or "--grep <regexp>"',
        '--fgrep|--grep',
        'string|regexp'
      );
    }

    if (argv.compilers) {
      throw createUnsupportedError(
        `--compilers is DEPRECATED and no longer supported.
        See https://git.io/vdcSr for migration information.`
      );
    }

    // load requires first, because it can impact "plugin" validation
    handleRequires(argv.require);
    validatePlugin(argv, 'reporter', Mocha.reporters);
    validatePlugin(argv, 'ui', Mocha.interfaces);

    return true;
  })
  .array(types.array)
  .boolean(types.boolean)
  .string(types.string)
  .number(types.number)
  .alias(aliases);

exports.handler = argv => {

debug('post-yargs config', argv);
const mocha = new Mocha(argv);
runMocha(mocha, argv);

};