Skip to content

Commit 1b17805

Browse files
authored
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`
1 parent 346421f commit 1b17805

File tree

6 files changed

+167
-68
lines changed

6 files changed

+167
-68
lines changed

lib/chai/core/assertions.js

+41-40
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,6 @@ Assertion.addProperty('any', function () {
233233
* @namespace BDD
234234
* @public
235235
*/
236-
237236
Assertion.addProperty('all', function () {
238237
flag(this, 'all', true);
239238
flag(this, 'any', false);
@@ -694,6 +693,17 @@ Assertion.addProperty('true', function () {
694693
);
695694
});
696695

696+
Assertion.addProperty('numeric', function () {
697+
const object = flag(this, 'object');
698+
699+
this.assert(
700+
['Number', 'BigInt'].includes(_.type(object))
701+
, 'expected #{this} to be numeric'
702+
, 'expected #{this} to not be numeric'
703+
, flag(this, 'negate') ? false : true
704+
);
705+
});
706+
697707
/**
698708
* ### .callable
699709
*
@@ -1208,27 +1218,19 @@ function assertAbove (n, msg) {
12081218
, msgPrefix = ((flagMsg) ? flagMsg + ': ' : '')
12091219
, ssfi = flag(this, 'ssfi')
12101220
, objType = _.type(obj).toLowerCase()
1211-
, nType = _.type(n).toLowerCase()
1212-
, errorMessage
1213-
, shouldThrow = true;
1221+
, nType = _.type(n).toLowerCase();
12141222

12151223
if (doLength && objType !== 'map' && objType !== 'set') {
12161224
new Assertion(obj, flagMsg, ssfi, true).to.have.property('length');
12171225
}
12181226

12191227
if (!doLength && (objType === 'date' && nType !== 'date')) {
1220-
errorMessage = msgPrefix + 'the argument to above must be a date';
1221-
} else if (nType !== 'number' && (doLength || objType === 'number')) {
1222-
errorMessage = msgPrefix + 'the argument to above must be a number';
1223-
} else if (!doLength && (objType !== 'date' && objType !== 'number')) {
1228+
throw new AssertionError(msgPrefix + 'the argument to above must be a date', undefined, ssfi);
1229+
} else if (!_.isNumeric(n) && (doLength || _.isNumeric(obj))) {
1230+
throw new AssertionError(msgPrefix + 'the argument to above must be a number', undefined, ssfi);
1231+
} else if (!doLength && (objType !== 'date' && !_.isNumeric(obj))) {
12241232
var printObj = (objType === 'string') ? "'" + obj + "'" : obj;
1225-
errorMessage = msgPrefix + 'expected ' + printObj + ' to be a number or a date';
1226-
} else {
1227-
shouldThrow = false;
1228-
}
1229-
1230-
if (shouldThrow) {
1231-
throw new AssertionError(errorMessage, undefined, ssfi);
1233+
throw new AssertionError(msgPrefix + 'expected ' + printObj + ' to be a number or a date', undefined, ssfi);
12321234
}
12331235

12341236
if (doLength) {
@@ -1299,7 +1301,7 @@ Assertion.addMethod('greaterThan', assertAbove);
12991301
* @name least
13001302
* @alias gte
13011303
* @alias greaterThanOrEqual
1302-
* @param {number} n
1304+
* @param {unknown} n
13031305
* @param {string} msg _optional_
13041306
* @namespace BDD
13051307
* @public
@@ -1322,9 +1324,9 @@ function assertLeast (n, msg) {
13221324

13231325
if (!doLength && (objType === 'date' && nType !== 'date')) {
13241326
errorMessage = msgPrefix + 'the argument to least must be a date';
1325-
} else if (nType !== 'number' && (doLength || objType === 'number')) {
1327+
} else if (!_.isNumeric(n) && (doLength || _.isNumeric(obj))) {
13261328
errorMessage = msgPrefix + 'the argument to least must be a number';
1327-
} else if (!doLength && (objType !== 'date' && objType !== 'number')) {
1329+
} else if (!doLength && (objType !== 'date' && !_.isNumeric(obj))) {
13281330
var printObj = (objType === 'string') ? "'" + obj + "'" : obj;
13291331
errorMessage = msgPrefix + 'expected ' + printObj + ' to be a number or a date';
13301332
} else {
@@ -1402,7 +1404,7 @@ Assertion.addMethod('greaterThanOrEqual', assertLeast);
14021404
* @name below
14031405
* @alias lt
14041406
* @alias lessThan
1405-
* @param {number} n
1407+
* @param {unknown} n
14061408
* @param {string} msg _optional_
14071409
* @namespace BDD
14081410
* @public
@@ -1422,12 +1424,12 @@ function assertBelow (n, msg) {
14221424
if (doLength && objType !== 'map' && objType !== 'set') {
14231425
new Assertion(obj, flagMsg, ssfi, true).to.have.property('length');
14241426
}
1425-
1427+
14261428
if (!doLength && (objType === 'date' && nType !== 'date')) {
14271429
errorMessage = msgPrefix + 'the argument to below must be a date';
1428-
} else if (nType !== 'number' && (doLength || objType === 'number')) {
1430+
} else if (!_.isNumeric(n) && (doLength || _.isNumeric(obj))) {
14291431
errorMessage = msgPrefix + 'the argument to below must be a number';
1430-
} else if (!doLength && (objType !== 'date' && objType !== 'number')) {
1432+
} else if (!doLength && (objType !== 'date' && !_.isNumeric(obj))) {
14311433
var printObj = (objType === 'string') ? "'" + obj + "'" : obj;
14321434
errorMessage = msgPrefix + 'expected ' + printObj + ' to be a number or a date';
14331435
} else {
@@ -1506,7 +1508,7 @@ Assertion.addMethod('lessThan', assertBelow);
15061508
* @name most
15071509
* @alias lte
15081510
* @alias lessThanOrEqual
1509-
* @param {number} n
1511+
* @param {unknown} n
15101512
* @param {string} msg _optional_
15111513
* @namespace BDD
15121514
* @public
@@ -1529,9 +1531,9 @@ function assertMost (n, msg) {
15291531

15301532
if (!doLength && (objType === 'date' && nType !== 'date')) {
15311533
errorMessage = msgPrefix + 'the argument to most must be a date';
1532-
} else if (nType !== 'number' && (doLength || objType === 'number')) {
1534+
} else if (!_.isNumeric(n) && (doLength || _.isNumeric(obj))) {
15331535
errorMessage = msgPrefix + 'the argument to most must be a number';
1534-
} else if (!doLength && (objType !== 'date' && objType !== 'number')) {
1536+
} else if (!doLength && (objType !== 'date' && !_.isNumeric(obj))) {
15351537
var printObj = (objType === 'string') ? "'" + obj + "'" : obj;
15361538
errorMessage = msgPrefix + 'expected ' + printObj + ' to be a number or a date';
15371539
} else {
@@ -1608,8 +1610,8 @@ Assertion.addMethod('lessThanOrEqual', assertMost);
16081610
* expect(4, 'nooo why fail??').to.be.within(1, 3);
16091611
*
16101612
* @name within
1611-
* @param {number} start lower bound inclusive
1612-
* @param {number} finish upper bound inclusive
1613+
* @param {unknown} start lower bound inclusive
1614+
* @param {unknown} finish upper bound inclusive
16131615
* @param {string} msg _optional_
16141616
* @namespace BDD
16151617
* @public
@@ -1636,9 +1638,9 @@ Assertion.addMethod('within', function (start, finish, msg) {
16361638

16371639
if (!doLength && (objType === 'date' && (startType !== 'date' || finishType !== 'date'))) {
16381640
errorMessage = msgPrefix + 'the arguments to within must be dates';
1639-
} else if ((startType !== 'number' || finishType !== 'number') && (doLength || objType === 'number')) {
1641+
} else if ((!_.isNumeric(start) || !_.isNumeric(finish)) && (doLength || _.isNumeric(obj))) {
16401642
errorMessage = msgPrefix + 'the arguments to within must be numbers';
1641-
} else if (!doLength && (objType !== 'date' && objType !== 'number')) {
1643+
} else if (!doLength && (objType !== 'date' && !_.isNumeric(obj))) {
16421644
var printObj = (objType === 'string') ? "'" + obj + "'" : obj;
16431645
errorMessage = msgPrefix + 'expected ' + printObj + ' to be a number or a date';
16441646
} else {
@@ -3013,19 +3015,18 @@ function closeTo(expected, delta, msg) {
30133015
, flagMsg = flag(this, 'message')
30143016
, ssfi = flag(this, 'ssfi');
30153017

3016-
new Assertion(obj, flagMsg, ssfi, true).is.a('number');
3017-
if (typeof expected !== 'number' || typeof delta !== 'number') {
3018-
flagMsg = flagMsg ? flagMsg + ': ' : '';
3019-
var deltaMessage = delta === undefined ? ", and a delta is required" : "";
3020-
throw new AssertionError(
3021-
flagMsg + 'the arguments to closeTo or approximately must be numbers' + deltaMessage,
3022-
undefined,
3023-
ssfi
3024-
);
3025-
}
3018+
new Assertion(obj, flagMsg, ssfi, true).is.numeric;
3019+
let message = 'A `delta` value is required for `closeTo`';
3020+
if (delta == undefined) throw new AssertionError(flagMsg ? `${flagMsg}: ${message}` : message, undefined, ssfi);
3021+
new Assertion(delta, flagMsg, ssfi, true).is.numeric;
3022+
message = 'A `expected` value is required for `closeTo`';
3023+
if (expected == undefined) throw new AssertionError(flagMsg ? `${flagMsg}: ${message}` : message, undefined, ssfi);
3024+
new Assertion(expected, flagMsg, ssfi, true).is.numeric;
3025+
3026+
const abs = (x) => x < 0n ? -x : x;
30263027

30273028
this.assert(
3028-
Math.abs(obj - expected) <= delta
3029+
abs(obj - expected) <= delta
30293030
, 'expected #{this} to be close to ' + expected + ' +/- ' + delta
30303031
, 'expected #{this} not to be close to ' + expected + ' +/- ' + delta
30313032
);

lib/chai/interface/assert.js

+39
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,45 @@ assert.isNotNumber = function (val, msg) {
706706
new Assertion(val, msg, assert.isNotNumber, true).to.not.be.a('number');
707707
};
708708

709+
/**
710+
* ### .isNumeric(value, [message])
711+
*
712+
* Asserts that `value` is a number or BigInt.
713+
*
714+
* var cups = 2;
715+
* assert.isNumeric(cups, 'how many cups');
716+
*
717+
* var cups = 10n;
718+
* assert.isNumeric(cups, 'how many cups');
719+
*
720+
* @name isNumeric
721+
* @param {unknown} val
722+
* @param {string} msg
723+
* @namespace Assert
724+
* @public
725+
*/
726+
assert.isNumeric = function (val, msg) {
727+
new Assertion(val, msg, assert.isNumeric, true).is.numeric;
728+
};
729+
730+
/**
731+
* ### .isNotNumeric(value, [message])
732+
*
733+
* Asserts that `value` is _not_ a number or BigInt.
734+
*
735+
* var cups = '2 cups please';
736+
* assert.isNotNumeric(cups, 'how many cups');
737+
*
738+
* @name isNotNumeric
739+
* @param {unknown} val
740+
* @param {string} msg
741+
* @namespace Assert
742+
* @public
743+
*/
744+
assert.isNotNumeric = function (val, msg) {
745+
new Assertion(val, msg, assert.isNotNumeric, true).is.not.numeric;
746+
};
747+
709748
/**
710749
* ### .isFinite(value, [message])
711750
*

lib/chai/utils/index.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import * as checkError from 'check-error';
1111
export {test} from './test.js';
1212

1313
// type utility
14-
export {type} from './type-detect.js';
14+
import {type} from './type-detect.js';
15+
export {type};
1516

1617
// expectTypes utility
1718
export {expectTypes} from './expectTypes.js';
@@ -105,3 +106,7 @@ export {getOperator} from './getOperator.js';
105106
export function isRegExp(obj) {
106107
return Object.prototype.toString.call(obj) === '[object RegExp]';
107108
}
109+
110+
export function isNumeric(obj) {
111+
return ['Number', 'BigInt'].includes(type(obj))
112+
}

test/assert.js

+59-8
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,23 @@ describe('assert', function () {
153153
assert.typeOf(function() {}, 'asyncfunction', 'blah');
154154
}, "blah: expected [Function] to be an asyncfunction");
155155

156+
assert.typeOf(5n, 'bigint');
157+
158+
assert.typeOf(() => {}, 'function');
159+
assert.typeOf(function() {}, 'function');
160+
assert.typeOf(async function() {}, 'asyncfunction');
161+
assert.typeOf(function*() {}, 'generatorfunction');
162+
assert.typeOf(async function*() {}, 'asyncgeneratorfunction');
163+
assert.typeOf(Symbol(), 'symbol');
164+
165+
err(function () {
166+
assert.typeOf(5, 'function', 'blah');
167+
}, "blah: expected 5 to be a function");
168+
169+
err(function () {
170+
assert.typeOf(function() {}, 'asyncfunction', 'blah');
171+
}, "blah: expected [Function] to be an asyncfunction");
172+
156173
err(function () {
157174
assert.typeOf(5, 'string', 'blah');
158175
}, "blah: expected 5 to be a string");
@@ -632,6 +649,27 @@ describe('assert', function () {
632649
}, "blah: expected 4 not to be a number");
633650
});
634651

652+
653+
it('isNumeric', function() {
654+
assert.isNumeric(1);
655+
assert.isNumeric(Number('3'));
656+
assert.isNumeric(6n);
657+
assert.isNumeric(BigInt(9));
658+
659+
err(function () {
660+
assert.isNumeric('1', 'blah');
661+
}, "blah: expected \'1\' to be numeric");
662+
});
663+
664+
it('isNotNumeric', function () {
665+
assert.isNotNumeric('hello');
666+
assert.isNotNumeric([ 5 ]);
667+
668+
err(function () {
669+
assert.isNotNumeric(4, 'blah');
670+
}, "blah: expected 4 to not be numeric");
671+
});
672+
635673
it('isFinite', function() {
636674
assert.isFinite(4);
637675
assert.isFinite(-10);
@@ -1855,6 +1893,7 @@ describe('assert', function () {
18551893
assert.closeTo(1.5, 1.0, 0.5);
18561894
assert.closeTo(10, 20, 20);
18571895
assert.closeTo(-10, 20, 30);
1896+
assert.closeTo(10, 10, 0);
18581897

18591898
err(function(){
18601899
assert.closeTo(2, 1.0, 0.5, 'blah');
@@ -1866,25 +1905,26 @@ describe('assert', function () {
18661905

18671906
err(function() {
18681907
assert.closeTo([1.5], 1.0, 0.5, 'blah');
1869-
}, "blah: expected [ 1.5 ] to be a number");
1908+
}, "blah: expected [ 1.5 ] to be numeric");
18701909

18711910
err(function() {
18721911
assert.closeTo(1.5, "1.0", 0.5, 'blah');
1873-
}, "blah: the arguments to closeTo or approximately must be numbers");
1912+
}, "blah: expected '1.0' to be numeric");
18741913

18751914
err(function() {
18761915
assert.closeTo(1.5, 1.0, true, 'blah');
1877-
}, "blah: the arguments to closeTo or approximately must be numbers");
1916+
}, "blah: expected true to be numeric");
18781917

18791918
err(function() {
18801919
assert.closeTo(1.5, 1.0, undefined, 'blah');
1881-
}, "blah: the arguments to closeTo or approximately must be numbers, and a delta is required");
1920+
}, "blah: A `delta` value is required for `closeTo`");
18821921
});
18831922

18841923
it('approximately', function(){
18851924
assert.approximately(1.5, 1.0, 0.5);
18861925
assert.approximately(10, 20, 20);
18871926
assert.approximately(-10, 20, 30);
1927+
assert.approximately(1n, 2n, 1n);
18881928

18891929
err(function(){
18901930
assert.approximately(2, 1.0, 0.5, 'blah');
@@ -1896,19 +1936,19 @@ describe('assert', function () {
18961936

18971937
err(function() {
18981938
assert.approximately([1.5], 1.0, 0.5);
1899-
}, "expected [ 1.5 ] to be a number");
1939+
}, "expected [ 1.5 ] to be numeric");
19001940

19011941
err(function() {
19021942
assert.approximately(1.5, "1.0", 0.5, 'blah');
1903-
}, "blah: the arguments to closeTo or approximately must be numbers");
1943+
}, "blah: expected '1.0' to be numeric");
19041944

19051945
err(function() {
19061946
assert.approximately(1.5, 1.0, true, 'blah');
1907-
}, "blah: the arguments to closeTo or approximately must be numbers");
1947+
}, "blah: expected true to be numeric");
19081948

19091949
err(function() {
19101950
assert.approximately(1.5, 1.0, undefined, 'blah');
1911-
}, "blah: the arguments to closeTo or approximately must be numbers, and a delta is required");
1951+
}, "blah: A `delta` value is required for `closeTo`");
19121952
});
19131953

19141954
it('sameMembers', function() {
@@ -2135,6 +2175,10 @@ describe('assert', function () {
21352175

21362176
it('above', function() {
21372177
assert.isAbove(5, 2, '5 should be above 2');
2178+
assert.isAbove(5n, 2, '5 should be above 2');
2179+
assert.isAbove(5, 2n, '5 should be above 2');
2180+
assert.isAbove(5n, 2n, '5 should be above 2');
2181+
assert.isAbove(9007199254740994n, 2, '9007199254740994 should be above 2');
21382182

21392183
err(function() {
21402184
assert.isAbove(1, 3, 'blah');
@@ -2186,6 +2230,8 @@ describe('assert', function () {
21862230
it('atLeast', function() {
21872231
assert.isAtLeast(5, 2, '5 should be above 2');
21882232
assert.isAtLeast(1, 1, '1 should be equal to 1');
2233+
assert.isAtLeast(5n, 2, '5 should be above 2');
2234+
assert.isAtLeast(1, 1n, '1 should be equal to 1');
21892235

21902236
err(function() {
21912237
assert.isAtLeast(1, 3, 'blah');
@@ -2231,6 +2277,9 @@ describe('assert', function () {
22312277

22322278
it('below', function() {
22332279
assert.isBelow(2, 5, '2 should be below 5');
2280+
assert.isBelow(2, 5n, '2 should be below 5');
2281+
assert.isBelow(2n, 5, '2 should be below 5');
2282+
assert.isBelow(2n, 5n, '2 should be below 5');
22342283

22352284
err(function() {
22362285
assert.isBelow(3, 1, 'blah');
@@ -2282,6 +2331,8 @@ describe('assert', function () {
22822331
it('atMost', function() {
22832332
assert.isAtMost(2, 5, '2 should be below 5');
22842333
assert.isAtMost(1, 1, '1 should be equal to 1');
2334+
assert.isAtMost(2n, 5, '2 should be below 5');
2335+
assert.isAtMost(1, 1n, '1 should be equal to 1');
22852336

22862337
err(function() {
22872338
assert.isAtMost(3, 1, 'blah');

0 commit comments

Comments
 (0)