diff --git a/lib/chai/core/assertions.js b/lib/chai/core/assertions.js index a1664297..e2971726 100644 --- a/lib/chai/core/assertions.js +++ b/lib/chai/core/assertions.js @@ -233,7 +233,6 @@ Assertion.addProperty('any', function () { * @namespace BDD * @public */ - Assertion.addProperty('all', function () { flag(this, 'all', true); flag(this, 'any', false); @@ -694,6 +693,17 @@ Assertion.addProperty('true', function () { ); }); +Assertion.addProperty('numeric', function () { + const object = flag(this, 'object'); + + this.assert( + ['Number', 'BigInt'].includes(_.type(object)) + , 'expected #{this} to be numeric' + , 'expected #{this} to not be numeric' + , flag(this, 'negate') ? false : true + ); +}); + /** * ### .callable * @@ -1208,27 +1218,19 @@ function assertAbove (n, msg) { , msgPrefix = ((flagMsg) ? flagMsg + ': ' : '') , ssfi = flag(this, 'ssfi') , objType = _.type(obj).toLowerCase() - , nType = _.type(n).toLowerCase() - , errorMessage - , shouldThrow = true; + , nType = _.type(n).toLowerCase(); if (doLength && objType !== 'map' && objType !== 'set') { new Assertion(obj, flagMsg, ssfi, true).to.have.property('length'); } if (!doLength && (objType === 'date' && nType !== 'date')) { - errorMessage = msgPrefix + 'the argument to above must be a date'; - } else if (nType !== 'number' && (doLength || objType === 'number')) { - errorMessage = msgPrefix + 'the argument to above must be a number'; - } else if (!doLength && (objType !== 'date' && objType !== 'number')) { + throw new AssertionError(msgPrefix + 'the argument to above must be a date', undefined, ssfi); + } else if (!_.isNumeric(n) && (doLength || _.isNumeric(obj))) { + throw new AssertionError(msgPrefix + 'the argument to above must be a number', undefined, ssfi); + } else if (!doLength && (objType !== 'date' && !_.isNumeric(obj))) { var printObj = (objType === 'string') ? "'" + obj + "'" : obj; - errorMessage = msgPrefix + 'expected ' + printObj + ' to be a number or a date'; - } else { - shouldThrow = false; - } - - if (shouldThrow) { - throw new AssertionError(errorMessage, undefined, ssfi); + throw new AssertionError(msgPrefix + 'expected ' + printObj + ' to be a number or a date', undefined, ssfi); } if (doLength) { @@ -1299,7 +1301,7 @@ Assertion.addMethod('greaterThan', assertAbove); * @name least * @alias gte * @alias greaterThanOrEqual - * @param {number} n + * @param {unknown} n * @param {string} msg _optional_ * @namespace BDD * @public @@ -1322,9 +1324,9 @@ function assertLeast (n, msg) { if (!doLength && (objType === 'date' && nType !== 'date')) { errorMessage = msgPrefix + 'the argument to least must be a date'; - } else if (nType !== 'number' && (doLength || objType === 'number')) { + } else if (!_.isNumeric(n) && (doLength || _.isNumeric(obj))) { errorMessage = msgPrefix + 'the argument to least must be a number'; - } else if (!doLength && (objType !== 'date' && objType !== 'number')) { + } else if (!doLength && (objType !== 'date' && !_.isNumeric(obj))) { var printObj = (objType === 'string') ? "'" + obj + "'" : obj; errorMessage = msgPrefix + 'expected ' + printObj + ' to be a number or a date'; } else { @@ -1402,7 +1404,7 @@ Assertion.addMethod('greaterThanOrEqual', assertLeast); * @name below * @alias lt * @alias lessThan - * @param {number} n + * @param {unknown} n * @param {string} msg _optional_ * @namespace BDD * @public @@ -1422,12 +1424,12 @@ function assertBelow (n, msg) { if (doLength && objType !== 'map' && objType !== 'set') { new Assertion(obj, flagMsg, ssfi, true).to.have.property('length'); } - + if (!doLength && (objType === 'date' && nType !== 'date')) { errorMessage = msgPrefix + 'the argument to below must be a date'; - } else if (nType !== 'number' && (doLength || objType === 'number')) { + } else if (!_.isNumeric(n) && (doLength || _.isNumeric(obj))) { errorMessage = msgPrefix + 'the argument to below must be a number'; - } else if (!doLength && (objType !== 'date' && objType !== 'number')) { + } else if (!doLength && (objType !== 'date' && !_.isNumeric(obj))) { var printObj = (objType === 'string') ? "'" + obj + "'" : obj; errorMessage = msgPrefix + 'expected ' + printObj + ' to be a number or a date'; } else { @@ -1506,7 +1508,7 @@ Assertion.addMethod('lessThan', assertBelow); * @name most * @alias lte * @alias lessThanOrEqual - * @param {number} n + * @param {unknown} n * @param {string} msg _optional_ * @namespace BDD * @public @@ -1529,9 +1531,9 @@ function assertMost (n, msg) { if (!doLength && (objType === 'date' && nType !== 'date')) { errorMessage = msgPrefix + 'the argument to most must be a date'; - } else if (nType !== 'number' && (doLength || objType === 'number')) { + } else if (!_.isNumeric(n) && (doLength || _.isNumeric(obj))) { errorMessage = msgPrefix + 'the argument to most must be a number'; - } else if (!doLength && (objType !== 'date' && objType !== 'number')) { + } else if (!doLength && (objType !== 'date' && !_.isNumeric(obj))) { var printObj = (objType === 'string') ? "'" + obj + "'" : obj; errorMessage = msgPrefix + 'expected ' + printObj + ' to be a number or a date'; } else { @@ -1608,8 +1610,8 @@ Assertion.addMethod('lessThanOrEqual', assertMost); * expect(4, 'nooo why fail??').to.be.within(1, 3); * * @name within - * @param {number} start lower bound inclusive - * @param {number} finish upper bound inclusive + * @param {unknown} start lower bound inclusive + * @param {unknown} finish upper bound inclusive * @param {string} msg _optional_ * @namespace BDD * @public @@ -1636,9 +1638,9 @@ Assertion.addMethod('within', function (start, finish, msg) { if (!doLength && (objType === 'date' && (startType !== 'date' || finishType !== 'date'))) { errorMessage = msgPrefix + 'the arguments to within must be dates'; - } else if ((startType !== 'number' || finishType !== 'number') && (doLength || objType === 'number')) { + } else if ((!_.isNumeric(start) || !_.isNumeric(finish)) && (doLength || _.isNumeric(obj))) { errorMessage = msgPrefix + 'the arguments to within must be numbers'; - } else if (!doLength && (objType !== 'date' && objType !== 'number')) { + } else if (!doLength && (objType !== 'date' && !_.isNumeric(obj))) { var printObj = (objType === 'string') ? "'" + obj + "'" : obj; errorMessage = msgPrefix + 'expected ' + printObj + ' to be a number or a date'; } else { @@ -3013,19 +3015,18 @@ function closeTo(expected, delta, msg) { , flagMsg = flag(this, 'message') , ssfi = flag(this, 'ssfi'); - new Assertion(obj, flagMsg, ssfi, true).is.a('number'); - if (typeof expected !== 'number' || typeof delta !== 'number') { - flagMsg = flagMsg ? flagMsg + ': ' : ''; - var deltaMessage = delta === undefined ? ", and a delta is required" : ""; - throw new AssertionError( - flagMsg + 'the arguments to closeTo or approximately must be numbers' + deltaMessage, - undefined, - ssfi - ); - } + new Assertion(obj, flagMsg, ssfi, true).is.numeric; + let message = 'A `delta` value is required for `closeTo`'; + if (delta == undefined) throw new AssertionError(flagMsg ? `${flagMsg}: ${message}` : message, undefined, ssfi); + new Assertion(delta, flagMsg, ssfi, true).is.numeric; + message = 'A `expected` value is required for `closeTo`'; + if (expected == undefined) throw new AssertionError(flagMsg ? `${flagMsg}: ${message}` : message, undefined, ssfi); + new Assertion(expected, flagMsg, ssfi, true).is.numeric; + + const abs = (x) => x < 0n ? -x : x; this.assert( - Math.abs(obj - expected) <= delta + abs(obj - expected) <= delta , 'expected #{this} to be close to ' + expected + ' +/- ' + delta , 'expected #{this} not to be close to ' + expected + ' +/- ' + delta ); diff --git a/lib/chai/interface/assert.js b/lib/chai/interface/assert.js index c31784f0..721ec2a3 100644 --- a/lib/chai/interface/assert.js +++ b/lib/chai/interface/assert.js @@ -706,6 +706,45 @@ assert.isNotNumber = function (val, msg) { new Assertion(val, msg, assert.isNotNumber, true).to.not.be.a('number'); }; +/** + * ### .isNumeric(value, [message]) + * + * Asserts that `value` is a number or BigInt. + * + * var cups = 2; + * assert.isNumeric(cups, 'how many cups'); + * + * var cups = 10n; + * assert.isNumeric(cups, 'how many cups'); + * + * @name isNumeric + * @param {unknown} val + * @param {string} msg + * @namespace Assert + * @public + */ +assert.isNumeric = function (val, msg) { + new Assertion(val, msg, assert.isNumeric, true).is.numeric; +}; + +/** + * ### .isNotNumeric(value, [message]) + * + * Asserts that `value` is _not_ a number or BigInt. + * + * var cups = '2 cups please'; + * assert.isNotNumeric(cups, 'how many cups'); + * + * @name isNotNumeric + * @param {unknown} val + * @param {string} msg + * @namespace Assert + * @public + */ +assert.isNotNumeric = function (val, msg) { + new Assertion(val, msg, assert.isNotNumeric, true).is.not.numeric; +}; + /** * ### .isFinite(value, [message]) * diff --git a/lib/chai/utils/index.js b/lib/chai/utils/index.js index 80306f79..fd4e6358 100644 --- a/lib/chai/utils/index.js +++ b/lib/chai/utils/index.js @@ -11,7 +11,8 @@ import * as checkError from 'check-error'; export {test} from './test.js'; // type utility -export {type} from './type-detect.js'; +import {type} from './type-detect.js'; +export {type}; // expectTypes utility export {expectTypes} from './expectTypes.js'; @@ -105,3 +106,7 @@ export {getOperator} from './getOperator.js'; export function isRegExp(obj) { return Object.prototype.toString.call(obj) === '[object RegExp]'; } + +export function isNumeric(obj) { + return ['Number', 'BigInt'].includes(type(obj)) +} diff --git a/test/assert.js b/test/assert.js index c10a6775..89395b05 100644 --- a/test/assert.js +++ b/test/assert.js @@ -153,6 +153,23 @@ describe('assert', function () { assert.typeOf(function() {}, 'asyncfunction', 'blah'); }, "blah: expected [Function] to be an asyncfunction"); + assert.typeOf(5n, 'bigint'); + + assert.typeOf(() => {}, 'function'); + assert.typeOf(function() {}, 'function'); + assert.typeOf(async function() {}, 'asyncfunction'); + assert.typeOf(function*() {}, 'generatorfunction'); + assert.typeOf(async function*() {}, 'asyncgeneratorfunction'); + assert.typeOf(Symbol(), 'symbol'); + + err(function () { + assert.typeOf(5, 'function', 'blah'); + }, "blah: expected 5 to be a function"); + + err(function () { + assert.typeOf(function() {}, 'asyncfunction', 'blah'); + }, "blah: expected [Function] to be an asyncfunction"); + err(function () { assert.typeOf(5, 'string', 'blah'); }, "blah: expected 5 to be a string"); @@ -632,6 +649,27 @@ describe('assert', function () { }, "blah: expected 4 not to be a number"); }); + + it('isNumeric', function() { + assert.isNumeric(1); + assert.isNumeric(Number('3')); + assert.isNumeric(6n); + assert.isNumeric(BigInt(9)); + + err(function () { + assert.isNumeric('1', 'blah'); + }, "blah: expected \'1\' to be numeric"); + }); + + it('isNotNumeric', function () { + assert.isNotNumeric('hello'); + assert.isNotNumeric([ 5 ]); + + err(function () { + assert.isNotNumeric(4, 'blah'); + }, "blah: expected 4 to not be numeric"); + }); + it('isFinite', function() { assert.isFinite(4); assert.isFinite(-10); @@ -1855,6 +1893,7 @@ describe('assert', function () { assert.closeTo(1.5, 1.0, 0.5); assert.closeTo(10, 20, 20); assert.closeTo(-10, 20, 30); + assert.closeTo(10, 10, 0); err(function(){ assert.closeTo(2, 1.0, 0.5, 'blah'); @@ -1866,25 +1905,26 @@ describe('assert', function () { err(function() { assert.closeTo([1.5], 1.0, 0.5, 'blah'); - }, "blah: expected [ 1.5 ] to be a number"); + }, "blah: expected [ 1.5 ] to be numeric"); err(function() { assert.closeTo(1.5, "1.0", 0.5, 'blah'); - }, "blah: the arguments to closeTo or approximately must be numbers"); + }, "blah: expected '1.0' to be numeric"); err(function() { assert.closeTo(1.5, 1.0, true, 'blah'); - }, "blah: the arguments to closeTo or approximately must be numbers"); + }, "blah: expected true to be numeric"); err(function() { assert.closeTo(1.5, 1.0, undefined, 'blah'); - }, "blah: the arguments to closeTo or approximately must be numbers, and a delta is required"); + }, "blah: A `delta` value is required for `closeTo`"); }); it('approximately', function(){ assert.approximately(1.5, 1.0, 0.5); assert.approximately(10, 20, 20); assert.approximately(-10, 20, 30); + assert.approximately(1n, 2n, 1n); err(function(){ assert.approximately(2, 1.0, 0.5, 'blah'); @@ -1896,19 +1936,19 @@ describe('assert', function () { err(function() { assert.approximately([1.5], 1.0, 0.5); - }, "expected [ 1.5 ] to be a number"); + }, "expected [ 1.5 ] to be numeric"); err(function() { assert.approximately(1.5, "1.0", 0.5, 'blah'); - }, "blah: the arguments to closeTo or approximately must be numbers"); + }, "blah: expected '1.0' to be numeric"); err(function() { assert.approximately(1.5, 1.0, true, 'blah'); - }, "blah: the arguments to closeTo or approximately must be numbers"); + }, "blah: expected true to be numeric"); err(function() { assert.approximately(1.5, 1.0, undefined, 'blah'); - }, "blah: the arguments to closeTo or approximately must be numbers, and a delta is required"); + }, "blah: A `delta` value is required for `closeTo`"); }); it('sameMembers', function() { @@ -2135,6 +2175,10 @@ describe('assert', function () { it('above', function() { assert.isAbove(5, 2, '5 should be above 2'); + assert.isAbove(5n, 2, '5 should be above 2'); + assert.isAbove(5, 2n, '5 should be above 2'); + assert.isAbove(5n, 2n, '5 should be above 2'); + assert.isAbove(9007199254740994n, 2, '9007199254740994 should be above 2'); err(function() { assert.isAbove(1, 3, 'blah'); @@ -2186,6 +2230,8 @@ describe('assert', function () { it('atLeast', function() { assert.isAtLeast(5, 2, '5 should be above 2'); assert.isAtLeast(1, 1, '1 should be equal to 1'); + assert.isAtLeast(5n, 2, '5 should be above 2'); + assert.isAtLeast(1, 1n, '1 should be equal to 1'); err(function() { assert.isAtLeast(1, 3, 'blah'); @@ -2231,6 +2277,9 @@ describe('assert', function () { it('below', function() { assert.isBelow(2, 5, '2 should be below 5'); + assert.isBelow(2, 5n, '2 should be below 5'); + assert.isBelow(2n, 5, '2 should be below 5'); + assert.isBelow(2n, 5n, '2 should be below 5'); err(function() { assert.isBelow(3, 1, 'blah'); @@ -2282,6 +2331,8 @@ describe('assert', function () { it('atMost', function() { assert.isAtMost(2, 5, '2 should be below 5'); assert.isAtMost(1, 1, '1 should be equal to 1'); + assert.isAtMost(2n, 5, '2 should be below 5'); + assert.isAtMost(1, 1n, '1 should be equal to 1'); err(function() { assert.isAtMost(3, 1, 'blah'); diff --git a/test/expect.js b/test/expect.js index ad702cf7..c9549d77 100644 --- a/test/expect.js +++ b/test/expect.js @@ -556,6 +556,9 @@ describe('expect', function () { expect('foo').to.have.lengthOf.within(2, 4); expect([ 1, 2, 3 ]).to.have.length.within(2, 4); expect([ 1, 2, 3 ]).to.have.lengthOf.within(2, 4); + expect(5n).to.be.within(5, 10); + expect(5).to.be.within(3n, 6); + expect(5).to.be.within(3, 5n); err(function(){ expect(5).to.not.be.within(4, 6, 'blah'); @@ -3279,31 +3282,31 @@ describe('expect', function () { err(function() { expect([1.5]).to.be.closeTo(1.0, 0.5, 'blah'); - }, "blah: expected [ 1.5 ] to be a number"); + }, "blah: expected [ 1.5 ] to be numeric"); err(function() { expect([1.5], 'blah').to.be.closeTo(1.0, 0.5); - }, "blah: expected [ 1.5 ] to be a number"); + }, "blah: expected [ 1.5 ] to be numeric"); err(function() { expect(1.5).to.be.closeTo("1.0", 0.5, 'blah'); - }, "blah: the arguments to closeTo or approximately must be numbers"); + }, "blah: expected '1.0' to be numeric"); err(function() { expect(1.5, 'blah').to.be.closeTo("1.0", 0.5); - }, "blah: the arguments to closeTo or approximately must be numbers"); + }, "blah: expected '1.0' to be numeric"); err(function() { expect(1.5).to.be.closeTo(1.0, true, 'blah'); - }, "blah: the arguments to closeTo or approximately must be numbers"); + }, "blah: expected true to be numeric"); err(function() { expect(1.5, 'blah').to.be.closeTo(1.0, true); - }, "blah: the arguments to closeTo or approximately must be numbers"); + }, "blah: expected true to be numeric"); err(function() { expect(1.5, 'blah').to.be.closeTo(1.0); - }, "blah: the arguments to closeTo or approximately must be numbers, and a delta is required"); + }, "blah: A `delta` value is required for `closeTo`"); }); it('approximately', function(){ @@ -3321,19 +3324,19 @@ describe('expect', function () { err(function() { expect([1.5]).to.be.approximately(1.0, 0.5); - }, "expected [ 1.5 ] to be a number"); + }, "expected [ 1.5 ] to be numeric"); err(function() { expect(1.5).to.be.approximately("1.0", 0.5); - }, "the arguments to closeTo or approximately must be numbers"); + }, "expected '1.0' to be numeric"); err(function() { expect(1.5).to.be.approximately(1.0, true); - }, "the arguments to closeTo or approximately must be numbers"); + }, "expected true to be numeric"); err(function() { expect(1.5).to.be.approximately(1.0); - }, "the arguments to closeTo or approximately must be numbers, and a delta is required"); + }, "A `delta` value is required for `closeTo`"); }); it('oneOf', function() { diff --git a/test/should.js b/test/should.js index baa7891f..4b7fbc48 100644 --- a/test/should.js +++ b/test/should.js @@ -2756,19 +2756,19 @@ describe('should', function() { err(function() { [1.5].should.be.closeTo(1.0, 0.5, 'blah'); - }, "blah: expected [ 1.5 ] to be a number"); + }, "blah: expected [ 1.5 ] to be numeric"); err(function() { (1.5).should.be.closeTo("1.0", 0.5, 'blah'); - }, "blah: the arguments to closeTo or approximately must be numbers"); + }, "blah: expected '1.0' to be numeric"); err(function() { (1.5).should.be.closeTo(1.0, true, 'blah'); - }, "blah: the arguments to closeTo or approximately must be numbers"); + }, "blah: expected true to be numeric"); err(function() { (1.5).should.be.closeTo(1.0, undefined, 'blah'); - }, "blah: the arguments to closeTo or approximately must be numbers, and a delta is required"); + }, "blah: A `delta` value is required for `closeTo`"); }); it('approximately', function(){ @@ -2780,19 +2780,19 @@ describe('should', function() { err(function() { [1.5].should.be.approximately(1.0, 0.5); - }, "expected [ 1.5 ] to be a number"); + }, "expected [ 1.5 ] to be numeric"); err(function() { (1.5).should.be.approximately("1.0", 0.5); - }, "the arguments to closeTo or approximately must be numbers"); + }, "expected '1.0' to be numeric"); err(function() { (1.5).should.be.approximately(1.0, true); - }, "the arguments to closeTo or approximately must be numbers"); + }, "expected true to be numeric"); err(function() { (1.5).should.be.approximately(1.0); - }, "the arguments to closeTo or approximately must be numbers, and a delta is required"); + }, "A `delta` value is required for `closeTo`"); }); it('include.members', function() {