From 1cbf7e44f73909870283e1460a209df21d6c99e0 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 4 Dec 2021 19:02:54 +1300 Subject: [PATCH 01/21] Add defaultOption, but not implemented --- lib/command.js | 1 + lib/option.js | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/lib/command.js b/lib/command.js index 695c840ec..f32d8f11a 100644 --- a/lib/command.js +++ b/lib/command.js @@ -516,6 +516,7 @@ Expecting one of '${allowedValues.join("', '")}'`); const oname = option.name(); const name = option.attributeName(); + // Have three behaviours that overloaded: default value, starting value for custom parse, default optional value let defaultValue = option.defaultValue; // preassign default value for --no-*, [optional], , or plain flag if boolean value diff --git a/lib/option.js b/lib/option.js index 2ec204401..ee871417c 100644 --- a/lib/option.js +++ b/lib/option.js @@ -28,6 +28,7 @@ class Option { } this.defaultValue = undefined; this.defaultValueDescription = undefined; + this.defaultOptionalValue = undefined; this.envVar = undefined; this.parseArg = undefined; this.hidden = false; @@ -61,6 +62,25 @@ class Option { return this; }; + /** + * Value to use when an optional option is used without option-argument on command line. + * + * @param {optionalValue} name + * @return {Option} + */ + + defaultOptional(optionalValue) { + if (optionalValue === undefined) { + if (this.defaultOptionalValue !== undefined) { + return this.defaultOptionalValue; + } + return this.defaultValue; // Legacy behaviour + } + + this.defaultOptionaValue = optionalValue; + return this; + }; + /** * Set the custom handler for processing CLI option arguments into option values. * From 604f531b409cacb6ed10d054cfc02896f741d111 Mon Sep 17 00:00:00 2001 From: John Gee Date: Tue, 7 Dec 2021 18:36:49 +1300 Subject: [PATCH 02/21] Start refactoring addOption to clarify logic --- lib/command.js | 20 +++++++++++--------- lib/option.js | 15 +++++++++++++++ 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/lib/command.js b/lib/command.js index f32d8f11a..36165fa3c 100644 --- a/lib/command.js +++ b/lib/command.js @@ -516,17 +516,19 @@ Expecting one of '${allowedValues.join("', '")}'`); const oname = option.name(); const name = option.attributeName(); - // Have three behaviours that overloaded: default value, starting value for custom parse, default optional value - let defaultValue = option.defaultValue; - - // preassign default value for --no-*, [optional], , or plain flag if boolean value - if (option.negate || option.optional || option.required || typeof defaultValue === 'boolean') { + const defaultValue = option.defaultValue; + if (option.isBoolean()) { + if (typeof defaultValue === 'boolean') { + this.setOptionValueWithSource(name, defaultValue, 'default'); + } + // else magic, treated as implicit value ???? + } else if (option.negate) { // when --no-foo we make sure default is true, unless a --foo option is already defined - if (option.negate) { - const positiveLongFlag = option.long.replace(/^--no-/, '--'); - defaultValue = this._findOption(positiveLongFlag) ? this.getOptionValue(name) : true; + const positiveLongFlag = option.long.replace(/^--no-/, '--'); + if (!this._findOption(positiveLongFlag)) { + this.setOptionValueWithSource(name, true, 'default'); } - // preassign only if we have a default + } else if (option.optional || option.required) { if (defaultValue !== undefined) { this.setOptionValueWithSource(name, defaultValue, 'default'); } diff --git a/lib/option.js b/lib/option.js index ee871417c..5c94b74a4 100644 --- a/lib/option.js +++ b/lib/option.js @@ -186,6 +186,21 @@ class Option { is(arg) { return this.short === arg || this.long === arg; }; + + /** + * Return whether a boolean option. + * + * Process of elimination. Boolean if does not have a required argument, + * does not have an optional argument, and not a negated option. + * + * @param {string} arg + * @return {boolean} + * @api private + */ + + isBoolean(arg) { + return !this.required && !this.optional && !this.negate; + }; } /** From 7c483ec7bfec9ab262ce8dfaed544f2a6c0ffa79 Mon Sep 17 00:00:00 2001 From: John Gee Date: Tue, 7 Dec 2021 18:57:20 +1300 Subject: [PATCH 03/21] Update comments --- lib/command.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/command.js b/lib/command.js index 36165fa3c..a741f5766 100644 --- a/lib/command.js +++ b/lib/command.js @@ -516,19 +516,21 @@ Expecting one of '${allowedValues.join("', '")}'`); const oname = option.name(); const name = option.attributeName(); + // store default value if appropriate const defaultValue = option.defaultValue; if (option.isBoolean()) { + // support boolean default to allow setting from environment (predates .env()) if (typeof defaultValue === 'boolean') { this.setOptionValueWithSource(name, defaultValue, 'default'); } - // else magic, treated as implicit value ???? } else if (option.negate) { // when --no-foo we make sure default is true, unless a --foo option is already defined const positiveLongFlag = option.long.replace(/^--no-/, '--'); if (!this._findOption(positiveLongFlag)) { this.setOptionValueWithSource(name, true, 'default'); } - } else if (option.optional || option.required) { + } else { + // (option.optional || option.required) if (defaultValue !== undefined) { this.setOptionValueWithSource(name, defaultValue, 'default'); } From ed7b74d6563a070edf4d7ec773b281c15f93b0f2 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 12 Dec 2021 21:47:29 +1300 Subject: [PATCH 04/21] Implement separate default and preset for options --- lib/command.js | 46 ++++++++++++++++++++-------------------------- lib/option.js | 25 +++++++++++-------------- 2 files changed, 31 insertions(+), 40 deletions(-) diff --git a/lib/command.js b/lib/command.js index a741f5766..452dd7500 100644 --- a/lib/command.js +++ b/lib/command.js @@ -516,24 +516,15 @@ Expecting one of '${allowedValues.join("', '")}'`); const oname = option.name(); const name = option.attributeName(); - // store default value if appropriate - const defaultValue = option.defaultValue; - if (option.isBoolean()) { - // support boolean default to allow setting from environment (predates .env()) - if (typeof defaultValue === 'boolean') { - this.setOptionValueWithSource(name, defaultValue, 'default'); - } - } else if (option.negate) { - // when --no-foo we make sure default is true, unless a --foo option is already defined + // store default value + if (option.negate) { + // --no-foo is special and defaults to true, unless a --foo option is already defined const positiveLongFlag = option.long.replace(/^--no-/, '--'); if (!this._findOption(positiveLongFlag)) { this.setOptionValueWithSource(name, true, 'default'); } - } else { - // (option.optional || option.required) - if (defaultValue !== undefined) { - this.setOptionValueWithSource(name, defaultValue, 'default'); - } + } else if (option.defaultValue !== undefined) { + this.setOptionValueWithSource(name, option.defaultValue, 'default'); } // register the option @@ -541,13 +532,17 @@ Expecting one of '${allowedValues.join("', '")}'`); // handler for cli and env supplied values const handleOptionValue = (val, invalidValueMessage, valueSource) => { - // Note: using closure to access lots of lexical scoped variables. - const oldValue = this.getOptionValue(name); + // val is null for optional option used without an optional-argument. + // val is undefined for boolean and negated option. + if (val == null && option.presetArg !== undefined) { + val = option.presetArg; + } // custom processing + const oldValue = this.getOptionValue(name); if (val !== null && option.parseArg) { try { - val = option.parseArg(val, oldValue === undefined ? defaultValue : oldValue); + val = option.parseArg(val, oldValue); } catch (err) { if (err.code === 'commander.invalidArgument') { const message = `${invalidValueMessage} ${err.message}`; @@ -559,18 +554,17 @@ Expecting one of '${allowedValues.join("', '")}'`); val = option._concatValue(val, oldValue); } - // unassigned or boolean value - if (typeof oldValue === 'boolean' || typeof oldValue === 'undefined') { - // if no value, negate false, and we have a default, then use it! - if (val == null) { - this.setOptionValueWithSource(name, option.negate ? false : defaultValue || true, valueSource); + // Fill-in appropriate missing values. Long winded but easy to follow. + if (val == null) { + if (option.negate) { + val = false; + } else if (option.isBoolean() || option.optional) { + val = true; } else { - this.setOptionValueWithSource(name, val, valueSource); + val = ''; // not normal, parse might have failed or be a mock function for testing } - } else if (val !== null) { - // reassign - this.setOptionValueWithSource(name, option.negate ? false : val, valueSource); } + this.setOptionValueWithSource(name, val, valueSource); }; this.on('option:' + oname, (val) => { diff --git a/lib/option.js b/lib/option.js index 5c94b74a4..b9d0780fc 100644 --- a/lib/option.js +++ b/lib/option.js @@ -28,7 +28,7 @@ class Option { } this.defaultValue = undefined; this.defaultValueDescription = undefined; - this.defaultOptionalValue = undefined; + this.presetArg = undefined; this.envVar = undefined; this.parseArg = undefined; this.hidden = false; @@ -63,21 +63,19 @@ class Option { }; /** - * Value to use when an optional option is used without option-argument on command line. + * Preset to use instead of `true` for boolean option, or optional without an option argument. + * The custom processing (parseArg) is called. * - * @param {optionalValue} name + * @example + * new Option('--color').default('GREYSCALE').preset('RGB'); + * new Option('--donate [amount]').preset('20').argParser(parseFloat); + * + * @param {value} * @return {Option} */ - defaultOptional(optionalValue) { - if (optionalValue === undefined) { - if (this.defaultOptionalValue !== undefined) { - return this.defaultOptionalValue; - } - return this.defaultValue; // Legacy behaviour - } - - this.defaultOptionaValue = optionalValue; + preset(arg) { + this.presetArg = arg; return this; }; @@ -190,8 +188,7 @@ class Option { /** * Return whether a boolean option. * - * Process of elimination. Boolean if does not have a required argument, - * does not have an optional argument, and not a negated option. + * Options are one of boolean, negated, required argument, or optional argument. * * @param {string} arg * @return {boolean} From 693e6294a7afe715b389e2ec26df762b60169d5a Mon Sep 17 00:00:00 2001 From: John Gee Date: Tue, 14 Dec 2021 22:03:46 +1300 Subject: [PATCH 05/21] Changed behaviour of boolean default, fix and extend test --- tests/options.bool.combo.test.js | 51 +++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/tests/options.bool.combo.test.js b/tests/options.bool.combo.test.js index 5aed5724d..d9b02811c 100644 --- a/tests/options.bool.combo.test.js +++ b/tests/options.bool.combo.test.js @@ -96,28 +96,51 @@ describe('boolean option combo, default false, short flags', () => { }); }); -// This is a somewhat undocumented special behaviour which appears in some examples. -// When a flag has a non-boolean default, it is used as the value (only) when the flag is specified. -// -// boolean option combo with non-boolean default +// boolean option combo with non-boolean default. +// Changed behaviour to normal default in Commander 9. describe('boolean option combo with non-boolean default', () => { - test('when boolean combo not specified then value is undefined', () => { - const flagValue = 'red'; - const program = createPepperProgramWithDefault(flagValue); + test('when boolean combo not specified then value is default', () => { + const program = createPepperProgramWithDefault('default'); program.parse(['node', 'test']); - expect(program.opts().pepper).toBeUndefined(); + expect(program.opts().pepper).toBe('default'); + }); + + test('when boolean combo positive then value is true', () => { + const program = createPepperProgramWithDefault('default'); + program.parse(['node', 'test', '--pepper']); + expect(program.opts().pepper).toBe(true); + }); + + test('when boolean combo negative then value is false', () => { + const program = createPepperProgramWithDefault('default'); + program.parse(['node', 'test', '--no-pepper']); + expect(program.opts().pepper).toBe(false); + }); +}); + +describe('boolean option combo with non-boolean default and preset', () => { + function createPepperProgramWithDefaultAndPreset() { + const program = new commander.Command(); + program + .addOption(new commander.Option('-p, --pepper').default('default').preset('preset')) + .option('-P, --no-pepper', 'remove pepper'); + return program; + } + + test('when boolean combo not specified then value is default', () => { + const program = createPepperProgramWithDefaultAndPreset(); + program.parse(['node', 'test']); + expect(program.opts().pepper).toBe('default'); }); - test('when boolean combo positive then value is "default" value', () => { - const flagValue = 'red'; - const program = createPepperProgramWithDefault(flagValue); + test('when boolean combo positive then value is preset', () => { + const program = createPepperProgramWithDefaultAndPreset(); program.parse(['node', 'test', '--pepper']); - expect(program.opts().pepper).toBe(flagValue); + expect(program.opts().pepper).toBe('preset'); }); test('when boolean combo negative then value is false', () => { - const flagValue = 'red'; - const program = createPepperProgramWithDefault(flagValue); + const program = createPepperProgramWithDefaultAndPreset(); program.parse(['node', 'test', '--no-pepper']); expect(program.opts().pepper).toBe(false); }); From 154adba8b58033855082968f432daf5ffbdc7dfa Mon Sep 17 00:00:00 2001 From: John Gee Date: Wed, 15 Dec 2021 17:22:51 +1300 Subject: [PATCH 06/21] Update unit tests with new default/preset behaviour --- tests/options.bool.test.js | 12 +++++------- tests/options.optional.test.js | 12 ++++++++++-- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/tests/options.bool.test.js b/tests/options.bool.test.js index 0f4827fc8..b2776e99a 100644 --- a/tests/options.bool.test.js +++ b/tests/options.bool.test.js @@ -84,27 +84,25 @@ describe('boolean flag on command', () => { }); }); -// This is a somewhat undocumented special behaviour which appears in some examples. -// When a flag has a non-boolean default, it is used as the value (only) when the flag is specified. -// // boolean flag with non-boolean default +// NB: behaviour changed in Commander v9 to have default be default describe('boolean flag with non-boolean default', () => { - test('when flag not specified then value is undefined', () => { + test('when flag not specified then value is "default"', () => { const flagValue = 'black'; const program = new commander.Command(); program .option('--olives', 'Add olives? Sorry we only have black.', flagValue); program.parse(['node', 'test']); - expect(program.opts().olives).toBeUndefined(); + expect(program.opts().olives).toBe(flagValue); }); - test('when flag specified then value is "default" value', () => { + test('when flag specified then value is true', () => { const flagValue = 'black'; const program = new commander.Command(); program .option('-v, --olives', 'Add olives? Sorry we only have black.', flagValue); program.parse(['node', 'test', '--olives']); - expect(program.opts().olives).toBe(flagValue); + expect(program.opts().olives).toBe(true); }); test('when flag implied and negated then value is false', () => { diff --git a/tests/options.optional.test.js b/tests/options.optional.test.js index bf13dc9b6..a2ed727c1 100644 --- a/tests/options.optional.test.js +++ b/tests/options.optional.test.js @@ -59,12 +59,20 @@ describe('option with optional value, with default', () => { expect(program.opts().cheese).toBe(cheeseType); }); - test('when option specified without value then value is default', () => { + test('when option specified without value then value is true', () => { const defaultValue = 'default'; const program = new commander.Command(); program .option('--cheese [type]', 'cheese type', defaultValue); program.parse(['node', 'test', '--cheese']); - expect(program.opts().cheese).toBe(defaultValue); + expect(program.opts().cheese).toBe(true); + }); + + test('when option specified without value and preset then value is preset', () => { + const program = new commander.Command(); + program + .addOption(new commander.Option('--cheese [type]').preset('preset')); + program.parse(['node', 'test', '--cheese']); + expect(program.opts().cheese).toBe('preset'); }); }); From ffcbced63eebe0c9c1b60bf14f42c1e26389a97c Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 18 Dec 2021 10:09:23 +1300 Subject: [PATCH 07/21] Add tests for default with all option types --- lib/command.js | 4 +- tests/options.default.test.js | 119 ++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 tests/options.default.test.js diff --git a/lib/command.js b/lib/command.js index 452dd7500..67086c149 100644 --- a/lib/command.js +++ b/lib/command.js @@ -518,10 +518,10 @@ Expecting one of '${allowedValues.join("', '")}'`); // store default value if (option.negate) { - // --no-foo is special and defaults to true, unless a --foo option is already defined + // --no-foo is special and defaults foo to true, unless a --foo option is already defined const positiveLongFlag = option.long.replace(/^--no-/, '--'); if (!this._findOption(positiveLongFlag)) { - this.setOptionValueWithSource(name, true, 'default'); + this.setOptionValueWithSource(name, option.defaultValue === undefined ? true : option.defaultValue, 'default'); } } else if (option.defaultValue !== undefined) { this.setOptionValueWithSource(name, option.defaultValue, 'default'); diff --git a/tests/options.default.test.js b/tests/options.default.test.js new file mode 100644 index 000000000..67f780c30 --- /dev/null +++ b/tests/options.default.test.js @@ -0,0 +1,119 @@ +const { Command, Option } = require('../'); + +describe('.option() with default and option not specified in parse', () => { + test('when boolean option with boolean default then value is default', () => { + const program = new Command(); + program.option('-d, --debug', 'description', false); + program.parse([], { from: 'user' }); + expect(program.opts().debug).toBe(false); + }); + + test('when boolean option with number zero default then value is zero', () => { + const program = new Command(); + program.option('-d, --debug', 'description', 0); + program.parse([], { from: 'user' }); + expect(program.opts().debug).toBe(0); + }); + + test('when boolean option with string default then value is default', () => { + const program = new Command(); + program.option('-d, --debug', 'description', 'default'); + program.parse([], { from: 'user' }); + expect(program.opts().debug).toBe('default'); + }); + + test('when required option-argument and default number then value is default', () => { + const program = new Command(); + program.option('-p, --port ', 'description', 80); + program.parse([], { from: 'user' }); + expect(program.opts().port).toBe(80); + }); + + test('when required option-argument and default string then value is default', () => { + const program = new Command(); + program.option('-p, --port ', 'description', 'foo'); + program.parse([], { from: 'user' }); + expect(program.opts().port).toBe('foo'); + }); + + test('when optional option-argument and default number then value is default', () => { + const program = new Command(); + program.option('-p, --port [port-number]', 'description', 80); + program.parse([], { from: 'user' }); + expect(program.opts().port).toBe(80); + }); + + test('when optional option-argument and default string then value is default', () => { + const program = new Command(); + program.option('-p, --port [port-number]', 'description', 'foo'); + program.parse([], { from: 'user' }); + expect(program.opts().port).toBe('foo'); + }); + + test('when negated and default string then value is default', () => { + // Bit tricky thinking about what default means for a negated option, but treat as with other options. + const program = new Command(); + program.option('--no-colour', 'description', 'RGB'); + program.parse([], { from: 'user' }); + expect(program.opts().colour).toBe('RGB'); + }); +}); + +describe('Option with default and option not specified in parse', () => { + test('when boolean option with boolean default then value is default', () => { + const program = new Command(); + program.addOption(new Option('-d, --debug').default(false)); + program.parse([], { from: 'user' }); + expect(program.opts().debug).toBe(false); + }); + + test('when boolean option with number zero default then value is zero', () => { + const program = new Command(); + program.addOption(new Option('-d, --debug').default(0)); + program.parse([], { from: 'user' }); + expect(program.opts().debug).toBe(0); + }); + + test('when boolean option with string default then value is default', () => { + const program = new Command(); + program.addOption(new Option('-d, --debug').default('default')); + program.parse([], { from: 'user' }); + expect(program.opts().debug).toBe('default'); + }); + + test('when required option-argument and default number then value is default', () => { + const program = new Command(); + program.addOption(new Option('-p, --port ').default(80)); + program.parse([], { from: 'user' }); + expect(program.opts().port).toBe(80); + }); + + test('when required option-argument and default string then value is default', () => { + const program = new Command(); + program.addOption(new Option('-p, --port ').default('foo')); + program.parse([], { from: 'user' }); + expect(program.opts().port).toBe('foo'); + }); + + test('when optional option-argument and default number then value is default', () => { + const program = new Command(); + program.addOption(new Option('-p, --port [port-number]').default(80)); + program.parse([], { from: 'user' }); + expect(program.opts().port).toBe(80); + }); + + test('when optional option-argument and default string then value is default', () => { + const program = new Command(); + program.addOption(new Option('-p, --port [port-number]').default('foo')); + program.parse([], { from: 'user' }); + expect(program.opts().port).toBe('foo'); + }); + + test('when negated and default string then value is default', () => { + // Bit tricky thinking about what default means for a negated option, but treat as with other options. + const program = new Command(); + program.addOption(new Option('--no-colour').default('RGB')); + program.parse([], { from: 'user' }); + expect(program.opts().colour).toBe('RGB'); + }); +}); From d15519daf31066b293c960bfa925d6f3e43f77ec Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 18 Dec 2021 10:20:25 +1300 Subject: [PATCH 08/21] Add tests that default does get overwritten --- tests/options.default.test.js | 74 +++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/tests/options.default.test.js b/tests/options.default.test.js index 67f780c30..39b9d86b0 100644 --- a/tests/options.default.test.js +++ b/tests/options.default.test.js @@ -117,3 +117,77 @@ describe('Option with default and option not specified in parse', () => { expect(program.opts().colour).toBe('RGB'); }); }); + +// Fairly obvious this needs to happen, but was broken for optional in past! +describe('default overwritten by specified option', () => { + test('when boolean option with boolean default then value is true', () => { + const program = new Command(); + program.option('-d, --debug', 'description', false); + program.parse(['-d'], { from: 'user' }); + expect(program.opts().debug).toBe(true); + }); + + test('when boolean option with number zero default then value is true', () => { + const program = new Command(); + program.option('-d, --debug', 'description', 0); + program.parse(['-d'], { from: 'user' }); + expect(program.opts().debug).toBe(true); + }); + + test('when boolean option with string default then value is true', () => { + const program = new Command(); + program.option('-d, --debug', 'description', 'default'); + program.parse(['-d'], { from: 'user' }); + expect(program.opts().debug).toBe(true); + }); + + test('when required option-argument and default number then value is from args', () => { + const program = new Command(); + program.option('-p, --port ', 'description', 80); + program.parse(['-p', '1234'], { from: 'user' }); + expect(program.opts().port).toBe('1234'); + }); + + test('when required option-argument and default string then value is from args', () => { + const program = new Command(); + program.option('-p, --port ', 'description', 'foo'); + program.parse(['-p', '1234'], { from: 'user' }); + expect(program.opts().port).toBe('1234'); + }); + + test('when optional option-argument and default number and option-argument specified then value is from args', () => { + const program = new Command(); + program.option('-p, --port [port-number]', 'description', 80); + program.parse(['-p', '1234'], { from: 'user' }); + expect(program.opts().port).toBe('1234'); + }); + + test('when optional option-argument and default string and option-argument specified then value is from args', () => { + const program = new Command(); + program.option('-p, --port [port-number]', 'description', 'foo'); + program.parse(['-p', '1234'], { from: 'user' }); + expect(program.opts().port).toBe('1234'); + }); + + test('when optional option-argument and default number and option-argument not specified then value is true', () => { + const program = new Command(); + program.option('-p, --port [port-number]', 'description', 80); + program.parse(['-p'], { from: 'user' }); + expect(program.opts().port).toBe(true); + }); + + test('when optional option-argument and default string and option-argument not specified then value is true', () => { + const program = new Command(); + program.option('-p, --port [port-number]', 'description', 'foo'); + program.parse(['-p'], { from: 'user' }); + expect(program.opts().port).toBe(true); + }); + + test('when negated and default string then value is false', () => { + // Bit tricky thinking about what default means for a negated option, but treat as with other options. + const program = new Command(); + program.option('--no-colour', 'description', 'RGB'); + program.parse(['--no-colour'], { from: 'user' }); + expect(program.opts().colour).toBe(false); + }); +}); From c2186c00688bdf71479d04172ce73c296efb20f5 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 18 Dec 2021 10:37:44 +1300 Subject: [PATCH 09/21] Fix JSDoc for new routines --- lib/command.js | 2 +- lib/option.js | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/command.js b/lib/command.js index 67086c149..3122ace28 100644 --- a/lib/command.js +++ b/lib/command.js @@ -561,7 +561,7 @@ Expecting one of '${allowedValues.join("', '")}'`); } else if (option.isBoolean() || option.optional) { val = true; } else { - val = ''; // not normal, parse might have failed or be a mock function for testing + val = ''; // not normal, parseArg might have failed or be a mock function for testing } } this.setOptionValueWithSource(name, val, valueSource); diff --git a/lib/option.js b/lib/option.js index b9d0780fc..e8aa401d1 100644 --- a/lib/option.js +++ b/lib/option.js @@ -70,7 +70,7 @@ class Option { * new Option('--color').default('GREYSCALE').preset('RGB'); * new Option('--donate [amount]').preset('20').argParser(parseFloat); * - * @param {value} + * @param {any} arg * @return {Option} */ @@ -190,12 +190,11 @@ class Option { * * Options are one of boolean, negated, required argument, or optional argument. * - * @param {string} arg * @return {boolean} * @api private */ - isBoolean(arg) { + isBoolean() { return !this.required && !this.optional && !this.negate; }; } From b72b73edcdd7dd3b3069686deea9f634f0cd3087 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 18 Dec 2021 11:01:36 +1300 Subject: [PATCH 10/21] Add tests for preset --- tests/options.preset.test.js | 50 ++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 tests/options.preset.test.js diff --git a/tests/options.preset.test.js b/tests/options.preset.test.js new file mode 100644 index 000000000..0442ef45b --- /dev/null +++ b/tests/options.preset.test.js @@ -0,0 +1,50 @@ +const { Command, Option } = require('../'); + +test('when boolean option with string preset used then value is preset', () => { + const program = new Command(); + program.addOption(new Option('-d, --debug').preset('foo')); + program.parse(['-d'], { from: 'user' }); + expect(program.opts().debug).toBe('foo'); +}); + +test('when boolean option with number preset used then value is preset', () => { + const program = new Command(); + program.addOption(new Option('-d, --debug').preset(80)); + program.parse(['-d'], { from: 'user' }); + expect(program.opts().debug).toBe(80); +}); + +test('when optional with string preset used then value is preset', () => { + const program = new Command(); + program.addOption(new Option('-p, --port [port]').preset('foo')); + program.parse(['-p'], { from: 'user' }); + expect(program.opts().port).toBe('foo'); +}); + +test('when optional with number preset used then value is preset', () => { + const program = new Command(); + program.addOption(new Option('-p, --port [port]').preset(80)); + program.parse(['-p'], { from: 'user' }); + expect(program.opts().port).toBe(80); +}); + +test('when optional with string preset used with option-argument then value is as specified', () => { + const program = new Command(); + program.addOption(new Option('-p, --port [port]').preset('foo')); + program.parse(['-p', '1234'], { from: 'user' }); + expect(program.opts().port).toBe('1234'); +}); + +test('when optional with preset and coerce used then preset is coerced', () => { + const program = new Command(); + program.addOption(new Option('-p, --port [port]').preset('4').argParser(parseFloat)); + program.parse(['-p'], { from: 'user' }); + expect(program.opts().port).toBe(4); +}); + +test('when optional with preset and variadic used then preset is concatenated', () => { + const program = new Command(); + program.addOption(new Option('-n, --name [name...]').preset('two')); + program.parse(['-n', 'one', '-n', '-n', 'three'], { from: 'user' }); + expect(program.opts().name).toEqual(['one', 'two', 'three']); +}); From 3f96d6ab7a3955b9c25d0b1c8b511b1efb8c3c14 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 18 Dec 2021 13:32:09 +1300 Subject: [PATCH 11/21] Add typings --- lib/option.js | 26 +++++++++++++------------- typings/index.d.ts | 20 ++++++++++++++++++++ typings/index.test-d.ts | 7 +++++++ 3 files changed, 40 insertions(+), 13 deletions(-) diff --git a/lib/option.js b/lib/option.js index e8aa401d1..bb356eaee 100644 --- a/lib/option.js +++ b/lib/option.js @@ -49,19 +49,6 @@ class Option { return this; }; - /** - * Set environment variable to check for option value. - * Priority order of option values is default < env < cli - * - * @param {string} name - * @return {Option} - */ - - env(name) { - this.envVar = name; - return this; - }; - /** * Preset to use instead of `true` for boolean option, or optional without an option argument. * The custom processing (parseArg) is called. @@ -79,6 +66,19 @@ class Option { return this; }; + /** + * Set environment variable to check for option value. + * Priority order of option values is default < env < cli + * + * @param {string} name + * @return {Option} + */ + + env(name) { + this.envVar = name; + return this; + }; + /** * Set the custom handler for processing CLI option arguments into option values. * diff --git a/typings/index.d.ts b/typings/index.d.ts index ee4fd4944..68b493492 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -99,6 +99,18 @@ export class Option { */ default(value: unknown, description?: string): this; + /** + * Preset to use instead of `true` for boolean option, or optional without an option argument. + * The custom processing (parseArg) is called. + * + * @example + * ```ts + * new Option('--color').default('GREYSCALE').preset('RGB'); + * new Option('--donate [amount]').preset('20').argParser(parseFloat); + * ``` + */ + preset(arg: unknown): this; + /** * Set environment variable to check for option value. * Priority order of option values is default < env < cli @@ -140,6 +152,14 @@ export class Option { * as a object attribute key. */ attributeName(): string; + + /** + * Return whether a boolean option. + * + * Options are one of boolean, negated, required argument, or optional argument. + */ + isBoolean(): boolean; + } export class Help { diff --git a/typings/index.test-d.ts b/typings/index.test-d.ts index d83c198c1..cce236c36 100644 --- a/typings/index.test-d.ts +++ b/typings/index.test-d.ts @@ -355,6 +355,10 @@ const baseOption = new commander.Option('-f,--foo', 'foo description'); expectType(baseOption.default(3)); expectType(baseOption.default(60, 'one minute')); +// preset +expectType(baseOption.preset(123)); +expectType(baseOption.preset('abc')); + // env expectType(baseOption.env('PORT')); @@ -383,6 +387,9 @@ expectType(baseOption.name()); // attributeName expectType(baseOption.attributeName()); +// isBoolean +expectType(baseOption.isBoolean()); + // Argument properties const baseArgument = new commander.Argument('(baseArgument.description); From 4bddfe537f0c321ed40b5197850b99f963adc94f Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 18 Dec 2021 14:47:03 +1300 Subject: [PATCH 12/21] Update README --- Readme.md | 8 +++++--- examples/options-extra.js | 5 ++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Readme.md b/Readme.md index 6347ebe97..8585623ac 100644 --- a/Readme.md +++ b/Readme.md @@ -146,7 +146,7 @@ pizza details: ### Default option value -You can specify a default value for an option which takes a value. +You can specify a default value for an option. Example file: [options-defaults.js](./examples/options-defaults.js) @@ -172,7 +172,7 @@ You can define a boolean option long name with a leading `no-` to set the option Defined alone this also makes the option true by default. If you define `--foo` first, adding `--no-foo` does not change the default value from what it would -otherwise be. You can specify a default boolean value for a boolean option and it can be overridden on command line. +otherwise be. Example file: [options-negatable.js](./examples/options-negatable.js) @@ -312,7 +312,8 @@ program .addOption(new Option('-s, --secret').hideHelp()) .addOption(new Option('-t, --timeout ', 'timeout in seconds').default(60, 'one minute')) .addOption(new Option('-d, --drink ', 'drink size').choices(['small', 'medium', 'large'])) - .addOption(new Option('-p, --port ', 'port number').env('PORT')); + .addOption(new Option('-p, --port ', 'port number').env('PORT')) + .addOption(new Option('--donate [amount]').preset('20').argParser(parseFloat)); ``` ```bash @@ -323,6 +324,7 @@ Options: -t, --timeout timeout in seconds (default: one minute) -d, --drink drink cup size (choices: "small", "medium", "large") -p, --port port number (env: PORT) + --donate [amount] -h, --help display help for command $ extra --drink huge diff --git a/examples/options-extra.js b/examples/options-extra.js index 19dc8b63a..dac9ca98e 100644 --- a/examples/options-extra.js +++ b/examples/options-extra.js @@ -11,7 +11,8 @@ program .addOption(new Option('-s, --secret').hideHelp()) .addOption(new Option('-t, --timeout ', 'timeout in seconds').default(60, 'one minute')) .addOption(new Option('-d, --drink ', 'drink cup size').choices(['small', 'medium', 'large'])) - .addOption(new Option('-p, --port ', 'port number').env('PORT')); + .addOption(new Option('-p, --port ', 'port number').env('PORT')) + .addOption(new Option('--donate [amount]').preset('20').argParser(parseFloat)); program.parse(); @@ -21,3 +22,5 @@ console.log('Options: ', program.opts()); // node options-extra.js --help // node options-extra.js --drink huge // PORT=80 node options-extra.js +// node options-extra.js --donate +// node options-extra.js --donate 30.50 From d60649380446c0734213dd541a24aa3cfded0d6e Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 18 Dec 2021 15:11:43 +1300 Subject: [PATCH 13/21] Add test of preset with negated --- tests/options.preset.test.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/options.preset.test.js b/tests/options.preset.test.js index 0442ef45b..e3d25b6bf 100644 --- a/tests/options.preset.test.js +++ b/tests/options.preset.test.js @@ -48,3 +48,10 @@ test('when optional with preset and variadic used then preset is concatenated', program.parse(['-n', 'one', '-n', '-n', 'three'], { from: 'user' }); expect(program.opts().name).toEqual(['one', 'two', 'three']); }); + +test('when negated with string preset used then value is preset', () => { + const program = new Command(); + program.addOption(new Option('--no-colour').preset('foo')); + program.parse(['--no-colour'], { from: 'user' }); + expect(program.opts().colour).toBe('foo'); +}); From 8ea7136b76ea956e712d240cd4a0aca267db806f Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 18 Dec 2021 15:18:08 +1300 Subject: [PATCH 14/21] Update comments --- tests/options.bool.test.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/options.bool.test.js b/tests/options.bool.test.js index b2776e99a..e82008202 100644 --- a/tests/options.bool.test.js +++ b/tests/options.bool.test.js @@ -85,13 +85,14 @@ describe('boolean flag on command', () => { }); // boolean flag with non-boolean default -// NB: behaviour changed in Commander v9 to have default be default +// NB: behaviour changed in Commander v9 to have default be default. +// These tests no longer match likely uses, but retained and updated to match current behaviour. describe('boolean flag with non-boolean default', () => { test('when flag not specified then value is "default"', () => { const flagValue = 'black'; const program = new commander.Command(); program - .option('--olives', 'Add olives? Sorry we only have black.', flagValue); + .option('--olives', 'Add green olives?', flagValue); program.parse(['node', 'test']); expect(program.opts().olives).toBe(flagValue); }); @@ -100,16 +101,16 @@ describe('boolean flag with non-boolean default', () => { const flagValue = 'black'; const program = new commander.Command(); program - .option('-v, --olives', 'Add olives? Sorry we only have black.', flagValue); + .option('-v, --olives', 'Add green olives?', flagValue); program.parse(['node', 'test', '--olives']); expect(program.opts().olives).toBe(true); }); - test('when flag implied and negated then value is false', () => { + test('when combo flag and negated then value is false', () => { const flagValue = 'black'; const program = new commander.Command(); program - .option('-v, --olives', 'Add olives? Sorry we only have black.', flagValue) + .option('-v, --olives', 'Add green olives?', flagValue) .option('--no-olives'); program.parse(['node', 'test', '--olives', '--no-olives']); expect(program.opts().olives).toBe(false); From 86aa16dd89b8715cd5fa4d048f198a370d7949d7 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 18 Dec 2021 15:38:07 +1300 Subject: [PATCH 15/21] Add tests of options being used twice --- tests/options.twice.test.js | 46 +++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 tests/options.twice.test.js diff --git a/tests/options.twice.test.js b/tests/options.twice.test.js new file mode 100644 index 000000000..9fa68ed06 --- /dev/null +++ b/tests/options.twice.test.js @@ -0,0 +1,46 @@ +const { Command, Option } = require('../'); + +// Test that when option specified twice, second use wins. +// Seems pretty obvious for boolean options, but there was a bug before Commander v9. + +test('when boolean option used twice then value is true', () => { + const program = new Command(); + program.option('-d, --debug'); + program.parse(['-d', '-d'], { from: 'user' }); + expect(program.opts().debug).toBe(true); +}); + +test('when boolean option with default used twice then value is true', () => { + const program = new Command(); + program.option('-d, --debug', 'description', 'foo'); + program.parse(['-d', '-d'], { from: 'user' }); + expect(program.opts().debug).toBe(true); +}); + +test('when boolean option with preset used twice then value is preset', () => { + const program = new Command(); + program.addOption(new Option('-d, --debug').preset('foo')); + program.parse(['-d', '-d'], { from: 'user' }); + expect(program.opts().debug).toBe('foo'); +}); + +test('when option with required argument used twice then value is from second use', () => { + const program = new Command(); + program.option('-p, --port '); + program.parse(['-p', '1', '-p', '2'], { from: 'user' }); + expect(program.opts().port).toBe('2'); +}); + +test('when option with optional argument used second time without value then value is true', () => { + const program = new Command(); + program.option('--donate [amount]'); + program.parse(['--donate', '123', '--donate'], { from: 'user' }); + expect(program.opts().donate).toBe(true); +}); + +test('when option with optional argument used second time with value then value is from second use', () => { + const program = new Command(); + program.option('--donate [amount]'); + program.parse(['--donate', '--donate', '123'], { from: 'user' }); + expect(program.opts().donate).toBe('123'); +}); From fe4c3ca255f589ce630d562c59f15704ba73c236 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 18 Dec 2021 16:18:45 +1300 Subject: [PATCH 16/21] Modify documentation for preset --- lib/option.js | 2 +- typings/index.d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/option.js b/lib/option.js index bb356eaee..e6d75cfa2 100644 --- a/lib/option.js +++ b/lib/option.js @@ -50,7 +50,7 @@ class Option { }; /** - * Preset to use instead of `true` for boolean option, or optional without an option argument. + * Preset to use when option used without option-argument, especially optional but also boolean and negated. * The custom processing (parseArg) is called. * * @example diff --git a/typings/index.d.ts b/typings/index.d.ts index d03348974..81d6f5a53 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -100,7 +100,7 @@ export class Option { default(value: unknown, description?: string): this; /** - * Preset to use instead of `true` for boolean option, or optional without an option argument. + * Preset to use when option used without option-argument, especially optional but also boolean and negated. * The custom processing (parseArg) is called. * * @example From d5afd611d4443460403aeee2ee8fd333553c8514 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 19 Dec 2021 09:58:11 +1300 Subject: [PATCH 17/21] Add prefix example usage in README --- Readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Readme.md b/Readme.md index 8585623ac..2d500e716 100644 --- a/Readme.md +++ b/Readme.md @@ -330,8 +330,8 @@ Options: $ extra --drink huge error: option '-d, --drink ' argument 'huge' is invalid. Allowed choices are small, medium, large. -$ PORT=80 extra -Options: { timeout: 60, port: '80' } +$ PORT=80 extra --donate +Options: { timeout: 60, donate: 20, port: '80' } ``` ### Custom option processing From 9c0f0360234d1c333587ed012d0cbf4fbf77434d Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 19 Dec 2021 10:44:45 +1300 Subject: [PATCH 18/21] Add preset to help --- Readme.md | 2 +- examples/options-extra.js | 2 +- lib/help.js | 3 +++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Readme.md b/Readme.md index 2d500e716..f7ae70fdb 100644 --- a/Readme.md +++ b/Readme.md @@ -324,7 +324,7 @@ Options: -t, --timeout timeout in seconds (default: one minute) -d, --drink drink cup size (choices: "small", "medium", "large") -p, --port port number (env: PORT) - --donate [amount] + --donate [amount] optional donation in dollars (preset: 20) -h, --help display help for command $ extra --drink huge diff --git a/examples/options-extra.js b/examples/options-extra.js index dac9ca98e..62c78debe 100644 --- a/examples/options-extra.js +++ b/examples/options-extra.js @@ -12,7 +12,7 @@ program .addOption(new Option('-t, --timeout ', 'timeout in seconds').default(60, 'one minute')) .addOption(new Option('-d, --drink ', 'drink cup size').choices(['small', 'medium', 'large'])) .addOption(new Option('-p, --port ', 'port number').env('PORT')) - .addOption(new Option('--donate [amount]').preset('20').argParser(parseFloat)); + .addOption(new Option('--donate [amount]', 'optional donation in dollars').preset('20').argParser(parseFloat)); program.parse(); diff --git a/lib/help.js b/lib/help.js index 2187f2b97..9908e784f 100644 --- a/lib/help.js +++ b/lib/help.js @@ -245,6 +245,9 @@ class Help { if (option.defaultValue !== undefined && !option.negate) { extraInfo.push(`default: ${option.defaultValueDescription || JSON.stringify(option.defaultValue)}`); } + if (option.presetArg !== undefined) { + extraInfo.push(`preset: ${option.presetArg}`); + } if (option.envVar !== undefined) { extraInfo.push(`env: ${option.envVar}`); } From 1426bb4903019a44bea512b8e620cd0ef109ab1f Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 19 Dec 2021 10:47:51 +1300 Subject: [PATCH 19/21] Update code example --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index f7ae70fdb..89a7a1f63 100644 --- a/Readme.md +++ b/Readme.md @@ -313,7 +313,7 @@ program .addOption(new Option('-t, --timeout ', 'timeout in seconds').default(60, 'one minute')) .addOption(new Option('-d, --drink ', 'drink size').choices(['small', 'medium', 'large'])) .addOption(new Option('-p, --port ', 'port number').env('PORT')) - .addOption(new Option('--donate [amount]').preset('20').argParser(parseFloat)); + .addOption(new Option('--donate [amount]', 'optional donation in dollars').preset('20').argParser(parseFloat)); ``` ```bash From fce2be889b93ec00aacc7eb0d59d3ffc7e61b2d6 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 19 Dec 2021 10:53:19 +1300 Subject: [PATCH 20/21] Use same format for preset as for default, add test --- lib/help.js | 2 +- tests/help.optionDescription.test.js | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/help.js b/lib/help.js index 9908e784f..3ad22e2d4 100644 --- a/lib/help.js +++ b/lib/help.js @@ -246,7 +246,7 @@ class Help { extraInfo.push(`default: ${option.defaultValueDescription || JSON.stringify(option.defaultValue)}`); } if (option.presetArg !== undefined) { - extraInfo.push(`preset: ${option.presetArg}`); + extraInfo.push(`preset: ${JSON.stringify(option.presetArg)}`); } if (option.envVar !== undefined) { extraInfo.push(`env: ${option.envVar}`); diff --git a/tests/help.optionDescription.test.js b/tests/help.optionDescription.test.js index 2653d1d99..71c998320 100644 --- a/tests/help.optionDescription.test.js +++ b/tests/help.optionDescription.test.js @@ -24,6 +24,13 @@ describe('optionDescription', () => { expect(helper.optionDescription(option)).toEqual('description (default: "default")'); }); + test('when option has preset value then return description and default value', () => { + const description = 'description'; + const option = new commander.Option('-a', description).preset('abc'); + const helper = new commander.Help(); + expect(helper.optionDescription(option)).toEqual('description (preset: "abc")'); + }); + test('when option has env then return description and env name', () => { const description = 'description'; const option = new commander.Option('-a', description).env('ENV'); From 9eec174417c0e2b6e551b18bef3654ddc9cb0350 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 19 Dec 2021 14:21:14 +1300 Subject: [PATCH 21/21] Be selective about default and preset shown in help --- lib/help.js | 16 +++++++++++----- tests/help.optionDescription.test.js | 27 ++++++++++++++++++++------- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/lib/help.js b/lib/help.js index 3ad22e2d4..ceab59f36 100644 --- a/lib/help.js +++ b/lib/help.js @@ -235,17 +235,23 @@ class Help { optionDescription(option) { const extraInfo = []; - // Some of these do not make sense for negated boolean and suppress for backwards compatibility. - if (option.argChoices && !option.negate) { + if (option.argChoices) { extraInfo.push( // use stringify to match the display of the default value `choices: ${option.argChoices.map((choice) => JSON.stringify(choice)).join(', ')}`); } - if (option.defaultValue !== undefined && !option.negate) { - extraInfo.push(`default: ${option.defaultValueDescription || JSON.stringify(option.defaultValue)}`); + if (option.defaultValue !== undefined) { + // default for boolean and negated more for programmer than end user, + // but show true/false for boolean option as may be for hand-rolled env or config processing. + const showDefault = option.required || option.optional || + (option.isBoolean() && typeof option.defaultValue === 'boolean'); + if (showDefault) { + extraInfo.push(`default: ${option.defaultValueDescription || JSON.stringify(option.defaultValue)}`); + } } - if (option.presetArg !== undefined) { + // preset for boolean and negated are more for programmer than end user + if (option.presetArg !== undefined && option.optional) { extraInfo.push(`preset: ${JSON.stringify(option.presetArg)}`); } if (option.envVar !== undefined) { diff --git a/tests/help.optionDescription.test.js b/tests/help.optionDescription.test.js index 71c998320..f5ca167a8 100644 --- a/tests/help.optionDescription.test.js +++ b/tests/help.optionDescription.test.js @@ -17,20 +17,33 @@ describe('optionDescription', () => { expect(helper.optionDescription(option)).toEqual(description); }); - test('when option has default value then return description and default value', () => { + test('when boolean option has default value true then return description and default value', () => { const description = 'description'; - const option = new commander.Option('-a', description).default('default'); + const option = new commander.Option('-a', description).default(true); const helper = new commander.Help(); - expect(helper.optionDescription(option)).toEqual('description (default: "default")'); + expect(helper.optionDescription(option)).toEqual('description (default: true)'); }); - test('when option has preset value then return description and default value', () => { + test('when boolean option has default value string then return description without default', () => { const description = 'description'; - const option = new commander.Option('-a', description).preset('abc'); + const option = new commander.Option('-a', description).default('foo'); + const helper = new commander.Help(); + expect(helper.optionDescription(option)).toEqual('description'); + }); + + test('when optional option has preset value then return description and default value', () => { + const description = 'description'; + const option = new commander.Option('--aa [value]', description).preset('abc'); const helper = new commander.Help(); expect(helper.optionDescription(option)).toEqual('description (preset: "abc")'); }); + test('when boolean option has preset value then return description without default', () => { + const description = 'description'; + const option = new commander.Option('--bb', description).preset('abc'); + const helper = new commander.Help(); + expect(helper.optionDescription(option)).toEqual('description'); + }); test('when option has env then return description and env name', () => { const description = 'description'; const option = new commander.Option('-a', description).env('ENV'); @@ -41,7 +54,7 @@ describe('optionDescription', () => { test('when option has default value description then return description and custom default description', () => { const description = 'description'; const defaultValueDescription = 'custom'; - const option = new commander.Option('-a', description).default('default value', defaultValueDescription); + const option = new commander.Option('-a ', description).default('default value', defaultValueDescription); const helper = new commander.Help(); expect(helper.optionDescription(option)).toEqual(`description (default: ${defaultValueDescription})`); }); @@ -49,7 +62,7 @@ describe('optionDescription', () => { test('when option has choices then return description and choices', () => { const description = 'description'; const choices = ['one', 'two']; - const option = new commander.Option('-a', description).choices(choices); + const option = new commander.Option('-a ', description).choices(choices); const helper = new commander.Help(); expect(helper.optionDescription(option)).toEqual('description (choices: "one", "two")'); });