text: 'use strict'
  /* global context, describe, it, beforeEach, afterEach */
  
  const expect = require('chai').expect
  const fs = require('fs')
  const path = require('path')
  const checkOutput = require('./helpers/utils').checkOutput
  const english = require('../locales/en.json')
  let yargs
  const YError = require('../lib/yerror')
  
  require('chai').should()
  
  const noop = () => {}
  const implicationsFailedPattern = new RegExp(english['Implications failed:'])
  
  describe('yargs dsl tests', () => {
    beforeEach(() => {
      yargs = require('../')
    })
  
    afterEach(() => {
      delete require.cache[require.resolve('../')]
    })
  
    it('should use bin name for $0, eliminating path', () => {
      process.argv[1] = '/usr/local/bin/ndm'
      process.env._ = '/usr/local/bin/ndm'
      process.execPath = '/usr/local/bin/ndm'
      const argv = yargs([]).parse()
      argv['$0'].should.equal('ndm')
      yargs.$0.should.equal('ndm')
    })
  
    it('accepts an object for aliases', () => {
      const argv = yargs([])
        .alias({
          cool: 'cat'
        })
        .default('cool', 33)
        .parse()
  
      argv.cat.should.eql(33)
    })
  
    it('do not populates argv with placeholder keys for unset options', () => {
      const argv = yargs([])
        .option('cool', {})
        .parse()
  
      Object.keys(argv).should.not.include('cool')
    })
  
    it('accepts an object for implies', () => {
      const r = checkOutput(() => yargs(['--x=33'])
        .implies({
          x: 'y'
        })
        .parse()
      )
  
      r.errors[2].should.match(implicationsFailedPattern)
    })
  
    it('accepts an object for describes', () => {
      const r = checkOutput(() => yargs([])
        .describe({
          x: 'really cool key'
        })
        .demand('x')
        .wrap(null)
        .parse()
      )
  
      r.errors[0].should.match(/really cool key/)
      r.result.should.not.have.property('[object Object]')
    })
  
    it('a function can be provided, to execute when a parsing failure occurs', (done) => {
      yargs(['--x=33'])
        .implies({
          x: 'y'
        })
        .fail((msg) => {
          msg.should.match(implicationsFailedPattern)
          return done()
        })
        .parse()
    })
  
    it('should set alias to string if option is string', () => {
      const argv = yargs(['--cat=99'])
        .options('c', {
          alias: 'cat',
          string: true
        })
        .parse()
  
      argv.cat.should.eql('99')
      argv.c.should.eql('99')
    })
  
    it('should allow a valid choice', () => {
      const argv = yargs(['--looks=good'])
        .option('looks', {
          choices: ['good', 'bad']
        })
        .parse()
  
      argv.looks.should.eql('good')
    })
  
    it('should ignore a missing array choice with an empty default', () => {
      const argv = yargs(['--looks', '--looks', 'good'])
        .option('looks', {
          type: 'array',
          default: [],
          choices: ['good', 'bad']
        })
        .parse()
  
      argv.looks.should.deep.eql(['good'])
    })
  
    it('should allow defaultDescription to be set with .option()', () => {
      const optDefaultDescriptions = yargs([])
        .option('port', {
          defaultDescription: '80 for HTTP and 443 for HTTPS'
        })
        .getOptions().defaultDescription
  
      optDefaultDescriptions.should.deep.equal({
        port: '80 for HTTP and 443 for HTTPS'
      })
    })
  
    it('should not require config object for an option', () => {
      const r = checkOutput(() => yargs([])
        .option('x')
        .parse()
      )
  
      expect(r.errors).to.deep.equal([])
    })
  
    describe('hide', () => {
      it('should add the key to hiddenOptions', () => {
        const options = yargs('')
          .hide('someKey')
          .getOptions()
        options.should.have.property('hiddenOptions')
        options.hiddenOptions.should.include('someKey')
      })
    })
  
    describe('showHidden', () => {
      it('should have a default show-hidden private option pre-configured', () => {
        const options = yargs('').getOptions()
        options.should.have.property('showHiddenOpt')
        options.showHiddenOpt.should.eql('show-hidden')
      })
      it('should not have show-hidden as an actual option described by default', () => {
        const options = yargs('').getOptions()
        options.key.should.not.have.property('show-hidden')
      })
      it('should set show-hidden option', () => {
        const options = yargs('')
          .showHidden()
          .getOptions()
        options.key.should.have.property('show-hidden')
      })
      it('should set custom-show-hidden option', () => {
        const options = yargs('')
          .showHidden('custom-show-hidden')
          .getOptions()
        options.key.should.have.property('custom-show-hidden')
        options.should.have.property('showHiddenOpt')
        options.showHiddenOpt.should.eql('custom-show-hidden')
      })
    })
  
    describe('showHelpOnFail', () => {
      it('should display custom failure message, if string is provided as first argument', () => {
        const r = checkOutput(() => yargs([])
          .showHelpOnFail('pork chop sandwiches')
          .demand('cat')
          .parse()
        )
  
        r.errors[4].should.match(/pork chop sandwiches/)
      })
  
      it('calling with no arguments should default to displaying help', () => {
        const r = checkOutput(() => yargs([])
          .showHelpOnFail()
          .demand('cat')
          .parse()
        )
  
        r.errors[2].should.match(/required argument/)
      })
    })
  
    describe('exitProcess', () => {
      describe('when exitProcess is set to false and a failure occurs', () => {
        it('should throw an exception', () => {
          checkOutput(() => {
            expect(() => {
              yargs([])
                .demand('cat')
                .showHelpOnFail(false)
                .exitProcess(false)
                .parse()
            }).to.throw(/Missing required argument/)
          })
        })
        it('should output the errors to stderr once', () => {
          const r = checkOutput(() => {
            try {
              yargs([])
                .demand('cat')
                .showHelpOnFail(false)
                .exitProcess(false)
                .parse()
            } catch (err) {
              // ignore the error, we only test the output here
            }
          })
          expect(r.logs).to.deep.equal([])
          expect(r.errors).to.deep.equal(['Missing required argument: cat'])
        })
      })
      it('should set exit process to true, if no argument provided', () => {
        const r = checkOutput(() => yargs([])
          .demand('cat')
          .exitProcess()
          .parse()
        )
  
        r.exit.should.eql(true)
      })
    })
  
    describe('reset', () => {
      it('should put yargs back into its initial state', () => {
        // create a command line with all the things.
        // so that we can confirm they're reset.
        const y = yargs(['--help'])
          .command('foo', 'bar', noop)
          .default('foo', 'bar')
          .describe('foo', 'foo variable')
          .demandCommand(1)
          .demandOption('foo')
          .string('foo')
          .alias('foo', 'bar')
          .string('foo')
          .choices('foo', ['bar', 'baz'])
          .coerce('foo', foo => `${foo}bar`)
          .implies('foo', 'snuh')
          .conflicts('qux', 'xyzzy')
          .group('foo', 'Group:')
          .exitProcess(false) // defaults to true.
          .global('foo', false)
          .global('qux', false)
          .env('YARGS')
          .reset()
  
        const emptyOptions = {
          array: [],
          boolean: ['help', 'version'],
          string: [],
          alias: {},
          default: {},
          key: { help: true, version: true },
          narg: {},
          defaultDescription: {},
          choices: {},
          coerce: {},
          skipValidation: [],
          count: [],
          normalize: [],
          number: [],
          config: {},
          configObjects: [],
          envPrefix: 'YARGS', // preserved as global
          hiddenOptions: [],
          demandedCommands: {},
          demandedOptions: {},
          local: [
            '_',
            'foo',
            'qux'
          ]
        }
  
        expect(y.getOptions()).to.deep.equal(emptyOptions)
        expect(y.getUsageInstance().getDescriptions()).to.deep.equal({
          help: '__yargsString__:Show help',
          version: '__yargsString__:Show version number'
        })
        expect(y.getValidationInstance().getImplied()).to.deep.equal({})
        expect(y.getValidationInstance().getConflicting()).to.deep.equal({})
        expect(y.getCommandInstance().getCommandHandlers()).to.deep.equal({})
        expect(y.getExitProcess()).to.equal(false)
        expect(y.getDemandedOptions()).to.deep.equal({})
        expect(y.getDemandedCommands()).to.deep.equal({})
        expect(y.getGroups()).to.deep.equal({})
      })
  
      it('does not invoke parse with an error if reset has been called and option is not global', (done) => {
        const y = yargs()
          .demand('cake')
          .global('cake', false)
  
        y.parse('hello', (err) => {
          err.message.should.match(/Missing required argument/)
        })
        y.reset()
        y.parse('cake', (err) => {
          expect(err).to.equal(null)
          return done()
        })
      })
    })
  
    describe('command', () => {
      it('executes command handler with parsed argv', (done) => {
        yargs(['blerg'])
          .command(
            'blerg',
            'handle blerg things',
            noop,
            (argv) => {
              // we should get the argv from the prior yargs.
              argv._[0].should.equal('blerg')
              return done()
            }
          )
          .exitProcess(false) // defaults to true.
          .parse()
      })
      it('runs all middleware before reaching the handler', function (done) {
        yargs(['foo'])
          .command(
            'foo',
            'handle foo things',
            function () {},
            function (argv) {
              // we should get the argv filled with data from the middleware
              argv._[0].should.equal('foo')
              argv.hello.should.equal('world')
              return done()
            },
            [function (argv) {
              return { hello: 'world' }
            }]
          )
          .exitProcess(false) // defaults to true.
          .parse()
      })
      it('recommends a similar command if no command handler is found', () => {
        const r = checkOutput(() => {
          yargs(['boat'])
            .command('goat')
            .recommendCommands()
            .parse()
        })
  
        r.errors[2].should.match(/Did you mean goat/)
      })
  
      it('does not recommend a similiar command if no similar command exists', () => {
        const r = checkOutput(() => {
          yargs(['foo'])
            .command('nothingSimilar')
            .recommendCommands()
            .parse()
        })
  
        r.logs.length.should.equal(0)
      })
  
      it('recommends the longest match first', () => {
        const r = checkOutput(() => {
          yargs(['boat'])
            .command('bot')
            .command('goat')
            .recommendCommands()
            .parse()
        })
  
        r.errors[2].should.match(/Did you mean goat/)
      })
  
      // see: https://github.com/yargs/yargs/issues/822
      it('does not print command recommendation if help message will be shown', (done) => {
        const parser = yargs()
          .command('goat')
          .help()
          .recommendCommands()
  
        parser.parse('boat help', {}, (err, _argv, output) => {
          if (err) return done(err)
          output.split('Commands:').length.should.equal(2)
          return done()
        })
      })
  
      it("skips executing root-level command if builder's help is executed", () => {
        const r = checkOutput(() => {
          yargs(['blerg', '-h'])
            .command(
              'blerg',
              'handle blerg things',
              yargs => yargs
                .command('snuh', 'snuh command')
                .help('h')
                .wrap(null),
              () => {
                throw Error('should not happen')
              }
            )
            .help('h')
            .parse()
        })
  
        r.logs[0].split('\n').should.deep.equal([
          'usage blerg',
          '',
          'handle blerg things',
          '',
          'Commands:',
          '  usage blerg snuh  snuh command',
          '',
          'Options:',
          '  --version  Show version number  [boolean]',
          '  -h         Show help  [boolean]'
        ])
      })
  
      it('executes top-level help if no handled command is provided', () => {
        const r = checkOutput(() => {
          yargs(['snuh', '-h'])
            .command('blerg', 'handle blerg things', yargs => yargs
              .command('snuh', 'snuh command')
              .help('h')
              .parse()
            )
            .help('h')
            .wrap(null)
            .parse()
        })
  
        r.logs[0].split('\n').should.deep.equal([
          'usage [command]',
          '',
          'Commands:',
          '  usage blerg  handle blerg things',
          '',
          'Options:',
          '  --version  Show version number  [boolean]',
          '  -h         Show help  [boolean]'
        ])
      })
  
      it("accepts an object for describing a command's options", () => {
        const r = checkOutput(() => {
          yargs(['blerg', '-h'])
            .command('blerg <foo>', 'handle blerg things', {
              foo: {
                default: 99
              },
              bar: {
                default: 'hello world'
              }
            })
            .help('h')
            .wrap(null)
            .parse()
        })
  
        const usageString = r.logs[0]
        usageString.should.match(/usage blerg <foo>/)
        usageString.should.match(/--foo.*default: 99/)
        usageString.should.match(/--bar.*default: "hello world"/)
      })
  
      it("accepts a module with a 'builder' and 'handler' key", () => {
        const argv = yargs(['blerg', 'bar'])
          .command('blerg <foo>', 'handle blerg things', require('./fixtures/command'))
          .parse()
  
        argv.banana.should.equal('cool')
        argv.batman.should.equal('sad')
        argv.foo.should.equal('bar')
  
        global.commandHandlerCalledWith.banana.should.equal('cool')
        global.commandHandlerCalledWith.batman.should.equal('sad')
        global.commandHandlerCalledWith.foo.should.equal('bar')
        delete global.commandHandlerCalledWith
      })
  
      it("accepts a module with a keys 'command', 'describe', 'builder', and 'handler'", () => {
        const argv = yargs(['blerg', 'bar'])
          .command(require('./fixtures/command-module'))
          .parse()
  
        argv.banana.should.equal('cool')
        argv.batman.should.equal('sad')
        argv.foo.should.equal('bar')
  
        global.commandHandlerCalledWith.banana.should.equal('cool')
        global.commandHandlerCalledWith.batman.should.equal('sad')
        global.commandHandlerCalledWith.foo.should.equal('bar')
        delete global.commandHandlerCalledWith
      })
  
      it('derives \'command\' string from filename when missing', () => {
        const argv = yargs('nameless --foo bar')
          .command(require('./fixtures/cmddir_noname/nameless'))
          .parse()
  
        argv.banana.should.equal('cool')
        argv.batman.should.equal('sad')
        argv.foo.should.equal('bar')
  
        global.commandHandlerCalledWith.banana.should.equal('cool')
        global.commandHandlerCalledWith.batman.should.equal('sad')
        global.commandHandlerCalledWith.foo.should.equal('bar')
        delete global.commandHandlerCalledWith
      })
  
      it('throws error for non-module command object missing \'command\' string', () => {
        expect(() => {
          yargs.command({
            desc: 'A command with no name',
            builder (yargs) { return yargs },
            handler (argv) {}
          })
        }).to.throw(/No command name given for module: {(\n )? desc: 'A command with no name',\n {2}builder: \[Function(: builder)?],\n {2}handler: \[Function(: handler)?](\n| )}/)
      })
    })
  
    describe('terminalWidth', () => {
      it('returns the maximum width of the terminal', function () {
        if (!process.stdout.isTTY) {
          return this.skip()
        }
  
        yargs.terminalWidth().should.be.gte(0)
      })
    })
  
    describe('number', () => {
      it('accepts number arguments when a number type is specified', () => {
        const argv = yargs('-w banana')
          .number('w')
          .parse()
  
        expect(typeof argv.w).to.equal('number')
      })
  
      it('should expose an options short-hand for numbers', () => {
        const argv = yargs('-w banana')
          .option('w', {
            number: true
          })
          .alias('w', 'x')
          .parse()
  
        expect(typeof argv.w).to.equal('number')
        expect(typeof argv.x).to.equal('number')
      })
    })
  
    describe('choices', () => {
      it('accepts an object', () => {
        const optChoices = yargs([])
          .choices({
            color: ['red', 'green', 'blue'],
            stars: [1, 2, 3, 4, 5]
          })
          .choices({
            size: ['xl', 'l', 'm', 's', 'xs']
          })
          .getOptions().choices
  
        optChoices.should.deep.equal({
          color: ['red', 'green', 'blue'],
          stars: [1, 2, 3, 4, 5],
          size: ['xl', 'l', 'm', 's', 'xs']
        })
      })
  
      it('accepts a string and array', () => {
        const optChoices = yargs([])
          .choices('meat', ['beef', 'chicken', 'pork', 'bison'])
          .choices('temp', ['rare', 'med-rare', 'med', 'med-well', 'well'])
          .getOptions().choices
  
        optChoices.should.deep.equal({
          meat: ['beef', 'chicken', 'pork', 'bison'],
          temp: ['rare', 'med-rare', 'med', 'med-well', 'well']
        })
      })
  
      it('accepts a string and single value', () => {
        const optChoices = yargs([])
          .choices('gender', 'male')
          .choices('gender', 'female')
          .getOptions().choices
  
        optChoices.should.deep.equal({
          gender: ['male', 'female']
        })
      })
    })
  
    describe('locale', () => {
      function loadLocale (locale) {
        delete require.cache[require.resolve('../')]
        yargs = require('../')
        process.env.LC_ALL = locale
      }
  
      it('uses english as a default locale', () => {
        ['LANGUAGE', 'LC_ALL', 'LANG', 'LC_MESSAGES'].forEach((e) => {
          delete process.env[e]
        })
        yargs.locale().should.equal('en_US')
      })
  
      it("detects the operating system's locale", () => {
        loadLocale('es_ES.UTF-8')
        yargs.locale().should.equal('es_ES')
        loadLocale('en_US.UTF-8')
      })
  
      it("should not detect the OS locale if detectLocale is 'false'", () => {
        loadLocale('es_ES.UTF-8')
  
        const r = checkOutput(() => {
          yargs(['snuh', '-h'])
            .command('blerg', 'blerg command')
            .help('h')
            .wrap(null)
            .detectLocale(false)
            .parse()
        })
  
        yargs.locale().should.equal('en')
        yargs.getDetectLocale().should.equal(false)
        r.logs.join(' ').should.match(/Commands:/)
  
        loadLocale('en_US.UTF-8')
      })
  
      it("allows a locale other than the default 'en' to be specified", () => {
        const r = checkOutput(() => {
          yargs(['snuh', '-h'])
            .command('blerg', 'blerg command')
            .help('h')
            .wrap(null)
            .locale('pirate')
            .parse()
        })
  
        r.logs.join(' ').should.match(/Choose yer command:/)
      })
  
      it('handles a missing locale', () => {
        loadLocale('zz_ZZ.UTF-8')
  
        const r = checkOutput(() => {
          yargs(['snuh', '-h'])
            .command('blerg', 'blerg command')
            .help('h')
            .wrap(null)
            .parse()
        })
  
        yargs.locale().should.equal('zz_ZZ')
        loadLocale('en_US.UTF-8')
        r.logs.join(' ').should.match(/Commands:/)
      })
  
      it('properly translates a region-specific locale file', () => {
        loadLocale('pt_BR.UTF-8')
  
        const r = checkOutput(() => {
          yargs(['-h'])
            .help('h')
            .wrap(null)
            .parse()
        })
  
        yargs.locale().should.equal('pt_BR')
        loadLocale('en_US.UTF-8')
        r.logs.join(' ').should.match(/Exibe ajuda/)
      })
  
      it('uses locale string for help option default desc on .locale().help()', () => {
        const r = checkOutput(() => {
          yargs(['-h'])
            .locale('pirate')
            .help('h')
            .wrap(null)
            .parse()
        })
  
        r.logs.join(' ').should.match(/Parlay this here code of conduct/)
      })
  
      it('uses locale string for help option default desc on .help().locale()', () => {
        const r = checkOutput(() => {
          yargs(['-h'])
            .help('h')
            .locale('pirate')
            .wrap(null)
            .parse()
        })
  
        r.logs.join(' ').should.match(/Parlay this here code of conduct/)
      })
  
      describe('updateLocale', () => {
        it('allows you to override the default locale strings', () => {
          const r = checkOutput(() => {
            yargs(['snuh', '-h'])
              .command('blerg', 'blerg command')
              .help('h')
              .wrap(null)
              .updateLocale({
                'Commands:': 'COMMANDS!'
              })
              .parse()
          })
  
          r.logs.join(' ').should.match(/COMMANDS!/)
        })
  
        it('allows you to use updateStrings() as an alias for updateLocale()', () => {
          const r = checkOutput(() => {
            yargs(['snuh', '-h'])
              .command('blerg', 'blerg command')
              .help('h')
              .wrap(null)
              .updateStrings({
                'Commands:': '!SDNAMMOC'
              })
              .parse()
          })
  
          r.logs.join(' ').should.match(/!SDNAMMOC/)
        })
      })
    })
  
    describe('env', () => {
      it('translates no arg as empty prefix (parser applies all env vars)', () => {
        const options = yargs.env().getOptions()
        options.envPrefix.should.equal('')
      })
  
      it('accepts true as a valid prefix (parser applies all env vars)', () => {
        const options = yargs.env(true).getOptions()
        options.envPrefix.should.equal(true)
      })
  
      it('accepts empty string as a valid prefix (parser applies all env vars)', () => {
        const options = yargs.env('').getOptions()
        options.envPrefix.should.equal('')
      })
  
      it('accepts a string prefix', () => {
        const options = yargs.env('COOL').getOptions()
        options.envPrefix.should.equal('COOL')
      })
  
      it('translates false as undefined prefix (disables parsing of env vars)', () => {
        const options = yargs.env(false).getOptions()
        expect(options.envPrefix).to.equal(undefined)
      })
    })
  
    describe('parse', () => {
      it('parses a simple string', () => {
        const a1 = yargs.parse('-x=2 --foo=bar')
        const a2 = yargs('-x=2 --foo=bar').parse()
        a1.x.should.equal(2)
        a2.x.should.equal(2)
  
        a1.foo.should.equal('bar')
        a2.foo.should.equal('bar')
      })
  
      it('parses a quoted string', () => {
        const a1 = yargs.parse('-x=\'marks "the" spot\' --foo "break \'dance\'"')
        const a2 = yargs('-x=\'marks "the" spot\' --foo "break \'dance\'"').parse()
  
        a1.x.should.equal('marks "the" spot')
        a2.x.should.equal('marks "the" spot')
  
        a1.foo.should.equal("break 'dance'")
        a2.foo.should.equal("break 'dance'")
      })
  
      it('parses an array', () => {
        const a1 = yargs.parse(['-x', '99', '--why=hello world'])
        const a2 = yargs(['-x', '99', '--why=hello world']).parse()
  
        a1.x.should.equal(99)
        a2.x.should.equal(99)
  
        a1.why.should.equal('hello world')
        a2.why.should.equal('hello world')
      })
  
      it('ignores implicit help command (with short-circuit)', () => {
        const parsed = yargs.help().parse('help', true)
        parsed._.should.deep.equal(['help'])
      })
  
      it('allows an optional context object to be provided', () => {
        const a1 = yargs.parse('-x=2 --foo=bar', {
          context: 'look at me go!'
        })
        a1.x.should.equal(2)
        a1.foo.should.equal('bar')
        a1.context.should.equal('look at me go!')
      })
  
      // see https://github.com/yargs/yargs/issues/724
      it('overrides parsed value of argv with context object', () => {
        const a1 = yargs.parse('-x=33', {
          x: 42
        })
        a1.x.should.equal(42)
      })
  
      it('parses process.parse() if no arguments are provided', () => {
        const r = checkOutput(() => {
          yargs(['--help'])
            .command('blerg', 'blerg command')
            .wrap(null)
            .parse()
        })
  
        r.logs[0].should.match(/Commands:[\s\S]*blerg command/)
      })
  
      it('can be called multiple times with the same behavior', () => {
        let counter = { foobar: 0 }
        yargs(['test', 'foobar'])
          .command(
            'test <name>',
            'increases counter',
            yargs => yargs.positional('name', {
              aliases: 'n',
              describe: 'a name',
              choices: ['foobar'],
              type: 'string'
            }),
            argv => { counter[argv.name]++ }
          )
          .fail((msg) => {
            expect.fail(undefined, undefined, msg)
          })
        yargs.parse()
        yargs.parse()
        yargs.parse()
        expect(counter.foobar).to.equal(3)
      })
    })
  
    describe('parsed', () => {
      it('should be false before parsing', () => {
        const warn = global.console.warn
        global.console.warn = () => {}
        yargs.parsed.should.equal(false)
        global.console.warn = warn
      })
  
      it('should not be false after parsing', () => {
        const warn = global.console.warn
        global.console.warn = () => {}
        yargs.parse()
        yargs.parsed.should.not.equal(false)
        global.console.warn = warn
      })
    })
  
    // yargs.parse(['foo', '--bar'], function (err, argv, output) {}
    context('function passed as second argument to parse', () => {
      it('does not print to stdout', () => {
        const r = checkOutput(() => {
          yargs()
            .help('h')
            .parse('-h', (_err, argv, output) => {})
        })
  
        r.logs.length.should.equal(0)
        r.errors.length.should.equal(0)
      })
  
      it('gets passed error as first argument', () => {
        let err = null
        const r = checkOutput(() => {
          yargs()
            .demand('robin')
            .parse('batman', (_err, argv, output) => {
              err = _err
            })
        })
        r.logs.length.should.equal(0)
        r.errors.length.should.equal(0)
        err.should.match(/Missing required argument/)
      })
  
      it('gets passed argv as second argument', () => {
        let argv = null
        const r = checkOutput(() => {
          yargs()
            .demand('robin')
            .parse('batman --foo', (_err, _argv, output) => {
              argv = _argv
            })
        })
        r.logs.length.should.equal(0)
        r.errors.length.should.equal(0)
        argv.foo.should.equal(true)
      })
  
      it('gets passed output as third argument', () => {
        let output = null
        const r = checkOutput(() => {
          yargs()
            .demand('robin')
            .help()
            .parse('--help', (_err, argv, _output) => {
              output = _output
            })
        })
        r.logs.length.should.equal(0)
        r.errors.length.should.equal(0)
        output.should.match(/--robin.*\[required]/)
      })
  
      it('reinstates original exitProcess setting after invocation', () => {
        let callbackCalled = false
        const r = checkOutput(() => {
          yargs
            .exitProcess(true)
            .help()
            .parse('--help', () => {
              callbackCalled = true
              yargs.getExitProcess().should.equal(false)
            })
        })
        r.logs.length.should.equal(0)
        r.errors.length.should.equal(0)
        r.exit.should.equal(false)
        callbackCalled.should.equal(true)
        yargs.getExitProcess().should.equal(true)
      })
  
      it('does not call callback if subsequently called without callback', () => {
        let callbackCalled = 0
        const callback = () => {
          callbackCalled++
        }
        yargs.help()
        const r1 = checkOutput(() => {
          yargs.parse('--help', callback)
        })
        const r2 = checkOutput(() => {
          yargs.parse('--help')
        })
        callbackCalled.should.equal(1)
        r1.logs.length.should.equal(0)
        r1.errors.length.should.equal(0)
        r1.exit.should.equal(false)
        r2.exit.should.equal(true)
        r2.errors.length.should.equal(0)
        r2.logs[0].should.match(/--help.*Show help.*\[boolean]/)
      })
  
      it('resets error state between calls to parse', () => {
        const y = yargs()
          .demand(2)
  
        let err1 = null
        let out1 = null
        let argv1 = null
        y.parse('foo', (err, argv, output) => {
          err1 = err
          argv1 = argv
          out1 = output
        })
  
        err1.message.should.match(/Not enough non-option arguments/)
        argv1._.should.include('foo')
        out1.should.match(/Not enough non-option arguments/)
  
        let err2 = null
        let argv2 = null
        let out2 = null
        y.parse('foo bar', (err, argv, output) => {
          err2 = err
          argv2 = argv
          out2 = output
        })
  
        expect(err2).to.equal(null)
        argv2._.should.deep.equal([
          'foo',
          'bar'
        ])
        expect(out2).to.equal('')
      })
  
      describe('commands', () => {
        it('does not invoke command handler if output is populated', () => {
          let err = null
          let handlerCalled = false
          const r = checkOutput(() => {
            yargs()
              .command('batman <api-token>', 'batman command', noop, () => {
                handlerCalled = true
              })
              .parse('batman --what', (_err, argv, output) => {
                err = _err
              })
          })
          r.logs.length.should.equal(0)
          r.errors.length.should.equal(0)
          err.message.should.match(/Not enough non-option arguments/)
          handlerCalled.should.equal(false)
        })
  
        it('invokes command handler normally if no output is populated', () => {
          let argv = null
          let output = null
          const r = checkOutput(() => {
            yargs()
              .command('batman <api-token>', 'batman command', noop, (_argv) => {
                argv = _argv
              })
              .parse('batman robin --what', (_err, argv, _output) => {
                output = _output
              })
          })
          r.logs.length.should.equal(0)
          r.errors.length.should.equal(0)
          output.should.equal('')
          argv['api-token'].should.equal('robin')
          argv.what.should.equal(true)
        })
  
        it('allows context object to be passed to parse', () => {
          let argv = null
          yargs()
            .command('batman <api-token>', 'batman command', noop, (_argv) => {
              argv = _argv
            })
            .parse('batman robin --what', {
              state: 'grumpy but rich'
            }, (_err, argv, _output) => {})
  
          argv.state.should.equal('grumpy but rich')
          argv['api-token'].should.equal('robin')
          argv.what.should.equal(true)
        })
  
        // see: https://github.com/yargs/yargs/issues/671
        it('does not fail if context object has cyclical reference', () => {
          let argv = null
          const context = { state: 'grumpy but rich' }
          context.res = context
          yargs()
            .command('batman <api-token>', 'batman command', noop, (_argv) => {
              argv = _argv
            })
            .parse('batman robin --what', context, (_err, argv, _output) => {})
  
          argv.state.should.equal('grumpy but rich')
          argv['api-token'].should.equal('robin')
          argv.what.should.equal(true)
        })
  
        it('allows nested sub-commands to be invoked multiple times', () => {
          const context = { counter: 0 }
  
          checkOutput(() => {
            const parser = yargs()
              .commandDir('fixtures/cmddir')
  
            parser.parse('dream within-a-dream --what', { context }, (_err, argv, _output) => {})
            parser.parse('dream within-a-dream --what', { context }, (_err, argv, _output) => {})
            parser.parse('dream within-a-dream --what', { context }, (_err, argv, _output) => {})
          })
  
          context.counter.should.equal(3)
        })
  
        it('overwrites the prior context object, when parse is called multiple times', () => {
          let argv = null
          const parser = yargs()
            .command('batman <api-token>', 'batman command', noop, (_argv) => {})
  
          parser.parse('batman robin --what', {
            state: 'grumpy but rich'
          }, (_err, _argv, _output) => {})
  
          parser.parse('batman robin --what', {
            state: 'the hero we need'
          }, (_err, _argv, _output) => {
            argv = _argv
          })
  
          argv.state.should.equal('the hero we need')
        })
  
        it('populates argv appropriately when parse is called multiple times', () => {
          const parser = yargs()
            .command('batman <api-token>', 'batman command', noop, (_argv) => {})
            .command('robin <egg>', 'robin command', noop, (_argv) => {})
  
          let argv1 = null
          parser.parse('batman abc123', (_err, argv, _output) => {
            argv1 = argv
          })
          let argv2 = null
          parser.parse('robin blue', (_err, argv, _output) => {
            argv2 = argv
          })
          expect(argv1.egg).to.equal(undefined)
          argv1['api-token'].should.equal('abc123')
  
          expect(argv2['api-token']).to.equal(undefined)
          argv2.egg.should.equal('blue')
        })
  
        it('populates output appropriately when parse is called multiple times', () => {
          const parser = yargs()
            .command('batman <api-token>', 'batman command', noop, (_argv) => {})
            .command('robin <egg>', 'robin command', noop, (_argv) => {})
            .wrap(null)
  
          let output1 = null
          parser.parse('batman help', (_err, _argv, output) => {
            output1 = output
          })
          let output2 = null
          parser.parse('robin help', (_err, _argv, output) => {
            output2 = output
          })
  
          output1.split('\n').should.deep.equal([
            'ndm batman <api-token>',
            '',
            'batman command',
            '',
            'Options:',
            '  --help     Show help  [boolean]',
            '  --version  Show version number  [boolean]'
          ])
  
          output2.split('\n').should.deep.equal([
            'ndm robin <egg>',
            '',
            'robin command',
            '',
            'Options:',
            '  --help     Show help  [boolean]',
            '  --version  Show version number  [boolean]'
          ])
        })
  
        it('resets errors when parse is called multiple times', () => {
          const parser = yargs()
            .command('batman <api-token>', 'batman command', noop, (_argv) => {})
            .command('robin <egg>', 'robin command', noop, (_argv) => {})
            .wrap(null)
  
          let error1 = null
          let output1 = null
          parser.parse('batman', (err, _argv, output) => {
            error1 = err
            output1 = output
          })
          let error2 = null
          let output2 = null
          parser.parse('robin help', (err, _argv, output) => {
            error2 = err
            output2 = output
          })
  
          error1.message.should.match(/Not enough non-option arguments/)
          output1.split('\n').should.deep.equal([
            'ndm batman <api-token>',
            '',
            'batman command',
            '',
            'Options:',
            '  --help     Show help  [boolean]',
            '  --version  Show version number  [boolean]',
            '',
            'Not enough non-option arguments: got 0, need at least 1'
          ])
  
          expect(error2).to.equal(undefined)
          output2.split('\n').should.deep.equal([
            'ndm robin <egg>',
            '',
            'robin command',
            '',
            'Options:',
            '  --help     Show help  [boolean]',
            '  --version  Show version number  [boolean]'
          ])
        })
  
        it('preserves top-level config when parse is called multiple times', () => {
          let x = 'wrong'
          let err
          let output
          // set some top-level, reset-able config
          const parser = yargs()
            .demand(1, 'Must call a command')
            .strict()
            .wrap(null)
            .command('one <x>', 'The one and only command')
          // first call parse with command, which calls reset
          parser.parse('one two', (_, argv) => {
            x = argv.x
          })
          // then call parse without command, which should enforce top-level config
          parser.parse('', (_err, argv, _output) => {
            err = _err || {}
            output = _output || ''
          })
          x.should.equal('two')
          err.should.have.property('message').and.equal('Must call a command')
          output.split('\n').should.deep.equal([
            'ndm <command>',
            '',
            'Commands:',
            '  ndm one <x>  The one and only command',
            '',
            'Options:',
            '  --help     Show help  [boolean]',
            '  --version  Show version number  [boolean]',
            '',
            'Must call a command'
          ])
        })
      })
    })
  
    describe('config', () => {
      it('allows a parsing function to be provided as a second argument', () => {
        const argv = yargs('--config ./test/fixtures/config.json')
          .config('config', cfgPath => JSON.parse(fs.readFileSync(cfgPath)))
          .global('config', false)
          .parse()
  
        argv.foo.should.equal('baz')
      })
  
      it('allows key to be specified with option shorthand', () => {
        const argv = yargs('--config ./test/fixtures/config.json')
          .option('config', {
            config: true,
            global: false
          })
          .parse()
  
        argv.foo.should.equal('baz')
      })
  
      it('can be disabled with option shorthand', () => {
        const argv = yargs('--config ./test/fixtures/config.json')
          .option('config', {
            config: false,
            global: false
          })
          .parse()
  
        argv.config.should.equal('./test/fixtures/config.json')
      })
  
      it('allows to pass a configuration object', () => {
        const argv = yargs
          .config({ foo: 1, bar: 2 })
          .parse()
  
        argv.foo.should.equal(1)
        argv.bar.should.equal(2)
      })
  
      describe('extends', () => {
        it('applies default configurations when given config object', () => {
          const argv = yargs
            .config({
              extends: './test/fixtures/extends/config_1.json',
              a: 1
            })
            .parse()
  
          argv.a.should.equal(1)
          argv.b.should.equal(22)
          argv.z.should.equal(15)
        })
  
        it('protects against circular extended configurations', () => {
          expect(() => {
            yargs.config({ extends: './test/fixtures/extends/circular_1.json' })
          }).to.throw(YError)
        })
  
        it('handles aboslute paths', () => {
          const absolutePath = path.join(process.cwd(), 'test', 'fixtures', 'extends', 'config_1.json')
  
          const argv = yargs
            .config({
              a: 2,
              extends: absolutePath
            })
            .parse()
  
          argv.a.should.equal(2)
          argv.b.should.equal(22)
          argv.z.should.equal(15)
        })
  
        it('tolerates null prototype config objects', () => {
          const argv = yargs
            .config({
              __proto__: null,
              a: 2,
              extends: './test/fixtures/extends/config_1.json'
            })
            .parse()
  
          argv.a.should.equal(2)
          argv.b.should.equal(22)
          argv.z.should.equal(15)
        })
  
        // see: https://www.npmjs.com/package/yargs-test-extends
        it('allows a module to be extended, rather than a JSON file', () => {
          const argv = yargs()
            .config({
              a: 2,
              extends: 'yargs-test-extends'
            })
            .parse()
  
          argv.a.should.equal(2)
          argv.c.should.equal(201)
        })
  
        it('ignores an extends key that does not look like a path or module', () => {
          const argv = yargs()
            .config({
              a: 2,
              extends: 'batman'
            })
            .parse()
  
          argv.a.should.equal(2)
          argv.extends.should.equal('batman')
        })
  
        it('allows files with .*rc extension to be extended', () => {
          const argv = yargs()
            .config({
              extends: './test/fixtures/extends/.myrc',
              a: 3
            })
            .parse()
  
          argv.a.should.equal(3)
          argv.b.should.equal(22)
          argv.c.should.equal(201)
          argv.z.should.equal(15)
        })
  
        it('deep merges configs when extending when deep-merge-config=true', () => {
          const argv = yargs()
            .parserConfiguration({ 'deep-merge-config': true })
            .config({
              extends: './test/fixtures/extends/config_deep.json',
              a: {
                b: 11,
                d: [5, 2],
                f: 'no',
                g: {
                  h: 122
                },
                i: [1, 2]
              }
            })
            .parse()
  
          argv.a.b.should.equal(11)
          argv.a.c.should.equal(12)
          argv.a.d.should.deep.equal([5, 2])
          argv.a.e.should.eql([1])
          argv.a.f.should.equal('no')
          argv.a.g.h.should.equal(122)
          argv.a.i.should.deep.equal([1, 2])
          argv.test.yes.should.equal(1)
        })
  
        it('deep merges multiple configs when extending when deep-merge-config=true', () => {
          const argv = yargs()
            .parserConfiguration({ 'deep-merge-config': true })
            .config({
              extends: './test/fixtures/extends/config_deep_2.json',
              a: {
                g: 3
              }
            })
            .parse()
  
          argv.test.yes.should.equal(1)
          argv.a.b.should.equal(12)
          argv.a.c.should.equal(12)
          argv.a.g.should.equal(3)
        })
  
        it('does not deep merge objects by default', () => {
          const argv = yargs()
            .config({
              extends: './test/fixtures/extends/config_deep.json',
              a: {
                b: 11,
                d: [5, 2],
                f: 'no',
                g: {
                  h: 122
                }
              }
            })
            .parse()
  
          argv.a.b.should.equal(11)
          argv.a.should.not.have.property('c')
          argv.a.d.should.deep.equal([5, 2])
          argv.a.should.not.have.property('e')
          argv.a.f.should.equal('no')
          argv.a.g.h.should.equal(122)
        })
      })
    })
  
    describe('normalize', () => {
      it('normalizes paths passed as arguments', () => {
        const argv = yargs('--path /foo/bar//baz/asdf/quux/..')
          .normalize(['path'])
          .parse()
  
        argv.path.should.equal(['', 'foo', 'bar', 'baz', 'asdf'].join(path.sep))
      })
  
      it('normalizes path when when it is updated', () => {
        const argv = yargs('--path /batman')
          .normalize(['path'])
          .parse()
  
        argv.path = '/foo/bar//baz/asdf/quux/..'
        argv.path.should.equal(['', 'foo', 'bar', 'baz', 'asdf'].join(path.sep))
      })
  
      it('allows key to be specified with option shorthand', () => {
        const argv = yargs('--path /batman')
          .option('path', {
            normalize: true
          })
          .parse()
  
        argv.path = '/foo/bar//baz/asdf/quux/..'
        argv.path.should.equal(['', 'foo', 'bar', 'baz', 'asdf'].join(path.sep))
      })
  
      it('can be disabled with option shorthand', () => {
        const argv = yargs('--path /batman')
          .option('path', {
            normalize: false
          })
          .parse()
  
        argv.path = 'mongodb://url'
        argv.path.should.equal('mongodb://url')
      })
    })
  
    describe('narg', () => {
      it('accepts a key as the first argument and a count as the second', () => {
        const argv = yargs('--foo a b c')
          .nargs('foo', 2)
          .parse()
  
        argv.foo.should.deep.equal(['a', 'b'])
        argv._.should.deep.equal(['c'])
      })
  
      it('accepts a hash of keys and counts', () => {
        const argv = yargs('--foo a b c')
          .nargs({
            foo: 2
          })
          .parse()
  
        argv.foo.should.deep.equal(['a', 'b'])
        argv._.should.deep.equal(['c'])
      })
  
      it('allows key to be specified with option shorthand', () => {
        const argv = yargs('--foo a b c')
          .option('foo', {
            nargs: 2
          })
          .parse()
  
        argv.foo.should.deep.equal(['a', 'b'])
        argv._.should.deep.equal(['c'])
      })
    })
  
    describe('global', () => {
      it('does not reset a global options when reset is called', () => {
        const y = yargs('--foo a b c')
          .option('foo', {
            nargs: 2
          })
          .option('bar', {
            nargs: 2,
            global: false
          })
          .global('foo')
          .reset()
        const options = y.getOptions()
        options.key.foo.should.equal(true)
        expect(options.key.bar).to.equal(undefined)
      })
  
      it('does not reset alias of global option', () => {
        const y = yargs('--foo a b c')
          .option('foo', {
            nargs: 2,
            alias: 'awesome-sauce'
          })
          .string('awesome-sauce')
          .demand('awesomeSauce')
          .option('bar', {
            nargs: 2,
            string: true,
            demand: true,
            global: false
          })
          .global('foo')
          .reset({
            foo: ['awesome-sauce', 'awesomeSauce']
          })
        const options = y.getOptions()
  
        options.key.foo.should.equal(true)
        options.string.should.include('awesome-sauce')
        Object.keys(options.demandedOptions).should.include('awesomeSauce')
  
        expect(options.key.bar).to.equal(undefined)
        options.string.should.not.include('bar')
        Object.keys(options.demandedOptions).should.not.include('bar')
      })
  
      it('should set help to global option by default', () => {
        const y = yargs('--foo')
          .help('help')
        const options = y.getOptions()
        options.local.should.not.include('help')
      })
  
      it('should set version to global option by default', () => {
        const y = yargs('--foo')
          .version()
        const options = y.getOptions()
        options.local.should.not.include('version')
      })
  
      it('should not reset usage descriptions of global options', () => {
        const y = yargs('--foo')
          .describe('bar', 'my awesome bar option')
          .describe('foo', 'my awesome foo option')
          .global('foo')
          .global('bar', false)
          .reset()
        const descriptions = y.getUsageInstance().getDescriptions()
        Object.keys(descriptions).should.include('foo')
        Object.keys(descriptions).should.not.include('bar')
      })
  
      it('should not reset implications of global options', () => {
        const y = yargs(['--x=33'])
          .implies({
            x: 'y'
          })
          .implies({
            z: 'w'
          })
          .global(['z'], false)
          .reset()
        const implied = y.getValidationInstance().getImplied()
        Object.keys(implied).should.include('x')
        Object.keys(implied).should.not.include('z')
      })
  
      it('should expose an options short-hand for declaring global options', () => {
        const y = yargs('--foo a b c')
          .option('foo', {
            nargs: 2
          })
          .option('bar', {
            nargs: 2,
            global: false
          })
          .reset()
        const options = y.getOptions()
        options.key.foo.should.equal(true)
        expect(options.key.bar).to.equal(undefined)
      })
    })
  
    describe('pkgConf', () => {
      it('uses values from package.json', () => {
        const argv = yargs('--foo a').pkgConf('repository').parse()
  
        argv.foo.should.equal('a')
        argv.type.should.equal('git')
      })
  
      it('combines yargs defaults with package.json values', () => {
        const argv = yargs('--foo a')
          .default('b', 99)
          .pkgConf('repository')
          .parse()
  
        argv.b.should.equal(99)
        argv.foo.should.equal('a')
        argv.type.should.equal('git')
      })
  
      it('should use value from package.json, if argv value is using default value', () => {
        const argv = yargs('--foo a')
          .default('b', 99)
          .pkgConf('repository')
          .default('type', 'default')
          .parse()
  
        argv.b.should.equal(99)
        argv.foo.should.equal('a')
        argv.type.should.equal('git')
      })
  
      it('should apply value from config object to all aliases', () => {
        const argv = yargs('--foo a')
          .pkgConf('repository')
          .alias('type', 't')
          .alias('t', 'u')
          .parse()
  
        argv.foo.should.equal('a')
        argv.type.should.equal('git')
        argv.t.should.equal('git')
        argv.u.should.equal('git')
      })
  
      it('is cool with a key not existing', () => {
        const argv = yargs('--foo a')
          .default('b', 99)
          .pkgConf('banana')
          .parse()
  
        argv.b.should.equal(99)
        argv.foo.should.equal('a')
        expect(argv.type).to.equal(undefined)
      })
  
      it('allows an alternative cwd to be specified', () => {
        const argv = yargs('--foo a')
          .pkgConf('blerg', './test/fixtures')
          .parse()
  
        argv.foo.should.equal('a')
        argv.dotNotation.should.equal(false)
      })
  
      it('doesn\'t mess up other pkg lookups when cwd is specified', () => {
        const r = checkOutput(() => yargs('--version')
          .pkgConf('repository', './test/fixtures')
          .version()
          .parse()
        )
        const options = yargs.getOptions()
  
        // assert pkgConf lookup (test/fixtures/package.json)
        options.configObjects.should.deep.equal([{ type: 'svn' }])
        // assert parseArgs and guessVersion lookup (package.json)
        expect(options.configuration['dot-notation']).to.equal(undefined)
        r.logs[0].should.not.equal('9.9.9') // breaks when yargs gets to this version
      })
  
      // see https://github.com/yargs/yargs/issues/485
      it('handles an invalid package.json', () => {
        const argv = yargs('--foo a')
          .pkgConf('yargs', './test/fixtures/broken-json')
          .parse()
  
        argv.foo.should.equal('a')
      })
  
      it('should apply default configurations from extended packages', () => {
        const argv = yargs().pkgConf('foo', 'test/fixtures/extends/packageA').parse()
  
        argv.a.should.equal(80)
        argv.b.should.equals('riffiwobbles')
      })
  
      it('should apply extended configurations from cwd when no path is given', () => {
        const argv = yargs('', 'test/fixtures/extends/packageA').pkgConf('foo').parse()
  
        argv.a.should.equal(80)
        argv.b.should.equals('riffiwobbles')
      })
    })
  
    describe('parserConfiguration', () => {
      it('overrides the default parser configuration', () => {
        const argv = yargs('--foo.bar 1 --no-baz 2')
          .parserConfiguration({ 'boolean-negation': false, 'dot-notation': false })
          .parse()
        expect(argv['foo.bar']).to.equal(1)
        argv.noBaz.should.equal(2)
      })
  
      it('supports --unknown-options-as-args', () => {
        const argv = yargs('--foo.bar 1 --no-baz 2')
          .parserConfiguration({ 'unknown-options-as-args': true })
          .parse()
        argv._.should.deep.eql(['--foo.bar', '1', '--no-baz', '2'])
        const argv2 = yargs('foo --foo.bar --cool 1 --no-baz 2')
          .command('foo', 'my foo command', (yargs) => {
            yargs.boolean('cool')
          }, () => {})
          .parserConfiguration({ 'unknown-options-as-args': true })
          .parse()
        argv2._.should.deep.eql(['foo', '--foo.bar', '1', '--no-baz', '2'])
        argv2.cool.should.equal(true)
      })
    })
  
    describe('skipValidation', () => {
      it('skips validation if an option with skipValidation is present', () => {
        const argv = yargs(['--koala', '--skip'])
          .demand(1)
          .fail((msg) => {
            expect.fail()
          })
          .skipValidation(['skip', 'reallySkip'])
          .parse()
        argv.koala.should.equal(true)
      })
  
      it('does not skip validation if no option with skipValidation is present', (done) => {
        const argv = yargs(['--koala'])
          .demand(1)
          .fail(msg => done())
          .skipValidation(['skip', 'reallySkip'])
          .parse()
        argv.koala.should.equal(true)
      })
  
      it('allows key to be specified with option shorthand', () => {
        const argv = yargs(['--koala', '--skip'])
          .demand(1)
          .fail((msg) => {
            expect.fail()
          })
          .option('skip', {
            skipValidation: true
          })
          .parse()
        argv.koala.should.equal(true)
      })
  
      it('allows having an option that skips validation but not skipping validation if that option is not used', () => {
        let skippedValidation = true
        yargs(['--no-skip'])
          .demand(5)
          .option('skip', {
            skipValidation: true
          })
          .fail((msg) => {
            skippedValidation = false
          })
          .parse()
        expect(skippedValidation).to.equal(false)
      })
    })
  
    describe('.help()', () => {
      it('enables `--help` option and `help` command without arguments', () => {
        const option = checkOutput(() => yargs('--help')
          .wrap(null)
          .parse()
        )
        const command = checkOutput(() => yargs('help')
          .wrap(null)
          .parse()
        )
        const expected = [
          'Options:',
          '  --help     Show help  [boolean]',
          '  --version  Show version number  [boolean]'
        ]
        option.logs[0].split('\n').should.deep.equal(expected)
        command.logs[0].split('\n').should.deep.equal(expected)
      })
  
      it('enables `--help` option and `help` command with `true` argument', () => {
        const option = checkOutput(() => yargs('--help')
          .help(true)
          .wrap(null)
          .parse()
        )
        const command = checkOutput(() => yargs('help')
          .help(true)
          .wrap(null)
          .parse()
        )
        const expected = [
          'Options:',
          '  --version  Show version number  [boolean]',
          '  --help     Show help  [boolean]'
        ]
        option.logs[0].split('\n').should.deep.equal(expected)
        command.logs[0].split('\n').should.deep.equal(expected)
      })
  
      it('enables given string as help option and command with string argument', () => {
        const option = checkOutput(() => yargs('--info')
          .help('info')
          .wrap(null)
          .parse()
        )
        const command = checkOutput(() => yargs('info')
          .help('info')
          .wrap(null)
          .parse()
        )
        const helpOption = checkOutput(() => yargs('--help')
          .help('info')
          .wrap(null)
          .parse()
        )
        const expected = [
          'Options:',
          '  --version  Show version number  [boolean]',
          '  --info     Show help  [boolean]'
        ]
        option.logs[0].split('\n').should.deep.equal(expected)
        command.logs[0].split('\n').should.deep.equal(expected)
        helpOption.result.should.have.property('help').and.equal(true)
      })
  
      it('enables given string as help option and command with custom description with two string arguments', () => {
        const option = checkOutput(() => yargs('--info')
          .help('info', 'Display info')
          .wrap(null)
          .parse()
        )
        const command = checkOutput(() => yargs('info')
          .help('info', 'Display info')
          .wrap(null)
          .parse()
        )
        const expected = [
          'Options:',
          '  --version  Show version number  [boolean]',
          '  --info     Display info  [boolean]'
        ]
        option.logs[0].split('\n').should.deep.equal(expected)
        command.logs[0].split('\n').should.deep.equal(expected)
      })
  
      it('enables given string as help option and command with custom description with two string arguments and `true` argument', () => {
        const option = checkOutput(() => yargs('--info')
          .help('info', 'Display info', true)
          .wrap(null)
          .parse()
        )
        const command = checkOutput(() => yargs('info')
          .help('info', 'Display info', true)
          .wrap(null)
          .parse()
        )
        const expected = [
          'Options:',
          '  --version  Show version number  [boolean]',
          '  --info     Display info  [boolean]'
        ]
        option.logs[0].split('\n').should.deep.equal(expected)
        command.logs[0].split('\n').should.deep.equal(expected)
      })
    })
  
    describe('.help() with .alias()', () => {
      it('uses multi-char (but not single-char) help alias as command', () => {
        const info = checkOutput(() => yargs('info')
          .help().alias('h', 'help').alias('h', 'info')
          .wrap(null)
          .parse()
        )
        const h = checkOutput(() => yargs('h')
          .help().alias('h', 'help').alias('h', 'info')
          .wrap(null)
          .parse()
        )
        info.logs[0].split('\n').should.deep.equal([
          'Options:',
          '  --version           Show version number  [boolean]',
          '  -h, --help, --info  Show help  [boolean]'
        ])
        h.result.should.have.property('_').and.deep.equal(['h'])
      })
    })
  
    describe('.coerce()', () => {
      it('supports string and function args (as option key and coerce function)', () => {
        const argv = yargs(['--file', path.join(__dirname, 'fixtures', 'package.json')])
          .coerce('file', arg => JSON.parse(fs.readFileSync(arg, 'utf8')))
          .parse()
        expect(argv.file).to.have.property('version').and.equal('9.9.9')
      })
  
      it('supports object arg (as map of multiple options)', () => {
        const argv = yargs('--expand abc --range 1..3')
          .coerce({
            expand (arg) {
              return arg.split('')
            },
            range (arg) {
              const arr = arg.split('..').map(Number)
              return { begin: arr[0], end: arr[1] }
            }
          })
          .parse()
        expect(argv.expand).to.deep.equal(['a', 'b', 'c'])
        expect(argv.range).to.have.property('begin').and.equal(1)
        expect(argv.range).to.have.property('end').and.equal(3)
      })
  
      it('supports array and function args (as option keys and coerce function)', () => {
        const argv = yargs(['--src', 'in', '--dest', 'out'])
          .coerce(['src', 'dest'], arg => path.resolve(arg))
          .parse()
        argv.src.should.match(/in/).and.have.length.above(2)
        argv.dest.should.match(/out/).and.have.length.above(3)
      })
  
      it('allows an error to be handled by fail() handler', () => {
        let msg
        let err
        let jsonErrMessage
        yargs('--json invalid')
          .coerce('json', (arg) => {
            try {
              JSON.parse(arg)
            } catch (err) {
              jsonErrMessage = err.message
            }
            return JSON.parse(arg)
          })
          .fail((m, e) => {
            msg = m
            err = e
          })
          .parse()
        expect(msg).to.equal(jsonErrMessage)
        expect(err).to.not.equal(undefined)
      })
  
      it('supports an option alias', () => {
        const argv = yargs('-d 2016-08-12')
          .coerce('date', Date.parse)
          .alias('date', 'd')
          .parse()
        argv.date.should.equal(1470960000000)
      })
  
      it('supports a global option within command', () => {
        let regex
        yargs('check --regex x')
          .global('regex')
          .coerce('regex', RegExp)
          .command('check', 'Check something', {}, (argv) => {
            regex = argv.regex
          })
          .parse()
        expect(regex).to.be.an.instanceof(RegExp)
        regex.toString().should.equal('/x/')
      })
  
      it('is supported by .option()', () => {
        const argv = yargs('--env SHELL=/bin/bash')
          .option('env', {
            coerce (arg) {
              const arr = arg.split('=')
              return { name: arr[0], value: arr[1] || '' }
            }
          })
          .parse()
        expect(argv.env).to.have.property('name').and.equal('SHELL')
        expect(argv.env).to.have.property('value').and.equal('/bin/bash')
      })
  
      it('supports positional and variadic args for a command', () => {
        let age
        let dates
        yargs('add 30days 2016-06-13 2016-07-18')
          .command('add <age> [dates..]', 'Testing', yargs => yargs
            .coerce('age', arg => parseInt(arg, 10) * 86400000)
            .coerce('dates', arg => arg.map(str => new Date(str))), (argv) => {
            age = argv.age
            dates = argv.dates
          })
          .parse()
        expect(age).to.equal(2592000000)
        expect(dates).to.have.lengthOf(2)
        dates[0].toString().should.equal(new Date('2016-06-13').toString())
        dates[1].toString().should.equal(new Date('2016-07-18').toString())
      })
  
      it('returns camelcase args for a command', () => {
        let age1
        let age2
        let dates
        yargs('add 30days 2016-06-13 2016-07-18')
          .command('add <age-in-days> [dates..]', 'Testing', yargs => yargs
            .coerce('age-in-days', arg => parseInt(arg, 10) * 86400000)
            .coerce('dates', arg => arg.map(str => new Date(str))), (argv) => {
            age1 = argv.ageInDays
            age2 = argv['age-in-days']
            dates = argv.dates
          })
          .parse()
        expect(age1).to.equal(2592000000)
        expect(age2).to.equal(2592000000)
        expect(dates).to.have.lengthOf(2)
        dates[0].toString().should.equal(new Date('2016-06-13').toString())
        dates[1].toString().should.equal(new Date('2016-07-18').toString())
      })
  
      it('allows an error from positional arg to be handled by fail() handler', () => {
        let msg
        let err
        yargs('throw ball')
          .command('throw <msg>', false, yargs => yargs
            .coerce('msg', (arg) => {
              throw new Error(arg)
            })
            .fail((m, e) => {
              msg = m
              err = e
            }))
          .parse()
        expect(msg).to.equal('ball')
        expect(err).to.not.equal(undefined)
      })
    })
  
    describe('stop parsing', () => {
      it('populates argv._ with unparsed arguments after "--"', () => {
        const argv = yargs.parse('--foo 33 --bar=99 -- --grep=foobar')
        argv.foo.should.equal(33)
        argv.bar.should.equal(99)
        argv._.length.should.equal(1)
        argv._[0].should.equal('--grep=foobar')
      })
    })
  
    describe('yargs context', () => {
      beforeEach(() => {
        delete require.cache[require.resolve('../')]
        yargs = require('../')
      })
  
      it('should begin with initial state', () => {
        const context = yargs.getContext()
        context.resets.should.equal(0)
        context.commands.should.deep.equal([])
      })
  
      it('should track number of resets', () => {
        const context = yargs.getContext()
        yargs.reset()
        context.resets.should.equal(1)
        yargs.reset()
        yargs.reset()
        context.resets.should.equal(3)
      })
  
      it('should track commands being executed', () => {
        let context
        yargs('one two')
          .command('one', 'level one', (yargs) => {
            context = yargs.getContext()
            context.commands.should.deep.equal(['one'])
            return yargs.command('two', 'level two', (yargs) => {
              context.commands.should.deep.equal(['one', 'two'])
            }, (argv) => {
              context.commands.should.deep.equal(['one', 'two'])
            })
          }, (argv) => {
            context.commands.should.deep.equal(['one'])
          })
          .parse()
        context.commands.should.deep.equal([])
      })
    })
  
    describe('positional', () => {
      it('defaults array with no arguments to []', () => {
        const args = yargs('cmd')
          .command('cmd [foo..]', 'run the cmd', (yargs) => {
            yargs.positional('foo', {
              describe: 'foo positionals'
            })
          })
          .parse()
        args.foo.should.eql([])
      })
  
      it('populates array with appropriate arguments', () => {
        const args = yargs('cmd /tmp/foo/bar a b')
          .command('cmd <file> [foo..]', 'run the cmd', (yargs) => {
            yargs
              .positional('file', {
                describe: 'the required bit'
              })
              .positional('foo', {
                describe: 'the variadic bit'
              })
          })
          .parse()
        args.file.should.equal('/tmp/foo/bar')
        args.foo.should.eql(['a', 'b'])
      })
  
      it('allows a conflicting argument to be specified', (done) => {
        yargs()
          .command('cmd <hero>', 'a command', (yargs) => {
            yargs.positional('hero', {
              conflicts: 'conflicting'
            })
          }).parse('cmd batman --conflicting', (err) => {
            err.message.should.equal(
              'Arguments hero and conflicting are mutually exclusive'
            )
            return done()
          })
      })
  
      it('allows a default to be set', () => {
        const argv = yargs('cmd')
          .command('cmd [heroes...]', 'a command', (yargs) => {
            yargs.positional('heroes', {
              default: ['batman', 'Iron Man']
            })
          }).parse()
        argv.heroes.should.eql(['batman', 'Iron Man'])
      })
  
      it('allows a defaultDescription to be set', () => {
        const r = checkOutput(() => yargs('cmd --help').wrap(null)
          .command('cmd [heroes...]', 'a command', (yargs) => {
            yargs.positional('heroes', {
              default: ['batman', 'Iron Man'],
              defaultDescription: 'batman and Iron Man'
            })
          }).parse()
        )
        r.logs.join('\n').split(/\n+/).should.deep.equal([
          'usage cmd [heroes...]',
          'a command',
          'Positionals:',
          '  heroes  [array] [default: batman and Iron Man]',
          'Options:',
          '  --help     Show help  [boolean]',
          '  --version  Show version number  [boolean]'
        ])
      })
  
      it('allows an implied argument to be specified', (done) => {
        yargs()
          .command('cmd <hero>', 'a command', (yargs) => {
            yargs.positional('hero', {
              implies: 'universe'
            })
          }).parse('cmd batman', (err) => {
            err.message.should.match(/hero -> universe/)
            return done()
          })
      })
  
      it('allows an alias to be provided', () => {
        const argv = yargs('cmd')
          .command('cmd [heroes...]', 'a command', (yargs) => {
            yargs.positional('heroes', {
              alias: 'do-gooders',
              default: ['batman', 'robin']
            })
          }).parse()
        argv.heroes.should.eql(['batman', 'robin'])
        argv.doGooders.should.eql(['batman', 'robin'])
        argv['do-gooders'].should.eql(['batman', 'robin'])
      })
  
      it('allows normalize to be specified', () => {
        const argv = yargs('cmd /tmp/awesome/../ /tmp/awesome/b/../')
          .command('cmd <files...>', 'a command', (yargs) => {
            yargs.positional('files', {
              normalize: true
            })
          }).parse()
        argv.files.should.eql([
          '/tmp/'.replace(/\//g, path.sep),
          '/tmp/awesome/'.replace(/\//g, path.sep)
        ])
      })
  
      it('allows a choices array to be specified', (done) => {
        yargs()
          .command('cmd <hero>', 'a command', (yargs) => {
            yargs.positional('hero', {
              choices: ['batman', 'Iron Man', 'robin']
            })
          }).parse('cmd joker', (err) => {
            err.message.should.match(
              /Argument: hero, Given: "joker", Choices: "batman"/
            )
            return done()
          })
      })
  
      it('allows a coerce method to be provided', () => {
        const argv = yargs('cmd batman')
          .command('cmd <hero>', 'a command', (yargs) => {
            yargs.positional('hero', {
              coerce: function (arg) {
                return arg.toUpperCase()
              },
              alias: 'do-gooder'
            })
          }).parse()
        argv.hero.should.equal('BATMAN')
        argv.doGooder.should.equal('BATMAN')
      })
  
      it('allows a boolean type to be specified', () => {
        const argv = yargs('cmd false')
          .command('cmd [run]', 'a command', (yargs) => {
            yargs.positional('run', {
              type: 'boolean'
            })
          }).parse()
        argv.run.should.equal(false)
      })
  
      it('allows a number type to be specified', () => {
        const argv = yargs('cmd nan')
          .command('cmd [count]', 'a command', (yargs) => {
            yargs.positional('count', {
              type: 'number'
            })
          }).parse()
        isNaN(argv.count).should.equal(true)
      })
  
      it('allows a string type to be specified', () => {
        const argv = yargs('cmd 33')
          .command('cmd [str]', 'a command', (yargs) => {
            yargs.positional('str', {
              type: 'string'
            })
          }).parse()
        argv.str.should.equal('33')
      })
  
      it('allows positional arguments for subcommands to be configured', () => {
        const argv = yargs('cmd subcommand 33')
          .command('cmd', 'a command', (yargs) => {
            yargs.command('subcommand [str]', 'a subcommand', (yargs) => {
              yargs.positional('str', {
                type: 'string'
              })
            })
          }).parse()
  
        argv.str.should.equal('33')
      })
  
      it("can only be used as part of a command's builder function", () => {
        expect(() => {
          yargs('foo')
            .positional('foo', {
              describe: 'I should not work'
            })
        }).to.throw(/\.positional\(\) can only be called/)
      })
  
      // see: https://github.com/yargs/yargs-parser/pull/110
      it('does not parse large scientific notation values, when type string', (done) => {
        yargs('cmd')
          .command('cmd deploy <version>', 'a command', (yargs) => {
            yargs
              .version(false)
              .positional('version', {
                type: 'string'
              })
          }).parse('cmd deploy 123e123', (err, argv) => {
            if (err) {
              return done(err)
            }
            argv.version.should.eql('123e123')
            return done()
          })
      })
    })
  
    // see: https://github.com/babel/babel/pull/10733
    it('should not fail if command handler freezes object', () => {
      const argv = yargs()
        .command('cmd', 'a command', (yargs) => {
          yargs.parserConfiguration({ 'populate--': true })
        }, (argv) => {
          Object.freeze(argv)
          argv._.should.eql(['cmd'])
          argv['--'].should.eql(['foo'])
        }).parse(['cmd', '--', 'foo'])
      argv._.should.eql(['cmd', 'foo'])
      // This should actually not be undefined, once we fix
      // #1482.
      argv['--'].should.eql(['foo'])
    })
  
    describe('onFinishCommand', () => {
      it('use with promise', (done) => {
        const result = 'noop-result'
        let calledTimes = 0
        yargs(['noop']).command('noop', 'a noop command', noop, async () => {
          return result
        }).onFinishCommand(async (commandResult) => {
          commandResult.should.eql(result)
          calledTimes++
        })
          .parse('noop')
  
        setTimeout(() => {
          calledTimes.should.eql(1)
          done()
        }, 5)
      })
  
      it('use without promise', (done) => {
        const result = 'noop-result'
        let calledTimes = 0
        yargs(['noop']).command('noop', 'a noop command', noop, () => {
          return result
        }).onFinishCommand((commandResult) => {
          commandResult.should.eql(result)
          calledTimes++
        })
          .parse('noop')
  
        setTimeout(() => {
          calledTimes.should.eql(1)
          done()
        }, 5)
      })
    })
  })
  