diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 66d6ee4..3a59e6f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -407,19 +407,19 @@ packages: resolution: {integrity: sha512-GNQqST7zI85dAFVyao6oiTeg5rNhO9FH1ZAd397qQhvwfxrrniNfuoewu8gPXyP0R4XBiiaCwhBL7w9S/F5guw==} engines: {node: '>=18.0'} - '@csstools/color-helpers@5.0.1': - resolution: {integrity: sha512-MKtmkA0BX87PKaO1NFRTFH+UnkgnmySQOvNxJubsadusqPEC2aJ9MOQiMceZJJ6oitUl/i0L6u0M1IrmAOmgBA==} + '@csstools/color-helpers@5.0.2': + resolution: {integrity: sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==} engines: {node: '>=18'} - '@csstools/css-calc@2.1.1': - resolution: {integrity: sha512-rL7kaUnTkL9K+Cvo2pnCieqNpTKgQzy5f+N+5Iuko9HAoasP+xgprVh7KN/MaJVvVL1l0EzQq2MoqBHKSrDrag==} + '@csstools/css-calc@2.1.2': + resolution: {integrity: sha512-TklMyb3uBB28b5uQdxjReG4L80NxAqgrECqLZFQbyLekwwlcDDS8r3f07DKqeo8C4926Br0gf/ZDe17Zv4wIuw==} engines: {node: '>=18'} peerDependencies: '@csstools/css-parser-algorithms': ^3.0.4 '@csstools/css-tokenizer': ^3.0.3 - '@csstools/css-color-parser@3.0.7': - resolution: {integrity: sha512-nkMp2mTICw32uE5NN+EsJ4f5N+IGFeCFu4bGpiKgb2Pq/7J/MpyLBeQ5ry4KKtRFZaYs6sTmcMYrSRIyj5DFKA==} + '@csstools/css-color-parser@3.0.8': + resolution: {integrity: sha512-pdwotQjCCnRPuNi06jFuP68cykU1f3ZWExLe/8MQ1LOs8Xq+fTkYgd+2V8mWUWMrOn9iS2HftPVaMZDaXzGbhQ==} engines: {node: '>=18'} peerDependencies: '@csstools/css-parser-algorithms': ^3.0.4 @@ -3928,8 +3928,8 @@ snapshots: '@asamuzakjp/css-color@2.8.3': dependencies: - '@csstools/css-calc': 2.1.1(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) - '@csstools/css-color-parser': 3.0.7(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-calc': 2.1.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-color-parser': 3.0.8(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) '@csstools/css-tokenizer': 3.0.3 lru-cache: 10.4.3 @@ -4279,17 +4279,17 @@ snapshots: '@cspell/url@8.17.5': {} - '@csstools/color-helpers@5.0.1': {} + '@csstools/color-helpers@5.0.2': {} - '@csstools/css-calc@2.1.1(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': + '@csstools/css-calc@2.1.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': dependencies: '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) '@csstools/css-tokenizer': 3.0.3 - '@csstools/css-color-parser@3.0.7(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': + '@csstools/css-color-parser@3.0.8(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': dependencies: - '@csstools/color-helpers': 5.0.1 - '@csstools/css-calc': 2.1.1(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/color-helpers': 5.0.2 + '@csstools/css-calc': 2.1.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) '@csstools/css-tokenizer': 3.0.3 diff --git a/src/predicate-factory/createNumberRangePredicate.ts b/src/predicate-factory/createNumberRangePredicate.ts new file mode 100644 index 0000000..00fdf61 --- /dev/null +++ b/src/predicate-factory/createNumberRangePredicate.ts @@ -0,0 +1,26 @@ +import { assertIsNumber } from '../assertion/assertIsNumber.js'; +import { isNumber } from '../predicate/isNumber.js'; + +import type { TypePredicateFn } from '../types/functions.js'; + +export const createNumberRangePredicate = ( + min: number, + max: number = Number.POSITIVE_INFINITY, +): TypePredicateFn => { + assertIsNumber(min, 'min is not a number'); + assertIsNumber(max, 'max is not a number'); + + if (min === Number.NEGATIVE_INFINITY && max === Number.POSITIVE_INFINITY) { + throw new RangeError('min and max cannot be -Infinity and Infinity'); + } + + if (min > max) { + throw new RangeError('min cannot be greater than max'); + } + + if (min === max) { + throw new RangeError('min and max cannot be the same'); + } + + return (input: unknown): input is Type => isNumber(input) && input >= min && input <= max; +}; diff --git a/src/predicate-factory/index.ts b/src/predicate-factory/index.ts index 7e6c22b..8a3519f 100644 --- a/src/predicate-factory/index.ts +++ b/src/predicate-factory/index.ts @@ -3,6 +3,8 @@ export * from './anyOf.js'; export * from './createIsEnumPredicate.js'; export * from './createIsInstanceOfPredicate.js'; export * from './createIsTuplePredicate.js'; +export * from './createNumberRangePredicate.js'; +export * from './createObjectShapePredicate.js'; export * from './createStringLengthRangePredicate.js'; export * from './createStringMatchingPredicate.js'; export * from './everyItem.js'; diff --git a/test/predicate-factory/createNumberRangePredicate.test.ts b/test/predicate-factory/createNumberRangePredicate.test.ts new file mode 100644 index 0000000..4064419 --- /dev/null +++ b/test/predicate-factory/createNumberRangePredicate.test.ts @@ -0,0 +1,34 @@ +import { describe, it, expect } from 'vitest'; + +import { createNumberRangePredicate } from '../../src/predicate-factory/createNumberRangePredicate.js'; + +describe('createNumberRangePredicate()', () => { + it('Returns a type predicate function', () => { + const fn = createNumberRangePredicate(1, 10); + + expect(fn).toBeInstanceOf(Function); + expect(fn(1)).toBeTruthy(); + expect(fn(11)).toBeFalsy(); + expect(fn('')).toBeFalsy(); + expect(fn(undefined)).toBeFalsy(); + }); + + it('Max is optional', () => { + const fn = createNumberRangePredicate(1); + + expect(fn(1)).toBeTruthy(); + expect(fn(Number.POSITIVE_INFINITY)).toBeTruthy(); + expect(fn(Number.NEGATIVE_INFINITY)).toBeFalsy(); + }); + + it.each([ + [1, Number.NaN], + [Number.NaN, 10], + [Number.NaN, Number.NaN], + [Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY], + [10, 1], + [1, 1], + ])('Invalid ranges throw - min: %o max: %o', (min, max) => { + expect(() => createNumberRangePredicate(min, max)).toThrowError(); + }); +});