ts-data-forge
Version:
[](https://www.npmjs.com/package/ts-data-forge) [](https://www.npmjs.com/package/ts-data-forge) [ • 7.64 kB
text/typescript
import { expectType } from '../expect-type.mjs';
import { pipe } from '../functional/index.mjs';
import { asNonZeroFiniteNumber } from './branded-types/index.mjs';
import { Num } from './num.mjs';
const testClamp = (
[a, b]: readonly [number, number],
target: number,
expected: number,
): void => {
test(`clamp ${target} to [${a}, ${b}]`, () => {
expect(Num.clamp(a, b)(target)).toBe(expected);
});
};
describe('Num test', () => {
describe('clamp', () => {
testClamp([0, 2], 2.3, 2);
testClamp([0, 2], -0.5, 0);
testClamp([0, 2], 1.5, 1.5);
testClamp([0, 2], Number.NaN, 0);
test('should support regular usage with three parameters', () => {
expectTypeOf(Num.clamp(15, 0, 10)).toEqualTypeOf<number>();
expect(Num.clamp(15, 0, 10)).toBe(10);
expect(Num.clamp(-5, 0, 10)).toBe(0);
expect(Num.clamp(5, 0, 10)).toBe(5);
expect(Num.clamp(Number.NaN, 0, 10)).toBe(0);
});
test('should work with pipe when curried', () => {
const clampTo0_10 = Num.clamp(0, 10);
const result1 = pipe(15).map(clampTo0_10).value;
expect(result1).toBe(10);
const result2 = pipe(-5).map(clampTo0_10).value;
expect(result2).toBe(0);
const result3 = pipe(7.5).map(clampTo0_10).value;
expect(result3).toBe(7.5);
});
test('should handle edge cases in curried form', () => {
const clampTo5_15 = Num.clamp(5, 15);
expectType<typeof clampTo5_15, (target: number) => number>('=');
// Invalid (non-finite) values return the lower bound
expect(clampTo5_15(Number.POSITIVE_INFINITY)).toBe(5);
expect(clampTo5_15(Number.NEGATIVE_INFINITY)).toBe(5);
expect(clampTo5_15(Number.NaN)).toBe(5);
});
test('should work with negative ranges', () => {
expect(Num.clamp(-15, -10, -5)).toBe(-10);
expect(Num.clamp(-7, -10, -5)).toBe(-7);
expect(Num.clamp(0, -10, -5)).toBe(-5);
const clampNegative = Num.clamp(-10, -5);
expect(clampNegative(-15)).toBe(-10);
expect(clampNegative(-7)).toBe(-7);
expect(clampNegative(0)).toBe(-5);
});
});
describe('isUintInRangeInclusive', () => {
test('truthy case', () => {
const f = Num.isUintInRangeInclusive(0, 4);
const x: number = 2;
if (f(x)) {
expectType<typeof x, 0 | 1 | 2 | 3 | 4>('=');
} else {
expectType<typeof x, number>('=');
}
assert.isTrue(f(x));
});
test('falsy case', () => {
const f = Num.isUintInRangeInclusive(0, 4);
const x: number = 100;
if (f(x)) {
expectType<typeof x, 0 | 1 | 2 | 3 | 4>('=');
} else {
expectType<typeof x, number>('=');
}
assert.isFalse(f(x));
});
});
describe('from', () => {
test('converts string to number', () => {
expect(Num.from('123')).toBe(123);
expect(Num.from('123.45')).toBe(123.45);
expect(Num.from('-42')).toBe(-42);
});
test('handles edge cases', () => {
expect(Num.from('')).toBe(0);
expect(Num.from('abc')).toBeNaN();
expect(Num.from(true)).toBe(1);
expect(Num.from(false)).toBe(0);
});
});
describe('isInRange', () => {
test('checks range (lower inclusive, upper exclusive)', () => {
const inRange = Num.isInRange(0, 10);
assert.isTrue(inRange(5));
assert.isTrue(inRange(0)); // inclusive lower bound
assert.isFalse(inRange(10)); // exclusive upper bound
assert.isFalse(inRange(-1));
assert.isFalse(inRange(15));
});
});
describe('isInRangeInclusive', () => {
test('checks range (inclusive)', () => {
const inRange = Num.isInRangeInclusive(0, 10);
assert.isTrue(inRange(5));
assert.isTrue(inRange(0)); // inclusive
assert.isTrue(inRange(10)); // inclusive
assert.isFalse(inRange(-1));
assert.isFalse(inRange(15));
});
});
describe('isUintInRange', () => {
test('checks uint range (lower inclusive, upper exclusive)', () => {
const inRange = Num.isUintInRange(0, 5);
assert.isTrue(inRange(2));
assert.isTrue(inRange(0)); // inclusive lower bound
assert.isFalse(inRange(5)); // exclusive upper bound
assert.isFalse(inRange(-1));
});
});
describe('isNonZero', () => {
test('type guard for non-zero numbers', () => {
const x: number = 5;
if (Num.isNonZero(x)) {
expectType<typeof x, NonZeroNumber>('=');
}
assert.isTrue(Num.isNonZero(5));
assert.isTrue(Num.isNonZero(-3));
assert.isFalse(Num.isNonZero(0));
});
});
describe('isNonNegative', () => {
test('type guard for non-negative numbers', () => {
assert.isTrue(Num.isNonNegative(5));
assert.isTrue(Num.isNonNegative(0));
assert.isFalse(Num.isNonNegative(-1));
});
});
describe('isPositive', () => {
test('type guard for positive numbers', () => {
assert.isTrue(Num.isPositive(5));
assert.isTrue(Num.isPositive(0.1));
assert.isFalse(Num.isPositive(0));
assert.isFalse(Num.isPositive(-1));
});
});
describe('div', () => {
test('basic division', () => {
expect(Num.div(10, 2)).toBe(5);
expect(Num.div(7, 3)).toBe(7 / 3);
expect(Num.div(-10, 2)).toBe(-5);
});
});
describe('divInt', () => {
test('integer division (floor)', () => {
expect(Num.divInt(10, 3)).toBe(3);
expect(Num.divInt(7, 2)).toBe(3);
expect(Num.divInt(-7, 2)).toBe(-4); // floor division
expect(Num.divInt(10.7, asNonZeroFiniteNumber(3.2))).toBe(3); // floors both operands
});
});
describe('roundAt', () => {
test('rounds to specified decimal places', () => {
expect(Num.roundAt(3.141_59, 2)).toBe(3.14);
expect(Num.roundAt(3.141_59, 3)).toBe(3.142);
expect(Num.roundAt(2.555, 2)).toBe(2.56);
expect(Num.roundAt(123.456, 1)).toBe(123.5);
});
});
describe('roundToInt', () => {
test('rounds to nearest integer', () => {
expect(Num.roundToInt(3.4)).toBe(3);
expect(Num.roundToInt(3.6)).toBe(4);
expect(Num.roundToInt(2.5)).toBe(3);
expect(Num.roundToInt(-2.3)).toBe(-1); // bitwise behavior: -2.3 + 0.5 = -1.8, then floor with bitwise OR
expect(Num.roundToInt(-2.7)).toBe(-2); // bitwise behavior: -2.7 + 0.5 = -2.2, then floor with bitwise OR
});
});
describe('round', () => {
test('creates rounding function with specified precision', () => {
const round2 = Num.round(2);
expect(round2(3.141_59)).toBe(3.14);
expect(round2(2.556)).toBe(2.56);
const round1 = Num.round(1);
expect(round1(3.141_59)).toBe(3.1);
expect(round1(2.56)).toBe(2.6);
const round3 = Num.round(3);
expect(round3(3.1416)).toBe(3.142);
expect(round3(2.7182)).toBe(2.718);
});
});
describe('mapNaN2Undefined', () => {
test('maps NaN to undefined', () => {
expect(Num.mapNaN2Undefined(5)).toBe(5);
expect(Num.mapNaN2Undefined(0)).toBe(0);
expect(Num.mapNaN2Undefined(-3.14)).toBe(-3.14);
expect(Num.mapNaN2Undefined(Number.NaN)).toBeUndefined();
});
});
describe('increment', () => {
test('increments SmallUint values', () => {
expect(Num.increment(0)).toBe(1);
expect(Num.increment(5)).toBe(6);
expect(Num.increment(38)).toBe(39);
});
});
describe('decrement', () => {
test('decrements PositiveSmallInt values', () => {
expect(Num.decrement(1)).toBe(0);
expect(Num.decrement(5)).toBe(4);
expect(Num.decrement(39)).toBe(38);
});
});
});