Support big int in approximently (#1606)

* Add `numeric` assertion

* Use `numeric` assertion in `approximately`

* Use home-made `abs` to support BigInt in `approximately`

* support bigint in "above" assertion

* add bigint test for typeOf

* add isNumeric and isNotNumeric to assert.js

* support BigInt in `atLeast`

* support bigint in `below`

* add support for bigint in `atMost`

* add bigint support to `within`
This commit is contained in:
Kristján Oddsson 2024-10-09 16:55:17 +00:00 committed by GitHub
parent 346421f2c6
commit 1b1780582c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 167 additions and 68 deletions

View file

@ -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
@ -1425,9 +1427,9 @@ function assertBelow (n, msg) {
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
);

View file

@ -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])
*

View file

@ -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))
}

View file

@ -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');

View file

@ -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() {

View file

@ -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() {