UNPKG

prompt

Version:

A beautiful command-line prompt for node.js

961 lines (909 loc) 29.1 kB
/* * prompt-test.js: Tests for prompt. * * (C) 2010, Nodejitsu Inc. * */ var assert = require('assert'), vows = require('vows'), prompt = require('../lib/prompt'), helpers = require('./helpers'), macros = require('./macros'), schema = helpers.schema; // fix for vows, util.print/puts was removed from node require('util').print = console.log; require('util').puts = console.log; // A helper to pass fragments of our schema into prompt as full schemas. function grab () { var names = [].slice.call(arguments), complete = { schema: {} }; names.forEach(function (name) { complete.path = [name]; complete.schema = schema.properties[name]; }); return complete; } // // Reset the prompt for mock testing // prompt.started = false; prompt.start({ stdin: helpers.stdin, stdout: helpers.stdout }); vows.describe('prompt').addBatch({ "When using prompt": { "the getInput() method": { "with a simple string prompt": { topic: function () { var that = this; helpers.stdout.once('data', function (msg) { that.msg = msg; }) prompt.getInput('test input', this.callback); helpers.stdin.writeNextTick('test value\n'); }, "should prompt to stdout and respond with data": function (err, input) { assert.isNull(err); assert.equal(input, 'test value'); assert.isTrue(this.msg.indexOf('test input') !== -1); } }, } } }).addBatch({ "When using prompt": { "the getInput() method": { "with any field that is not supposed to be empty": { "and we don't provide any input": { topic: function () { var that = this; helpers.stdout.once('data', function (msg) { that.msg = msg; }); helpers.stderr.once('data', function (msg) { that.errmsg = msg; }); prompt.getInput(grab('notblank'), function () {}); prompt.once('invalid', this.callback.bind(null, null)) helpers.stdin.writeNextTick('\n'); }, "should prompt with an error": function (_, prop, input) { assert.isObject(prop); assert.equal(input, ''); assert.isTrue(this.errmsg.indexOf('Invalid input') !== -1); assert.isTrue(this.msg.indexOf('notblank') !== -1); } } } } } }).addBatch({ "When using prompt": { "the getInput() method": { "with a hidden field that is not supposed to be empty": { "and we provide valid input": { topic: function () { var that = this; helpers.stdout.once('data', function (msg) { that.msg = msg; }); prompt.getInput('password', this.callback); helpers.stdin.writeNextTick('trustno1\n'); }, "should prompt to stdout and respond with data": function (err, input) { assert.isNull(err); assert.equal(input, 'trustno1'); assert.isTrue(this.msg.indexOf('password') !== -1); } }, } } } }).addBatch({ "When using prompt": { "the getInput() method": { "with a hidden field that is not supposed to be empty": { "and we don't provide an input": { topic: function () { var that = this; helpers.stdout.once('data', function (msg) { that.msg = msg; }); helpers.stderr.once('data', function (msg) { that.errmsg = msg; }); prompt.getInput(grab('password'), function () {} ); prompt.once('invalid', this.callback.bind(null, null)) helpers.stdin.writeNextTick('\n'); }, "should prompt with an error": function (ign, prop, input) { assert.isObject(prop); assert.equal(input, ''); assert.isTrue(this.errmsg.indexOf('Invalid input') !== -1); assert.isTrue(this.msg.indexOf('password') !== -1); } } } } } }).addBatch({ "When using prompt": { "the getInput() method": { "with an integer field": { "and we provide valid input": { topic: function () { var that = this; helpers.stdout.once('data', function (msg) { that.msg = msg; }); prompt.getInput(grab('integer'), this.callback); helpers.stdin.writeNextTick('42\n'); }, "should prompt to stdout and respond with data": function (err, input) { assert.isNull(err); assert.equal(input, '42'); assert.isTrue(this.msg.indexOf('integer') !== -1); } }, } } } }).addBatch({ "When using prompt": { "the getInput() method": { "with an integer field": { "and we don't provide an integer": { topic: function () { var that = this; helpers.stdout.once('data', function (msg) { that.msg = msg; }); helpers.stderr.once('data', function (msg) { that.errmsg = msg; }) prompt.getInput(grab('integer'), this.callback); prompt.once('invalid', function () { prompt.once('prompt', function () { process.nextTick(function () { helpers.stdin.writeNextTick('42\n'); }) }) }); helpers.stdin.writeNextTick('4.2\n'); }, "should prompt with an error before completing the operation": function (err, input) { assert.isNull(err); assert.equal(input, '42'); assert.isTrue(this.errmsg.indexOf('Invalid input') !== -1); assert.isTrue(this.msg.indexOf('integer') !== -1); } } } } } }).addBatch({ "When using prompt": { "the getInput() method": { "with a boolean field": { "and we provide valid input": { topic: function () { var that = this; helpers.stdout.once('data', function (msg) { that.msg = msg; }); prompt.getInput(grab('boolean'), this.callback); helpers.stdin.writeNextTick('true\n'); }, "should prompt to stdout and respond with data": function (err, input) { assert.isNull(err); assert.equal(input, true); assert.isTrue(this.msg.indexOf('boolean') !== -1); } }, } } } }).addBatch({ "When using prompt": { "the getInput() method": { "with a boolean field": { "and we don't provide an bool": { topic: function () { var that = this; helpers.stdout.once('data', function (msg) { that.msg = msg; }); helpers.stderr.once('data', function (msg) { that.errmsg = msg; }) prompt.getInput(grab('boolean'), this.callback); prompt.once('invalid', function () { prompt.once('prompt', function () { process.nextTick(function () { helpers.stdin.writeNextTick('F\n'); }) }) }); helpers.stdin.writeNextTick('4.2\n'); }, "should prompt with an error before completing the operation": function (err, input) { assert.isNull(err); assert.equal(input, false); assert.isTrue(this.errmsg.indexOf('Invalid input') !== -1); assert.isTrue(this.msg.indexOf('boolean') !== -1); } } } } } }).addBatch({ "When using prompt": { "the getInput() method": { "with a complex property prompt": { "and a valid input": { topic: function () { var that = this; helpers.stdout.once('data', function (msg) { that.msg = msg; }); prompt.getInput(grab('username'), this.callback); helpers.stdin.writeNextTick('some-user\n'); }, "should prompt to stdout and respond with data": function (err, input) { assert.isNull(err); assert.equal(input, 'some-user'); assert.isTrue(this.msg.indexOf('username') !== -1); } } } } } }).addBatch({ "When using prompt": { "the getInput() method": { "with a complex property prompt": { "and an invalid input": { topic: function () { var that = this; helpers.stdout.once('data', function (msg) { that.msg = msg; }); helpers.stderr.once('data', function (msg) { that.errmsg = msg; }) prompt.getInput(grab('username'), this.callback); prompt.once('invalid', function () { prompt.once('prompt', function () { process.nextTick(function () { helpers.stdin.writeNextTick('some-user\n'); }) }) }); helpers.stdin.writeNextTick('some -user\n'); }, "should prompt with an error before completing the operation": function (err, input) { assert.isNull(err); assert.equal(input, 'some-user'); assert.isTrue(this.errmsg.indexOf('Username can only be letters, numbers, and dashes') !== -1); assert.isTrue(this.msg.indexOf('username') !== -1); } } } } } }).addBatch({ "When using prompt": { "the getInput() method": { "with a complex property prompt": { "with an invalid validator (array)": { topic: function () { var that = this, called; prompt.getInput(grab('badValidator'), function (err) { if (!called) { called = true; that.callback(err); } }); helpers.stdin.writeNextTick('some-user\n'); }, "should respond with an error": function (err, ign) { assert.isTrue(!!err); } } } } } }).addBatch({ "When using prompt": { "the get() method": { "with a simple string prompt": { "that is not a property in prompt.properties": { topic: function () { var that = this; helpers.stdout.once('data', function (msg) { that.msg = msg; }) prompt.get('test input', this.callback); helpers.stdin.writeNextTick('test value\n'); }, "should prompt to stdout and respond with the value": function (err, result) { assert.isNull(err); assert.include(result, 'test input'); assert.equal(result['test input'], 'test value'); assert.isTrue(this.msg.indexOf('test input') !== -1); } }, } } } }).addBatch({ "When using prompt": { "the get() method": { "with a simple string prompt": { "that is a property name in prompt.properties": { "with a string literal default value": { topic: function () { var that = this; helpers.stdout.once('data', function (msg) { that.msg = msg; }); prompt.properties.riffwabbles = schema.properties.riffwabbles; prompt.get('riffwabbles', this.callback); helpers.stdin.writeNextTick('\n'); }, "should prompt to stdout and respond with the default value": function (err, result) { assert.isNull(err); assert.notStrictEqual(this.msg.indexOf('riffwabbles'), -1); assert.notStrictEqual(this.msg.indexOf('(foobizzles)'), -1); assert.include(result, 'riffwabbles'); assert.equal(result['riffwabbles'], schema.properties['riffwabbles'].default); } }, } } } } }).addBatch({ "When using prompt": { "the get() method": { "with a simple string prompt": { "that is a property name in prompt.properties": { "with a function default returning a string literal": { topic: function () { var that = this; helpers.stdout.once('data', function (msg) { that.msg = msg; }); prompt.properties.functiondefaulttest = schema.properties.functiondefaulttest; prompt.get('functionDefaultTest', this.callback); helpers.stdin.writeNextTick('\n'); }, "should respond with the default value function's return value": function (err, result) { assert.isNull(err); assert.notStrictEqual(this.msg.indexOf('function default test'), -1); assert.notStrictEqual(this.msg.indexOf('(test)'), -1); assert.include(result, 'functionDefaultTest'); assert.strictEqual(result['functionDefaultTest'], 'test'); } }, } } } } }).addBatch({ "When using prompt": { "the get() method": { "with a simple string prompt": { "that is a property name in prompt.properties": { "with a function default returning a string literal": { topic: function () { var that = this; helpers.stdout.once('data', function (msg) { // we really need the second message, so we'll // ignore the first and look out for the second helpers.stdout.once('data', function (msg) { that.msg = msg; }) }); prompt.properties.animal = schema.properties.animal; prompt.properties.functiondefaultpluralanimal = schema.properties.functiondefaultpluralanimal; prompt.get(['animal', 'functiondefaultpluralanimal'], this.callback); helpers.stdin.writeSequence(['cat\n', '\n']); }, "should respond with the default value function's return value": function (err, result) { assert.isNull(err); assert.notStrictEqual(this.msg.indexOf('function default plural animal'), -1); assert.notStrictEqual(this.msg.indexOf('(cats)'), -1); assert.strictEqual(result['animal'], 'cat'); assert.include(result, 'functiondefaultpluralanimal'); assert.strictEqual(result['functiondefaultpluralanimal'], 'cats'); } }, } } } } }).addBatch({ "When using prompt": { "the get() method": { "with a simple string prompt": { "that is a property name in prompt.properties": { "with a function default that returns undefined": { topic: function () { var that = this; helpers.stdout.once('data', function (msg) { that.msg = msg; }); prompt.properties.functiondefaultundefined = schema.properties.functiondefaultundefined; prompt.get('functionDefaultUndefined', this.callback); helpers.stdin.writeNextTick('\n'); }, "should prompt without a default value": function (err, result) { assert.isNull(err); assert.notStrictEqual(this.msg.indexOf('function default undefined'), -1); assert.strictEqual(this.msg.indexOf('('), -1); assert.include(result, 'functionDefaultUndefined'); assert.strictEqual(result['functionDefaultUndefined'], ''); } }, } } } } }).addBatch({ "When using prompt": { "the get() method": { "with a simple string prompt": { "that is a property name in prompt.properties": { "that expects a numeric value": { "and gets valid input": { topic: function () { var that = this; helpers.stdout.once('data', function (msg) { that.msg = msg; }); prompt.properties.number = schema.properties.number; prompt.get('number', this.callback); helpers.stdin.writeNextTick('15\n'); }, "should prompt to stdout and respond with a numeric value": function (err, result) { assert.isNull(err); assert.include(result, 'number'); assert.equal(result['number'], 15); } } } } } } } }).addBatch({ "When using prompt": { "the get() method": { "with a simple string prompt": { "that is a property name in prompt.properties": { "with a sync function validator (.validator)": { topic: function () { var that = this; helpers.stdout.once('data', function (msg) { that.msg = msg; }); prompt.get(helpers.schema.properties.fnvalidator, this.callback); helpers.stdin.writeNextTick('fn123\n'); }, "should accept a value that is checked": function (err, result) { assert.isNull(err); assert.equal(result['fnvalidator'],'fn123'); } } } } } } }).addBatch({ "When using prompt": { "the get() method": { "with a simple string prompt": { "that is a property name in prompt.properties": { "with a sync function validator (.conform)": { topic: function () { var that = this; helpers.stdout.once('data', function (msg) { that.msg = msg; }); prompt.get(grab('fnconform'), this.callback); helpers.stdin.writeNextTick('fn123\n'); }, "should accept a value that is checked": function (err, result) { assert.isNull(err); assert.equal(result['fnconform'],'fn123'); } } // // Remark Does not work with revalidator // // "with a callback validator": { // topic: function () { // var that = this; // // helpers.stdout.once('data', function (msg) { // that.msg = msg; // }); // // prompt.get(grab('cbvalidator'), this.callback); // helpers.stdin.writeNextTick('cb123\n'); // }, // "should not accept a value that is correct": function (err, result) { // assert.isNull(err); // assert.equal(result['cbvalidator'],'cb123'); // } // } } }, } } }).addBatch({ "When using prompt": { "the get() method": { "with a simple string prompt": { "that is a property name in prompt.properties": { "with a sync function before (.before)": { topic: function() { var that = this; helpers.stdout.once('data', function(msg) { that.msg = msg; }); prompt.get({ properties: { fnbefore: { before: function(v) { return 'v' + v; } } } }, this.callback); helpers.stdin.writeNextTick('fn456\n'); }, "should modify user's input": function(e, result) { assert.equal(result.fnbefore, 'vfn456'); } } } } } } }).addBatch({ "When using prompt": { "the get() method": { "skip prompt with prompt.overide": { topic: function () { prompt.override = { coconihet: 'whatever' } prompt.get('coconihet', this.callback); }, "skips prompt and uses overide": function (err, results) { assert.equal(results.coconihet, 'whatever') } } } } }).addBatch({ "When using prompt": { "the get() method": { "skip prompt with falsy prompt.overide": { topic: function () { prompt.override = { coconihet: false } prompt.get('coconihet', this.callback); }, "skips prompt and uses overide": function (err, results) { assert.equal(results.coconihet, false) } } } } }).addBatch({ "When using prompt": { "the addProperties() method": { topic: function () { prompt.addProperties({}, ['foo', 'bar'], this.callback); helpers.stdin.writeSequence(['foo\n', 'bar\n']); }, "should add the properties to the object": function (err, obj) { assert.isNull(err); assert.isObject(obj); assert.equal(obj.foo, 'foo'); assert.equal(obj.bar, 'bar'); } } } }).addBatch({ "When using prompt": { "the addProperties() method": { topic: function () { prompt.addProperties({'foo': 'foo', 'bar': 'bar'}, [], this.callback); }, "should return the object as-is when no properties provided": function (err, obj) { assert.isNull(err); assert.isObject(obj); assert.equal(obj.foo, 'foo'); assert.equal(obj.bar, 'bar'); } } } }).addBatch({ "When using prompt": { "the get() method": { "with old schema": { topic: function () { var that = this; helpers.stdout.once('data', function (msg) { that.msg = msg; }); prompt.properties.username = schema.properties.oldschema; prompt.get('username', this.callback); helpers.stdin.writeSequence(['\n', 'hell$\n', 'hello\n']); }, "should prompt to stdout and respond with the default value": function (err, result) { assert.isNull(err); assert.isTrue(this.msg.indexOf('username') !== -1); assert.include(result, 'username'); assert.equal(result.username, 'hello'); } } } } }).addBatch({ "When using prompt": { "the history() method": { "when used inside of a complex property": { "with correct value(s)": { topic: function () { prompt.get([grab('animal'), grab('sound')], this.callback); helpers.stdin.writeSequence(['dog\n', 'woof\n']); }, "should respond with the values entered": function (err, result) { assert.isTrue(!err); assert.equal(result.animal, 'dog'); assert.equal(result.sound, 'woof'); assert.equal(prompt.history('nothing'), null); assert.deepEqual(prompt.history('animal'), { property: 'animal', value: 'dog' }); } }, } } } }).addBatch({ "When using prompt": { "the history() method": { "when used inside of a complex property": { "with an incorrect value": { topic: function () { prompt.get([grab('animal'), grab('sound')], function () {}); prompt.once('invalid', this.callback.bind(null, null)); helpers.stdin.writeSequence(['dog\n', 'meow\n']); }, "should prompt for the error": function (ign, property, line) { assert.equal(property.path.join(''), 'sound'); assert.equal(line, 'meow'); } } } } } }).addBatch({ "when using prompt": { "the get() method": { topic: function () { prompt.override = { xyz: 468, abc: 123 } prompt.get(['xyz', 'abc'], this.callback); }, "should respond with overrides": function (err, results) { assert.isNull(err); assert.deepEqual(results, { xyz: 468, abc: 123 }); } } } }).addBatch({ "when using prompt": { "the get() method also works as a Promise": { topic: function () { var that = this; prompt.override = { xyz: 468, abc: 123 }; prompt.get(['xyz', 'abc']) .then(function (result) { return that.callback(null, result); }); }, "should respond with overrides": function (err, results) { assert.isNull(err); assert.deepEqual(results, { xyz: 468, abc: 123 }); } } } }).addBatch({ "When using prompt": { "with fancy properties": { "the get() method": { topic: function () { prompt.override = { UVW: 5423, DEF: 64235 } prompt.get({ properties: { 'UVW': { description: 'a custom message', default: 6 }, 'DEF': { description: 'a custom message', default: 6 } } }, this.callback); }, "should respond with overrides": function (err, results) { assert.isNull(err); assert.deepEqual(results, { UVW: 5423, DEF: 64235 }); } } } } }).addBatch({ "When using prompt": { "with a type and a description property": { "the get() method": { topic: function () { var that = this; helpers.stdout.once('data', function (msg) { that.msg = msg; }); prompt.get({ name: 'test', type: 'number', description: 'Please input a number' }, this.callback); helpers.stdin.writeNextTick('42\n'); }, "should prompt to stdout and respond with the value": function (err, result) { assert.isNull(err); assert.include(result, 'test'); assert.equal(result['test'], '42'); assert.isTrue(this.msg.indexOf('Please input a number') !== -1); } }, } } }) .addBatch( macros.shouldConfirm({ messages: ['with a string message'], prop: 'test', response: 'Y' }) ).addBatch( macros.shouldConfirm({ messages: ['with an empty string and default yes'], prop: 'test', response: '' }, { default: 'yes' }) ).addBatch( macros.shouldNotConfirm({ messages: ['with a string message'], prop: 'test', response: 'N' }) ).addBatch( macros.shouldConfirm({ messages: ['with a string message'], prop: 'test', response: 'YES' }) ).addBatch( macros.shouldNotConfirm({ messages: ['with a string message'], prop: 'test', response: 'NO' }) ).addBatch( macros.shouldConfirm({ messages: ['with a string message'], prop: 'test', response: 'T' }) ).addBatch( macros.shouldNotConfirm({ messages: ['with a string message'], prop: 'test', response: 'F' }) ).addBatch( macros.shouldConfirm({ messages: ['with a string message'], prop: 'test', response: 'TRUE' }) ).addBatch( macros.shouldNotConfirm({ messages: ['with a string message'], prop: 'test', response: 'FALSE' }) ).addBatch( macros.shouldConfirm({ messages: ['with an object', 'and description set'], prop: { description: 'a custom message' }, response: 'Y' }) ).addBatch( macros.shouldConfirm({ messages: ['with an object', 'and they forgot the description'], prop: { description: 'a custom message' }, response: 'Y' }) ).addBatch( macros.shouldConfirm({ messages: ['with an object', 'and custom validators'], prop: { description: 'node or jitsu?', pattern: /^(node|jitsu)/i, yes: /^node/i }, response: 'node' }) ).addBatch( macros.shouldNotConfirm({ messages: ['with an object', 'and custom validators'], prop: { description: 'node or jitsu?', pattern: /^(node|jitsu)/i, yes: /^node/i }, response: 'jitsu' }) ).addBatch( macros.shouldConfirm({ messages: ['with multiple strings'], prop: ["test", "test2", "test3"], response: ['Y\n', 'y\n', 'YES\n'] }) ).addBatch( macros.shouldNotConfirm({ messages: ['with multiple strings'], prop: ["test", "test2", "test3"], response: ['Y\n', 'N\n', 'YES\n'] }) ).addBatch( macros.shouldNotConfirm({ messages: ['with multiple strings'], prop: ["test", "test2", "test3"], response: ['n\n', 'NO\n', 'N\n'] }) ).addBatch( macros.shouldConfirm({ messages: ['with multiple objects'], prop: [ { message: "test" }, { message: "test2" } ], response: ['y\n', 'y\n'] }) ).addBatch( macros.shouldNotConfirm({ messages: ['with multiple objects'], prop: [ { message: "test" }, { message: "test2" } ], response: ['n\n', 'n\n'] }) ).addBatch( macros.shouldNotConfirm({ messages: ['with multiple objects'], prop: [ { message: "test" }, { message: "test2" } ], response: ['n\n', 'y\n'] }) ).export(module);