'use strict' /* global describe, it, beforeEach, after */ const checkUsage = require('../build/test/helpers/utils').checkOutput const yargs = require('../') /* polyfill Promise for older Node.js */ require('es6-promise').polyfill() require('chai').should() describe('Completion', () => { beforeEach(() => { yargs.reset() }) after(() => { yargs.reset() }) describe('default completion behavior', () => { it('avoids repeating already included commands', () => { process.env.SHELL = '/bin/bash' const r = checkUsage(() => yargs(['./completion', '--get-yargs-completions', 'apple']) .command('foo', 'bar') .command('apple', 'banana') .argv ) // should not suggest foo for completion unless foo is subcommand of apple r.logs.should.not.include('apple') }) it('avoids repeating already included options', () => { const r = checkUsage(() => yargs(['./completion', '--get-yargs-completions', './completion', '--foo', '--']) .options({ foo: { describe: 'foo option' }, bar: { describe: 'bar option' } }) .completion() .argv ) r.logs.should.include('--bar') r.logs.should.not.include('--foo') }) it('avoids repeating options whose aliases are already included', () => { const r = checkUsage(() => yargs(['./completion', '--get-yargs-completions', './completion', '--f', '--']) .options({ foo: { describe: 'foo option', alias: 'f' }, bar: { describe: 'bar option' } }) .completion() .argv ) r.logs.should.include('--bar') r.logs.should.not.include('--foo') }) it('completes short options with a single dash when the user did not already enter two dashes', () => { const r = checkUsage(() => yargs(['./completion', '--get-yargs-completions', './completion', '']) .options({ f: { describe: 'f option', alias: 'foo' } }) .argv ) r.logs.should.include('-f') r.logs.should.not.include('--f') }) it('completes short options with two dashes when the user already entered two dashes', () => { const r = checkUsage(() => yargs(['./completion', '--get-yargs-completions', './completion', '--']) .options({ f: { describe: 'f option', alias: 'foo' } }) .argv ) r.logs.should.include('--f') r.logs.should.not.include('-f') }) it('completes single digit options with two dashes', () => { const r = checkUsage(() => yargs(['./completion', '--get-yargs-completions', './completion', '']) .options({ 1: { describe: '1 option', alias: 'one' } }) .argv ) r.logs.should.include('--1') r.logs.should.not.include('-1') }) it('completes with no- prefix flags defaulting to true when boolean-negation is set', () => { const r = checkUsage(() => yargs(['./completion', '--get-yargs-completions', './completion', '']) .options({ foo: { describe: 'foo flag', type: 'boolean', default: true }, bar: { describe: 'bar flag', type: 'boolean' } }) .parserConfiguration({ 'boolean-negation': true }) .argv ) r.logs.should.include('--no-foo') r.logs.should.include('--foo') r.logs.should.not.include('--no-bar') r.logs.should.include('--bar') }) it('avoids repeating flags whose negated counterparts are already included', () => { const r = checkUsage(() => yargs(['./completion', '--get-yargs-completions', './completion', '--no-foo', '--no-bar', '']) .options({ foo: { describe: 'foo flag', type: 'boolean', default: true }, bar: { describe: 'bar flag', type: 'boolean' }, baz: { describe: 'bar flag', type: 'boolean' } }) .parserConfiguration({ 'boolean-negation': true }) .argv ) r.logs.should.not.include('--no-foo') r.logs.should.not.include('--foo') r.logs.should.not.include('--no-bar') r.logs.should.not.include('--bar') r.logs.should.include('--baz') }) it('ignores no- prefix flags when boolean-negation is not set', () => { const r = checkUsage(() => yargs(['./completion', '--get-yargs-completions', './completion', '--no-bar', '']) .options({ foo: { describe: 'foo flag', type: 'boolean', default: true }, bar: { describe: 'bar flag', type: 'boolean' } }) .argv ) r.logs.should.not.include('--no-foo') r.logs.should.include('--foo') r.logs.should.not.include('--no-bar') r.logs.should.include('--bar') }) it('completes options for the correct command', () => { process.env.SHELL = '/bin/bash' const r = checkUsage(() => yargs(['./completion', '--get-yargs-completions', 'cmd2', '--o']) .help(false) .version(false) .command('cmd1', 'first command', (subYargs) => { subYargs.options({ opt1: { describe: 'first option' } }) }) .command('cmd2', 'second command', (subYargs) => { subYargs.options({ opt2: { describe: 'second option' } }) }) .completion() .argv ) r.logs.should.have.length(1) r.logs.should.include('--opt2') }) it('does not complete hidden commands', () => { process.env.SHELL = '/bin/bash' const r = checkUsage(() => yargs(['./completion', '--get-yargs-completions', 'cmd']) .command('cmd1', 'first command') .command('cmd2', false) .completion('completion', false) .argv ) r.logs.should.have.length(1) r.logs.should.include('cmd1') }) it('does not include positional arguments', function () { process.env.SHELL = '/bin/bash' var r = checkUsage(function () { return yargs(['./completion', '--get-yargs-completions', 'cmd']) .command('cmd1 [arg]', 'first command') .command('cmd2 ', 'second command') .completion('completion', false) .argv }) r.logs.should.have.length(2) r.logs.should.include('cmd1') r.logs.should.include('cmd2') }) it('works if command has no options', () => { process.env.SHELL = '/bin/bash' const r = checkUsage(() => yargs(['./completion', '--get-yargs-completions', 'foo', '--b']) .help(false) .version(false) .command('foo', 'foo command', (subYargs) => { return subYargs.completion().parse() }) .completion() .argv ) r.logs.should.have.length(0) }) it("returns arguments as completion suggestion, if next contains '-'", () => { process.env.SHELL = '/bin/basg' const r = checkUsage(() => yargs(['./usage', '--get-yargs-completions', '-f']) .option('foo', { describe: 'foo option' }) .command('bar', 'bar command') .completion() .argv ) r.logs.should.include('--foo') r.logs.should.not.include('bar') }) }) describe('generateCompletionScript()', () => { it('replaces completion command variable with custom completion command in script', () => { const r = checkUsage(() => yargs([]).completion('flintlock') .showCompletionScript(), ['ndm']) r.logs[0].should.match(/ndm flintlock >>/) }) it('if $0 has a .js extension, a ./ prefix is added', () => { const r = checkUsage(() => yargs([]) .showCompletionScript(), ['test.js']) r.logs[0].should.match(/\.\/test.js --get-yargs-completions/) }) it('allows $0 and cmd to be set', () => { const r = checkUsage(() => yargs([]) .showCompletionScript('/path/to/my/app', 'show-completions-script')) r.logs[0].should.match(/Installation: \/path\/to\/my\/app show-completions-script/) }) }) describe('completion()', () => { it('shows completion script if command registered with completion(cmd) is called', () => { const r = checkUsage(() => yargs(['completion']) .completion('completion') .argv, ['ndm']) r.logs[0].should.match(/ndm --get-yargs-completions/) }) it('allows a custom function to be registered for completion', () => { const r = checkUsage(() => yargs(['--get-yargs-completions']) .help('h') .completion('completion', (current, argv) => ['cat', 'bat']) .argv ) r.logs.should.include('cat') r.logs.should.include('bat') }) it('passes current arg for completion and the parsed arguments thus far to custom function', () => { const r = checkUsage(() => yargs(['ndm', '--get-yargs-completions', '--cool', 'ma']) .completion('completion', (current, argv) => { if (current === 'ma' && argv.cool) return ['success!'] }) .argv ) r.logs.should.include('success!') }) it('if a promise is returned, completions can be asynchronous', (done) => { checkUsage((cb) => { yargs(['--get-yargs-completions']) .completion('completion', (current, argv) => new Promise((resolve, reject) => { setTimeout(() => { resolve(['apple', 'banana']) }, 10) })) .parse() }, null, (err, r) => { if (err) throw err r.logs.should.include('apple') r.logs.should.include('banana') return done() }) }) it('if a promise is returned, errors are handled', (done) => { checkUsage(() => { yargs(['--get-yargs-completions']) .completion('completion', (current, argv) => new Promise((resolve, reject) => { setTimeout(() => { reject(new Error('Test')) }, 10) })) .parse() }, null, (err) => { err.message.should.equal('Test') return done() }) }) it('if a callback parameter is provided, completions can be asynchronous', (done) => { checkUsage(() => { yargs(['--get-yargs-completions']) .completion('completion', (current, argv, completion) => { setTimeout(() => { completion(['apple', 'banana']) }, 10) }) .parse() }, null, (err, r) => { if (err) throw err r.logs.should.include('apple') r.logs.should.include('banana') return done() }) }) }) describe('bash', () => { it('returns a list of commands as completion suggestions', () => { process.env.SHELL = '/bin/bash' const r = checkUsage(() => yargs(['./completion', '--get-yargs-completions', '']) .command('foo', 'bar') .command('apple', 'banana') .completion() .argv ) r.logs.should.include('apple') r.logs.should.include('foo') }) it('avoids interruption from command recommendations', () => { process.env.SHELL = '/bin/bash' const r = checkUsage(() => yargs(['./completion', '--get-yargs-completions', './completion', 'a']) .command('apple', 'fruit') .command('aardvark', 'animal') .recommendCommands() .completion() .argv ) r.errors.should.deep.equal([]) r.logs.should.include('apple') r.logs.should.include('aardvark') }) it('avoids interruption from default command', () => { process.env.SHELL = '/bin/bash' const r = checkUsage(() => yargs(['./usage', '--get-yargs-completions', './usage', '']) .usage('$0 [thing]', 'skipped', subYargs => { subYargs.command('aardwolf', 'is a thing according to google') }) .command('aardvark', 'animal') .completion() .argv ) r.errors.should.deep.equal([]) r.logs.should.not.include('aardwolf') r.logs.should.include('aardvark') }) it('completes options for a command', () => { process.env.SHELL = '/bin/bash' const r = checkUsage(() => yargs(['./completion', '--get-yargs-completions', 'foo', '--b']) .command('foo', 'foo command', subYargs => subYargs.options({ bar: { describe: 'bar option' } }) .help(true) .version(false)) .completion() .argv ) r.logs.should.have.length(2) r.logs.should.include('--bar') r.logs.should.include('--help') }) describe('generateCompletionScript()', () => { it('replaces application variable with $0 in script', () => { process.env.SHELL = '/bin/bash' const r = checkUsage(() => yargs([]) .showCompletionScript(), ['ndm']) r.logs[0].should.match(/bashrc/) r.logs[0].should.match(/ndm --get-yargs-completions/) }) }) // fixes for #177. it('does not apply validation when --get-yargs-completions is passed in', () => { process.env.SHELL = '/bin/bash' const r = checkUsage(() => { try { return yargs(['./completion', '--get-yargs-completions', '--']) .option('foo', {}) .completion() .strict() .argv } catch (e) { console.log(e.message) } }) r.errors.length.should.equal(0) r.logs.should.include('--foo') }) describe('getCompletion()', () => { it('returns default completion to callback', () => { process.env.SHELL = '/bin/bash' const r = checkUsage(() => { yargs() .command('foo', 'bar') .command('apple', 'banana') .completion() .getCompletion([''], (completions) => { ;(completions || []).forEach((completion) => { console.log(completion) }) }) }) r.logs.should.include('apple') r.logs.should.include('foo') }) it('returns default completion to callback for options', () => { process.env.SHELL = '/bin/bash' const r = checkUsage(() => { yargs() .option('apple') .option('foo') .completion() .getCompletion(['$0', '-'], (completions) => { ;(completions || []).forEach((completion) => { console.log(completion) }) }) }) r.logs.should.include('--apple') r.logs.should.include('--foo') }) }) }) describe('zsh', () => { it('returns a list of commands as completion suggestions', () => { process.env.SHELL = '/bin/zsh' const r = checkUsage(() => yargs(['./completion', '--get-yargs-completions', '']) .command('foo', 'bar') .command('apple', 'banana') .completion() .argv ) r.logs.should.include('apple:banana') r.logs.should.include('foo:bar') }) it('avoids interruption from command recommendations', () => { process.env.SHELL = '/bin/zsh' const r = checkUsage(() => yargs(['./completion', '--get-yargs-completions', './completion', 'a']) .command('apple', 'fruit') .command('aardvark', 'animal') .recommendCommands() .completion() .argv ) r.errors.should.deep.equal([]) r.logs.should.include('apple:fruit') r.logs.should.include('aardvark:animal') }) it('avoids interruption from default command', () => { process.env.SHELL = '/bin/zsh' const r = checkUsage(() => yargs(['./usage', '--get-yargs-completions', './usage', '']) .usage('$0 [thing]', 'skipped', subYargs => { subYargs.command('aardwolf', 'is a thing according to google') }) .command('aardvark', 'animal') .completion() .argv ) r.errors.should.deep.equal([]) r.logs.should.not.include('aardwolf') r.logs.should.include('aardvark:animal') }) it('completes options for a command', () => { process.env.SHELL = '/bin/zsh' const r = checkUsage(() => yargs(['./completion', '--get-yargs-completions', 'foo', '--b']) .command('foo', 'foo command', subYargs => subYargs.options({ bar: { describe: 'bar option' } }) .help(true) .version(false)) .completion() .argv ) r.logs.should.have.length(2) r.logs.should.include('--bar:bar option') r.logs.should.include('--help:Show help') }) it('replaces application variable with $0 in script', () => { process.env.SHELL = '/bin/zsh' const r = checkUsage(() => yargs([]) .showCompletionScript(), ['ndm']) r.logs[0].should.match(/zshrc/) r.logs[0].should.match(/ndm --get-yargs-completions/) }) describe('getCompletion()', () => { it('returns default completion to callback', () => { process.env.SHELL = '/bin/zsh' const r = checkUsage(() => { yargs() .command('foo', 'bar') .command('apple', 'banana') .completion() .getCompletion([''], (completions) => { ;(completions || []).forEach((completion) => { console.log(completion) }) }) }) r.logs.should.include('apple:banana') r.logs.should.include('foo:bar') }) }) it('does not apply validation when --get-yargs-completions is passed in', () => { process.env.SHELL = '/bin/zsh' const r = checkUsage(() => { try { return yargs(['./completion', '--get-yargs-completions', '--']) .option('foo', { describe: 'bar' }) .completion() .strict() .argv } catch (e) { console.log(e.message) } }) r.errors.length.should.equal(0) r.logs.should.include('--foo:bar') }) it('bails out early when full command matches', () => { process.env.SHELL = '/bin/zsh' const r = checkUsage(() => { try { return yargs(['./completion', '--get-yargs-completions', 'dream']) .commandDir('./fixtures/cmddir', { recurse: true }) .demand(1) .strict() .completion() .argv } catch (e) { console.log(e.message) } }) r.errors.length.should.equal(0) r.logs.should.include('of-memory:Dream about a specific memory') }) }) })