diff --git a/lib/inquirer.js b/lib/inquirer.js index 292fccdbf..7c33199b5 100644 --- a/lib/inquirer.js +++ b/lib/inquirer.js @@ -54,6 +54,7 @@ inquirer.createPromptModule = function(opt) { promptModule.restoreDefaultPrompts = function() { this.registerPrompt('list', require('./prompts/list')); this.registerPrompt('input', require('./prompts/input')); + this.registerPrompt('number', require('./prompts/number')); this.registerPrompt('confirm', require('./prompts/confirm')); this.registerPrompt('rawlist', require('./prompts/rawlist')); this.registerPrompt('expand', require('./prompts/expand')); diff --git a/lib/prompts/number.js b/lib/prompts/number.js new file mode 100644 index 000000000..959a320cb --- /dev/null +++ b/lib/prompts/number.js @@ -0,0 +1,28 @@ +'use strict'; +/** + * `input` type prompt + */ + +var Input = require('./input'); + +/** + * Extention of the Input prompt specifically for use with number inputs. + */ + +class NumberPrompt extends Input { + filterInput(input) { + if (input && typeof input === 'string') { + input = input.trim(); + // Match a number in the input + let numberMatch = input.match(/(^-?\d+|^\d+\.\d*|^\d*\.\d+)(e\d+)?$/); + // If a number is found, return that input. + if (numberMatch) { + return Number(numberMatch[0]); + } + } + // If the input was invalid return the default value. + return this.opt.default == null ? NaN : this.opt.default; + } +} + +module.exports = NumberPrompt; diff --git a/test/helpers/fixtures.js b/test/helpers/fixtures.js index 8987681aa..d8fba1b51 100644 --- a/test/helpers/fixtures.js +++ b/test/helpers/fixtures.js @@ -6,6 +6,11 @@ module.exports = { name: 'name' }, + number: { + message: 'message', + name: 'name' + }, + confirm: { message: 'message', name: 'name' diff --git a/test/specs/prompts/number.js b/test/specs/prompts/number.js new file mode 100644 index 000000000..3c43d01cf --- /dev/null +++ b/test/specs/prompts/number.js @@ -0,0 +1,116 @@ +var expect = require('chai').expect; +var _ = require('lodash'); +var ReadlineStub = require('../../helpers/readline'); +var fixtures = require('../../helpers/fixtures'); + +var NumberPrompt = require('../../../lib/prompts/number'); + +const ACCEPTABLE_ERROR = 0.001; + +describe('`number` prompt', function() { + beforeEach(function() { + this.fixture = _.clone(fixtures.number); + this.rl = new ReadlineStub(); + this.number = new NumberPrompt(this.fixture, this.rl); + }); + + it('should parse the largest number', function(done) { + this.number.run().then(answer => { + expect(answer).to.equal(Number.MAX_SAFE_INTEGER); + done(); + }); + + this.rl.emit('line', String(Number.MAX_SAFE_INTEGER)); + }); + + it('should parse the smallest number', function(done) { + this.number.run().then(answer => { + expect(answer).to.equal(Number.MIN_SAFE_INTEGER); + done(); + }); + + this.rl.emit('line', String(Number.MIN_SAFE_INTEGER)); + }); + + it('should parse an integer', function(done) { + this.number.run().then(answer => { + expect(answer).to.equal(42); + done(); + }); + + this.rl.emit('line', '42'); + }); + + it('should parse negative numbers', function(done) { + this.number.run().then(answer => { + expect(answer).to.equal(-363); + done(); + }); + + this.rl.emit('line', '-363'); + }); + + it('should parse a regular float', function(done) { + this.number.run().then(answer => { + expect(answer).to.be.closeTo(4353.43, ACCEPTABLE_ERROR); + done(); + }); + + this.rl.emit('line', '4353.43'); + }); + + it('should parse a float with no digits before the decimal', function(done) { + this.number.run().then(answer => { + expect(answer).to.be.closeTo(0.01264, ACCEPTABLE_ERROR); + done(); + }); + + this.rl.emit('line', '.01264'); + }); + + it('should parse a float with no digits after the decimal', function(done) { + this.number.run().then(answer => { + expect(answer).to.be.closeTo(1234.0, ACCEPTABLE_ERROR); + done(); + }); + + this.rl.emit('line', '1234.'); + }); + + it('should parse a float with exponents', function(done) { + this.number.run().then(answer => { + expect(answer).to.be.closeTo(534e12, ACCEPTABLE_ERROR); + done(); + }); + + this.rl.emit('line', '534e12'); + }); + + it('should parse any other string as NaN', function(done) { + this.number.run().then(answer => { + expect(answer).to.be.NaN; // eslint-disable-line no-unused-expressions + done(); + }); + + this.rl.emit('line', 'The cat'); + }); + + it('should parse the empty string as NaN', function(done) { + this.number.run().then(answer => { + expect(answer).to.be.NaN; // eslint-disable-line no-unused-expressions + done(); + }); + + this.rl.emit('line', ''); + }); + + it('should return default value if it is set on a bad input', function(done) { + this.number.opt.default = 11; + this.number.run().then(answer => { + expect(answer).to.equal(11); + done(); + }); + + this.rl.emit('line', ''); + }); +});