diff --git a/README.md b/README.md index 327d1be0..b0806bab 100644 --- a/README.md +++ b/README.md @@ -201,7 +201,7 @@ const stringArray = s.array(s.string); const stringArray = s.string.array; ``` -ShapeShift includes a handful of string-specific validations: +ShapeShift includes a handful of array-specific validations: ```typescript s.string.array.lengthLt(5); // Must have less than 5 elements @@ -210,6 +210,9 @@ s.string.array.lengthGt(5); // Must have more than 5 elements s.string.array.lengthGe(5); // Must have 5 or more elements s.string.array.lengthEq(5); // Must have exactly 5 elements s.string.array.lengthNe(5); // Must not have exactly 5 elements +s.string.array.lengthRange(0, 4); // Must have at least 0 elements and less than 4 elements (in math, that is [0, 4)) +s.string.array.lengthRangeInclusive(0, 4); // Must have at least 0 elements and at most 4 elements (in math, that is [0, 4]) +s.string.array.lengthRangeExclusive(0, 4); // Must have more than 0 element and less than 4 elements (in math, that is (0, 4)) ``` > **Note**: All `.length` methods define tuple types with the given amount of elements. For example, `s.string.array.lengthGe(2)`'s inferred type is `[string, string, ...string[]]` diff --git a/src/constraints/ArrayLengthConstraints.ts b/src/constraints/ArrayLengthConstraints.ts index e3171070..4024fe15 100644 --- a/src/constraints/ArrayLengthConstraints.ts +++ b/src/constraints/ArrayLengthConstraints.ts @@ -3,7 +3,7 @@ import { Result } from '../lib/Result'; import type { IConstraint } from './base/IConstraint'; import { Comparator, eq, ge, gt, le, lt, ne } from './util/operators'; -export type ArrayConstraintName = `s.array(T).length${'Lt' | 'Le' | 'Gt' | 'Ge' | 'Eq' | 'Ne'}`; +export type ArrayConstraintName = `s.array(T).length${'Lt' | 'Le' | 'Gt' | 'Ge' | 'Eq' | 'Ne' | 'Range' | 'RangeInclusive' | 'RangeExclusive'}`; function arrayLengthComparator(comparator: Comparator, name: ArrayConstraintName, expected: string, length: number): IConstraint { return { @@ -44,3 +44,36 @@ export function arrayLengthNe(value: number): IConstraint { const expected = `expected.length !== ${value}`; return arrayLengthComparator(ne, 's.array(T).lengthNe', expected, value); } + +export function arrayLengthRange(start: number, endBefore: number): IConstraint { + const expected = `expected.length >= ${start} && expected.length < ${endBefore}`; + return { + run(input: T[]) { + return input.length >= start && input.length < endBefore // + ? Result.ok(input) + : Result.err(new ConstraintError('s.array(T).lengthRange', 'Invalid Array length', input, expected)); + } + }; +} + +export function arrayLengthRangeInclusive(start: number, end: number): IConstraint { + const expected = `expected.length >= ${start} && expected.length <= ${end}`; + return { + run(input: T[]) { + return input.length >= start && input.length <= end // + ? Result.ok(input) + : Result.err(new ConstraintError('s.array(T).lengthRangeInclusive', 'Invalid Array length', input, expected)); + } + }; +} + +export function arrayLengthRangeExclusive(startAfter: number, endBefore: number): IConstraint { + const expected = `expected.length > ${startAfter} && expected.length < ${endBefore}`; + return { + run(input: T[]) { + return input.length > startAfter && input.length < endBefore // + ? Result.ok(input) + : Result.err(new ConstraintError('s.array(T).lengthRangeExclusive', 'Invalid Array length', input, expected)); + } + }; +} diff --git a/src/constraints/StringConstraints.ts b/src/constraints/StringConstraints.ts index 82321ca2..ec0d3aaa 100644 --- a/src/constraints/StringConstraints.ts +++ b/src/constraints/StringConstraints.ts @@ -1,9 +1,9 @@ +import { isIP, isIPv4, isIPv6 } from 'node:net'; import { ConstraintError } from '../lib/errors/ConstraintError'; import { Result } from '../lib/Result'; import type { IConstraint } from './base/IConstraint'; -import { Comparator, eq, ge, gt, le, lt, ne } from './util/operators'; -import { isIP, isIPv4, isIPv6 } from 'node:net'; import { validateEmail } from './util/emailValidator'; +import { Comparator, eq, ge, gt, le, lt, ne } from './util/operators'; export type StringConstraintName = | `s.string.${`length${'Lt' | 'Le' | 'Gt' | 'Ge' | 'Eq' | 'Ne'}` | 'regex' | 'url' | 'uuid' | 'email' | `ip${'v4' | 'v6' | ''}`}`; diff --git a/src/constraints/type-exports.ts b/src/constraints/type-exports.ts index a991f240..49fd424a 100644 --- a/src/constraints/type-exports.ts +++ b/src/constraints/type-exports.ts @@ -5,10 +5,12 @@ export type { arrayLengthGt, arrayLengthLe, arrayLengthLt, - arrayLengthNe + arrayLengthNe, + arrayLengthRange, + arrayLengthRangeInclusive } from './ArrayLengthConstraints'; export type { IConstraint } from './base/IConstraint'; -export type { BigIntConstraintName, bigintEq, bigintGe, bigintGt, bigintLe, bigintLt, bigintNe, bigintDivisibleBy } from './BigIntConstraints'; +export type { BigIntConstraintName, bigintDivisibleBy, bigintEq, bigintGe, bigintGt, bigintLe, bigintLt, bigintNe } from './BigIntConstraints'; export type { BooleanConstraintName, booleanFalse, booleanTrue } from './BooleanConstraints'; export type { DateConstraintName, dateEq, dateGe, dateGt, dateInvalid, dateLe, dateLt, dateNe, dateValid } from './DateConstraints'; export type { @@ -28,17 +30,17 @@ export type { } from './NumberConstraints'; export type { StringConstraintName, - StringProtocol, StringDomain, - UrlOptions, + stringEmail, + stringIp, stringLengthEq, stringLengthGe, stringLengthGt, stringLengthLe, stringLengthLt, stringLengthNe, - stringEmail, + StringProtocol, stringRegex, stringUrl, - stringIp + UrlOptions } from './StringConstraints'; diff --git a/src/index.ts b/src/index.ts index 7763d75c..7b305ff1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,6 +9,5 @@ export * from './lib/errors/ExpectedValidationError'; export * from './lib/errors/MissingPropertyError'; export * from './lib/errors/UnknownPropertyError'; export * from './lib/errors/ValidationError'; - export * from './lib/Result'; export * from './type-exports'; diff --git a/src/type-exports.ts b/src/type-exports.ts index 74ba6fc9..ecf3e0b9 100644 --- a/src/type-exports.ts +++ b/src/type-exports.ts @@ -6,6 +6,8 @@ export type { arrayLengthLe, arrayLengthLt, arrayLengthNe, + arrayLengthRange, + arrayLengthRangeInclusive, BigIntConstraintName, bigintDivisibleBy, bigintEq, diff --git a/src/validators/ArrayValidator.ts b/src/validators/ArrayValidator.ts index 69857d4c..16b9d24b 100644 --- a/src/validators/ArrayValidator.ts +++ b/src/validators/ArrayValidator.ts @@ -1,4 +1,14 @@ -import { arrayLengthEq, arrayLengthGe, arrayLengthGt, arrayLengthLe, arrayLengthLt, arrayLengthNe } from '../constraints/ArrayLengthConstraints'; +import { + arrayLengthEq, + arrayLengthGe, + arrayLengthGt, + arrayLengthLe, + arrayLengthLt, + arrayLengthNe, + arrayLengthRange, + arrayLengthRangeExclusive, + arrayLengthRangeInclusive +} from '../constraints/ArrayLengthConstraints'; import type { IConstraint } from '../constraints/base/IConstraint'; import type { BaseError } from '../lib/errors/BaseError'; import { CombinedPropertyError } from '../lib/errors/CombinedPropertyError'; @@ -19,23 +29,44 @@ export class ArrayValidator extends BaseValidator { } public lengthLe(length: N): BaseValidator]>> { - return this.addConstraint(arrayLengthLe(length) as IConstraint) as any; + return this.addConstraint(arrayLengthLe(length)) as any; } public lengthGt(length: N): BaseValidator<[...Tuple, T, ...T[]]> { - return this.addConstraint(arrayLengthGt(length) as IConstraint) as any; + return this.addConstraint(arrayLengthGt(length)) as any; } public lengthGe(length: N): BaseValidator<[...Tuple, ...T[]]> { - return this.addConstraint(arrayLengthGe(length) as IConstraint) as any; + return this.addConstraint(arrayLengthGe(length)) as any; } public lengthEq(length: N): BaseValidator<[...Tuple]> { - return this.addConstraint(arrayLengthEq(length) as IConstraint) as any; + return this.addConstraint(arrayLengthEq(length)) as any; } public lengthNe(length: number): BaseValidator<[...T[]]> { - return this.addConstraint(arrayLengthNe(length) as IConstraint); + return this.addConstraint(arrayLengthNe(length)) as any; + } + + public lengthRange( + start: S, + endBefore: E + ): BaseValidator]>>, ExpandSmallerTuples]>>>> { + return this.addConstraint(arrayLengthRange(start, endBefore)) as any; + } + + public lengthRangeInclusive( + startAt: S, + endAt: E + ): BaseValidator]>, ExpandSmallerTuples]>>>> { + return this.addConstraint(arrayLengthRangeInclusive(startAt, endAt)) as any; + } + + public lengthRangeExclusive( + startAfter: S, + endBefore: E + ): BaseValidator]>>, ExpandSmallerTuples<[...Tuple]>>> { + return this.addConstraint(arrayLengthRangeExclusive(startAfter, endBefore)) as any; } protected override clone(): this { diff --git a/src/validators/BaseValidator.ts b/src/validators/BaseValidator.ts index 60de54b3..d6e2025a 100644 --- a/src/validators/BaseValidator.ts +++ b/src/validators/BaseValidator.ts @@ -4,7 +4,7 @@ import type { CombinedError } from '../lib/errors/CombinedError'; import type { CombinedPropertyError } from '../lib/errors/CombinedPropertyError'; import type { ValidationError } from '../lib/errors/ValidationError'; import { Result } from '../lib/Result'; -import { ArrayValidator, LiteralValidator, NullishValidator, SetValidator, UnionValidator, DefaultValidator } from './imports'; +import { ArrayValidator, DefaultValidator, LiteralValidator, NullishValidator, SetValidator, UnionValidator } from './imports'; export abstract class BaseValidator { protected constraints: readonly IConstraint[] = []; diff --git a/src/validators/BigIntValidator.ts b/src/validators/BigIntValidator.ts index d758f5cc..9ebc4053 100644 --- a/src/validators/BigIntValidator.ts +++ b/src/validators/BigIntValidator.ts @@ -1,5 +1,5 @@ import type { IConstraint } from '../constraints/base/IConstraint'; -import { bigintEq, bigintGe, bigintGt, bigintLe, bigintLt, bigintNe, bigintDivisibleBy } from '../constraints/BigIntConstraints'; +import { bigintDivisibleBy, bigintEq, bigintGe, bigintGt, bigintLe, bigintLt, bigintNe } from '../constraints/BigIntConstraints'; import { ValidationError } from '../lib/errors/ValidationError'; import { Result } from '../lib/Result'; import { BaseValidator } from './imports'; diff --git a/src/validators/TupleValidator.ts b/src/validators/TupleValidator.ts index 33497cb0..99c9160c 100644 --- a/src/validators/TupleValidator.ts +++ b/src/validators/TupleValidator.ts @@ -1,9 +1,9 @@ -import { BaseValidator } from './imports'; -import { Result } from '../lib/Result'; -import { ValidationError } from '../lib/errors/ValidationError'; -import { CombinedPropertyError } from '../lib/errors/CombinedPropertyError'; import type { IConstraint } from '../constraints/base/IConstraint'; import type { BaseError } from '../lib/errors/BaseError'; +import { CombinedPropertyError } from '../lib/errors/CombinedPropertyError'; +import { ValidationError } from '../lib/errors/ValidationError'; +import { Result } from '../lib/Result'; +import { BaseValidator } from './imports'; export class TupleValidator extends BaseValidator<[...T]> { private readonly validators: BaseValidator<[...T]>[] = []; diff --git a/tests/validators/array.test.ts b/tests/validators/array.test.ts index a8246700..95197a16 100644 --- a/tests/validators/array.test.ts +++ b/tests/validators/array.test.ts @@ -30,12 +30,7 @@ describe('ArrayValidator', () => { expect<[string] | []>(lengthLtPredicate.parse(value)).toEqual(value); }); - test.each([ - [ - ['Hello', 'there'], - ['foo', 'bar', 'baaz'] - ] - ])('GIVEN %p THEN throws ConstraintError', (value) => { + test.each([[['Hello', 'there']], [['foo', 'bar', 'baaz']]])('GIVEN %p THEN throws ConstraintError', (value) => { expectError( () => lengthLtPredicate.parse(value), new ConstraintError('s.array(T).lengthLt', 'Invalid Array length', value, 'expected.length < 2') @@ -46,7 +41,7 @@ describe('ArrayValidator', () => { describe('lengthLe', () => { const lengthLePredicate = s.string.array.lengthLe(2); - test.each([[['Hello'], ['Hello', 'there']]])('GIVEN %p THEN returns given value', (value) => { + test.each([[['Hello']], [['Hello', 'there']]])('GIVEN %p THEN returns given value', (value) => { expect<[string, string] | [string] | []>(lengthLePredicate.parse(value)).toEqual(value); }); @@ -65,7 +60,7 @@ describe('ArrayValidator', () => { expect<[string, string, string, ...string[]]>(lengthGtPredicate.parse(value)).toEqual(value); }); - test.each([[['Hello'], []]])('GIVEN %p THEN throws ConstraintError', (value) => { + test.each([[['Hello']], [[]]])('GIVEN %p THEN throws ConstraintError', (value) => { expectError( () => lengthGtPredicate.parse(value), new ConstraintError('s.array(T).lengthGt', 'Invalid Array length', value, 'expected.length > 2') @@ -76,16 +71,11 @@ describe('ArrayValidator', () => { describe('lengthGe', () => { const lengthGePredicate = s.string.array.lengthGe(2); - test.each([ - [ - ['Hello', 'there'], - ['foo', 'bar', 'baaz'] - ] - ])('GIVEN %p THEN returns given value', (value) => { + test.each([[['Hello', 'there']], [['foo', 'bar', 'baaz']]])('GIVEN %p THEN returns given value', (value) => { expect<[string, string, ...string[]]>(lengthGePredicate.parse(value)).toEqual(value); }); - test.each([[[], ['foo']]])('GIVEN %p THEN throws ConstraintError', (value) => { + test.each([[[]], [['foo']]])('GIVEN %p THEN throws ConstraintError', (value) => { expectError( () => lengthGePredicate.parse(value), new ConstraintError('s.array(T).lengthGe', 'Invalid Array length', value, 'expected.length >= 2') @@ -100,7 +90,7 @@ describe('ArrayValidator', () => { expect<[string, string]>(lengthPredicate.parse(value)).toEqual(value); }); - test.each([[[], ['Hello']]])('GIVEN %p THEN throws ConstraintError', (value) => { + test.each([[[]], [['Hello']]])('GIVEN %p THEN throws ConstraintError', (value) => { expectError( () => lengthPredicate.parse(value), new ConstraintError('s.array(T).lengthEq', 'Invalid Array length', value, 'expected.length === 2') @@ -111,22 +101,75 @@ describe('ArrayValidator', () => { describe('lengthNe', () => { const lengthNotEqPredicate = s.string.array.lengthNe(2); - test.each([[['foo', 'bar', 'baaz'], ['foo']]])('GIVEN %p THEN returns given value', (value) => { + test.each([[['foo', 'bar', 'baaz']], [['foo']]])('GIVEN %p THEN returns given value', (value) => { expect(lengthNotEqPredicate.parse(value)).toEqual(value); }); - test.each([ - [ - ['Hello', 'there'], - ['foo', 'bar'] - ] - ])('GIVEN %p THEN throws ConstraintError', (value) => { + test.each([[['Hello', 'there']], [['foo', 'bar']]])('GIVEN %p THEN throws ConstraintError', (value) => { expectError( () => lengthNotEqPredicate.parse(value), new ConstraintError('s.array(T).lengthNe', 'Invalid Array length', value, 'expected.length !== 2') ); }); }); + + describe('lengthRange', () => { + const lengthRangePredicate = s.string.array.lengthRange(0, 2); + + test.each([[[] as string[]], [['foo']]])('GIVEN %p THEN returns given value', (value) => { + expect<[] | [string]>(lengthRangePredicate.parse(value)).toEqual(value); + }); + + test.each([[['hewwo', 'there']]])('GIVEN %p THEN throws ConstraintError', (value) => { + expectError( + () => lengthRangePredicate.parse(value), + new ConstraintError('s.array(T).lengthRange', 'Invalid Array length', value, 'expected.length >= 0 && expected.length < 2') + ); + }); + }); + + describe('lengthRangeInclusive', () => { + const lengthRangeInclusivePredicate = s.string.array.lengthRangeInclusive(0, 2); + + test.each([[[] as string[]], [['foo']], [['hewwo', 'there']]])('GIVEN %p THEN returns given value', (value) => { + expect<[] | [string] | [string, string]>(lengthRangeInclusivePredicate.parse(value)).toEqual(value); + }); + + test.each([[['hewwo', 'there', 'buddy']]])('GIVEN %p THEN throws ConstraintError', (value) => { + expectError( + () => lengthRangeInclusivePredicate.parse(value), + new ConstraintError( + 's.array(T).lengthRangeInclusive', + 'Invalid Array length', + value, + 'expected.length >= 0 && expected.length <= 2' + ) + ); + }); + + describe('lengthRangeExclusive', () => { + const lengthRangeExclusivePredicate = s.string.array.lengthRangeExclusive(0, 2); + + test.each([[['foo']]])('GIVEN %p THEN returns given value', (value) => { + expect<[string]>(lengthRangeExclusivePredicate.parse(value)).toEqual(value); + }); + + test.each([[[] as string[]], [['hewwo', 'there']], [['hewwo', 'there', 'buddy']]])( + 'GIVEN %p THEN throws ConstraintError', + (value) => { + expectError( + () => lengthRangeExclusivePredicate.parse(value), + new ConstraintError( + 's.array(T).lengthRangeExclusive', + 'Invalid Array length', + value, + 'expected.length > 0 && expected.length < 2' + ) + ); + } + ); + }); + }); }); test('GIVEN clone THEN returns similar instance', () => { diff --git a/tests/validators/base.test.ts b/tests/validators/base.test.ts index 40442bda..64bf2402 100644 --- a/tests/validators/base.test.ts +++ b/tests/validators/base.test.ts @@ -5,11 +5,11 @@ describe('BaseValidator', () => { describe('optional', () => { const optionalPredicate = s.string.optional; - test.each([undefined, 'hello'])('GIVEN %s THEN returns given value', (input) => { + test.each([undefined, 'hello'])('GIVEN %p THEN returns given value', (input) => { expect(optionalPredicate.parse(input)).toEqual(input); }); - test.each([null, 0, false, true])('GIVEN %s THEN throws CombinedError', (input) => { + test.each([null, 0, false, true])('GIVEN %p THEN throws CombinedError', (input) => { expectError( () => optionalPredicate.parse(input), new CombinedError([ @@ -23,11 +23,11 @@ describe('BaseValidator', () => { describe('nullable', () => { const nullablePredicate = s.string.nullable; - test.each([null, 'Hello There'])('GIVEN %s THEN returns given value', (input) => { + test.each([null, 'Hello There'])('GIVEN %p THEN returns given value', (input) => { expect(nullablePredicate.parse(input)).toBe(input); }); - test.each([0, false, true])('GIVEN %s THEN throws CombinedError', (input) => { + test.each([0, false, true])('GIVEN %p THEN throws CombinedError', (input) => { expectError( () => nullablePredicate.parse(input), new CombinedError([ @@ -41,11 +41,11 @@ describe('BaseValidator', () => { describe('nullish', () => { const nullishPredicate = s.string.nullish; - test.each(['Hello There', undefined, null])('GIVEN %s THEN returns the given value', (input) => { + test.each(['Hello There', undefined, null])('GIVEN %p THEN returns the given value', (input) => { expect(nullishPredicate.parse(input)).toBe(input); }); - test.each([0, false, true])('GIVEN %s THEN throws CombinedError', (input) => { + test.each([0, false, true])('GIVEN %p THEN throws CombinedError', (input) => { expectError( () => nullishPredicate.parse(input), new CombinedError([ @@ -75,11 +75,11 @@ describe('BaseValidator', () => { const numberSetPredicate = s.number.set; const input = new Set([1, 2, 3]); - test('GIVEN a set of string THEN returns the given value', () => { + test('GIVEN a set of numbers THEN returns the given value', () => { expect>(numberSetPredicate.parse(input)).toStrictEqual(input); }); - test('GIVEN s.string.set THEN returns s.set(s.string)', () => { + test('GIVEN s.number.set THEN returns s.set(s.number)', () => { const setNumberPredicate = s.set(s.number); expectClonedValidator(numberSetPredicate, setNumberPredicate); @@ -89,11 +89,11 @@ describe('BaseValidator', () => { describe('or', () => { const stringOrPredicate = s.string.or(s.number); - test.each(['Hello There', 6])('GIVEN a string or number THEN returns a string', (input) => { + test.each(['Hello There', 6])('GIVEN a string or number (%p) THEN returns a string or number', (input) => { expect(stringOrPredicate.parse(input)).toBe(input); }); - test.each([false, true, null])('GIVEN %s THEN throws CombinedError', (input) => { + test.each([false, true, null])('GIVEN %p THEN throws CombinedError', (input) => { expectError( () => stringOrPredicate.parse(input), new CombinedError([ @@ -128,7 +128,7 @@ describe('BaseValidator', () => { expect(unionTransformPredicate.parse(6)).toStrictEqual(6); }); - test.each([false, true, null, undefined])('GIVEN %s THEN throws CombinedError', (input) => { + test.each([false, true, null, undefined])('GIVEN %p THEN throws CombinedError', (input) => { expectError( () => unionTransformPredicate.parse(input), new CombinedError([ diff --git a/tests/validators/bigint.test.ts b/tests/validators/bigint.test.ts index dd2fa3b9..76a0e223 100644 --- a/tests/validators/bigint.test.ts +++ b/tests/validators/bigint.test.ts @@ -143,18 +143,18 @@ describe('BigIntValidator', () => { }); describe('intN', () => { - const absPredicate = s.bigint.intN(5); + const intNPredicate = s.bigint.intN(5); test.each([smallInteger, largeInteger])('GIVEN %d THEN returns transformed the result from BigInt.asIntN', (input) => { - expect(absPredicate.parse(input)).toBe(BigInt.asIntN(5, input)); + expect(intNPredicate.parse(input)).toBe(BigInt.asIntN(5, input)); }); }); describe('uintN', () => { - const absPredicate = s.bigint.uintN(5); + const uintNPredicate = s.bigint.uintN(5); test.each([smallInteger, largeInteger])('GIVEN %d THEN returns transformed the result from BigInt.asUintN', (input) => { - expect(absPredicate.parse(input)).toBe(BigInt.asUintN(5, input)); + expect(uintNPredicate.parse(input)).toBe(BigInt.asUintN(5, input)); }); }); diff --git a/tests/validators/date.test.ts b/tests/validators/date.test.ts index f97a9059..a3a1b3f0 100644 --- a/tests/validators/date.test.ts +++ b/tests/validators/date.test.ts @@ -9,7 +9,7 @@ describe('DateValidator', () => { expect(predicate.parse(date)).toBe(date); }); - test.each(['abc', '', null, undefined])('GIVEN a non-date THEN throws ValidationError', (input) => { + test.each(['abc', '', null, undefined])('GIVEN a non-date (%p) THEN throws ValidationError', (input) => { expectError(() => predicate.parse(input), new ValidationError('s.date', 'Expected a Date', input)); }); diff --git a/tests/validators/enum.test.ts b/tests/validators/enum.test.ts index 05c2260d..7860d055 100644 --- a/tests/validators/enum.test.ts +++ b/tests/validators/enum.test.ts @@ -4,11 +4,11 @@ import { expectError } from '../common/macros/comparators'; describe('EnumValidator', () => { const predicate = s.enum('a', 'b', 'c'); - test.each(['a', 'b', 'c'])('GIVEN a string %s THEN returns a string', (input) => { + test.each(['a', 'b', 'c'])('GIVEN a string (%p) THEN returns a string', (input) => { expect(predicate.parse(input)).toBe(input); }); - test.each(['d', 'e', 'f', 1, null, true])('GIVEN a invalid value %s THEN throws CombinedError', (input) => { + test.each(['d', 'e', 'f', 1, null, true])('GIVEN a invalid value (%p) THEN throws CombinedError', (input) => { expectError( () => predicate.parse(input), new CombinedError([ diff --git a/tests/validators/never.test.ts b/tests/validators/never.test.ts index cdff5b16..e018d9ae 100644 --- a/tests/validators/never.test.ts +++ b/tests/validators/never.test.ts @@ -4,7 +4,7 @@ import { expectError } from '../common/macros/comparators'; describe('NeverValidator', () => { const predicate = s.never; - test.each([123, 'hello'])('GIVEN a value THEN throws ConstraintError', (input) => { + test.each([123, 'hello'])('GIVEN %p THEN throws ConstraintError', (input) => { expectError(() => predicate.parse(input), new ValidationError('s.never', 'Expected a value to not be passed', input)); }); }); diff --git a/tests/validators/null.test.ts b/tests/validators/null.test.ts index 7d6d09df..de0171f8 100644 --- a/tests/validators/null.test.ts +++ b/tests/validators/null.test.ts @@ -8,7 +8,7 @@ describe('NullValidator', () => { expect(predicate.parse(null)).toBe(null); }); - test.each([undefined, 123, 'Hello', {}])('GIVEN non-null %s THEN throws ExpectedValidationError', (input) => { + test.each([undefined, 123, 'Hello', {}])('GIVEN %p THEN throws ExpectedValidationError', (input) => { expectError(() => predicate.parse(input), new ExpectedValidationError('s.literal(V)', 'Expected values to be equals', input, null)); }); }); diff --git a/tests/validators/nullish.test.ts b/tests/validators/nullish.test.ts index 7372b9f7..47111a03 100644 --- a/tests/validators/nullish.test.ts +++ b/tests/validators/nullish.test.ts @@ -4,11 +4,11 @@ import { expectError } from '../common/macros/comparators'; describe('NullishValidator', () => { const predicate = s.nullish; - test.each([null, undefined])('GIVEN a value THEN returns the given value', (input) => { + test.each([null, undefined])('GIVEN %p THEN returns the given value', (input) => { expect(predicate.parse(input)).toBe(input); }); - test.each([123, 'hello'])('GIVEN a value THEN throws ValidationError', (input) => { + test.each([123, 'hello'])('GIVEN %p THEN throws ValidationError', (input) => { expectError(() => predicate.parse(input), new ValidationError('s.nullish', 'Expected undefined or null', input)); }); }); diff --git a/tests/validators/passthrough.test.ts b/tests/validators/passthrough.test.ts index 1bcb367b..66f28017 100644 --- a/tests/validators/passthrough.test.ts +++ b/tests/validators/passthrough.test.ts @@ -3,7 +3,7 @@ import { s } from '../../src'; describe('AnyValidator', () => { const predicate = s.any; - test.each([1, 'hello', null])('GIVEN anything %s THEN returns the given value', (input) => { + test.each([1, 'hello', null])('GIVEN anything (%p) THEN returns the given value', (input) => { expect(predicate.parse(input)).toBe(input); }); }); @@ -11,7 +11,7 @@ describe('AnyValidator', () => { describe('UnknownValidator', () => { const predicate = s.unknown; - test.each([1, 'hello', null])('GIVEN anything %s THEN returns the given value', (input) => { + test.each([1, 'hello', null])('GIVEN anything (%p) THEN returns the given value', (input) => { expect(predicate.parse(input)).toBe(input); }); }); diff --git a/tests/validators/set.test.ts b/tests/validators/set.test.ts index 95cceaa9..28e08c54 100644 --- a/tests/validators/set.test.ts +++ b/tests/validators/set.test.ts @@ -4,17 +4,17 @@ import { expectClonedValidator, expectError } from '../common/macros/comparators describe('SetValidator', () => { const predicate = s.set(s.string); - test.each([123, 'foo', [], {}, new Map()])("GIVEN a value which isn't a set %s THEN throws ValidationError", (input) => { + test.each([123, 'foo', [], {}, new Map()])("GIVEN a value which isn't a set (%p) THEN throws ValidationError", (input) => { expectError(() => predicate.parse(input), new ValidationError('s.set(T)', 'Expected a set', input)); }); - test.each(['1', 'a', 'foo'])('GIVEN a set with string value %s THEN returns the given set', (input) => { + test.each(['1', 'a', 'foo'])('GIVEN a set with string value (%p) THEN returns the given set', (input) => { const set = new Set([input]); expect(predicate.parse(set)).toStrictEqual(set); }); - test.each([123, [], {}])('GIVEN a set with non-string value %s THEN throw CombinedError', (input) => { + test.each([123, [], {}])('GIVEN a set with non-string value (%p) THEN throw CombinedError', (input) => { const set = new Set([input]); expectError(() => predicate.parse(set), new CombinedError([new ValidationError('s.string', 'Expected a string primitive', input)])); diff --git a/tests/validators/string.test.ts b/tests/validators/string.test.ts index 6826ace7..06d9cfa2 100644 --- a/tests/validators/string.test.ts +++ b/tests/validators/string.test.ts @@ -16,11 +16,11 @@ describe('StringValidator', () => { describe('lengthLt', () => { const lengthLtPredicate = s.string.lengthLt(5); - test.each(['Hi'])('GIVEN %s THEN returns given value', (input) => { + test.each(['Hi'])('GIVEN %p THEN returns given value', (input) => { expect(lengthLtPredicate.parse(input)).toBe(input); }); - test.each(['Hello', 'Foo Bar'])('GIVEN %s THEN throws a ConstraintError', (input) => { + test.each(['Hello', 'Foo Bar'])('GIVEN %p THEN throws a ConstraintError', (input) => { expectError( () => lengthLtPredicate.parse(input), new ConstraintError('s.string.lengthLt', 'Invalid string length', input, 'expected.length < 5') @@ -31,11 +31,11 @@ describe('StringValidator', () => { describe('lengthLe', () => { const lengthLePredicate = s.string.lengthLe(5); - test.each(['Hi', 'Hello'])('GIVEN %s THEN returns given value', (input) => { + test.each(['Hi', 'Hello'])('GIVEN %p THEN returns given value', (input) => { expect(lengthLePredicate.parse(input)).toBe(input); }); - test.each(['Foo Bar'])('GIVEN %s THEN throws a ConstraintError', (input) => { + test.each(['Foo Bar'])('GIVEN %p THEN throws a ConstraintError', (input) => { expectError( () => lengthLePredicate.parse(input), new ConstraintError('s.string.lengthLe', 'Invalid string length', input, 'expected.length <= 5') @@ -46,11 +46,11 @@ describe('StringValidator', () => { describe('lengthGt', () => { const lengthGtPredicate = s.string.lengthGt(5); - test.each(['Foo Bar'])('GIVEN %s THEN returns given value', (input) => { + test.each(['Foo Bar'])('GIVEN %p THEN returns given value', (input) => { expect(lengthGtPredicate.parse(input)).toBe(input); }); - test.each(['Hi', 'Hello'])('GIVEN %s THEN throws a ConstraintError', (input) => { + test.each(['Hi', 'Hello'])('GIVEN %p THEN throws a ConstraintError', (input) => { expectError( () => lengthGtPredicate.parse(input), new ConstraintError('s.string.lengthGt', 'Invalid string length', input, 'expected.length > 5') @@ -61,11 +61,11 @@ describe('StringValidator', () => { describe('lengthGe', () => { const lengthGePredicate = s.string.lengthGe(5); - test.each(['Hello', 'Foo Bar'])('GIVEN %s THEN returns given value', (input) => { + test.each(['Hello', 'Foo Bar'])('GIVEN %p THEN returns given value', (input) => { expect(lengthGePredicate.parse(input)).toBe(input); }); - test.each(['Hi'])('GIVEN %s THEN throws a ConstraintError', (input) => { + test.each(['Hi'])('GIVEN %p THEN throws a ConstraintError', (input) => { expectError( () => lengthGePredicate.parse(input), new ConstraintError('s.string.lengthGe', 'Invalid string length', input, 'expected.length >= 5') @@ -76,11 +76,11 @@ describe('StringValidator', () => { describe('lengthEq', () => { const lengthEqPredicate = s.string.lengthEq(5); - test.each(['Hello'])('GIVEN %s THEN returns given value', (input) => { + test.each(['Hello'])('GIVEN %p THEN returns given value', (input) => { expect(lengthEqPredicate.parse(input)).toBe(input); }); - test.each(['Hi', 'Foo Bar'])('GIVEN %s THEN throws a ConstraintError', (input) => { + test.each(['Hi', 'Foo Bar'])('GIVEN %p THEN throws a ConstraintError', (input) => { expectError( () => lengthEqPredicate.parse(input), new ConstraintError('s.string.lengthEq', 'Invalid string length', input, 'expected.length === 5') @@ -91,11 +91,11 @@ describe('StringValidator', () => { describe('lengthNe', () => { const lengthNePredicate = s.string.lengthNe(5); - test.each(['Hi', 'Foo Bar'])('GIVEN %s THEN returns given value', (input) => { + test.each(['Hi', 'Foo Bar'])('GIVEN %p THEN returns given value', (input) => { expect(lengthNePredicate.parse(input)).toBe(input); }); - test.each(['Hello'])('GIVEN %s THEN throws a ConstraintError', (input) => { + test.each(['Hello'])('GIVEN %p THEN throws a ConstraintError', (input) => { expectError( () => lengthNePredicate.parse(input), new ConstraintError('s.string.lengthNe', 'Invalid string length', input, 'expected.length !== 5') @@ -108,11 +108,11 @@ describe('StringValidator', () => { describe('email', () => { const emailPredicate = s.string.email; - test.each(['hi@hello.com', 'foo@bar.net', 'hello+world@example.com'])('GIVEN %s THEN returns given value', (input) => { + test.each(['hi@hello.com', 'foo@bar.net', 'hello+world@example.com'])('GIVEN %p THEN returns given value', (input) => { expect(emailPredicate.parse(input)).toBe(input); }); - test.each(['hi@hello', 'foo@bar', 'foo@bar.com/', 'foo@bar.com/index?search=ok'])('GIVEN %s THEN throws a ConstraintError', (input) => { + test.each(['hi@hello', 'foo@bar', 'foo@bar.com/', 'foo@bar.com/index?search=ok'])('GIVEN %p THEN throws a ConstraintError', (input) => { expectError( () => emailPredicate.parse(input), new ConstraintError('s.string.email', 'Invalid email address', input, 'expected to be an email address') @@ -124,11 +124,11 @@ describe('StringValidator', () => { describe('Without any options', () => { const urlPredicate = s.string.url(); - test.each(['https://google.com', 'http://foo.bar'])('GIVEN %s THEN returns given value', (input) => { + test.each(['https://google.com', 'http://foo.bar'])('GIVEN %p THEN returns given value', (input) => { expect(urlPredicate.parse(input)).toBe(input); }); - test.each(['google.com', 'foo.bar'])('GIVEN %s THEN throws a ConstraintError', (input) => { + test.each(['google.com', 'foo.bar'])('GIVEN %p THEN throws a ConstraintError', (input) => { expectError( () => urlPredicate.parse(input), new ConstraintError('s.string.url', 'Invalid URL', input, 'expected to match an URL') @@ -139,11 +139,11 @@ describe('StringValidator', () => { describe('With protocol', () => { const urlPredicateWithProtocol = s.string.url({ allowedProtocols: ['git:'] }); - test.each(['git://foo.bar'])('GIVEN %s THEN returns given value', (input) => { + test.each(['git://foo.bar'])('GIVEN %p THEN returns given value', (input) => { expect(urlPredicateWithProtocol.parse(input)).toBe(input); }); - test.each(['https://google.com', 'http://foo.bar'])('GIVEN %s THEN throws a ConstraintError', (input) => { + test.each(['https://google.com', 'http://foo.bar'])('GIVEN %p THEN throws a ConstraintError', (input) => { expectError( () => urlPredicateWithProtocol.parse(input), new ConstraintError('s.string.url', 'Invalid URL protocol', input, `expected ${new URL(input).protocol} to be one of: git:`) @@ -154,11 +154,11 @@ describe('StringValidator', () => { describe('With domain', () => { const urlPredicateWithDomain = s.string.url({ allowedDomains: ['google.com'] }); - test.each(['https://google.com', 'http://google.com'])('GIVEN %s THEN returns given value', (input) => { + test.each(['https://google.com', 'http://google.com'])('GIVEN %p THEN returns given value', (input) => { expect(urlPredicateWithDomain.parse(input)).toBe(input); }); - test.each(['https://foo.bar', 'http://foo.bar'])('GIVEN %s THEN throws a ConstraintError', (input) => { + test.each(['https://foo.bar', 'http://foo.bar'])('GIVEN %p THEN throws a ConstraintError', (input) => { expectError( () => urlPredicateWithDomain.parse(input), new ConstraintError('s.string.url', 'Invalid URL domain', input, 'expected foo.bar to be one of: google.com') @@ -178,11 +178,11 @@ describe('StringValidator', () => { describe('uuid5', () => { const uuid5Predicate = s.string.uuid({ version: 5 }); - test.each([uuid5])('GIVEN %s THEN returns given value', (input) => { + test.each([uuid5])('GIVEN %p THEN returns given value', (input) => { expect(uuid5Predicate.parse(input)).toBe(input); }); - test.each([uuid1, uuid3, uuid4])('GIVEN %s THEN throws a ConstraintError', (input) => { + test.each([uuid1, uuid3, uuid4])('GIVEN %p THEN throws a ConstraintError', (input) => { expectError( () => uuid5Predicate.parse(input), new ConstraintError('s.string.uuid', 'Invalid string format', input, 'expected to match UUIDv5') @@ -193,11 +193,11 @@ describe('StringValidator', () => { describe('uuid4', () => { const uuid4Predicate = s.string.uuid({ version: 4 }); - test.each([uuid4])('GIVEN %s THEN returns given value', (input) => { + test.each([uuid4])('GIVEN %p THEN returns given value', (input) => { expect(uuid4Predicate.parse(input)).toBe(input); }); - test.each([...invalidUuids, uuid5])('GIVEN %s THEN throws a ConstraintError', (input) => { + test.each([...invalidUuids, uuid5])('GIVEN %p THEN throws a ConstraintError', (input) => { expectError( () => uuid4Predicate.parse(input), new ConstraintError('s.string.uuid', 'Invalid string format', input, 'expected to match UUIDv4') @@ -222,11 +222,11 @@ describe('StringValidator', () => { describe('uuid3', () => { const uuid3Predicate = s.string.uuid({ version: 3 }); - test.each([uuid3])('GIVEN %s THEN returns given value', (input) => { + test.each([uuid3])('GIVEN %p THEN returns given value', (input) => { expect(uuid3Predicate.parse(input)).toBe(input); }); - test.each([...invalidUuids, uuid4, uuid5])('GIVEN %s THEN throws a ConstraintError', (input) => { + test.each([...invalidUuids, uuid4, uuid5])('GIVEN %p THEN throws a ConstraintError', (input) => { expectError( () => uuid3Predicate.parse(input), new ConstraintError('s.string.uuid', 'Invalid string format', input, 'expected to match UUIDv3') @@ -237,11 +237,11 @@ describe('StringValidator', () => { describe('with version range', () => { const uuidRangePredicate = s.string.uuid({ version: '1-4' }); - test.each([uuid1, uuid3, uuid3])('GIVEN %s THEN returns given value', (input) => { + test.each([uuid1, uuid3, uuid3])('GIVEN %p THEN returns given value', (input) => { expect(uuidRangePredicate.parse(input)).toBe(input); }); - test.each([uuid5, nullUuid])('GIVEN %s THEN throws a ConstraintError', (input) => { + test.each([uuid5, nullUuid])('GIVEN %p THEN throws a ConstraintError', (input) => { expectError( () => uuidRangePredicate.parse(input), new ConstraintError('s.string.uuid', 'Invalid string format', input, `expected to match UUID in range of 1-4`) @@ -261,11 +261,11 @@ describe('StringValidator', () => { const regex = /^[a-z]+$/; const regexPredicate = s.string.regex(regex); - test.each(['abc', 'xyz'])('GIVEN %s THEN returns given value', (input) => { + test.each(['abc', 'xyz'])('GIVEN %p THEN returns given value', (input) => { expect(regexPredicate.parse(input)).toBe(input); }); - test.each(['ABC', '123A'])('GIVEN %s THEN throws a ConstraintError', (input) => { + test.each(['ABC', '123A'])('GIVEN %p THEN throws a ConstraintError', (input) => { expectError( () => regexPredicate.parse(input), new ConstraintError('s.string.regex', 'Invalid string format', input, `expected ${regex}.test(expected) to be true`) @@ -286,11 +286,11 @@ describe('StringValidator', () => { describe('default', () => { const ipPredicate = s.string.ip(); - test.each([...v4Ips, ...v6Ips])('GIVEN %s THEN returns given value', (input) => { + test.each([...v4Ips, ...v6Ips])('GIVEN %p THEN returns given value', (input) => { expect(ipPredicate.parse(input)).toBe(input); }); - test.each(invalidIps)('GIVEN %s THEN throws a ConstraintError', (input) => { + test.each(invalidIps)('GIVEN %p THEN throws a ConstraintError', (input) => { expectError( () => ipPredicate.parse(input), new ConstraintError('s.string.ip', 'Invalid ip address', input, 'expected to be an ip address') @@ -301,11 +301,11 @@ describe('StringValidator', () => { describe('v4', () => { const ipv4Predicate = s.string.ipv4; - test.each(v4Ips)('GIVEN %s THEN returns given value', (input) => { + test.each(v4Ips)('GIVEN %p THEN returns given value', (input) => { expect(ipv4Predicate.parse(input)).toBe(input); }); - test.each([...v6Ips, ...invalidIps])('GIVEN %s THEN throws a ConstraintError', (input) => { + test.each([...v6Ips, ...invalidIps])('GIVEN %p THEN throws a ConstraintError', (input) => { expectError( () => ipv4Predicate.parse(input), new ConstraintError('s.string.ipv4', 'Invalid ipv4 address', input, 'expected to be an ipv4 address') @@ -316,11 +316,11 @@ describe('StringValidator', () => { describe('v6', () => { const ipv6Predicate = s.string.ipv6; - test.each(v6Ips)('GIVEN %s THEN returns given value', (input) => { + test.each(v6Ips)('GIVEN %p THEN returns given value', (input) => { expect(ipv6Predicate.parse(input)).toBe(input); }); - test.each([...v4Ips, ...invalidIps])('GIVEN %s THEN throws a ConstraintError', (input) => { + test.each([...v4Ips, ...invalidIps])('GIVEN %p THEN throws a ConstraintError', (input) => { expectError( () => ipv6Predicate.parse(input), new ConstraintError('s.string.ipv6', 'Invalid ipv6 address', input, 'expected to be an ipv6 address') diff --git a/tests/validators/tuple.test.ts b/tests/validators/tuple.test.ts index b5699151..598cd0b1 100644 --- a/tests/validators/tuple.test.ts +++ b/tests/validators/tuple.test.ts @@ -8,7 +8,7 @@ describe('TupleValidator', () => { expect<[string, number]>(predicate.parse(['foo', 1])).toStrictEqual(['foo', 1]); }); - test.each([false, 1, 'Hello', null, undefined])('GIVEN %s THEN throws ValidationError', (input) => { + test.each([false, 1, 'Hello', null, undefined])('GIVEN %p THEN throws ValidationError', (input) => { expectError(() => predicate.parse(input), new ValidationError('s.tuple(T)', 'Expected an array', input)); }); @@ -17,7 +17,7 @@ describe('TupleValidator', () => { [null, 'bar'], [undefined, {}], [{}, null] - ])('GIVEN [%s, %s] tuple THEN throws CombinedError', (a, b) => { + ])('GIVEN [%p, %p] tuple THEN throws CombinedError', (a, b) => { expectError( () => predicate.parse([a, b]), new CombinedPropertyError([ diff --git a/tests/validators/undefined.test.ts b/tests/validators/undefined.test.ts index 74fdd7e2..e7294d62 100644 --- a/tests/validators/undefined.test.ts +++ b/tests/validators/undefined.test.ts @@ -8,7 +8,7 @@ describe('UndefinedValidator', () => { expect(predicate.parse(undefined)).toBe(undefined); }); - test.each([null, 123, 'Hello'])('GIVEN %s THEN throws ExpectedValidationError', (input) => { + test.each([null, 123, 'Hello'])('GIVEN %p THEN throws ExpectedValidationError', (input) => { expectError(() => predicate.parse(input), new ExpectedValidationError('s.literal(V)', 'Expected values to be equals', input, undefined)); }); }); diff --git a/tests/validators/union.test.ts b/tests/validators/union.test.ts index 106be96f..0245cd9f 100644 --- a/tests/validators/union.test.ts +++ b/tests/validators/union.test.ts @@ -28,11 +28,11 @@ describe('UnionValidator', () => { describe('or', () => { const orPredicate = predicate.or(s.string.array); - test.each([5, 'foo', ['bar']])('GIVEN %s THEN returns the input', (value) => { + test.each([5, 'foo', ['bar']])('GIVEN %p THEN returns the input', (value) => { expect(orPredicate.parse(value)).toStrictEqual(value); }); - test.each([null, undefined, true])('GIVEN %s THEN throws CombinedError', (value) => { + test.each([null, undefined, true])('GIVEN %p THEN throws CombinedError', (value) => { expectError( () => orPredicate.parse(value), new CombinedError([ @@ -51,11 +51,11 @@ describe('UnionValidator', () => { describe('optional', () => { const optionalPredicate = predicate.optional; - test.each([undefined, 'hello', 5])('GIVEN %s THEN returns %s', (value) => { + test.each([undefined, 'hello', 5])('GIVEN %p THEN returns %p', (value) => { expect(optionalPredicate.parse(value)).toBe(value); }); - test.each([null, true, {}])('GIVEN %s THEN throws CombinedError', (value) => { + test.each([null, true, {}])('GIVEN %p THEN throws CombinedError', (value) => { expectError( () => optionalPredicate.parse(value), new CombinedError([ @@ -77,11 +77,11 @@ describe('UnionValidator', () => { describe('nullable', () => { const nullablePredicate = predicate.nullable; - test.each([null, 'hello', 5])('GIVEN %s THEN returns %s', (value) => { + test.each([null, 'hello', 5])('GIVEN %p THEN returns %p', (value) => { expect(nullablePredicate.parse(value)).toBe(value); }); - test.each([undefined, true, {}])('GIVEN %s THEN throws CombinedError', (value) => { + test.each([undefined, true, {}])('GIVEN %p THEN throws CombinedError', (value) => { expectError( () => nullablePredicate.parse(value), new CombinedError([ @@ -103,11 +103,11 @@ describe('UnionValidator', () => { describe('nullish', () => { const nullishPredicate = predicate.nullish; - test.each([null, undefined, 'hello', 5])('GIVEN %s THEN returns %s', (value) => { + test.each([null, undefined, 'hello', 5])('GIVEN %p THEN returns %p', (value) => { expect(nullishPredicate.parse(value)).toBe(value); }); - test.each([true, {}])('GIVEN %s THEN throws CombinedError', (value) => { + test.each([true, {}])('GIVEN %p THEN throws CombinedError', (value) => { expectError( () => nullishPredicate.parse(value), new CombinedError([