Skip to main content
Module

x/yargs/test/yargs.cjs

yargs the modern, pirate-themed successor to optimist.
Go to Latest
File
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232
'use strict';/* global context, describe, it, beforeEach, afterEach *//* eslint-disable no-unused-vars */
const assert = require('assert');const expect = require('chai').expect;const fs = require('fs');const path = require('path');const checkOutput = require('./helpers/utils.cjs').checkOutput;const english = require('../locales/en.json');let yargs;require('chai').should();
const noop = () => {};async function wait() { return new Promise(resolve => { setTimeout(resolve, 10); });}const implicationsFailedPattern = new RegExp(english['Implications failed:']);
function clearRequireCache() { delete require.cache[require.resolve('../index.cjs')]; delete require.cache[require.resolve('../build/index.cjs')];}function isPromise(maybePromise) { return typeof maybePromise.then === 'function';}
describe('yargs dsl tests', () => { const oldProcess = {versions: {}};
beforeEach(() => { process.execPath = 'node'; process._ = 'node'; process.argv[1] = 'node';
oldProcess.argv = process.argv; oldProcess.defaultApp = process.defaultApp; oldProcess.versions.electron = process.versions.electron; yargs = require('../index.cjs'); });
afterEach(() => { process.argv = oldProcess.argv; process.defaultApp = oldProcess.defaultApp; process.versions.electron = oldProcess.versions.electron; clearRequireCache(); });
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('should not remove the 1st argument of bundled electron apps', () => { clearRequireCache(); process.argv = ['/usr/local/bin/app', '-f', 'toto', 'tutu']; process.versions.electron = '10.0.0-nightly.20200211'; yargs = require('../index.cjs'); const argv = yargs.parse(); argv.should.have.property('f'); argv.f.should.equal('toto'); argv._.should.deep.equal(['tutu']); });
it('should remove the 1st argument of unbundled electron apps', () => { clearRequireCache(); process.argv = ['/usr/local/bin/electron', 'app.js', '-f', 'toto', 'tutu']; process.versions.electron = '10.0.0-nightly.20200211'; // Same syntax as in electron Object.defineProperty(process, 'defaultApp', { configurable: false, enumerable: true, value: true, }); yargs = require('../index.cjs'); const argv = yargs.parse(); argv.should.have.property('f'); argv.f.should.equal('toto'); argv._.should.deep.equal(['tutu']); });
it('accepts an object for aliases', () => { const argv = yargs([]) .alias({ cool: 'cat', }) .default('cool', 33) .parse();
argv.cat.should.eql(33); });
it('does not populate 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') .getInternalMethods() .reset();
const emptyOptions = { array: [], boolean: ['help', 'version'], string: [], alias: {}, default: {}, key: {help: true, version: true}, narg: {}, defaultDescription: {}, choices: {}, skipValidation: [], count: [], normalize: [], number: [], config: {}, configObjects: [], envPrefix: 'YARGS', // preserved as global hiddenOptions: [], demandedCommands: {}, demandedOptions: {}, deprecatedOptions: {}, local: ['_', 'foo', 'qux'], };
expect(y.getOptions()).to.deep.equal(emptyOptions); expect( y.getInternalMethods().getUsageInstance().getDescriptions() ).to.deep.equal({ help: '__yargsString__:Show help', version: '__yargsString__:Show version number', }); expect( y.getInternalMethods().getValidationInstance().getImplied() ).to.deep.equal({}); expect( y.getInternalMethods().getValidationInstance().getConflicting() ).to.deep.equal({}); expect( y.getInternalMethods().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.getInternalMethods().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', done => { yargs(['foo']) .command( 'foo', 'handle foo things', () => {}, 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/); });
it('counts tranposition as one mistake', () => { const r = checkOutput(() => { yargs(['baot']) .command('boat') .command('bot') .recommendCommands() .parse(); });
r.errors[2].should.match(/Did you mean boat/); });
// 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', () => { if (process.stdout.isTTY) { yargs.terminalWidth().should.be.gte(0); } else { expect(yargs.terminalWidth()).to.equal(null); } }); });
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) { clearRequireCache(); yargs = require('../index.cjs'); 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/); });
// Addresses: https://github.com/yargs/yargs/issues/2178 it('does not enter infinite loop when locale is invalid', () => { // Save env vars const lcAll = process.env.LC_ALL; const lcMessages = process.env.LC_MESSAGES; const lang = process.env.LANG; const language = process.env.LANGUAGE; // Change delete process.env.LC_ALL; delete process.env.LC_MESSAGES; process.env.LANG = '.UTF-8'; delete process.env.LANGUAGE; try { yargs .command({ command: 'cmd1', desc: 'cmd1 desc', builder: () => {}, handler: _argv => {}, }) .parse(); } catch { expect.fail(); } finally { // Restore process.env.LC_ALL = lcAll; process.env.LC_MESSAGES = lcMessages; process.env.LANG = lang; process.env.LANGUAGE = language; } });
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('also works on default option group', () => { const r = checkOutput(() => { yargs(['-h']) .help('h') .wrap(null) .updateLocale({ 'Options:': 'OPTIONS!', }) .parse(); });
r.logs.join(' ').should.match(/OPTIONS!/); });
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', () => { const 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([ 'node batman <api-token>', '', 'batman command', '', 'Options:', ' --help Show help [boolean]', ' --version Show version number [boolean]', ]);
output2 .split('\n') .should.deep.equal([ 'node 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([ 'node 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([ 'node 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([ 'node <command>', '', 'Commands:', ' node 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', () => { const {YError} = require('../build/index.cjs'); expect(() => { yargs.config({extends: './test/fixtures/extends/circular_1.json'}); }).to.throw(YError); });
it('handles absolute 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') .getInternalMethods() .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') .getInternalMethods() .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) .getInternalMethods() .reset(); const descriptions = y .getInternalMethods() .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) .getInternalMethods() .reset(); const implied = y .getInternalMethods() .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, }) .getInternalMethods() .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') .defaults('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'), ]) .alias('file', 'f') .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); });
it('throws error if coerce callback is missing', () => { assert.throws(() => { yargs().coerce(['a', 'b']); }, /coerce callback must be provided/); assert.throws(() => { yargs().coerce('c'); }, /coerce callback must be provided/); });
// Refs: https://github.com/yargs/yargs/issues/1909 it('shows coerced option in help', async () => { const help = await yargs() .option('option1', { describe: 'option1 description', type: 'string', demandOption: true, }) .option('option2', { describe: 'option2 description', type: 'string', demandOption: true, }) .coerce('option2', () => undefined) .getHelp(); help.should.match(/option2 description/); }); });
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(() => { clearRequireCache(); yargs = require('../index.cjs'); });
it('should track commands being executed', () => { let context; yargs('one two') .command( 'one', 'level one', yargs => { context = yargs.getInternalMethods().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: arg => 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('allows positionals to be defined for default command', async () => { const help = await yargs() .command('* [foo]', 'default command') .positional('foo', { default: 33, type: 'number', }) .getHelp(); help.should.include('default: 33'); help.should.include('default command'); });
// 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']); });
// See: https://github.com/yargs/yargs/issues/1098 it('should allow array and requires arg to be used in conjunction', () => { const argv = yargs(['-i', 'item1', 'item2', 'item3']).option('i', { alias: 'items', type: 'array', requiresArg: true, }).argv; argv.items.should.eql(['item1', 'item2', 'item3']); argv.i.should.eql(['item1', 'item2', 'item3']); });
// See: https://github.com/yargs/yargs/issues/1570 describe('"nargs" with "array"', () => { it('should not consume more than nargs items', () => { const argv = yargs([ '-i', 'item1', 'item2', '-i', 'item3', 'item4', ]).option('i', { alias: 'items', type: 'array', nargs: 1, }).argv; argv.items.should.eql(['item1', 'item3']); argv.i.should.eql(['item1', 'item3']); argv._.should.eql(['item2', 'item4']); });
it('should apply nargs with higher precedence than requiresArg: true', () => { const argv = yargs([ '-i', 'item1', 'item2', '-i', 'item3', 'item4', ]).option('i', { alias: 'items', type: 'array', nargs: 1, requiresArg: true, }).argv; argv.items.should.eql(['item1', 'item3']); argv.i.should.eql(['item1', 'item3']); argv._.should.eql(['item2', 'item4']); });
// TODO: make this work with aliases, using a check similar to // checkAllAliases() in yargs-parser. it('should apply nargs with higher precedence than requiresArg()', () => { const argv = yargs(['-i', 'item1', 'item2', '-i', 'item3', 'item4']) .option('items', { alias: 'i', type: 'array', nargs: 1, }) .requiresArg(['items']).argv; argv.items.should.eql(['item1', 'item3']); argv.i.should.eql(['item1', 'item3']); argv._.should.eql(['item2', 'item4']); });
it('should raise error if not enough values follow nargs key', done => { yargs .option('i', { alias: 'items', type: 'array', nargs: 1, }) .parse(['-i'], err => { err.message.should.match(/Not enough arguments following: i/); return done(); }); }); });
// See: https://github.com/nodejs/node/issues/31951 describe('should not pollute the prototype', () => { it('does not pollute, when .parse() is called', () => { yargs.parse([ '-f.__proto__.foo', '99', '-x.y.__proto__.bar', '100', '--__proto__', '200', ]); Object.keys({}.__proto__).length.should.equal(0) // eslint-disable-line expect({}.foo).to.equal(undefined); expect({}.bar).to.equal(undefined); });
it('does not pollute, when .argv is called', () => { yargs(['-f.__proto__.foo', '99', '-x.y.__proto__.bar', '100', '--__proto__', '200']).argv // eslint-disable-line Object.keys({}.__proto__).length.should.equal(0) // eslint-disable-line expect({}.foo).to.equal(undefined); expect({}.bar).to.equal(undefined); });
// TODO(bcoe): due to replacement of __proto__ with ___proto___ parser // hints are not properly applied, we should move to an alternate approach // in the future: it('does not pollute, when options are set', () => { yargs .option('__proto__', { describe: 'pollute pollute', nargs: 33, }) .default('__proto__', {hello: 'world'}) .parse(['--foo']); Object.keys({}.__proto__).length.should.equal(0) // eslint-disable-line }); });
describe('parsing --value as a value in -f=--value and --bar=--value', () => { it('should work in the general case', () => { const argv = yargs([ '-f=--item1', 'item2', '--bar=--item3', 'item4', ]).argv; argv.f.should.eql('--item1'); argv.bar.should.eql('--item3'); argv._.should.eql(['item2', 'item4']); });
it('should work with array', () => { const argv = yargs([ '-f=--item1', 'item2', '--bar=--item3', 'item4', ]).array(['f', 'bar']).argv; argv.f.should.eql(['--item1', 'item2']); argv.bar.should.eql(['--item3', 'item4']); argv._.should.eql([]); });
it('should work with nargs', () => { const argv = yargs([ '-f=--item1', 'item2', 'item3', '--bar=--item4', 'item5', 'item6', ]) .option('f', {nargs: 2}) .option('bar', {nargs: 2}).argv; argv.f.should.eql(['--item1', 'item2']); argv.bar.should.eql(['--item4', 'item5']); argv._.should.eql(['item3', 'item6']); });
it('should work with both array and nargs', () => { const argv = yargs([ '-f=--item1', 'item2', '-f', 'item3', 'item4', '--bar=--item5', 'item6', '--bar', 'item7', 'item8', ]).option('f', {alias: 'bar', array: true, nargs: 1}).argv; argv.f.should.eql(['--item1', 'item3', '--item5', 'item7']); argv.bar.should.eql(['--item1', 'item3', '--item5', 'item7']); argv._.should.eql(['item2', 'item4', 'item6', 'item8']); }); });
it('throws error for unsupported Node.js versions', () => { process.env.YARGS_MIN_NODE_VERSION = '55'; clearRequireCache(); expect(() => { require('../index.cjs'); }).to.throw(/yargs supports a minimum Node.js version of 55/); delete process.env.YARGS_MIN_NODE_VERSION; });
// Handling of strings that look like numbers, see: // https://github.com/yargs/yargs/issues/1758 describe('bug #1758', () => { it('does not drop .0 if flag is configured as string', () => { const argv = yargs('cmd --foo 33.0') .command('cmd [str]', 'a command', yargs => { return yargs.option('foo', { type: 'string', }); }) .parse(); argv.foo.should.equal('33.0'); });
it('does not drop .0 if positional is configured as string', () => { const argv = yargs('cmd 33.0') .command('cmd [str]', 'a command', yargs => { return yargs.positional('str', { type: 'string', }); }) .parse(); argv.str.should.equal('33.0'); });
it('continues to parse values in _ as numbers, when they look like numbers', () => { const argv = yargs('hello 33.0').parse(); argv._[0].should.equal('hello'); argv._[1].should.equal(33); }); });
// See: https://github.com/yargs/yargs/issues/1420 describe('async', () => { describe('parse', () => { it('calls parse callback once async handler has resolved', done => { let executionCount = 0; yargs() .command( 'cmd [str]', 'a command', () => {}, async argv => { return new Promise(resolve => { setTimeout(() => { argv.addedAsync = 99; executionCount++; return resolve(argv); }, 5); }); } ) .parse('cmd foo', async (_err, argv) => { argv.addedAsync.should.equal(99); argv.str.should.equal('foo'); executionCount.should.equal(1); return done(); }); }); it('calls parse callback once deeply nested promise has resolved', done => { let executionCount = 0; yargs() .command( 'cmd', 'a command', yargs => { yargs.command( 'foo [apple]', 'foo command', () => {}, async argv => { return new Promise(resolve => { setTimeout(() => { argv.addedAsync = 99; executionCount++; return resolve(argv); }, 5); }); } ); }, () => {} ) .parse('cmd foo orange', async (_err, argv) => { argv.addedAsync.should.equal(99); argv.apple.should.equal('orange'); executionCount.should.equal(1); return done(); }); }); it('populates err with async rejection', done => { let executionCount = 0; yargs() .command( 'cmd [str]', 'a command', () => {}, async argv => { return new Promise((resolve, reject) => { setTimeout(() => { executionCount++; return reject(Error('async error')); }, 5); }); } ) .parse('cmd foo', async err => { err.message.should.equal('async error'); executionCount.should.equal(1); return done(); }); }); it('caches nested help output, so that it can be output by showHelp()', done => { let executionCount = 0; const y = yargs(); y.command( 'cmd [str]', 'a command', () => {}, async argv => { return new Promise((resolve, reject) => { setTimeout(() => { executionCount++; return reject(Error('async error')); }, 5); }); } ).parse('cmd foo', async (_err, argv) => { y.showHelp(output => { output.should.match(/a command/); executionCount.should.equal(1); return done(); }); }); }); it('caches deeply nested help output, so that it can be output by showHelp()', done => { let executionCount = 0; const y = yargs(); y.command( 'cmd', 'a command', yargs => { yargs.command( 'inner [foo]', 'inner command', () => {}, async argv => { return new Promise((resolve, reject) => { setTimeout(() => { executionCount++; return reject(Error('async error')); }, 5); }); } ); }, () => {} ).parse('cmd inner bar', async (_err, argv) => { y.showHelp(output => { output.should.match(/inner command/); executionCount.should.equal(1); return done(); }); }); }); }); describe('argv', () => { it('returns promise that resolves argv on success', async () => { let executionCount = 0; const argvPromise = yargs('cmd foo').command( 'cmd [str]', 'a command', () => {}, async argv => { return new Promise(resolve => { setTimeout(() => { argv.addedAsync = 99; executionCount++; return resolve(); }, 5); }); } ).argv; (typeof argvPromise.then).should.equal('function'); const argv = await argvPromise; argv.addedAsync.should.equal(99); argv.str.should.equal('foo'); executionCount.should.equal(1); }); it('returns deeply nested promise that resolves argv on success', async () => { let executionCount = 0; const argvPromise = yargs('cmd foo orange') .command( 'cmd', 'a command', yargs => { yargs.command( 'foo [apple]', 'foo command', () => {}, async argv => { return new Promise(resolve => { setTimeout(() => { argv.addedAsync = 99; executionCount++; return resolve(); }, 5); }); } ); }, () => {} ) .parse(); (typeof argvPromise.then).should.equal('function'); const argv = await argvPromise; argv.addedAsync.should.equal(99); argv.apple.should.equal('orange'); executionCount.should.equal(1); }); it('returns promise that can be caught if rejected', async () => { let executionCount = 0; const argv = yargs('cmd foo') .fail(false) .command( 'cmd [str]', 'a command', () => {}, async argv => { return new Promise((resolve, reject) => { setTimeout(() => { executionCount++; return reject(Error('async error')); }, 5); }); } ).argv; (typeof argv.then).should.equal('function'); try { await argv; throw Error('unreachable'); } catch (err) { err.message.should.equal('async error'); executionCount.should.equal(1); } }); it('caches nested help output, so that it can be output by showHelp()', async () => { let executionCount = 0; const y = yargs(); const argv = y .fail(false) .command( 'cmd [str]', 'a command', () => {}, async argv => { return new Promise((resolve, reject) => { setTimeout(() => { executionCount++; return reject(Error('async error')); }, 5); }); } ) .parse('cmd foo'); (typeof argv.then).should.equal('function'); try { await argv; throw Error('unreachable'); } catch (err) { const output = await y.getHelp(); output.should.match(/a command/); executionCount.should.equal(1); } }); it('caches deeply nested help output, so that it can be output by showHelp()', async () => { let executionCount = 0; const y = yargs('cmd inner bar'); const argv = y.fail(false).command( 'cmd', 'a command', yargs => { yargs.command( 'inner [foo]', 'inner command', () => {}, async argv => { return new Promise((resolve, reject) => { setTimeout(() => { executionCount++; return reject(Error('async error')); }, 5); }); } ); }, () => {} ).argv; (typeof argv.then).should.equal('function'); try { await argv; throw Error('unreachable'); } catch (err) { const output = await y.getHelp(); output.should.match(/inner command/); executionCount.should.equal(1); } }); }); });
describe('getHelp', () => { it('should run parse() and return help, if parse() not yet called', async () => { const y = yargs(['--foo']) .options('foo', { alias: 'f', describe: 'foo option', }) .wrap(null); const help = await y.getHelp(); help .split('\n') .should.deep.equal([ 'Options:', ' --help Show help [boolean]', ' --version Show version number [boolean]', ' -f, --foo foo option', ]); }); it('should display top-level help with no command given', async () => { const y = yargs('--help') .command( ['list [pattern]', 'ls', '*'], 'List key-value pairs for pattern', {}, noop ) .command('get <key>', 'Get value for key', {}, noop) .command('set <key> [value]', 'Set value for key', {}, noop); const help = await y.getHelp(); help .split('\n') .should.deep.equal([ 'node [pattern]', '', 'List key-value pairs for pattern', '', 'Commands:', ' node list [pattern] List key-value pairs for pattern', ' [default] [aliases: ls]', ' node get <key> Get value for key', ' node set <key> [value] Set value for key', '', 'Options:', ' --help Show help [boolean]', ' --version Show version number [boolean]', ]); }); it('should allow help to be output for failed command', async () => { const y = yargs('foo') .command( 'foo', 'foo command', () => {}, async () => { return new Promise((resolve, reject) => { setTimeout(() => { return reject(Error('async failure')); }); }); } ) .fail(false); try { await y.argv; throw Error('unreachable'); } catch (err) { err.message.should.equal('async failure'); (await y.getHelp()).should.match(/foo command/); } }); it('should allow help to be output for successful command', async () => { const y = yargs('foo').command( 'foo', 'foo command', () => {}, async argv => { return new Promise((resolve, reject) => { setTimeout(() => { argv.addedAsync = 99; return resolve(); }); }); } ); const argv = await y.argv; (await y.getHelp()).should.match(/foo command/); argv.addedAsync.should.equal(99); argv._.should.eql(['foo']); }); it('should not run handler or middleware', async () => { let commandCalled = false; let middlewareCalled = false; const y = yargs('foo') .command( 'foo', 'foo command', () => {}, async argv => { commandCalled = true; } ) .middleware(() => { middlewareCalled = true; }); (await y.getHelp()).should.match(/foo command/); commandCalled.should.equal(false); middlewareCalled.should.equal(false); }); // Refs: #1853 it('should use cached help message for nested synchronous commands', async () => { const y = yargs('object').command('object', 'object command', yargs => { yargs.command('get', 'get command'); }); const argv = y.argv; const help = await y.getHelp(); help.should.match(/node object get/); argv._.should.eql(['object']); }); it('should return appropriate help message when async builder used', async () => { const help = await yargs('foo') .command( 'foo [bar]', 'foo command', async yargs => { wait(); return yargs.positional('foo', { demand: true, default: 'hello', type: 'string', }); }, async argv => {} ) .getHelp(); help.should.match(/default: "hello"/); help.should.match(/foo command/); }); }); describe('getters', () => { it('has getter for strict commands', () => { const y1 = yargs('foo').strictCommands(); const y2 = yargs('bar'); assert.strictEqual(y1.getStrictCommands(), true); assert.strictEqual(y2.getStrictCommands(), false); }); it('has getter for strict options', () => { const y1 = yargs('foo').strictOptions(); const y2 = yargs('bar'); assert.strictEqual(y1.getStrictOptions(), true); assert.strictEqual(y2.getStrictOptions(), false); }); }); describe('parseAsync', () => { it('returns promise when parse is synchronous', () => { const argv = yargs('foo').parseAsync(); assert.strictEqual(isPromise(argv), true); }); it('returns promise when parse is asynchronous', async () => { const argv = yargs('--foo bar') .middleware(async () => { await wait(); }) .parseAsync(); assert.strictEqual(isPromise(argv), true); assert.strictEqual((await argv).foo, 'bar'); }); }); describe('parseSync', () => { it('succeeds if no async functions used during parsing', () => { const argv = yargs('foo 33') .command( 'foo [bar]', 'foo command', () => {}, () => {} ) .middleware(argv => { argv.bar *= 2; }) .parseSync(); assert.strictEqual(argv.bar, 66); }); it('throws if any async method is used', () => { assert.throws(() => { yargs('foo 33') .command( 'foo [bar]', 'foo command', () => {}, () => {} ) .middleware(async argv => { argv.bar *= 2; }) .parseSync(); }, /.*parseSync\(\) must not be used.*/); }); });});