Skip to content

Commit

Permalink
feat(prompt): New if-no-arg prompt option (#17)
Browse files Browse the repository at this point in the history
Authored-By: iamturns <matt@iamturns.com>

* Add new `if-no-arg` prompt option
* Update README.md explaining new prompt option
* Add tests for each prompt option
  • Loading branch information
iamturns authored and nanovazquez committed Apr 4, 2019
1 parent 6a504b0 commit c91a74e
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 67 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,11 @@ And then simply call your CLI with no parameters.
| type | string | _(Required)_ The type of the option to prompt (e.g. `input`, `confirm`, etc.). **We provide all prompt types supported by [Inquirer](https://github.com/SBoudrias/Inquirer.js/#prompt-types).**|
| describe | string | _(Required)_ The message to display when prompting the option (e.g. `Do you like pizza?`) |
| default | any | The default value of the option. |
| prompt | string | _(Default is `if-empty`)_ Property to decide whether to prompt the option or not. Possible values: `always`, `never` and `if-empty`, which prompts the option if the value wasn't set via command line parameters or using the default property. |
| prompt | string | _(Default is `if-empty`)_ Property to decide whether to prompt the option or not. Possible values: `always`, `never`, `if-no-arg` (prompts if the option was not sent via command line parameters) and `if-empty` (prompts if the value was not sent via command line parameters and it doesn't have a default property). |

### Prompt just some questions (mixed mode)

You can opt-out options from interactive mode by setting the `prompt` property to `never`. By default, its value is `if-empty`, prompting the question to the user if the value was not set via command line parameters or it doesn't have a default property. Last, you can use `always` to always prompt the option.
You can opt-out options from interactive mode by setting the `prompt` property to `never`. By default, its value is `if-empty`, prompting the question to the user if the value was not set via command line parameters or it doesn't have a default property. Setting to `if-no-arg` will only prompt the question if no argument is provided. Lastly, you can use `always` to always prompt the option.

**my-cli.js**
```js
Expand Down
9 changes: 9 additions & 0 deletions src/is-args-provided.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* Checks if the argument received is provided in the argument collection
* @param {Object} arg - The arg to check (e.g. `likesPizza`)
* @param {Object} processArgs - The collection of process arguments (e.g. `["--interactive", "--likesPizza=true"]`)
* @return {boolean} - Returns true if the argument is present in the collection of process arguments, false otherwise.
*/
module.exports = (arg, processArgs) => {
return processArgs.some((argProvided) => argProvided === `--${arg}` || argProvided.startsWith(`--${arg}=`));
};
25 changes: 12 additions & 13 deletions src/yargs-interactive.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const yargs = require('yargs');
const interactiveMode = require('./interactive-mode');
const filterObject = require('./filter-object');
const isEmpty = require('./is-empty');
const isArgProvided = require('./is-args-provided');

// Set up yargs options
let yargsInteractive = (processArgs = process.argv.slice(2), cwd) => {
Expand All @@ -10,22 +11,16 @@ let yargsInteractive = (processArgs = process.argv.slice(2), cwd) => {
// Add interactive functionality
yargsConfig.interactive = (options = {}) => {
// Merge options sent by parameters with interactive option
const mergedOptions = Object.assign(
{},
options,
{
interactive: {
default: !!(options.interactive && options.interactive.default),
prompt: 'never',
}
const mergedOptions = Object.assign({}, options, {
interactive: {
default: !!(options.interactive && options.interactive.default),
prompt: 'never'
}
);
});

// Run yargs with interactive option
// and get the requested arguments
const argv = yargsConfig
.options(mergedOptions)
.argv;
const argv = yargsConfig.options(mergedOptions).argv;

// Filter options to prompt based on the "if-empty" property
const interactiveOptions = filterObject(mergedOptions, (item, key) => {
Expand All @@ -39,6 +34,11 @@ let yargsInteractive = (processArgs = process.argv.slice(2), cwd) => {
return true;
}

// Prompt items that are set with 'if-no-arg' and values were not send via command line parameters
if (item.prompt === 'if-no-arg') {
return !isArgProvided(key, processArgs);
}

// Cases: item.prompt === "if-empty" or item.prompt undefined (fallbacks to "if-empty")
// Prompt the items that are empty (i.e. a value was not sent via parameter OR doesn't have a default value)
return isEmpty(argv[key]) && isEmpty(item.default);
Expand All @@ -53,5 +53,4 @@ let yargsInteractive = (processArgs = process.argv.slice(2), cwd) => {
return yargsConfig;
};


module.exports = yargsInteractive;
171 changes: 119 additions & 52 deletions test/yargs-interactive.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,19 @@ function checkProperties(result, expectedValues = {}) {
assert.equal(result.interactive, !!expectedValues.interactive, 'interactive');
}

function getOptionKeys({options, prompt, hasDefaultValue}) {
const checkDefaultValue = (item) => (hasDefaultValue && item.default) || (!hasDefaultValue && item.default === undefined);
return Object.keys(options).reduce((acc, key) => {
const item = options[key];
const shouldCheckDefaultValue = hasDefaultValue !== undefined;
if (item.prompt === prompt && ((shouldCheckDefaultValue && checkDefaultValue(item)) || !shouldCheckDefaultValue)) {
acc.push(key);
}

return acc;
}, []);
}

describe('yargsInteractive', () => {
let result;
let interactiveModeStub;
Expand All @@ -24,6 +37,7 @@ describe('yargsInteractive', () => {

describe('with no interactive', () => {
before(() => {
interactiveModeStub.resetHistory();
result = yargsInteractive()
.usage('$0 <command> [args]')
.version()
Expand All @@ -42,12 +56,13 @@ describe('yargsInteractive', () => {

describe('with no options', () => {
before(() => {
interactiveModeStub.resetHistory();
return yargsInteractive()
.usage('$0 <command> [args]')
.version()
.help()
.interactive()
.then((output) => result = output);
.then((output) => (result = output));
});

it('should return yargs default properties', () => {
Expand Down Expand Up @@ -76,12 +91,13 @@ describe('yargsInteractive', () => {

describe('and no parameters', () => {
before(() => {
interactiveModeStub.resetHistory();
return yargsInteractive()
.usage('$0 <command> [args]')
.version()
.help()
.interactive(options)
.then((output) => result = output);
.then((output) => (result = output));
});

it('should return yargs default properties', () => {
Expand All @@ -99,13 +115,14 @@ describe('yargsInteractive', () => {
let expectedParameters;

before(() => {
interactiveModeStub.resetHistory();
expectedParameters = {directory: 'abc', projectName: 'def'};
return yargsInteractive(Object.keys(expectedParameters).map((key) => `--${key}=${expectedParameters[key]}`))
.usage('$0 <command> [args]')
.version()
.help()
.interactive(options)
.then((output) => result = output);
.then((output) => (result = output));
});

it('should return yargs default properties', () => {
Expand All @@ -121,12 +138,13 @@ describe('yargsInteractive', () => {

describe('and interactive parameter', () => {
before(() => {
interactiveModeStub.resetHistory();
return yargsInteractive(`--interactive`)
.usage('$0 <command> [args]')
.version()
.help()
.interactive(options)
.then((output) => result = output);
.then((output) => (result = output));
});

it('should return yargs default properties', () => {
Expand All @@ -138,62 +156,16 @@ describe('yargsInteractive', () => {
});
});

describe('and interactive parameters with prompt option', () => {
let expectedParameters;

before(() => {
options = {
directory: {
type: 'input',
default: '.',
describe: 'Target directory',
},
projectName: {
type: 'input',
describe: 'Project name',
prompt: 'if-empty',
},
user: {
type: 'input',
describe: 'user',
prompt: 'never'
},
password: {
type: 'input',
describe: 'user',
prompt: 'always'
},
};

expectedParameters = {directory: 'abc', projectName: 'def'};
return yargsInteractive(Object.keys(expectedParameters).map((key) => `--${key}=${expectedParameters[key]}`))
.usage('$0 <command> [args]')
.version()
.help()
.interactive(options)
.then((output) => result = output);
});

it('should return yargs default properties', () => {
checkProperties(result);
});

it('should return options with values sent by parameter', () => {
Object.keys(options).forEach((key) => {
assert.equal(result[key], expectedParameters[key], key);
});
});
});

describe('and interactive option', () => {
before(() => {
interactiveModeStub.resetHistory();
const optionsWithInteractive = Object.assign({}, options, {interactive: {default: true}});
return yargsInteractive()
.usage('$0 <command> [args]')
.version()
.help()
.interactive(optionsWithInteractive)
.then((output) => result = output);
.then((output) => (result = output));
});

it('should return yargs default properties', () => {
Expand All @@ -205,4 +177,99 @@ describe('yargsInteractive', () => {
});
});
});

describe('with options using different prompt values', () => {
let yargsInteractiveArgs;
let promptedOptions;
let options;

before(() => {
interactiveModeStub.resetHistory();
yargsInteractiveArgs = [`--interactive`, `--option8='value'`];
options = {
option1: {type: 'input', describe: 'option1', default: '.', prompt: 'always'},
option2: {type: 'input', describe: 'option2', default: '.', prompt: 'never'},

// if-empty
option3: {type: 'input', describe: 'option3', prompt: 'if-empty'},
option4: {type: 'input', describe: 'option4', default: '.', prompt: 'if-empty'},

// no prompt (defaults to 'if-empty')
option5: {type: 'input', describe: 'option5'},
option6: {type: 'input', describe: 'option6'},

// if-no-arg
option7: {type: 'input', describe: 'option7', default: '.', prompt: 'if-no-arg'},
option8: {type: 'input', describe: 'option8', default: '.', prompt: 'if-no-arg'}
};

return yargsInteractive(yargsInteractiveArgs)
.usage('$0 <command> [args]')
.version()
.help()
.interactive(options)
.then((output) => {
result = output;
promptedOptions = interactiveModeStub.args[0][0];
});
});

it('should prompt options with prompt set as "always"', () => {
const optionKeys = getOptionKeys({options, prompt: 'always'});
optionKeys.forEach((key) => assert.ok(promptedOptions[key], `${key} not prompted`));
});

it('should not prompt options with prompt set as "never"', () => {
const optionKeys = getOptionKeys({options, prompt: 'never'});
optionKeys.forEach((key) => assert.ok(promptedOptions[key] === undefined, `${key} should not prompt`));
});

// if-empty

it('should prompt options with no value set and prompt set as "if-empty"', () => {
const optionKeys = getOptionKeys({options, prompt: 'if-empty', hasDefaultValue: false});
optionKeys.forEach((key) => assert.ok(promptedOptions[key], `${key} should prompt`));
});

it('should not prompt options with default value set and prompt set as "if-empty"', () => {
const optionKeys = getOptionKeys({options, prompt: 'if-empty', hasDefaultValue: true});
optionKeys.forEach((key) => assert.ok(promptedOptions[key] === undefined, `${key} should not prompt`));
});

// no prompt (defaults to 'if-empty')

it('should prompt options with no value set and prompt not set', () => {
const optionKeys = getOptionKeys({options, prompt: undefined});
optionKeys.forEach((key) => assert.ok(promptedOptions[key], `${key} should prompt`));
});

it('should not prompt options with default value set and prompt not set', () => {
const optionKeys = getOptionKeys({options, prompt: undefined, hasDefaultValue: false});
optionKeys.forEach((key) => assert.ok(promptedOptions[key], `${key} should prompt`));
});

// if-no-arg

it('should prompt options with no args and prompt set as "if-no-arg"', () => {
const optionKeys = getOptionKeys({options, prompt: 'if-no-arg'});
optionKeys.forEach((key) => {
const promptedOption = promptedOptions[key];
const optionSentByArgument = yargsInteractiveArgs.find((arg) => arg.startsWith(`--${key}`)) !== undefined;
if (!optionSentByArgument) {
assert.ok(promptedOption, `${key} should prompt`);
}
});
});

it('should not prompt options with value sent in args and prompt set as "if-no-arg"', () => {
const optionKeys = getOptionKeys({options, prompt: 'if-no-arg'});
optionKeys.forEach((key) => {
const promptedOption = promptedOptions[key];
const optionSentByArgument = yargsInteractiveArgs.find((arg) => arg.startsWith(`--${key}`)) !== undefined;
if (optionSentByArgument) {
assert.ok(promptedOption === undefined, `${key} should not prompt`);
}
});
});
});
});

0 comments on commit c91a74e

Please sign in to comment.