diff --git a/src/locale.js b/src/locale.js index 2a413618f..35346f064 100644 --- a/src/locale.js +++ b/src/locale.js @@ -22,6 +22,9 @@ export let string = { export let number = { min: '${path} must be greater than or equal to ${min}', max: '${path} must be less than or equal to ${max}', + less: '${path} must be less than ${less}', + more: '${path} must be greater than ${more}', + notEqual: '${path} must be not equal to ${notEqual}', positive: '${path} must be a positive number', negative: '${path} must be a negative number', integer: '${path} must be an integer', diff --git a/src/number.js b/src/number.js index 66f312096..343d6e37b 100644 --- a/src/number.js +++ b/src/number.js @@ -9,86 +9,123 @@ let isNaN = value => value != +value let isInteger = val => isAbsent(val) || val === (val | 0) export default function NumberSchema() { - if ( !(this instanceof NumberSchema)) - return new NumberSchema() + if ( !(this instanceof NumberSchema)) + return new NumberSchema() - MixedSchema.call(this, { type: 'number' }) + MixedSchema.call(this, { type: 'number' }) - this.withMutation(() => { - this.transform(function(value) { - if (this.isType(value)) return value + this.withMutation(() => { + this.transform(function(value) { + if (this.isType(value)) return value - let parsed = parseFloat(value); - if (this.isType(parsed)) return parsed + let parsed = parseFloat(value); + if (this.isType(parsed)) return parsed - return NaN; - }) - }) + return NaN; + }) + }) } inherits(NumberSchema, MixedSchema, { - _typeCheck(value) { - if (value instanceof Number) - value = value.valueOf(); - - return typeof value === 'number' && !isNaN(value) - }, - - min(min, msg) { - return this.test({ - name: 'min', - exclusive: true, - params: { min }, - message: msg || locale.min, - test(value) { - return isAbsent(value) || value >= this.resolve(min) - } - }) - }, - - max(max, msg) { - return this.test({ - name: 'max', - exclusive: true, - params: { max }, - message: msg || locale.max, - test(value) { - return isAbsent(value) || value <= this.resolve(max) - } - }) - }, - - positive(msg) { - return this.min(0, msg || locale.positive) - }, - - negative(msg) { - return this.max(0, msg || locale.negative) - }, - - integer(msg) { - msg = msg || locale.integer; - - return this.test('integer', msg, isInteger) - }, - - truncate() { - return this.transform(value => - !isAbsent(value) ? (value | 0) : value) - }, - - round(method) { - var avail = ['ceil', 'floor', 'round', 'trunc'] - method = (method && method.toLowerCase()) || 'round' - - // this exists for symemtry with the new Math.trunc - if (method === 'trunc') - return this.truncate() - - if (avail.indexOf(method.toLowerCase()) === -1) - throw new TypeError('Only valid options for round() are: ' + avail.join(', ')) - - return this.transform(value => !isAbsent(value) ? Math[method](value) : value) - } + _typeCheck(value) { + if (value instanceof Number) + value = value.valueOf(); + + return typeof value === 'number' && !isNaN(value) + }, + + min(min, msg) { + return this.test({ + name: 'min', + exclusive: true, + params: { min }, + message: msg || locale.min, + test(value) { + return isAbsent(value) || value >= this.resolve(min) + } + }) + }, + + max(max, msg) { + return this.test({ + name: 'max', + exclusive: true, + params: { max }, + message: msg || locale.max, + test(value) { + return isAbsent(value) || value <= this.resolve(max) + } + }) + }, + + less(less, msg) { + return this.test({ + name: 'less', + exclusive: true, + params: { less }, + message: msg || locale.less, + test(value) { + return isAbsent(value) || value < this.resolve(less) + } + }) + }, + + + more(more, msg) { + return this.test({ + name: 'more', + exclusive: true, + params: { more }, + message: msg || locale.more, + test(value) { + return isAbsent(value) || value > this.resolve(more) + } + }) + }, + + notEqual(notEqual, msg) { + return this.test({ + name: 'notEqual', + exclusive: true, + params: { notEqual }, + message: msg || locale.notEqual, + test(value) { + return isAbsent(value) || value !== this.resolve(notEqual) + } + }) + }, + + positive(msg) { + return this.min(0, msg || locale.positive) + }, + + negative(msg) { + return this.max(0, msg || locale.negative) + }, + + integer(msg) { + msg = msg || locale.integer; + + return this.test('integer', msg, isInteger) + }, + + truncate() { + return this.transform(value => + !isAbsent(value) ? (value | 0) : value) + }, + + round(method) { + var avail = ['ceil', 'floor', 'round', 'trunc'] + method = (method && method.toLowerCase()) || 'round' + + // this exists for symemtry with the new Math.trunc + if (method === 'trunc') + return this.truncate() + + if (avail.indexOf(method.toLowerCase()) === -1) + throw new TypeError('Only valid options for round() are: ' + avail.join(', ')) + + return this.transform(value => !isAbsent(value) ? Math[method](value) : value) + } }) diff --git a/test/number.js b/test/number.js index 2964301cb..0210766e8 100644 --- a/test/number.js +++ b/test/number.js @@ -138,6 +138,59 @@ describe('Number types', function() { }) }) + describe('less', () => { + var schema = number().less(5); + + TestHelpers.validateAll(schema, { + valid: [ + 4, + -10, + [null, schema.nullable()] + ], + invalid: [ + 5, + 7, + null, + [14, schema.less(10).less(14)] + ] + }) + }) + + describe('more', () => { + var schema = number().more(5); + + TestHelpers.validateAll(schema, { + valid: [ + 6, + 56445435, + [null, schema.nullable()] + ], + invalid: [ + 5, + -10, + null, + [64, schema.more(52).more(74)] + ] + }) + }) + + describe('notEqual', () => { + var schema = number().notEqual(5); + + TestHelpers.validateAll(schema, { + valid: [ + 6, + 56445435, + [null, schema.nullable()] + ], + invalid: [ + 5, + null, + [52, schema.notEqual(52).notEqual(74)] + ] + }) + }) + describe('integer', ()=> { TestHelpers.validateAll( number().integer(),