Skip to content

Commit

Permalink
Support big int in approximently (#1606)
Browse files Browse the repository at this point in the history
* 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`
  • Loading branch information
koddsson authored Oct 9, 2024
1 parent 346421f commit 1b17805
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 68 deletions.
81 changes: 41 additions & 40 deletions lib/chai/core/assertions.js
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,6 @@ Assertion.addProperty('any', function () {
* @namespace BDD
* @public
*/

Assertion.addProperty('all', function () {
flag(this, 'all', true);
flag(this, 'any', false);
Expand Down Expand Up @@ -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
*
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand All @@ -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 {
Expand Down Expand Up @@ -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
Expand All @@ -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 {
Expand Down Expand Up @@ -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
Expand All @@ -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 {
Expand Down Expand Up @@ -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
Expand All @@ -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 {
Expand Down Expand Up @@ -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
);
Expand Down
39 changes: 39 additions & 0 deletions lib/chai/interface/assert.js
Original file line number Diff line number Diff line change
Expand Up @@ -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])
*
Expand Down
7 changes: 6 additions & 1 deletion lib/chai/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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) {

Check warning on line 110 in lib/chai/utils/index.js

View workflow job for this annotation

GitHub Actions / build

Missing JSDoc comment
return ['Number', 'BigInt'].includes(type(obj))
}
67 changes: 59 additions & 8 deletions test/assert.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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');
Expand All @@ -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');
Expand All @@ -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() {
Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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');
Expand Down
Loading

0 comments on commit 1b17805

Please sign in to comment.