From f53ba7033e96491377d1033bc17825336d0c1a3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Guyon?= Date: Sat, 15 Aug 2020 18:16:29 +0200 Subject: [PATCH 01/36] Add testAsync check --- src/asyncCheck.ts | 8 ++++ src/index.ts | 1 + src/testAsync.ts | 54 +++++++++++++++++++++++++++ test/testAsync.test.ts | 85 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 148 insertions(+) create mode 100644 src/asyncCheck.ts create mode 100644 src/testAsync.ts create mode 100644 test/testAsync.test.ts diff --git a/src/asyncCheck.ts b/src/asyncCheck.ts new file mode 100644 index 0000000..1be363f --- /dev/null +++ b/src/asyncCheck.ts @@ -0,0 +1,8 @@ +import Result from "./result"; + +export default AsyncCheck; + +type AsyncCheck = ( + value: I, + ...args: A +) => Promise>; diff --git a/src/index.ts b/src/index.ts index 69c92cc..c2945b3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,6 +6,7 @@ export { default as Check } from "./check"; export { default as pass } from "./pass"; export { default as fail } from "./fail"; export { default as test } from "./test"; +export { default as testAsync } from "./testAsync"; export { default as transform } from "./transform"; export { default as is } from "./is"; diff --git a/src/testAsync.ts b/src/testAsync.ts new file mode 100644 index 0000000..6c49237 --- /dev/null +++ b/src/testAsync.ts @@ -0,0 +1,54 @@ +import AsyncCheck from "./asyncCheck"; +import ok from "./ok"; +import err from "./error"; + +export default function testAsync( + predicate: (value: V, ...args: A) => Promise, + error?: string, +): AsyncCheck; +export default function testAsync( + predicate: (value: V, ...args: A) => Promise, + error: string, + path: unknown[], + getInvalidValue: (value: V, ...args: A) => unknown, +): AsyncCheck; + +/** + * Creates an async check function that succeeds or fails based on a predicate + * function. + * + * ```js + * const check = testAsync(async value => await doesIdAlreadyExist(value)); + * + * await check("new-id"); + * // => { isOk: true, ... } + * + * await check("existing-id"); + * // => { isOk: false, ... } + * ``` + * + * @param predicate The async predicate function to test with. + * @param error The error to give when the predicate fails. + * @param path The path to give with the error. + * @param getInvalidValue A function to get the invalid value from the passed + * value. + * @returns An async check function. + */ +export default function testAsync( + predicate: (value: V, ...args: A) => Promise, + error: string = "is invalid", + path: unknown[] = [], + getInvalidValue?: (value: V, ...args: A) => unknown, +): AsyncCheck { + return async (value: V, ...args: A) => { + if (await predicate(value, ...args)) { + return ok(value); + } else { + return err( + getInvalidValue ? getInvalidValue(value, ...args) : value, + error, + path, + ); + } + }; +} diff --git a/test/testAsync.test.ts b/test/testAsync.test.ts new file mode 100644 index 0000000..4623d64 --- /dev/null +++ b/test/testAsync.test.ts @@ -0,0 +1,85 @@ +import * as check from "../src"; + +test("check succeeds when given test succeeds", async () => { + const checkValue = check.testAsync(async (value) => value === 42); + const result = await checkValue(42); + + expect(result).toEqual({ + isOk: true, + value: 42, + }); +}); + +test("check fails when given test fails", async () => { + const checkValue = check.testAsync(async (value) => value === 42); + const result = await checkValue(43); + + expect(result).toEqual({ + isOk: false, + error: "is invalid", + invalidValue: 43, + path: [], + }); +}); + +test("given error is returned with the invalid result", async () => { + const checkValue = check.testAsync( + async (value) => value === 42, + "is not 42", + ); + const result = await checkValue(43); + + expect(result).toEqual({ + isOk: false, + error: "is not 42", + invalidValue: 43, + path: [], + }); +}); + +test("given path and invalid value are returned with the invalid result", async () => { + const checkValue = check.testAsync( + async ({ value }) => value === 42, + "is invalid", + ["value"], + ({ value }) => value, + ); + const result = await checkValue({ value: 43 }); + + expect(result).toEqual({ + isOk: false, + error: "is invalid", + invalidValue: 43, + path: ["value"], + }); +}); + +test("additional arguments are passed to the test function", async () => { + const checkValue = check.testAsync( + async (value, ...args) => + args.length === 2 && args[0] === "one" && args[1] === "two", + ); + const result = await checkValue(42, "one", "two"); + + expect(result).toEqual({ + isOk: true, + value: 42, + }); +}); + +test("additional arguments are passed to the function to get the invalid value", async () => { + const checkValue = check.testAsync( + async (value, ...args: unknown[]) => value === 42, + "is invalid", + [], + (value, ...args: unknown[]) => args, + ); + const result = await checkValue(43, "one", "two"); + + expect(result).toEqual({ + isOk: false, + error: "is invalid", + invalidValue: ["one", "two"], + path: [], + }); +}); From 7524ceb1bf9e7b66f0c1f993f03e068cd87ce16c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Guyon?= Date: Sat, 15 Aug 2020 18:33:14 +0200 Subject: [PATCH 02/36] Add transformAsync check --- src/index.ts | 1 + src/transform.ts | 12 ------------ src/transformAsync.ts | 28 ++++++++++++++++++++++++++++ test/transform.test.ts | 2 +- test/transformAsync.test.ts | 21 +++++++++++++++++++++ 5 files changed, 51 insertions(+), 13 deletions(-) create mode 100644 src/transformAsync.ts create mode 100644 test/transformAsync.test.ts diff --git a/src/index.ts b/src/index.ts index c2945b3..9271d9d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,6 +8,7 @@ export { default as fail } from "./fail"; export { default as test } from "./test"; export { default as testAsync } from "./testAsync"; export { default as transform } from "./transform"; +export { default as transformAsync } from "./transformAsync"; export { default as is } from "./is"; export { default as null } from "./null"; diff --git a/src/transform.ts b/src/transform.ts index c197a00..24361af 100644 --- a/src/transform.ts +++ b/src/transform.ts @@ -14,18 +14,6 @@ import ok from "./ok"; * // } * ``` * - * All the arguments passed to resulting check function are passed to the - * transform function: - * ```js - * const check = transform((value, other) => value / other); - * - * check(42, 2); - * // => { - * // isOk: true, - * // value: 21, - * // } - * ``` - * * @param trans The function to transform with. * @returns A check function. */ diff --git a/src/transformAsync.ts b/src/transformAsync.ts new file mode 100644 index 0000000..3ab68f6 --- /dev/null +++ b/src/transformAsync.ts @@ -0,0 +1,28 @@ +import AsyncCheck from "./asyncCheck"; +import ok from "./ok"; + +/** + * Creates an async check function that transforms a value into another. + * + * ```js + * const check = transformAsync(async id => await getRecordFromDb(id)); + * + * await check("some-id"); + * // => { + * // isOk: true, + * // value: { id: "some-id", ... }, + * // } + * ``` + * + * @param trans The function to transform with. + * @returns An async check function. + */ +export default function transformAsync( + trans: (value: I, ...args: A) => Promise, +): AsyncCheck { + return async (value: I, ...args: A) => { + const nextValue = await trans(value, ...args); + + return ok(nextValue); + }; +} diff --git a/test/transform.test.ts b/test/transform.test.ts index f1bcea1..4f9c4f5 100644 --- a/test/transform.test.ts +++ b/test/transform.test.ts @@ -10,7 +10,7 @@ test("check succeeds with transformed value", () => { }); }); -test("additional arguments are passed to the transformed function", () => { +test("additional arguments are passed to the transform function", () => { const checkValue = check.transform((value, ...args) => args); const result = checkValue(42, "one", "two"); diff --git a/test/transformAsync.test.ts b/test/transformAsync.test.ts new file mode 100644 index 0000000..6560e03 --- /dev/null +++ b/test/transformAsync.test.ts @@ -0,0 +1,21 @@ +import * as check from "../src"; + +test("check succeeds with transformed value", async () => { + const checkValue = check.transformAsync(async (value) => String(value)); + const result = await checkValue(42); + + expect(result).toEqual({ + isOk: true, + value: "42", + }); +}); + +test("additional arguments are passed to the transform function", async () => { + const checkValue = check.transformAsync(async (value, ...args) => args); + const result = await checkValue(42, "one", "two"); + + expect(result).toEqual({ + isOk: true, + value: ["one", "two"], + }); +}); From d3507f55c80ca10aadf20319489581610fb2350c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Guyon?= Date: Sat, 15 Aug 2020 19:15:41 +0200 Subject: [PATCH 03/36] Add notAsync check --- src/index.ts | 1 + src/notAsync.ts | 54 +++++++++++++++++++++++++ test/notAsync.test.ts | 93 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 148 insertions(+) create mode 100644 src/notAsync.ts create mode 100644 test/notAsync.test.ts diff --git a/src/index.ts b/src/index.ts index 9271d9d..8f41ab3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -46,6 +46,7 @@ export { default as minLength } from "./minLength"; export { default as maxLength } from "./maxLength"; export { default as not } from "./not"; +export { default as notAsync } from "./notAsync"; export { default as chain } from "./chain"; export { default as oneOf } from "./oneOf"; diff --git a/src/notAsync.ts b/src/notAsync.ts new file mode 100644 index 0000000..a9b2193 --- /dev/null +++ b/src/notAsync.ts @@ -0,0 +1,54 @@ +import AsyncCheck from "./asyncCheck"; +import ok from "./ok"; +import err from "./error"; + +export default function notAsync( + check: AsyncCheck, + error?: string, +): AsyncCheck; +export default function notAsync( + check: AsyncCheck, + error: string, + path: unknown[], + getInvalidValue: (value: V, ...args: A) => unknown, +): AsyncCheck; + +/** + * Creates an async check function that negates another async check function. + * + * ```js + * const check = notAsync(async id => await doesIdExistInDb(id)); + * + * await check("new-id"); + * // => { isOk: true, ... } + * await check("existing-id"); + * // => { isOk: false, ... } + * ``` + * + * @param check The check to negate. + * @param error The error to give if the value is invalid. + * @param path The path to give with the error. + * @param getInvalidValue A function to get the invalid value from the passed + * value. + * @returns An async check function. + */ +export default function notAsync( + check: AsyncCheck, + error: string = "is invalid", + path: unknown[] = [], + getInvalidValue?: (value: V, ...args: A) => unknown, +): AsyncCheck { + return async (value, ...args) => { + const result = await check(value, ...args); + + if (result.isOk) { + return err( + getInvalidValue ? getInvalidValue(value, ...args) : value, + error, + path, + ); + } else { + return ok(value); + } + }; +} diff --git a/test/notAsync.test.ts b/test/notAsync.test.ts new file mode 100644 index 0000000..9232a6e --- /dev/null +++ b/test/notAsync.test.ts @@ -0,0 +1,93 @@ +import * as check from "../src"; + +test("check succeeds when child check fails", async () => { + const checkValue = check.notAsync( + check.testAsync(async (value) => value === 42), + ); + const result = await checkValue(43); + + expect(result).toEqual({ + isOk: true, + value: 43, + }); +}); + +test("check fails when child check succeeds", async () => { + const checkValue = check.notAsync( + check.testAsync(async (value) => value === 42), + ); + const result = await checkValue(42); + + expect(result).toEqual({ + isOk: false, + error: "is invalid", + invalidValue: 42, + path: [], + }); +}); + +test("given error is returned with the invalid result", async () => { + const checkValue = check.notAsync( + check.testAsync(async (value) => value === 42), + "is 42", + ); + const result = await checkValue(42); + + expect(result).toEqual({ + isOk: false, + error: "is 42", + invalidValue: 42, + path: [], + }); +}); + +test("given path and invalid value are returned with the invalid result", async () => { + const checkValue = check.notAsync( + check.testAsync(async ({ value }) => value === 42), + "is invalid", + ["value"], + ({ value }) => value, + ); + const result = await checkValue({ value: 42 }); + + expect(result).toEqual({ + isOk: false, + error: "is invalid", + invalidValue: 42, + path: ["value"], + }); +}); + +test("additional arguments are passed to the child check", async () => { + const checkValue = check.notAsync( + check.testAsync( + async (value, ...args) => + args.length === 2 && args[0] === "one" && args[1] === "two", + ), + ); + const result = await checkValue(42, "one", "two"); + + expect(result).toEqual({ + isOk: false, + error: "is invalid", + invalidValue: 42, + path: [], + }); +}); + +test("additional arguments are passed to the function to get the invalid value", async () => { + const checkValue = check.notAsync( + check.testAsync(async (value, ...args: unknown[]) => value === 42), + "is invalid", + [], + (value, ...args: unknown[]) => args, + ); + const result = await checkValue(42, "one", "two"); + + expect(result).toEqual({ + isOk: false, + error: "is invalid", + invalidValue: ["one", "two"], + path: [], + }); +}); From be1afd159bed8a9e6d70a7622098af05b2e809fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Guyon?= Date: Sat, 15 Aug 2020 21:49:52 +0200 Subject: [PATCH 04/36] Add chainAsync check --- src/chainAsync.ts | 255 ++++++++++++++++++++++++++++++++++++++++ src/index.ts | 1 + src/maybeAsyncCheck.ts | 8 ++ test/chainAsync.test.ts | 58 +++++++++ 4 files changed, 322 insertions(+) create mode 100644 src/chainAsync.ts create mode 100644 src/maybeAsyncCheck.ts create mode 100644 test/chainAsync.test.ts diff --git a/src/chainAsync.ts b/src/chainAsync.ts new file mode 100644 index 0000000..61311b6 --- /dev/null +++ b/src/chainAsync.ts @@ -0,0 +1,255 @@ +import AsyncCheck from "./asyncCheck"; +import MaybeAsyncCheck from "./maybeAsyncCheck"; +import ok from "./ok"; + +export default function chainAsync(): AsyncCheck< + V1, + V1, + A +>; +export default function chainAsync( + ...checks: [MaybeAsyncCheck] +): AsyncCheck; +export default function chainAsync( + ...checks: [MaybeAsyncCheck, MaybeAsyncCheck] +): AsyncCheck; +export default function chainAsync( + ...checks: [ + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + ] +): AsyncCheck; +export default function chainAsync( + ...checks: [ + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + ] +): AsyncCheck; +export default function chainAsync( + ...checks: [ + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + ] +): AsyncCheck; +export default function chainAsync< + V1, + V2, + V3, + V4, + V5, + V6, + V7, + A extends unknown[] +>( + ...checks: [ + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + ] +): AsyncCheck; +export default function chainAsync< + V1, + V2, + V3, + V4, + V5, + V6, + V7, + V8, + A extends unknown[] +>( + ...checks: [ + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + ] +): AsyncCheck; +export default function chainAsync< + V1, + V2, + V3, + V4, + V5, + V6, + V7, + V8, + V9, + A extends unknown[] +>( + ...checks: [ + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + ] +): AsyncCheck; +export default function chainAsync< + V1, + V2, + V3, + V4, + V5, + V6, + V7, + V8, + V9, + V10, + A extends unknown[] +>( + ...checks: [ + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + ] +): AsyncCheck; +export default function chainAsync< + V1, + V2, + V3, + V4, + V5, + V6, + V7, + V8, + V9, + V10, + V11, + A extends unknown[] +>( + ...checks: [ + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + ] +): AsyncCheck; +export default function chainAsync< + V1, + V2, + V3, + V4, + V5, + V6, + V7, + V8, + V9, + V10, + V11, + V12, + A extends unknown[] +>( + ...checks: [ + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + ] +): AsyncCheck; +export default function chainAsync< + V1, + V2, + V3, + V4, + V5, + V6, + V7, + V8, + V9, + V10, + V11, + V12, + V13, + A extends unknown[] +>( + ...checks: [ + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + ] +): AsyncCheck; + +/** + * Creates an async check function that chains a set of child async check + * functions. + * + * The chain stops at the first failure. + * + * ```js + * const check = chainAsync( + * string(), + * trim(), + * maxLength(6), + * testAsync(value => doesNotExistInDb(value), "already exists"), + * ); + * + * check("unique value"); + * // => { isOk: true, ... } + * check("existing value"); + * // => { isOk: false, ... } + * ``` + * + * @param checks The async check functions to chain together. + * @returns An async check function. + */ +export default function chainAsync( + ...checks: Array> +): AsyncCheck { + return async (value, ...args) => { + for (const check of checks) { + const result = await check(value, ...args); + + if (result.isOk) { + value = result.value; + } else { + return result; + } + } + + return ok(value); + }; +} diff --git a/src/index.ts b/src/index.ts index 8f41ab3..2b62f92 100644 --- a/src/index.ts +++ b/src/index.ts @@ -48,6 +48,7 @@ export { default as maxLength } from "./maxLength"; export { default as not } from "./not"; export { default as notAsync } from "./notAsync"; export { default as chain } from "./chain"; +export { default as chainAsync } from "./chainAsync"; export { default as oneOf } from "./oneOf"; export { default as optional } from "./optional"; diff --git a/src/maybeAsyncCheck.ts b/src/maybeAsyncCheck.ts new file mode 100644 index 0000000..10b1a2f --- /dev/null +++ b/src/maybeAsyncCheck.ts @@ -0,0 +1,8 @@ +import Result from "./result"; + +export default MaybeAsyncCheck; + +type MaybeAsyncCheck = ( + value: I, + ...args: A +) => Promise> | Result; diff --git a/test/chainAsync.test.ts b/test/chainAsync.test.ts new file mode 100644 index 0000000..8297320 --- /dev/null +++ b/test/chainAsync.test.ts @@ -0,0 +1,58 @@ +import * as check from "../src"; + +test("check succeeds when all child checks succeed", async () => { + const checkValue = check.chainAsync( + check.string(), + check.trim(), + check.testAsync(async (value) => value === "jerome"), + ); + const result = await checkValue(" jerome "); + + expect(result).toEqual({ + isOk: true, + value: "jerome", + }); +}); + +test("check succeeds when no checks are given", async () => { + const checkValue = check.chainAsync(); + const result = await checkValue("jerome"); + + expect(result).toEqual({ + isOk: true, + value: "jerome", + }); +}); + +test("check fails with error from first failing child check", async () => { + const checkValue = check.chainAsync( + check.string(), + check.trim(), + check.testAsync(async (value) => value === "jerome", "is not jerome"), + check.toUpper(), + ); + const result = await checkValue(" john "); + + expect(result).toEqual({ + isOk: false, + error: "is not jerome", + invalidValue: "john", + path: [], + }); +}); + +test("additional arguments are passed to the child checks", async () => { + const checkValue = check.chainAsync( + async (value, ...args) => check.ok(args), + async (value, ...args) => check.ok([value, args]), + ); + const result = await checkValue(null, "one", "two"); + + expect(result).toEqual({ + isOk: true, + value: [ + ["one", "two"], + ["one", "two"], + ], + }); +}); From c64d18b0eb7f27067d710ad9ce1b22644528f55b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Guyon?= Date: Sat, 15 Aug 2020 22:37:36 +0200 Subject: [PATCH 05/36] Refer to sync version in doc of async checks --- src/chainAsync.ts | 18 +++++++++--------- src/notAsync.ts | 10 +++++----- src/testAsync.ts | 9 ++++----- src/transformAsync.ts | 10 +++++----- 4 files changed, 23 insertions(+), 24 deletions(-) diff --git a/src/chainAsync.ts b/src/chainAsync.ts index 61311b6..39237d7 100644 --- a/src/chainAsync.ts +++ b/src/chainAsync.ts @@ -214,22 +214,22 @@ export default function chainAsync< ): AsyncCheck; /** - * Creates an async check function that chains a set of child async check - * functions. - * - * The chain stops at the first failure. + * Asynchronous version of [[`chain`]]. * * ```js * const check = chainAsync( * string(), * trim(), - * maxLength(6), - * testAsync(value => doesNotExistInDb(value), "already exists"), + * testAsync(async value => value === "jerome"), * ); * - * check("unique value"); - * // => { isOk: true, ... } - * check("existing value"); + * await check(" jerome "); + * // => { + * // isOk: true, + * // value: "jerome", + * // } + * + * await check("john"); * // => { isOk: false, ... } * ``` * diff --git a/src/notAsync.ts b/src/notAsync.ts index a9b2193..5f60e01 100644 --- a/src/notAsync.ts +++ b/src/notAsync.ts @@ -14,18 +14,18 @@ export default function notAsync( ): AsyncCheck; /** - * Creates an async check function that negates another async check function. + * Asynchronous version of [[`not`]]. * * ```js - * const check = notAsync(async id => await doesIdExistInDb(id)); + * const check = notAsync(testAsync(async value => value === 42)); * - * await check("new-id"); + * await check(43); * // => { isOk: true, ... } - * await check("existing-id"); + * await check(42); * // => { isOk: false, ... } * ``` * - * @param check The check to negate. + * @param check The async check to negate. * @param error The error to give if the value is invalid. * @param path The path to give with the error. * @param getInvalidValue A function to get the invalid value from the passed diff --git a/src/testAsync.ts b/src/testAsync.ts index 6c49237..ecd00c1 100644 --- a/src/testAsync.ts +++ b/src/testAsync.ts @@ -14,16 +14,15 @@ export default function testAsync( ): AsyncCheck; /** - * Creates an async check function that succeeds or fails based on a predicate - * function. + * Asynchronous version of [[`test`]]. * * ```js - * const check = testAsync(async value => await doesIdAlreadyExist(value)); + * const check = testAsync(async value => value === 42); * - * await check("new-id"); + * await check(42); * // => { isOk: true, ... } * - * await check("existing-id"); + * await check(43); * // => { isOk: false, ... } * ``` * diff --git a/src/transformAsync.ts b/src/transformAsync.ts index 3ab68f6..0743038 100644 --- a/src/transformAsync.ts +++ b/src/transformAsync.ts @@ -2,19 +2,19 @@ import AsyncCheck from "./asyncCheck"; import ok from "./ok"; /** - * Creates an async check function that transforms a value into another. + * Asynchronous version of [[`transform`]]. * * ```js - * const check = transformAsync(async id => await getRecordFromDb(id)); + * const check = transformAsync(async value => value / 2); * - * await check("some-id"); + * await check(42); * // => { * // isOk: true, - * // value: { id: "some-id", ... }, + * // value: 21, * // } * ``` * - * @param trans The function to transform with. + * @param trans The async function to transform with. * @returns An async check function. */ export default function transformAsync( From 1f895217ad69601335c23fdf610ad1da77ccf075 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Guyon?= Date: Sun, 16 Aug 2020 17:52:19 +0200 Subject: [PATCH 06/36] Use mocks in tests for test function --- test/test.test.ts | 84 ++++++++++++++++++++++++++++------------------- 1 file changed, 50 insertions(+), 34 deletions(-) diff --git a/test/test.test.ts b/test/test.test.ts index 4923dbd..32ddc30 100644 --- a/test/test.test.ts +++ b/test/test.test.ts @@ -1,7 +1,7 @@ import * as check from "../src"; -test("check succeeds when given test succeeds", () => { - const checkValue = check.test((value) => value === 42); +test("check succeeds when given predicate succeeds", () => { + const checkValue = check.test(() => true); const result = checkValue(42); expect(result).toEqual({ @@ -10,73 +10,89 @@ test("check succeeds when given test succeeds", () => { }); }); -test("check fails when given test fails", () => { - const checkValue = check.test((value) => value === 42); - const result = checkValue(43); +test("check fails when given predicate fails", () => { + const checkValue = check.test(() => false); + const result = checkValue(42); expect(result).toEqual({ isOk: false, error: "is invalid", - invalidValue: 43, + invalidValue: 42, path: [], }); }); +test("given predicate is called with the value to validate", () => { + const predicate = jest.fn(() => true); + const checkValue = check.test(predicate); + checkValue(42); + + expect(predicate).toHaveBeenCalledTimes(1); + expect(predicate).toHaveBeenCalledWith(42); +}); + +test("given predicate is called with the additional arguments", () => { + const predicate = jest.fn(() => true); + const checkValue = check.test(predicate); + checkValue(42, "one", "two"); + + expect(predicate).toHaveBeenCalledTimes(1); + expect(predicate).toHaveBeenCalledWith(expect.anything(), "one", "two"); +}); + test("given error is returned with the invalid result", () => { - const checkValue = check.test((value) => value === 42, "is not 42"); - const result = checkValue(43); + const checkValue = check.test(() => false, "is wrong"); + const result = checkValue(42); expect(result).toEqual({ isOk: false, - error: "is not 42", - invalidValue: 43, + error: "is wrong", + invalidValue: 42, path: [], }); }); test("given path and invalid value are returned with the invalid result", () => { const checkValue = check.test( - ({ value }) => value === 42, + () => false, "is invalid", ["value"], - ({ value }) => value, + () => "invalid value", ); - const result = checkValue({ value: 43 }); + const result = checkValue(42); expect(result).toEqual({ isOk: false, error: "is invalid", - invalidValue: 43, + invalidValue: "invalid value", path: ["value"], }); }); -test("additional arguments are passed to the test function", () => { +test("given function to get the invalid value is called with the validated value", () => { + const getInvalidValue = jest.fn(() => "invalid value"); const checkValue = check.test( - (value, ...args) => - args.length === 2 && args[0] === "one" && args[1] === "two", + () => false, + "is invalid", + ["value"], + getInvalidValue, ); - const result = checkValue(42, "one", "two"); + checkValue(42); - expect(result).toEqual({ - isOk: true, - value: 42, - }); + expect(getInvalidValue).toHaveBeenCalledTimes(1); + expect(getInvalidValue).toHaveBeenCalledWith(42); }); -test("additional arguments are passed to the function to get the invalid value", () => { - const checkValue = check.test( - (value, ...args: unknown[]) => value === 42, +test("given function to get the invalid value is called with the additional arguments", () => { + const getInvalidValue = jest.fn(() => "invalid value"); + const checkValue = check.test( + () => false, "is invalid", - [], - (value, ...args: unknown[]) => args, + ["value"], + getInvalidValue, ); - const result = checkValue(43, "one", "two"); + checkValue(42, "one", "two"); - expect(result).toEqual({ - isOk: false, - error: "is invalid", - invalidValue: ["one", "two"], - path: [], - }); + expect(getInvalidValue).toHaveBeenCalledTimes(1); + expect(getInvalidValue).toHaveBeenCalledWith(expect.anything(), "one", "two"); }); From fe3cdeb614cbafaaf449904cad0646dc154c9211 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Guyon?= Date: Sun, 16 Aug 2020 18:09:39 +0200 Subject: [PATCH 07/36] Use mocks in tests for testAsync function --- test/testAsync.test.ts | 87 ++++++++++++++++++++++++------------------ 1 file changed, 50 insertions(+), 37 deletions(-) diff --git a/test/testAsync.test.ts b/test/testAsync.test.ts index 4623d64..16779eb 100644 --- a/test/testAsync.test.ts +++ b/test/testAsync.test.ts @@ -1,7 +1,7 @@ import * as check from "../src"; -test("check succeeds when given test succeeds", async () => { - const checkValue = check.testAsync(async (value) => value === 42); +test("check succeeds when given predicate succeeds", async () => { + const checkValue = check.testAsync(async () => true); const result = await checkValue(42); expect(result).toEqual({ @@ -10,76 +10,89 @@ test("check succeeds when given test succeeds", async () => { }); }); -test("check fails when given test fails", async () => { - const checkValue = check.testAsync(async (value) => value === 42); - const result = await checkValue(43); +test("check fails when given predicate fails", async () => { + const checkValue = check.testAsync(async () => false); + const result = await checkValue(42); expect(result).toEqual({ isOk: false, error: "is invalid", - invalidValue: 43, + invalidValue: 42, path: [], }); }); +test("given predicate is called with the value to validate", async () => { + const predicate = jest.fn(async () => true); + const checkValue = check.testAsync(predicate); + await checkValue(42); + + expect(predicate).toHaveBeenCalledTimes(1); + expect(predicate).toHaveBeenCalledWith(42); +}); + +test("given predicate is called with the additional arguments", async () => { + const predicate = jest.fn(async () => true); + const checkValue = check.testAsync(predicate); + await checkValue(42, "one", "two"); + + expect(predicate).toHaveBeenCalledTimes(1); + expect(predicate).toHaveBeenCalledWith(expect.anything(), "one", "two"); +}); + test("given error is returned with the invalid result", async () => { - const checkValue = check.testAsync( - async (value) => value === 42, - "is not 42", - ); - const result = await checkValue(43); + const checkValue = check.testAsync(async () => false, "is wrong"); + const result = await checkValue(42); expect(result).toEqual({ isOk: false, - error: "is not 42", - invalidValue: 43, + error: "is wrong", + invalidValue: 42, path: [], }); }); test("given path and invalid value are returned with the invalid result", async () => { const checkValue = check.testAsync( - async ({ value }) => value === 42, + async () => false, "is invalid", ["value"], - ({ value }) => value, + () => "invalid value", ); - const result = await checkValue({ value: 43 }); + const result = await checkValue(42); expect(result).toEqual({ isOk: false, error: "is invalid", - invalidValue: 43, + invalidValue: "invalid value", path: ["value"], }); }); -test("additional arguments are passed to the test function", async () => { +test("given function to get the invalid value is called with the validated value", async () => { + const getInvalidValue = jest.fn(() => "invalid value"); const checkValue = check.testAsync( - async (value, ...args) => - args.length === 2 && args[0] === "one" && args[1] === "two", + async () => false, + "is invalid", + ["value"], + getInvalidValue, ); - const result = await checkValue(42, "one", "two"); + await checkValue(42); - expect(result).toEqual({ - isOk: true, - value: 42, - }); + expect(getInvalidValue).toHaveBeenCalledTimes(1); + expect(getInvalidValue).toHaveBeenCalledWith(42); }); -test("additional arguments are passed to the function to get the invalid value", async () => { - const checkValue = check.testAsync( - async (value, ...args: unknown[]) => value === 42, +test("given function to get the invalid value is called with the additional arguments", async () => { + const getInvalidValue = jest.fn(() => "invalid value"); + const checkValue = check.testAsync( + async () => false, "is invalid", - [], - (value, ...args: unknown[]) => args, + ["value"], + getInvalidValue, ); - const result = await checkValue(43, "one", "two"); + await checkValue(42, "one", "two"); - expect(result).toEqual({ - isOk: false, - error: "is invalid", - invalidValue: ["one", "two"], - path: [], - }); + expect(getInvalidValue).toHaveBeenCalledTimes(1); + expect(getInvalidValue).toHaveBeenCalledWith(expect.anything(), "one", "two"); }); From c577d3374028af540efcfcd43e392c51620be91b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Guyon?= Date: Sun, 16 Aug 2020 18:15:32 +0200 Subject: [PATCH 08/36] Use mocks in tests for transform function --- test/transform.test.ts | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/test/transform.test.ts b/test/transform.test.ts index 4f9c4f5..43ce1cc 100644 --- a/test/transform.test.ts +++ b/test/transform.test.ts @@ -1,21 +1,29 @@ import * as check from "../src"; test("check succeeds with transformed value", () => { - const checkValue = check.transform(String); + const checkValue = check.transform(() => "jerome"); const result = checkValue(42); expect(result).toEqual({ isOk: true, - value: "42", + value: "jerome", }); }); -test("additional arguments are passed to the transform function", () => { - const checkValue = check.transform((value, ...args) => args); - const result = checkValue(42, "one", "two"); +test("given transform function is called with the value to transform", () => { + const transform = jest.fn(() => "jerome"); + const checkValue = check.transform(transform); + checkValue(42); - expect(result).toEqual({ - isOk: true, - value: ["one", "two"], - }); + expect(transform).toHaveBeenCalledTimes(1); + expect(transform).toHaveBeenCalledWith(42); +}); + +test("given transform function is called with the additional arguments", () => { + const transform = jest.fn(() => "jerome"); + const checkValue = check.transform(transform); + checkValue(42, "one", "two"); + + expect(transform).toHaveBeenCalledTimes(1); + expect(transform).toHaveBeenCalledWith(expect.anything(), "one", "two"); }); From 57d17c9f2caf4c8fd9fc5d4c72b56900eaa32ba0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Guyon?= Date: Sun, 16 Aug 2020 18:50:33 +0200 Subject: [PATCH 09/36] Use mocks in tests for transformAsync function --- test/transformAsync.test.ts | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/test/transformAsync.test.ts b/test/transformAsync.test.ts index 6560e03..98580a5 100644 --- a/test/transformAsync.test.ts +++ b/test/transformAsync.test.ts @@ -1,21 +1,31 @@ import * as check from "../src"; test("check succeeds with transformed value", async () => { - const checkValue = check.transformAsync(async (value) => String(value)); + const checkValue = check.transformAsync(async () => "jerome"); const result = await checkValue(42); expect(result).toEqual({ isOk: true, - value: "42", + value: "jerome", }); }); -test("additional arguments are passed to the transform function", async () => { - const checkValue = check.transformAsync(async (value, ...args) => args); - const result = await checkValue(42, "one", "two"); +test("given transform function is called with the value to transform", async () => { + const transform = jest.fn(async () => "jerome"); + const checkValue = check.transformAsync(transform); + await checkValue(42); - expect(result).toEqual({ - isOk: true, - value: ["one", "two"], - }); + expect(transform).toHaveBeenCalledTimes(1); + expect(transform).toHaveBeenCalledWith(42); +}); + +test("given transform function is called with the additional arguments", async () => { + const transform = jest.fn(async () => "jerome"); + const checkValue = check.transformAsync( + transform, + ); + await checkValue(42, "one", "two"); + + expect(transform).toHaveBeenCalledTimes(1); + expect(transform).toHaveBeenCalledWith(expect.anything(), "one", "two"); }); From 9039c0c0eba8dc34f2792143c6d174f3621af1a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Guyon?= Date: Sun, 16 Aug 2020 19:09:41 +0200 Subject: [PATCH 10/36] Use mocks in tests for optional function --- test/optional.test.ts | 42 +++++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/test/optional.test.ts b/test/optional.test.ts index 8001b70..06642a4 100644 --- a/test/optional.test.ts +++ b/test/optional.test.ts @@ -1,32 +1,32 @@ import * as check from "../src"; test("check succeeds when given value is present and child check succeeds", () => { - const checkValue = check.optional(check.toString()); + const checkValue = check.optional(() => check.ok("value")); for (const value of ["jerome", "", 42, 0, null]) { const result = checkValue(value); expect(result).toEqual({ isOk: true, - value: value === null ? "" : String(value), + value: "value", }); } }); test("check fails when given value is present and child check fails", () => { - const checkValue = check.optional(check.string()); + const checkValue = check.optional(() => check.error("value", "is wrong")); const result = checkValue(42); expect(result).toEqual({ isOk: false, - error: "is not a string", - invalidValue: 42, + error: "is wrong", + invalidValue: "value", path: [], }); }); test("check succeeds when given value is not present", () => { - const checkValue = check.optional(check.string()); + const checkValue = check.optional(() => check.error("value", "is wrong")); const result = checkValue(undefined); expect(result).toEqual({ @@ -35,12 +35,28 @@ test("check succeeds when given value is not present", () => { }); }); -test("additional arguments are passed to the child check", () => { - const checkValue = check.optional((value, ...args) => check.ok(args)); - const result = checkValue(42, "one", "two"); +test("child check is called with given value", () => { + const checkChild = jest.fn(() => check.ok("value")); + const checkValue = check.optional(checkChild); + checkValue(42); - expect(result).toEqual({ - isOk: true, - value: ["one", "two"], - }); + expect(checkChild).toHaveBeenCalledTimes(1); + expect(checkChild).toHaveBeenCalledWith(42); +}); + +test("child check is called with the additional arguments", () => { + const checkChild = jest.fn(() => check.ok("value")); + const checkValue = check.optional(checkChild); + checkValue(42, "one", "two"); + + expect(checkChild).toHaveBeenCalledTimes(1); + expect(checkChild).toHaveBeenCalledWith(expect.anything(), "one", "two"); +}); + +test("child check is not called when given value is not present", () => { + const checkChild = jest.fn(() => check.ok("value")); + const checkValue = check.optional(checkChild); + checkValue(undefined); + + expect(checkChild).not.toHaveBeenCalled(); }); From 93b5f67c7656930147c7b05ec53399c34508b0f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Guyon?= Date: Sun, 16 Aug 2020 20:33:17 +0200 Subject: [PATCH 11/36] Use mocks in tests for nullable function --- test/nullable.test.ts | 42 +++++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/test/nullable.test.ts b/test/nullable.test.ts index e9eafc4..84df9a0 100644 --- a/test/nullable.test.ts +++ b/test/nullable.test.ts @@ -1,32 +1,32 @@ import * as check from "../src"; test("check succeeds when given value is not null and child check succeeds", () => { - const checkValue = check.nullable(check.toString()); + const checkValue = check.nullable(() => check.ok("value")); for (const value of ["jerome", "", 42, 0, undefined]) { const result = checkValue(value); expect(result).toEqual({ isOk: true, - value: value === undefined ? "" : String(value), + value: "value", }); } }); test("check fails when given value is not null and child check fails", () => { - const checkValue = check.nullable(check.string()); + const checkValue = check.nullable(() => check.error("value", "is wrong")); const result = checkValue(42); expect(result).toEqual({ isOk: false, - error: "is not a string", - invalidValue: 42, + error: "is wrong", + invalidValue: "value", path: [], }); }); test("check succeeds when given value is null", () => { - const checkValue = check.nullable(check.string()); + const checkValue = check.nullable(() => check.error("value", "is wrong")); const result = checkValue(null); expect(result).toEqual({ @@ -35,12 +35,28 @@ test("check succeeds when given value is null", () => { }); }); -test("additional arguments are passed to the child check", () => { - const checkValue = check.nullable((value, ...args) => check.ok(args)); - const result = checkValue(42, "one", "two"); +test("child check is called with given value", () => { + const checkChild = jest.fn(() => check.ok("value")); + const checkValue = check.nullable(checkChild); + checkValue(42); - expect(result).toEqual({ - isOk: true, - value: ["one", "two"], - }); + expect(checkChild).toHaveBeenCalledTimes(1); + expect(checkChild).toHaveBeenCalledWith(42); +}); + +test("child check is called with the additional arguments", () => { + const checkChild = jest.fn(() => check.ok("value")); + const checkValue = check.nullable(checkChild); + checkValue(42, "one", "two"); + + expect(checkChild).toHaveBeenCalledTimes(1); + expect(checkChild).toHaveBeenCalledWith(expect.anything(), "one", "two"); +}); + +test("child check is not called when given value is null", () => { + const checkChild = jest.fn(() => check.ok("value")); + const checkValue = check.nullable(checkChild); + checkValue(null); + + expect(checkChild).not.toHaveBeenCalled(); }); From 2c4c8e8db4a7fbb8dc3e104cd8f2ee81f310d88a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Guyon?= Date: Sun, 16 Aug 2020 21:59:56 +0200 Subject: [PATCH 12/36] Use mocks in tests for not function --- test/not.test.ts | 76 ++++++++++++++++++++++++++++-------------------- 1 file changed, 44 insertions(+), 32 deletions(-) diff --git a/test/not.test.ts b/test/not.test.ts index 95ed14c..519253c 100644 --- a/test/not.test.ts +++ b/test/not.test.ts @@ -1,7 +1,7 @@ import * as check from "../src"; test("check succeeds when child check fails", () => { - const checkValue = check.not(check.is(42)); + const checkValue = check.not(() => check.error("value", "is wrong")); const result = checkValue(43); expect(result).toEqual({ @@ -11,7 +11,7 @@ test("check succeeds when child check fails", () => { }); test("check fails when child check succeeds", () => { - const checkValue = check.not(check.is(42)); + const checkValue = check.not(() => check.ok("value")); const result = checkValue(42); expect(result).toEqual({ @@ -22,13 +22,31 @@ test("check fails when child check succeeds", () => { }); }); +test("child check is called with given value", () => { + const checkChild = jest.fn(() => check.ok("value")); + const checkValue = check.not(checkChild); + checkValue(42); + + expect(checkChild).toHaveBeenCalledTimes(1); + expect(checkChild).toHaveBeenCalledWith(42); +}); + +test("child check is called with the additional arguments", () => { + const checkChild = jest.fn(() => check.ok("value")); + const checkValue = check.not(checkChild); + checkValue(42, "one", "two"); + + expect(checkChild).toHaveBeenCalledTimes(1); + expect(checkChild).toHaveBeenCalledWith(expect.anything(), "one", "two"); +}); + test("given error is returned with the invalid result", () => { - const checkValue = check.not(check.is(42), "is 42"); + const checkValue = check.not(() => check.ok("value"), "is wrong"); const result = checkValue(42); expect(result).toEqual({ isOk: false, - error: "is 42", + error: "is wrong", invalidValue: 42, path: [], }); @@ -36,51 +54,45 @@ test("given error is returned with the invalid result", () => { test("given path and invalid value are returned with the invalid result", () => { const checkValue = check.not( - check.test(({ value }) => value === 42), + () => check.ok("value"), "is invalid", ["value"], - ({ value }) => value, + () => "invalid value", ); - const result = checkValue({ value: 42 }); + const result = checkValue(42); expect(result).toEqual({ isOk: false, error: "is invalid", - invalidValue: 42, + invalidValue: "invalid value", path: ["value"], }); }); -test("additional arguments are passed to the child check", () => { +test("given function to get the invalid value is called with the validated value", () => { + const getInvalidValue = jest.fn(() => "invalid value"); const checkValue = check.not( - check.test( - (value, ...args) => - args.length === 2 && args[0] === "one" && args[1] === "two", - ), + () => check.ok("value"), + "is invalid", + ["value"], + getInvalidValue, ); - const result = checkValue(42, "one", "two"); + checkValue(42); - expect(result).toEqual({ - isOk: false, - error: "is invalid", - invalidValue: 42, - path: [], - }); + expect(getInvalidValue).toHaveBeenCalledTimes(1); + expect(getInvalidValue).toHaveBeenCalledWith(42); }); -test("additional arguments are passed to the function to get the invalid value", () => { - const checkValue = check.not( - check.is(42), +test("given function to get the invalid value is called with the additional arguments", () => { + const getInvalidValue = jest.fn(() => "invalid value"); + const checkValue = check.not( + () => check.ok("value"), "is invalid", - [], - (value, ...args: unknown[]) => args, + ["value"], + getInvalidValue, ); - const result = checkValue(42, "one", "two"); + checkValue(42, "one", "two"); - expect(result).toEqual({ - isOk: false, - error: "is invalid", - invalidValue: ["one", "two"], - path: [], - }); + expect(getInvalidValue).toHaveBeenCalledTimes(1); + expect(getInvalidValue).toHaveBeenCalledWith(expect.anything(), "one", "two"); }); From 1223d8fbb7c38e06601adc5ec60c50621303d1c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Guyon?= Date: Sun, 16 Aug 2020 22:09:19 +0200 Subject: [PATCH 13/36] Use mocks in test for notAsync function --- test/notAsync.test.ts | 83 +++++++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 38 deletions(-) diff --git a/test/notAsync.test.ts b/test/notAsync.test.ts index 9232a6e..d4d1082 100644 --- a/test/notAsync.test.ts +++ b/test/notAsync.test.ts @@ -1,8 +1,8 @@ import * as check from "../src"; test("check succeeds when child check fails", async () => { - const checkValue = check.notAsync( - check.testAsync(async (value) => value === 42), + const checkValue = check.notAsync(async () => + check.error("value", "is wrong"), ); const result = await checkValue(43); @@ -13,9 +13,7 @@ test("check succeeds when child check fails", async () => { }); test("check fails when child check succeeds", async () => { - const checkValue = check.notAsync( - check.testAsync(async (value) => value === 42), - ); + const checkValue = check.notAsync(async () => check.ok("value")); const result = await checkValue(42); expect(result).toEqual({ @@ -26,16 +24,31 @@ test("check fails when child check succeeds", async () => { }); }); +test("child check is called with given value", async () => { + const checkChild = jest.fn(async () => check.ok("value")); + const checkValue = check.notAsync(checkChild); + await checkValue(42); + + expect(checkChild).toHaveBeenCalledTimes(1); + expect(checkChild).toHaveBeenCalledWith(42); +}); + +test("child check is called with the additional arguments", async () => { + const checkChild = jest.fn(async () => check.ok("value")); + const checkValue = check.notAsync(checkChild); + await checkValue(42, "one", "two"); + + expect(checkChild).toHaveBeenCalledTimes(1); + expect(checkChild).toHaveBeenCalledWith(expect.anything(), "one", "two"); +}); + test("given error is returned with the invalid result", async () => { - const checkValue = check.notAsync( - check.testAsync(async (value) => value === 42), - "is 42", - ); + const checkValue = check.notAsync(async () => check.ok("value"), "is wrong"); const result = await checkValue(42); expect(result).toEqual({ isOk: false, - error: "is 42", + error: "is wrong", invalidValue: 42, path: [], }); @@ -43,51 +56,45 @@ test("given error is returned with the invalid result", async () => { test("given path and invalid value are returned with the invalid result", async () => { const checkValue = check.notAsync( - check.testAsync(async ({ value }) => value === 42), + async () => check.ok("value"), "is invalid", ["value"], - ({ value }) => value, + () => "invalid value", ); - const result = await checkValue({ value: 42 }); + const result = await checkValue(42); expect(result).toEqual({ isOk: false, error: "is invalid", - invalidValue: 42, + invalidValue: "invalid value", path: ["value"], }); }); -test("additional arguments are passed to the child check", async () => { +test("given function to get the invalid value is called with the validated value", async () => { + const getInvalidValue = jest.fn(() => "invalid value"); const checkValue = check.notAsync( - check.testAsync( - async (value, ...args) => - args.length === 2 && args[0] === "one" && args[1] === "two", - ), + async () => check.ok("value"), + "is invalid", + ["value"], + getInvalidValue, ); - const result = await checkValue(42, "one", "two"); + await checkValue(42); - expect(result).toEqual({ - isOk: false, - error: "is invalid", - invalidValue: 42, - path: [], - }); + expect(getInvalidValue).toHaveBeenCalledTimes(1); + expect(getInvalidValue).toHaveBeenCalledWith(42); }); -test("additional arguments are passed to the function to get the invalid value", async () => { - const checkValue = check.notAsync( - check.testAsync(async (value, ...args: unknown[]) => value === 42), +test("given function to get the invalid value is called with the additional arguments", async () => { + const getInvalidValue = jest.fn(() => "invalid value"); + const checkValue = check.notAsync( + async () => check.ok("value"), "is invalid", - [], - (value, ...args: unknown[]) => args, + ["value"], + getInvalidValue, ); - const result = await checkValue(42, "one", "two"); + await checkValue(42, "one", "two"); - expect(result).toEqual({ - isOk: false, - error: "is invalid", - invalidValue: ["one", "two"], - path: [], - }); + expect(getInvalidValue).toHaveBeenCalledTimes(1); + expect(getInvalidValue).toHaveBeenCalledWith(expect.anything(), "one", "two"); }); From 1a93412562a46fc4011428f8f6ae272834d25a1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Guyon?= Date: Sun, 16 Aug 2020 22:38:50 +0200 Subject: [PATCH 14/36] Use mocks in tests for chain function --- test/chain.test.ts | 84 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 58 insertions(+), 26 deletions(-) diff --git a/test/chain.test.ts b/test/chain.test.ts index 5de815d..620acce 100644 --- a/test/chain.test.ts +++ b/test/chain.test.ts @@ -2,57 +2,89 @@ import * as check from "../src"; test("check succeeds when all child checks succeed", () => { const checkValue = check.chain( - check.string(), - check.trim(), - check.maxLength(6), + () => check.ok(1), + () => check.ok(2), + () => check.ok(3), ); - const result = checkValue(" jerome "); + const result = checkValue(42); expect(result).toEqual({ isOk: true, - value: "jerome", + value: 3, }); }); test("check succeeds when no checks are given", () => { const checkValue = check.chain(); - const result = checkValue("jerome"); + const result = checkValue(42); expect(result).toEqual({ isOk: true, - value: "jerome", + value: 42, }); }); test("check fails with error from first failing child check", () => { const checkValue = check.chain( - check.string(), - check.trim(), - check.minLength(8), - check.pattern(/[A-Z]/), + () => check.ok(1), + () => check.ok(2), + () => check.error(3, "is wrong"), + () => check.error(4, "is wrong"), ); - const result = checkValue(" jerome "); + const result = checkValue(42); expect(result).toEqual({ isOk: false, - error: "is too short", - invalidValue: "jerome", + error: "is wrong", + invalidValue: 3, path: [], }); }); -test("additional arguments are passed to the child checks", () => { - const checkValue = check.chain( - (value, ...args) => check.ok(args), - (value, ...args) => check.ok([value, args]), +test("child checks are called with previous valid value", () => { + const checkOne = jest.fn(() => check.ok(1)); + const checkTwo = jest.fn(() => check.ok(2)); + const checkThree = jest.fn(() => check.ok(3)); + const checkValue = check.chain(checkOne, checkTwo, checkThree); + checkValue(42); + + expect(checkOne).toHaveBeenCalledTimes(1); + expect(checkOne).toHaveBeenCalledWith(42); + expect(checkTwo).toHaveBeenCalledTimes(1); + expect(checkTwo).toHaveBeenCalledWith(1); + expect(checkThree).toHaveBeenCalledTimes(1); + expect(checkThree).toHaveBeenCalledWith(2); +}); + +test("child checks are called with the additional arguments", () => { + const checkOne = jest.fn(() => check.ok(1)); + const checkTwo = jest.fn(() => check.ok(2)); + const checkThree = jest.fn(() => check.ok(3)); + const checkValue = check.chain( + checkOne, + checkTwo, + checkThree, ); - const result = checkValue(null, "one", "two"); + checkValue(42, "one", "two"); - expect(result).toEqual({ - isOk: true, - value: [ - ["one", "two"], - ["one", "two"], - ], - }); + expect(checkOne).toHaveBeenCalledTimes(1); + expect(checkOne).toHaveBeenCalledWith(expect.anything(), "one", "two"); + expect(checkTwo).toHaveBeenCalledTimes(1); + expect(checkTwo).toHaveBeenCalledWith(expect.anything(), "one", "two"); + expect(checkThree).toHaveBeenCalledTimes(1); + expect(checkThree).toHaveBeenCalledWith(expect.anything(), "one", "two"); +}); + +test("child checks are not called after first failure", () => { + const checkOne = jest.fn(() => check.ok(1)); + const checkTwo = jest.fn(() => check.error(2, "is wrong")); + const checkThree = jest.fn(() => check.ok(3)); + const checkFour = jest.fn(() => check.ok(4)); + const checkValue = check.chain(checkOne, checkTwo, checkThree, checkFour); + checkValue(42); + + expect(checkOne).toHaveBeenCalledTimes(1); + expect(checkTwo).toHaveBeenCalledTimes(1); + expect(checkThree).not.toHaveBeenCalled(); + expect(checkFour).not.toHaveBeenCalled(); }); From c3e5e1fde9b285b2cbd728b7a79b020c65b4b7e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Guyon?= Date: Sun, 16 Aug 2020 22:47:57 +0200 Subject: [PATCH 15/36] Use mocks in tests for chainAsync function --- test/chainAsync.test.ts | 98 ++++++++++++++++++++++++++++++++--------- 1 file changed, 76 insertions(+), 22 deletions(-) diff --git a/test/chainAsync.test.ts b/test/chainAsync.test.ts index 8297320..85ed434 100644 --- a/test/chainAsync.test.ts +++ b/test/chainAsync.test.ts @@ -2,57 +2,111 @@ import * as check from "../src"; test("check succeeds when all child checks succeed", async () => { const checkValue = check.chainAsync( - check.string(), - check.trim(), - check.testAsync(async (value) => value === "jerome"), + async () => check.ok(1), + async () => check.ok(2), + async () => check.ok(3), ); - const result = await checkValue(" jerome "); + const result = await checkValue(42); expect(result).toEqual({ isOk: true, - value: "jerome", + value: 3, }); }); test("check succeeds when no checks are given", async () => { const checkValue = check.chainAsync(); - const result = await checkValue("jerome"); + const result = await checkValue(42); expect(result).toEqual({ isOk: true, - value: "jerome", + value: 42, }); }); test("check fails with error from first failing child check", async () => { const checkValue = check.chainAsync( - check.string(), - check.trim(), - check.testAsync(async (value) => value === "jerome", "is not jerome"), - check.toUpper(), + async () => check.ok(1), + async () => check.ok(2), + async () => check.error(3, "is wrong"), + async () => check.error(4, "is wrong"), ); - const result = await checkValue(" john "); + const result = await checkValue(42); expect(result).toEqual({ isOk: false, - error: "is not jerome", - invalidValue: "john", + error: "is wrong", + invalidValue: 3, path: [], }); }); -test("additional arguments are passed to the child checks", async () => { +test("child checks are called with previous valid value", async () => { + const checkOne = jest.fn(async () => check.ok(1)); + const checkTwo = jest.fn(async () => check.ok(2)); + const checkThree = jest.fn(async () => check.ok(3)); + const checkValue = check.chainAsync(checkOne, checkTwo, checkThree); + await checkValue(42); + + expect(checkOne).toHaveBeenCalledTimes(1); + expect(checkOne).toHaveBeenCalledWith(42); + expect(checkTwo).toHaveBeenCalledTimes(1); + expect(checkTwo).toHaveBeenCalledWith(1); + expect(checkThree).toHaveBeenCalledTimes(1); + expect(checkThree).toHaveBeenCalledWith(2); +}); + +test("child checks are called with the additional arguments", async () => { + const checkOne = jest.fn(async () => check.ok(1)); + const checkTwo = jest.fn(async () => check.ok(2)); + const checkThree = jest.fn(async () => check.ok(3)); + const checkValue = check.chainAsync< + unknown, + number, + number, + number, + unknown[] + >(checkOne, checkTwo, checkThree); + await checkValue(42, "one", "two"); + + expect(checkOne).toHaveBeenCalledTimes(1); + expect(checkOne).toHaveBeenCalledWith(expect.anything(), "one", "two"); + expect(checkTwo).toHaveBeenCalledTimes(1); + expect(checkTwo).toHaveBeenCalledWith(expect.anything(), "one", "two"); + expect(checkThree).toHaveBeenCalledTimes(1); + expect(checkThree).toHaveBeenCalledWith(expect.anything(), "one", "two"); +}); + +test("child checks are not called after first failure", async () => { + const checkOne = jest.fn(async () => check.ok(1)); + const checkTwo = jest.fn(async () => check.error(2, "is wrong")); + const checkThree = jest.fn(async () => check.ok(3)); + const checkFour = jest.fn(async () => check.ok(4)); + const checkValue = check.chainAsync( + checkOne, + checkTwo, + checkThree, + checkFour, + ); + await checkValue(42); + + expect(checkOne).toHaveBeenCalledTimes(1); + expect(checkTwo).toHaveBeenCalledTimes(1); + expect(checkThree).not.toHaveBeenCalled(); + expect(checkFour).not.toHaveBeenCalled(); +}); + +test("synchronous child checks are handled", async () => { const checkValue = check.chainAsync( - async (value, ...args) => check.ok(args), - async (value, ...args) => check.ok([value, args]), + async () => check.ok(1), + () => check.ok(2), + async () => check.ok(3), + () => check.ok(4), ); - const result = await checkValue(null, "one", "two"); + const result = await checkValue(42); expect(result).toEqual({ isOk: true, - value: [ - ["one", "two"], - ["one", "two"], - ], + value: 4, }); }); From 41e446051c8d22d38c27511b2b08ebc2ca6adf28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Guyon?= Date: Mon, 17 Aug 2020 15:19:30 +0200 Subject: [PATCH 16/36] Use mocks in tests for oneOf function --- test/oneOf.test.ts | 74 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 55 insertions(+), 19 deletions(-) diff --git a/test/oneOf.test.ts b/test/oneOf.test.ts index 298c886..e540bae 100644 --- a/test/oneOf.test.ts +++ b/test/oneOf.test.ts @@ -2,42 +2,78 @@ import * as check from "../src"; test("check succeeds when at least one child check succeeds", () => { const checkValue = check.oneOf( - check.number(), - check.chain(check.string(), check.toLower()), - check.chain(check.string(), check.toUpper()), + () => check.error(1, "is wrong"), + () => check.ok(2), + () => check.ok(3), ); - const result = checkValue("Jerome"); + const result = checkValue(42); expect(result).toEqual({ isOk: true, - value: "jerome", + value: 2, }); }); test("check fails with error from last failing child check", () => { const checkValue = check.oneOf( - check.string(), - check.number(), - check.boolean(), + () => check.error(1, "is wrong"), + () => check.error(2, "is wrong"), + () => check.error(3, "is wrong"), ); - const result = checkValue(null); + const result = checkValue(42); expect(result).toEqual({ isOk: false, - error: "is not a boolean", - invalidValue: null, + error: "is wrong", + invalidValue: 3, path: [], }); }); -test("additional arguments are passed to the child checks", () => { - const checkValue = check.oneOf(check.fail(), (value, ...args) => - check.ok(args), +test("child checks are called with given value", () => { + const checkOne = jest.fn(() => check.error(1, "is wrong")); + const checkTwo = jest.fn(() => check.error(2, "is wrong")); + const checkThree = jest.fn(() => check.error(3, "is wrong")); + const checkValue = check.oneOf(checkOne, checkTwo, checkThree); + checkValue(42); + + expect(checkOne).toHaveBeenCalledTimes(1); + expect(checkOne).toHaveBeenCalledWith(42); + expect(checkTwo).toHaveBeenCalledTimes(1); + expect(checkTwo).toHaveBeenCalledWith(42); + expect(checkThree).toHaveBeenCalledTimes(1); + expect(checkThree).toHaveBeenCalledWith(42); +}); + +test("child checks are called with the additional arguments", () => { + const checkOne = jest.fn(() => check.error(1, "is wrong")); + const checkTwo = jest.fn(() => check.error(2, "is wrong")); + const checkThree = jest.fn(() => check.error(3, "is wrong")); + const checkValue = check.oneOf( + checkOne, + checkTwo, + checkThree, ); - const result = checkValue(null, "one", "two"); + checkValue(42, "one", "two"); - expect(result).toEqual({ - isOk: true, - value: ["one", "two"], - }); + expect(checkOne).toHaveBeenCalledTimes(1); + expect(checkOne).toHaveBeenCalledWith(expect.anything(), "one", "two"); + expect(checkTwo).toHaveBeenCalledTimes(1); + expect(checkTwo).toHaveBeenCalledWith(expect.anything(), "one", "two"); + expect(checkThree).toHaveBeenCalledTimes(1); + expect(checkThree).toHaveBeenCalledWith(expect.anything(), "one", "two"); +}); + +test("child checks are not called after first success", () => { + const checkOne = jest.fn(() => check.error(1, "is wrong")); + const checkTwo = jest.fn(() => check.ok(2)); + const checkThree = jest.fn(() => check.ok(3)); + const checkFour = jest.fn(() => check.ok(4)); + const checkValue = check.oneOf(checkOne, checkTwo, checkThree, checkFour); + checkValue(42); + + expect(checkOne).toHaveBeenCalledTimes(1); + expect(checkTwo).toHaveBeenCalledTimes(1); + expect(checkThree).not.toHaveBeenCalled(); + expect(checkFour).not.toHaveBeenCalled(); }); From 1945dfcddd528f33c9f7998de6b633141a3b7b07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Guyon?= Date: Mon, 17 Aug 2020 15:33:05 +0200 Subject: [PATCH 17/36] Use mocks in tests for shape function --- test/shape.test.ts | 119 ++++++++++++++++++++------------------------- 1 file changed, 54 insertions(+), 65 deletions(-) diff --git a/test/shape.test.ts b/test/shape.test.ts index 2e2dda3..58c5844 100644 --- a/test/shape.test.ts +++ b/test/shape.test.ts @@ -1,97 +1,86 @@ import * as check from "../src"; -test("check succeeds when given value has the right shape", () => { - const checkValue = check.shape< - { one: string; two: string }, - { one: string; two: string }, - [] - >({ - one: check.chain(check.trim(), check.is("value one")), - two: check.chain(check.trim(), check.is("value two")), - }); - const result = checkValue({ - one: " value one ", - two: " value two ", +test("check succeeds when all child checks succeed", () => { + const checkValue = check.shape({ + one: () => check.ok(1), + two: () => check.ok(2), }); + const result = checkValue({}); expect(result).toEqual({ isOk: true, value: { - one: "value one", - two: "value two", + one: 1, + two: 2, }, }); }); -test("check fails when given value has the wrong shape", () => { - const checkValue = check.shape< - { one: string; two: string }, - { one: string; two: string }, - [] - >({ - one: check.is("value one", 'is not equal to "value one"'), - two: check.is("value two", 'is not equal to "value two"'), - }); - const result = checkValue({ - one: "value one", - two: "value asdf", +test("check fails when at least one child check fails", () => { + const checkValue = check.shape({ + one: () => check.ok(1), + two: () => check.error(2, "is wrong"), }); + const result = checkValue({}); expect(result).toEqual({ isOk: false, - error: 'is not equal to "value two"', - invalidValue: "value asdf", + error: "is wrong", + invalidValue: 2, path: ["two"], }); }); -test("correct path is returned with the error", () => { - const checkValue = check.shape< - { one: { two: string } }, - { one: { two: string } }, - [] - >({ - one: check.shape({ - two: check.is("valid", "is invalid"), - }), +test("child checks are called with value at corresponding key", () => { + const checkOne = jest.fn(() => check.ok(1)); + const checkTwo = jest.fn(() => check.ok(2)); + const checkValue = check.shape({ + one: checkOne, + two: checkTwo, }); - const result = checkValue({ - one: { - two: "invalid", - }, + checkValue({ + one: 1, + two: 2, }); - expect(result).toEqual({ - isOk: false, - error: "is invalid", - invalidValue: "invalid", - path: ["one", "two"], - }); + expect(checkOne).toHaveBeenCalledTimes(1); + expect(checkOne).toHaveBeenCalledWith(1); + expect(checkTwo).toHaveBeenCalledTimes(1); + expect(checkTwo).toHaveBeenCalledWith(2); }); -test("additional arguments are passed to the child checks", () => { - const checkValue = check.shape< - { one: unknown; two: unknown }, - { one: unknown[]; two: unknown[] }, - unknown[] - >({ - one: (value, ...args) => check.ok(args), - two: (value, ...args) => check.ok(args), +test("child checks are called with the additional arguments", () => { + const checkOne = jest.fn(() => check.ok(1)); + const checkTwo = jest.fn(() => check.ok(2)); + const checkValue = check.shape({ + one: checkOne, + two: checkTwo, }); - const result = checkValue( + checkValue( { - one: "value one", - two: "value two", + one: 1, + two: 2, }, - "arg one", - "arg two", + "one", + "two", ); + expect(checkOne).toHaveBeenCalledTimes(1); + expect(checkOne).toHaveBeenCalledWith(expect.anything(), "one", "two"); + expect(checkTwo).toHaveBeenCalledTimes(1); + expect(checkTwo).toHaveBeenCalledWith(expect.anything(), "one", "two"); +}); + +test("correct path is returned with the error", () => { + const checkValue = check.shape({ + one: () => check.error(1, "is wrong", ["two"]), + }); + const result = checkValue({}); + expect(result).toEqual({ - isOk: true, - value: { - one: ["arg one", "arg two"], - two: ["arg one", "arg two"], - }, + isOk: false, + error: "is wrong", + invalidValue: 1, + path: ["one", "two"], }); }); From 0daa2cc1dffdf17cd369b24d711c01588161d323 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Guyon?= Date: Mon, 17 Aug 2020 15:51:24 +0200 Subject: [PATCH 18/36] Use mocks in tests for items function --- test/items.test.ts | 51 ++++++++++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/test/items.test.ts b/test/items.test.ts index 13edccc..9807ae9 100644 --- a/test/items.test.ts +++ b/test/items.test.ts @@ -1,7 +1,7 @@ import * as check from "../src"; -test("check succeeds when given value has only valid items", () => { - const checkValue = check.items(check.chain(check.string(), check.trim())); +test("check succeeds when child check succeeds on all items", () => { + const checkValue = check.items(check.trim()); const result = checkValue(["one", " two "]); expect(result).toEqual({ @@ -10,7 +10,7 @@ test("check succeeds when given value has only valid items", () => { }); }); -test("check fails when given value has invalid items", () => { +test("check fails when child check fails on at least one item", () => { const checkValue = check.items(check.is("valid", "is invalid")); const result = checkValue(["valid", "invalid one", "invalid two"]); @@ -22,27 +22,38 @@ test("check fails when given value has invalid items", () => { }); }); -test("correct path is returned with the error", () => { - const checkValue = check.items(check.items(check.is("valid", "is invalid"))); - const result = checkValue([["valid", "invalid"]]); +test("child check is called with all items", () => { + const checkItem = jest.fn(() => check.ok(42)); + const checkValue = check.items(checkItem); + checkValue([1, 2, 3]); - expect(result).toEqual({ - isOk: false, - error: "is invalid", - invalidValue: "invalid", - path: [0, 1], - }); + expect(checkItem).toHaveBeenCalledTimes(3); + expect(checkItem).toHaveBeenNthCalledWith(1, 1); + expect(checkItem).toHaveBeenNthCalledWith(2, 2); + expect(checkItem).toHaveBeenNthCalledWith(3, 3); }); -test("additional arguments are passed to the child check", () => { - const checkValue = check.items((value, ...args) => check.ok(args)); - const result = checkValue([1, 2], "one", "two"); +test("child check is called with the additional arguments", () => { + const checkItem = jest.fn(() => check.ok(42)); + const checkValue = check.items(checkItem); + checkValue([1, 2, 3], "one", "two"); + + expect(checkItem).toHaveBeenCalledTimes(3); + expect(checkItem).toHaveBeenNthCalledWith(1, expect.anything(), "one", "two"); + expect(checkItem).toHaveBeenNthCalledWith(2, expect.anything(), "one", "two"); + expect(checkItem).toHaveBeenNthCalledWith(3, expect.anything(), "one", "two"); +}); + +test("correct path is returned with the error", () => { + const checkValue = check.items((value) => + value === "valid" ? check.ok(value) : check.error(value, "is wrong", [2]), + ); + const result = checkValue(["valid", "invalid"]); expect(result).toEqual({ - isOk: true, - value: [ - ["one", "two"], - ["one", "two"], - ], + isOk: false, + error: "is wrong", + invalidValue: "invalid", + path: [1, 2], }); }); From 2706f0d52dc116f0948f952035dddb3d73a559ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Guyon?= Date: Mon, 17 Aug 2020 16:34:37 +0200 Subject: [PATCH 19/36] Use mocks in tests for tuple function --- test/tuple.test.ts | 101 +++++++++++++++++++++++---------------------- 1 file changed, 52 insertions(+), 49 deletions(-) diff --git a/test/tuple.test.ts b/test/tuple.test.ts index d36892d..1899f0c 100644 --- a/test/tuple.test.ts +++ b/test/tuple.test.ts @@ -1,93 +1,96 @@ import * as check from "../src"; -test("check succeeds when given value has the right shape", () => { - const checkValue = check.tuple([ - check.chain(check.trim(), check.is("value one")), - check.chain(check.trim(), check.is("value two")), - ]); - const result = checkValue([" value one ", " value two "]); +test("check succeeds when all child checks succeed", () => { + const checkValue = check.tuple([() => check.ok(1), () => check.ok(2)]); + const result = checkValue(["one", "two"]); expect(result).toEqual({ isOk: true, - value: ["value one", "value two"], + value: [1, 2], }); }); test("check fails when given value does not have the right length", () => { - const checkValue = check.tuple([ - check.is("value one"), - check.is("value two"), + const checkValue = check.tuple([ + () => check.error(1, "is wrong"), + () => check.error(2, "is wrong"), ]); - const result = checkValue(["asdf"]); + const result = checkValue(["one"]); expect(result).toEqual({ isOk: false, error: "does not have 2 items", - invalidValue: ["asdf"], + invalidValue: ["one"], path: [], }); }); -test("check fails when some items are invalid", () => { - const checkValue = check.tuple([ - check.is("value one"), - check.is("value two", "is invalid two"), - check.is("value three", "is invalid three"), +test("check fails when at least one child check fails", () => { + const checkValue = check.tuple([ + () => check.ok(1), + () => check.error(2, "is wrong"), + () => check.error(3, "is wrong"), ]); - const result = checkValue(["value one", "invalid", "invalid"]); + const result = checkValue(["one", "two", "three"]); expect(result).toEqual({ isOk: false, - error: "is invalid two", - invalidValue: "invalid", + error: "is wrong", + invalidValue: 2, path: [1], }); }); +test("child checks are called with value at corresponding index", () => { + const checkOne = jest.fn(() => check.ok(1)); + const checkTwo = jest.fn(() => check.ok(2)); + const checkValue = check.tuple([checkOne, checkTwo]); + checkValue(["one", "two"]); + + expect(checkOne).toHaveBeenCalledTimes(1); + expect(checkOne).toHaveBeenCalledWith("one"); + expect(checkTwo).toHaveBeenCalledTimes(1); + expect(checkTwo).toHaveBeenCalledWith("two"); +}); + +test("child checks are called with the additional arguments", () => { + const checkOne = jest.fn(() => check.ok(1)); + const checkTwo = jest.fn(() => check.ok(2)); + const checkValue = check.tuple([checkOne, checkTwo]); + checkValue([1, 2], "one", "two"); + + expect(checkOne).toHaveBeenCalledTimes(1); + expect(checkOne).toHaveBeenCalledWith(expect.anything(), "one", "two"); + expect(checkTwo).toHaveBeenCalledTimes(1); + expect(checkTwo).toHaveBeenCalledWith(expect.anything(), "one", "two"); +}); + test("given length error is returned with the invalid result", () => { - const checkValue = check.tuple( - [check.is("value one"), check.is("value two")], + const checkValue = check.tuple( + [() => check.ok(1), () => check.ok(2)], "does not have two items", ); - const result = checkValue(["value one"]); + const result = checkValue(["one"]); expect(result).toEqual({ isOk: false, error: "does not have two items", - invalidValue: ["value one"], + invalidValue: ["one"], path: [], }); }); test("correct path is returned with the item error", () => { - const checkValue = check.tuple([ - check.tuple([ - check.is("value one", "is invalid"), - check.is("value two", "is invalid"), - ]), + const checkValue = check.tuple([ + () => check.ok(1), + () => check.error(2, "is wrong", [2]), ]); - const result = checkValue([["value one", "invalid"]]); + const result = checkValue(["one", "two"]); expect(result).toEqual({ isOk: false, - error: "is invalid", - invalidValue: "invalid", - path: [0, 1], - }); -}); - -test("additional arguments are passed to the child checks", () => { - const checkValue = check.tuple([ - (value, ...args) => check.ok(args), - (value, ...args) => check.ok(args), - ]); - const result = checkValue(["value one", "value two"], "arg one", "arg two"); - - expect(result).toEqual({ - isOk: true, - value: [ - ["arg one", "arg two"], - ["arg one", "arg two"], - ], + error: "is wrong", + invalidValue: 2, + path: [1, 2], }); }); From 7899fff8962caf5ee9dbd69928fa87c211289a43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Guyon?= Date: Mon, 17 Aug 2020 16:53:14 +0200 Subject: [PATCH 20/36] Use mocks in tests for entries function --- test/entries.test.ts | 96 ++++++++++++++++++++++++++------------------ test/items.test.ts | 6 +-- 2 files changed, 59 insertions(+), 43 deletions(-) diff --git a/test/entries.test.ts b/test/entries.test.ts index 3e4ba33..da8c9b4 100644 --- a/test/entries.test.ts +++ b/test/entries.test.ts @@ -1,6 +1,6 @@ import * as check from "../src"; -test("check succeeds when given value has only valid key-value pairs", () => { +test("check succeeds when child checks succeeds on all key-value pairs", () => { const checkValue = check.entries( check.chain(check.trim(), check.pattern(/^[0-9]+$/)), check.chain(check.string(), check.trim()), @@ -19,7 +19,7 @@ test("check succeeds when given value has only valid key-value pairs", () => { }); }); -test("check fails when given value has invalid keys", () => { +test("check fails when child key check fails on at least one key", () => { const checkValue = check.entries(check.is("one", "is invalid"), check.pass()); const result = checkValue({ one: 1, two: 2 }); @@ -31,7 +31,7 @@ test("check fails when given value has invalid keys", () => { }); }); -test("check fails when given value has invalid values", () => { +test("check fails when child value check fails on at least one value", () => { const checkValue = check.entries(check.pass(), check.is("one", "is invalid")); const result = checkValue({ 1: "one", 2: "two" }); @@ -43,53 +43,69 @@ test("check fails when given value has invalid values", () => { }); }); -test("correct path is returned with the value error", () => { - const checkValue = check.entries( - check.pass(), - check.entries(check.trim(), check.is("one", "is invalid")), - ); - const result = checkValue({ - foo: { - 1: "one", - " 2 ": "two", - }, +test("child checks are called with all key-value pairs", () => { + const checkKey = jest.fn(() => check.ok("key")); + const checkValue = jest.fn(() => check.ok("value")); + const checkEntries = check.entries(checkKey, checkValue); + checkEntries({ + one: 1, + two: 2, }); - expect(result).toEqual({ - isOk: false, - error: "is invalid", - invalidValue: "two", - path: ["foo", " 2 "], - }); + expect(checkKey).toHaveBeenCalledTimes(2); + expect(checkKey).toHaveBeenCalledWith("one"); + expect(checkKey).toHaveBeenCalledWith("two"); + expect(checkValue).toHaveBeenCalledTimes(2); + expect(checkValue).toHaveBeenCalledWith(1); + expect(checkValue).toHaveBeenCalledWith(2); }); -test("additional arguments are passed to the child key check", () => { - const checkValue = check.entries( - (value, ...args) => check.ok(value + JSON.stringify(args)), - check.pass(), +test("child checks are called with the additional arguments", () => { + const checkKey = jest.fn(() => check.ok("key")); + const checkValue = jest.fn(() => check.ok("value")); + const checkEntries = check.entries( + checkKey, + checkValue, ); - const result = checkValue({ 1: 1, 2: 2 }, "one", "two"); - - expect(result).toEqual({ - isOk: true, - value: { - ["1" + JSON.stringify(["one", "two"])]: 1, - ["2" + JSON.stringify(["one", "two"])]: 2, + checkEntries( + { + one: 1, + two: 2, }, - }); + "one", + "two", + ); + + expect(checkKey).toHaveBeenCalledTimes(2); + expect(checkKey).toHaveBeenNthCalledWith(1, expect.anything(), "one", "two"); + expect(checkKey).toHaveBeenNthCalledWith(2, expect.anything(), "one", "two"); + expect(checkValue).toHaveBeenCalledTimes(2); + expect(checkValue).toHaveBeenNthCalledWith( + 1, + expect.anything(), + "one", + "two", + ); + expect(checkValue).toHaveBeenNthCalledWith( + 2, + expect.anything(), + "one", + "two", + ); }); -test("additional arguments are passed to child value check", () => { - const checkValue = check.entries(check.pass(), (value, ...args) => - check.ok(args), +test("correct path is returned with the value error", () => { + const checkValue = check.entries(check.trim(), () => + check.error("invalid", "is wrong", ["two"]), ); - const result = checkValue({ 1: 1, 2: 2 }, "one", "two"); + const result = checkValue({ + " one ": 1, + }); expect(result).toEqual({ - isOk: true, - value: { - 1: ["one", "two"], - 2: ["one", "two"], - }, + isOk: false, + error: "is wrong", + invalidValue: "invalid", + path: [" one ", "two"], }); }); diff --git a/test/items.test.ts b/test/items.test.ts index 9807ae9..369005b 100644 --- a/test/items.test.ts +++ b/test/items.test.ts @@ -28,9 +28,9 @@ test("child check is called with all items", () => { checkValue([1, 2, 3]); expect(checkItem).toHaveBeenCalledTimes(3); - expect(checkItem).toHaveBeenNthCalledWith(1, 1); - expect(checkItem).toHaveBeenNthCalledWith(2, 2); - expect(checkItem).toHaveBeenNthCalledWith(3, 3); + expect(checkItem).toHaveBeenCalledWith(1); + expect(checkItem).toHaveBeenCalledWith(2); + expect(checkItem).toHaveBeenCalledWith(3); }); test("child check is called with the additional arguments", () => { From 4d4414737438bcbcd165b80fa05f6ebbf0b037e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Guyon?= Date: Mon, 17 Aug 2020 18:42:50 +0200 Subject: [PATCH 21/36] Add oneOfAsync check --- src/index.ts | 1 + src/oneOfAsync.ts | 267 ++++++++++++++++++++++++++++++++++++++++ test/oneOfAsync.test.ts | 86 +++++++++++++ 3 files changed, 354 insertions(+) create mode 100644 src/oneOfAsync.ts create mode 100644 test/oneOfAsync.test.ts diff --git a/src/index.ts b/src/index.ts index 2b62f92..d0be855 100644 --- a/src/index.ts +++ b/src/index.ts @@ -50,6 +50,7 @@ export { default as notAsync } from "./notAsync"; export { default as chain } from "./chain"; export { default as chainAsync } from "./chainAsync"; export { default as oneOf } from "./oneOf"; +export { default as oneOfAsync } from "./oneOfAsync"; export { default as optional } from "./optional"; export { default as nullable } from "./nullable"; diff --git a/src/oneOfAsync.ts b/src/oneOfAsync.ts new file mode 100644 index 0000000..49e27c8 --- /dev/null +++ b/src/oneOfAsync.ts @@ -0,0 +1,267 @@ +import AsyncCheck from "./asyncCheck"; +import MaybeAsyncCheck from "./maybeAsyncCheck"; + +export default function oneOfAsync( + ...checks: [MaybeAsyncCheck] +): AsyncCheck; +export default function oneOfAsync( + ...checks: [MaybeAsyncCheck, MaybeAsyncCheck] +): AsyncCheck; +export default function oneOfAsync( + ...checks: [ + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + ] +): AsyncCheck; +export default function oneOfAsync( + ...checks: [ + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + ] +): AsyncCheck; +export default function oneOfAsync( + ...checks: [ + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + ] +): AsyncCheck; +export default function oneOfAsync< + I, + O1, + O2, + O3, + O4, + O5, + O6, + A extends unknown[] +>( + ...checks: [ + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + ] +): AsyncCheck; +export default function oneOfAsync< + I, + O1, + O2, + O3, + O4, + O5, + O6, + O7, + A extends unknown[] +>( + ...checks: [ + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + ] +): AsyncCheck; +export default function oneOfAsync< + I, + O1, + O2, + O3, + O4, + O5, + O6, + O7, + O8, + A extends unknown[] +>( + ...checks: [ + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + ] +): AsyncCheck; +export default function oneOfAsync< + I, + O1, + O2, + O3, + O4, + O5, + O6, + O7, + O8, + O9, + A extends unknown[] +>( + ...checks: [ + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + ] +): AsyncCheck; +export default function oneOfAsync< + I, + O1, + O2, + O3, + O4, + O5, + O6, + O7, + O8, + O9, + O10, + A extends unknown[] +>( + ...checks: [ + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + ] +): AsyncCheck; +export default function oneOfAsync< + I, + O1, + O2, + O3, + O4, + O5, + O6, + O7, + O8, + O9, + O10, + O11, + A extends unknown[] +>( + ...checks: [ + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + ] +): AsyncCheck; +export default function oneOfAsync< + I, + O1, + O2, + O3, + O4, + O5, + O6, + O7, + O8, + O9, + O10, + O11, + O12, + A extends unknown[] +>( + ...checks: [ + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + ] +): AsyncCheck< + I, + O1 | O2 | O3 | O4 | O5 | O6 | O7 | O8 | O9 | O10 | O11 | O12, + A +>; + +/** + * Asynchronous version of [[`oneOf`]]. + * + * ```js + * const check = oneOfAsync( + * integer(), + * chainAsync(string(), transformAsync(async value => value.trim())), + * fail("is not an integer or a string"), + * ); + * + * await check(42); + * // => { + * // isOk: true, + * // value: 42, + * // } + * + * await check(" jerome "); + * // => { + * // isOk: true, + * // value: "jerome", + * // } + * + * await check(true); + * // => { + * // isOk: false, + * // error: "is not an integer or a string", + * // ... + * // } + * ``` + * + * @param checks The async check functions to try. + * @returns An async check function. + */ +export default function oneOfAsync( + ...checks: Array> +): AsyncCheck { + if (checks.length === 0) { + throw new Error("oneOfAsync: expected at least one check function"); + } + + return async (value, ...args) => { + let result: any; + + for (const check of checks) { + result = await check(value, ...args); + + if (result.isOk) { + return result; + } + } + + return result; + }; +} diff --git a/test/oneOfAsync.test.ts b/test/oneOfAsync.test.ts new file mode 100644 index 0000000..ca0bbf8 --- /dev/null +++ b/test/oneOfAsync.test.ts @@ -0,0 +1,86 @@ +import * as check from "../src"; + +test("check succeeds when at least one child check succeeds", async () => { + const checkValue = check.oneOfAsync( + async () => check.error(1, "is wrong"), + async () => check.ok(2), + async () => check.ok(3), + ); + const result = await checkValue(42); + + expect(result).toEqual({ + isOk: true, + value: 2, + }); +}); + +test("check fails with error from last failing child check", async () => { + const checkValue = check.oneOfAsync( + async () => check.error(1, "is wrong"), + async () => check.error(2, "is wrong"), + async () => check.error(3, "is wrong"), + ); + const result = await checkValue(42); + + expect(result).toEqual({ + isOk: false, + error: "is wrong", + invalidValue: 3, + path: [], + }); +}); + +test("child checks are called with given value", async () => { + const checkOne = jest.fn(async () => check.error(1, "is wrong")); + const checkTwo = jest.fn(async () => check.error(2, "is wrong")); + const checkThree = jest.fn(async () => check.error(3, "is wrong")); + const checkValue = check.oneOfAsync(checkOne, checkTwo, checkThree); + await checkValue(42); + + expect(checkOne).toHaveBeenCalledTimes(1); + expect(checkOne).toHaveBeenCalledWith(42); + expect(checkTwo).toHaveBeenCalledTimes(1); + expect(checkTwo).toHaveBeenCalledWith(42); + expect(checkThree).toHaveBeenCalledTimes(1); + expect(checkThree).toHaveBeenCalledWith(42); +}); + +test("child checks are called with the additional arguments", async () => { + const checkOne = jest.fn(async () => check.error(1, "is wrong")); + const checkTwo = jest.fn(async () => check.error(2, "is wrong")); + const checkThree = jest.fn(async () => check.error(3, "is wrong")); + const checkValue = check.oneOfAsync< + unknown, + unknown, + unknown, + unknown, + unknown[] + >(checkOne, checkTwo, checkThree); + await checkValue(42, "one", "two"); + + expect(checkOne).toHaveBeenCalledTimes(1); + expect(checkOne).toHaveBeenCalledWith(expect.anything(), "one", "two"); + expect(checkTwo).toHaveBeenCalledTimes(1); + expect(checkTwo).toHaveBeenCalledWith(expect.anything(), "one", "two"); + expect(checkThree).toHaveBeenCalledTimes(1); + expect(checkThree).toHaveBeenCalledWith(expect.anything(), "one", "two"); +}); + +test("child checks are not called after first success", async () => { + const checkOne = jest.fn(async () => check.error(1, "is wrong")); + const checkTwo = jest.fn(async () => check.ok(2)); + const checkThree = jest.fn(async () => check.ok(3)); + const checkFour = jest.fn(async () => check.ok(4)); + const checkValue = check.oneOfAsync( + checkOne, + checkTwo, + checkThree, + checkFour, + ); + await checkValue(42); + + expect(checkOne).toHaveBeenCalledTimes(1); + expect(checkTwo).toHaveBeenCalledTimes(1); + expect(checkThree).not.toHaveBeenCalled(); + expect(checkFour).not.toHaveBeenCalled(); +}); From 5dc09e4e176f7398425062af3228537059a183db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Guyon?= Date: Mon, 17 Aug 2020 21:42:44 +0200 Subject: [PATCH 22/36] Add optionalAsync check --- src/index.ts | 1 + src/optionalAsync.ts | 32 ++++++++++++++++++ test/optionalAsync.test.ts | 68 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 src/optionalAsync.ts create mode 100644 test/optionalAsync.test.ts diff --git a/src/index.ts b/src/index.ts index d0be855..076e530 100644 --- a/src/index.ts +++ b/src/index.ts @@ -53,6 +53,7 @@ export { default as oneOf } from "./oneOf"; export { default as oneOfAsync } from "./oneOfAsync"; export { default as optional } from "./optional"; +export { default as optionalAsync } from "./optionalAsync"; export { default as nullable } from "./nullable"; export { default as items } from "./items"; diff --git a/src/optionalAsync.ts b/src/optionalAsync.ts new file mode 100644 index 0000000..4ed9b6a --- /dev/null +++ b/src/optionalAsync.ts @@ -0,0 +1,32 @@ +import AsyncCheck from "./asyncCheck"; +import MaybeAsyncCheck from "./maybeAsyncCheck"; +import ok from "./ok"; + +/** + * Asynchronous version of [[`optional`]]. + * + * ```js + * const check = optionalAsync(testAsync(async value => value === 42)); + * + * await check(undefined); + * // => { isOk: true, ... } + * await check(42); + * // => { isOk: true, ... } + * await check("jerome"); + * // => { isOk: false, ... } + * ``` + * + * @param check The async check function to run when the value is present. + * @returns An async check function. + */ +export default function optionalAsync( + check: MaybeAsyncCheck, +): AsyncCheck { + return async (value, ...args) => { + if (value === undefined) { + return ok(undefined); + } else { + return await check(value, ...args); + } + }; +} diff --git a/test/optionalAsync.test.ts b/test/optionalAsync.test.ts new file mode 100644 index 0000000..8b3f5d8 --- /dev/null +++ b/test/optionalAsync.test.ts @@ -0,0 +1,68 @@ +import * as check from "../src"; + +test("check succeeds when given value is present and child check succeeds", async () => { + const checkValue = check.optionalAsync(async () => check.ok("value")); + + for (const value of ["jerome", "", 42, 0, null]) { + const result = await checkValue(value); + + expect(result).toEqual({ + isOk: true, + value: "value", + }); + } +}); + +test("check fails when given value is present and child check fails", async () => { + const checkValue = check.optionalAsync(async () => + check.error("value", "is wrong"), + ); + const result = await checkValue(42); + + expect(result).toEqual({ + isOk: false, + error: "is wrong", + invalidValue: "value", + path: [], + }); +}); + +test("check succeeds when given value is not present", async () => { + const checkValue = check.optionalAsync(async () => + check.error("value", "is wrong"), + ); + const result = await checkValue(undefined); + + expect(result).toEqual({ + isOk: true, + value: undefined, + }); +}); + +test("child check is called with given value", async () => { + const checkChild = jest.fn(async () => check.ok("value")); + const checkValue = check.optionalAsync(checkChild); + await checkValue(42); + + expect(checkChild).toHaveBeenCalledTimes(1); + expect(checkChild).toHaveBeenCalledWith(42); +}); + +test("child check is called with the additional arguments", async () => { + const checkChild = jest.fn(async () => check.ok("value")); + const checkValue = check.optionalAsync( + checkChild, + ); + await checkValue(42, "one", "two"); + + expect(checkChild).toHaveBeenCalledTimes(1); + expect(checkChild).toHaveBeenCalledWith(expect.anything(), "one", "two"); +}); + +test("child check is not called when given value is not present", async () => { + const checkChild = jest.fn(async () => check.ok("value")); + const checkValue = check.optionalAsync(checkChild); + await checkValue(undefined); + + expect(checkChild).not.toHaveBeenCalled(); +}); From df9a2b50a57a3eca6d163a1159564f6862f83426 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Guyon?= Date: Mon, 17 Aug 2020 21:47:51 +0200 Subject: [PATCH 23/36] Add tests for handling of sync checks --- test/oneOfAsync.test.ts | 15 +++++++++++++++ test/optionalAsync.test.ts | 10 ++++++++++ 2 files changed, 25 insertions(+) diff --git a/test/oneOfAsync.test.ts b/test/oneOfAsync.test.ts index ca0bbf8..b557144 100644 --- a/test/oneOfAsync.test.ts +++ b/test/oneOfAsync.test.ts @@ -84,3 +84,18 @@ test("child checks are not called after first success", async () => { expect(checkThree).not.toHaveBeenCalled(); expect(checkFour).not.toHaveBeenCalled(); }); + +test("synchronous child checks are handled", async () => { + const checkValue = check.oneOfAsync( + async () => check.error(1, "is wrong"), + () => check.error(2, "is wrong"), + async () => check.error(3, "is wrong"), + () => check.ok(4), + ); + const result = await checkValue(42); + + expect(result).toEqual({ + isOk: true, + value: 4, + }); +}); diff --git a/test/optionalAsync.test.ts b/test/optionalAsync.test.ts index 8b3f5d8..5fcea25 100644 --- a/test/optionalAsync.test.ts +++ b/test/optionalAsync.test.ts @@ -66,3 +66,13 @@ test("child check is not called when given value is not present", async () => { expect(checkChild).not.toHaveBeenCalled(); }); + +test("synchronous child checks are handled", async () => { + const checkValue = check.optionalAsync(() => check.ok("value")); + const result = await checkValue(42); + + expect(result).toEqual({ + isOk: true, + value: "value", + }); +}); From 452ec6a040d639d89755937acf23ea26a8372294 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Guyon?= Date: Mon, 17 Aug 2020 22:00:49 +0200 Subject: [PATCH 24/36] Add nullableAsync check --- src/index.ts | 1 + src/nullableAsync.ts | 32 ++++++++++++++++ test/nullableAsync.test.ts | 78 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+) create mode 100644 src/nullableAsync.ts create mode 100644 test/nullableAsync.test.ts diff --git a/src/index.ts b/src/index.ts index 076e530..a44f31c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -55,6 +55,7 @@ export { default as oneOfAsync } from "./oneOfAsync"; export { default as optional } from "./optional"; export { default as optionalAsync } from "./optionalAsync"; export { default as nullable } from "./nullable"; +export { default as nullableAsync } from "./nullableAsync"; export { default as items } from "./items"; export { default as shape } from "./shape"; diff --git a/src/nullableAsync.ts b/src/nullableAsync.ts new file mode 100644 index 0000000..423b5ec --- /dev/null +++ b/src/nullableAsync.ts @@ -0,0 +1,32 @@ +import AsyncCheck from "./asyncCheck"; +import MaybeAsyncCheck from "./maybeAsyncCheck"; +import ok from "./ok"; + +/** + * Asynchronous version of [[`nullable`]]. + * + * ```js + * const check = nullableAsync(testAsync(async value => value === 42)); + * + * await check(null); + * // => { isOk: true, ... } + * await check(42); + * // => { isOk: true, ... } + * await check("jerome"); + * // => { isOk: false, ... } + * ``` + * + * @param check The async check function to run when the value is present. + * @returns An async check function. + */ +export default function nullableAsync( + check: MaybeAsyncCheck, +): AsyncCheck { + return async (value, ...args) => { + if (value === null) { + return ok(null); + } else { + return await check(value, ...args); + } + }; +} diff --git a/test/nullableAsync.test.ts b/test/nullableAsync.test.ts new file mode 100644 index 0000000..9a59ff2 --- /dev/null +++ b/test/nullableAsync.test.ts @@ -0,0 +1,78 @@ +import * as check from "../src"; + +test("check succeeds when given value is not null and child check succeeds", async () => { + const checkValue = check.nullableAsync(async () => check.ok("value")); + + for (const value of ["jerome", "", 42, 0, undefined]) { + const result = await checkValue(value); + + expect(result).toEqual({ + isOk: true, + value: "value", + }); + } +}); + +test("check fails when given value is not null and child check fails", async () => { + const checkValue = check.nullableAsync(async () => + check.error("value", "is wrong"), + ); + const result = await checkValue(42); + + expect(result).toEqual({ + isOk: false, + error: "is wrong", + invalidValue: "value", + path: [], + }); +}); + +test("check succeeds when given value is null", async () => { + const checkValue = check.nullableAsync(async () => + check.error("value", "is wrong"), + ); + const result = await checkValue(null); + + expect(result).toEqual({ + isOk: true, + value: null, + }); +}); + +test("child check is called with given value", async () => { + const checkChild = jest.fn(async () => check.ok("value")); + const checkValue = check.nullableAsync(checkChild); + await checkValue(42); + + expect(checkChild).toHaveBeenCalledTimes(1); + expect(checkChild).toHaveBeenCalledWith(42); +}); + +test("child check is called with the additional arguments", async () => { + const checkChild = jest.fn(async () => check.ok("value")); + const checkValue = check.nullableAsync( + checkChild, + ); + await checkValue(42, "one", "two"); + + expect(checkChild).toHaveBeenCalledTimes(1); + expect(checkChild).toHaveBeenCalledWith(expect.anything(), "one", "two"); +}); + +test("child check is not called when given value is null", async () => { + const checkChild = jest.fn(async () => check.ok("value")); + const checkValue = check.nullableAsync(checkChild); + await checkValue(null); + + expect(checkChild).not.toHaveBeenCalled(); +}); + +test("synchronous child checks are handled", async () => { + const checkValue = check.nullableAsync(() => check.ok("value")); + const result = await checkValue(42); + + expect(result).toEqual({ + isOk: true, + value: "value", + }); +}); From 3303dfbd8fd3eb5f99296879ab936c0cf35dc3cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Guyon?= Date: Mon, 17 Aug 2020 22:42:05 +0200 Subject: [PATCH 25/36] Add shapeAsync check --- src/index.ts | 1 + src/shapeAsync.ts | 74 ++++++++++++++++++++++++++++ test/shapeAsync.test.ts | 106 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 181 insertions(+) create mode 100644 src/shapeAsync.ts create mode 100644 test/shapeAsync.test.ts diff --git a/src/index.ts b/src/index.ts index a44f31c..62a1f28 100644 --- a/src/index.ts +++ b/src/index.ts @@ -59,5 +59,6 @@ export { default as nullableAsync } from "./nullableAsync"; export { default as items } from "./items"; export { default as shape } from "./shape"; +export { default as shapeAsync } from "./shapeAsync"; export { default as tuple } from "./tuple"; export { default as entries } from "./entries"; diff --git a/src/shapeAsync.ts b/src/shapeAsync.ts new file mode 100644 index 0000000..477c0f4 --- /dev/null +++ b/src/shapeAsync.ts @@ -0,0 +1,74 @@ +import AsyncCheck from "./asyncCheck"; +import MaybeAsyncCheck from "./maybeAsyncCheck"; +import ok from "./ok"; + +export default function shapeAsync( + checks: { [P in keyof O]: MaybeAsyncCheck }, +): AsyncCheck; +export default function shapeAsync< + I extends { [P in keyof O]: I[P] }, + O, + A extends unknown[] +>( + checks: { [P in keyof O]: MaybeAsyncCheck }, +): AsyncCheck; + +/** + * Asynchronous version of [[`shape`]]. + * + * ```js + * const check = shapeAsync({ + * name: chainAsync(string(), transformAsync(async value => value.trim())), + * age: integer(), + * }); + * + * await check({ + * name: " Jérôme ", + * age: 30, + * }); + * // => { + * // isOk: true, + * // value: { + * // name: "Jérôme", + * // age: 30, + * // }, + * // } + * + * await check({ + * name: "Jérôme", + * age: "thirty", + * }); + * // => { isOk: false, ... } + * ``` + * + * @param checks An object containing async check functions. + * @returns An async check function. + */ +export default function shapeAsync(checks: { + [key: string]: MaybeAsyncCheck; +}): AsyncCheck { + const keys = Object.keys(checks); + + return async (input, ...args) => { + const output: any = {}; + const results = await Promise.all( + keys.map((key) => checks[key](input[key], ...args)), + ); + + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const result = results[i]; + + if (result.isOk) { + output[key] = result.value; + } else { + return { + ...result, + path: [key, ...result.path], + }; + } + } + + return ok(output); + }; +} diff --git a/test/shapeAsync.test.ts b/test/shapeAsync.test.ts new file mode 100644 index 0000000..fa3e5f8 --- /dev/null +++ b/test/shapeAsync.test.ts @@ -0,0 +1,106 @@ +import * as check from "../src"; + +test("check succeeds when all child checks succeed", async () => { + const checkValue = check.shapeAsync({ + one: async () => check.ok(1), + two: async () => check.ok(2), + }); + const result = await checkValue({}); + + expect(result).toEqual({ + isOk: true, + value: { + one: 1, + two: 2, + }, + }); +}); + +test("check fails when at least one child check fails", async () => { + const checkValue = check.shapeAsync({ + one: async () => check.ok(1), + two: async () => check.error(2, "is wrong"), + }); + const result = await checkValue({}); + + expect(result).toEqual({ + isOk: false, + error: "is wrong", + invalidValue: 2, + path: ["two"], + }); +}); + +test("child checks are called with value at corresponding key", async () => { + const checkOne = jest.fn(async () => check.ok(1)); + const checkTwo = jest.fn(async () => check.ok(2)); + const checkValue = check.shapeAsync({ + one: checkOne, + two: checkTwo, + }); + await checkValue({ + one: 1, + two: 2, + }); + + expect(checkOne).toHaveBeenCalledTimes(1); + expect(checkOne).toHaveBeenCalledWith(1); + expect(checkTwo).toHaveBeenCalledTimes(1); + expect(checkTwo).toHaveBeenCalledWith(2); +}); + +test("child checks are called with the additional arguments", async () => { + const checkOne = jest.fn(async () => check.ok(1)); + const checkTwo = jest.fn(async () => check.ok(2)); + const checkValue = check.shapeAsync({ + one: checkOne, + two: checkTwo, + }); + await checkValue( + { + one: 1, + two: 2, + }, + "one", + "two", + ); + + expect(checkOne).toHaveBeenCalledTimes(1); + expect(checkOne).toHaveBeenCalledWith(expect.anything(), "one", "two"); + expect(checkTwo).toHaveBeenCalledTimes(1); + expect(checkTwo).toHaveBeenCalledWith(expect.anything(), "one", "two"); +}); + +test("correct path is returned with the error", async () => { + const checkValue = check.shapeAsync({ + one: async () => check.error(1, "is wrong", ["two"]), + }); + const result = await checkValue({}); + + expect(result).toEqual({ + isOk: false, + error: "is wrong", + invalidValue: 1, + path: ["one", "two"], + }); +}); + +test("synchronous child checks are handled", async () => { + const checkValue = check.shapeAsync({ + one: async () => check.ok(1), + two: () => check.ok(2), + three: async () => check.ok(3), + four: () => check.ok(4), + }); + const result = await checkValue({}); + + expect(result).toEqual({ + isOk: true, + value: { + one: 1, + two: 2, + three: 3, + four: 4, + }, + }); +}); From 3422de9fccbe5556004439c93ec9b03316602cfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Guyon?= Date: Tue, 18 Aug 2020 16:19:32 +0200 Subject: [PATCH 26/36] Improve typing of tuple check --- src/tuple.ts | 51 ++++++++++++++++++++++++++++++++++++++-------- test/tuple.test.ts | 5 ++++- 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/src/tuple.ts b/src/tuple.ts index 2718981..2edbdb5 100644 --- a/src/tuple.ts +++ b/src/tuple.ts @@ -2,14 +2,47 @@ import Check from "./check"; import ok from "./ok"; import err from "./error"; -export default function tuple< - I extends unknown[], - O extends unknown[], - A extends unknown[] ->( - checks: { [P in keyof O]: Check }, +export default function tuple( + checks: [], + lengthError?: string, +): Check; +export default function tuple( + checks: [Check], + lengthError?: string, +): Check; +export default function tuple( + checks: [Check, Check], + lengthError?: string, +): Check; +export default function tuple( + checks: [Check, Check, Check], + lengthError?: string, +): Check; +export default function tuple( + checks: [Check, Check, Check, Check], + lengthError?: string, +): Check; +export default function tuple( + checks: [ + Check, + Check, + Check, + Check, + Check, + ], + lengthError?: string, +): Check; +export default function tuple( + checks: [ + Check, + Check, + Check, + Check, + Check, + Check, + ], lengthError?: string, -): Check; +): Check; /** * Creates a check function that validates the items of an array. @@ -35,11 +68,11 @@ export default function tuple< * * @param checks An array of check functions. * @param lengthError The error to give when the value does not have the right - * length + * length. * @returns A check function. */ export default function tuple( - checks: any, + checks: Array>, lengthError = checks.length === 1 ? "does not have 1 item" : `does not have ${checks.length} items`, diff --git a/test/tuple.test.ts b/test/tuple.test.ts index 1899f0c..f3ff731 100644 --- a/test/tuple.test.ts +++ b/test/tuple.test.ts @@ -56,7 +56,10 @@ test("child checks are called with value at corresponding index", () => { test("child checks are called with the additional arguments", () => { const checkOne = jest.fn(() => check.ok(1)); const checkTwo = jest.fn(() => check.ok(2)); - const checkValue = check.tuple([checkOne, checkTwo]); + const checkValue = check.tuple([ + checkOne, + checkTwo, + ]); checkValue([1, 2], "one", "two"); expect(checkOne).toHaveBeenCalledTimes(1); From b7cf74a22c39ddb48af89a3799ce02542c97832d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Guyon?= Date: Tue, 18 Aug 2020 16:29:04 +0200 Subject: [PATCH 27/36] Add tupleAsync check --- src/index.ts | 1 + src/tupleAsync.ts | 124 ++++++++++++++++++++++++++++++++++++++++ test/tupleAsync.test.ts | 117 +++++++++++++++++++++++++++++++++++++ 3 files changed, 242 insertions(+) create mode 100644 src/tupleAsync.ts create mode 100644 test/tupleAsync.test.ts diff --git a/src/index.ts b/src/index.ts index 62a1f28..0b22390 100644 --- a/src/index.ts +++ b/src/index.ts @@ -61,4 +61,5 @@ export { default as items } from "./items"; export { default as shape } from "./shape"; export { default as shapeAsync } from "./shapeAsync"; export { default as tuple } from "./tuple"; +export { default as tupleAsync } from "./tupleAsync"; export { default as entries } from "./entries"; diff --git a/src/tupleAsync.ts b/src/tupleAsync.ts new file mode 100644 index 0000000..2d9709c --- /dev/null +++ b/src/tupleAsync.ts @@ -0,0 +1,124 @@ +import AsyncCheck from "./asyncCheck"; +import MaybeAsyncCheck from "./maybeAsyncCheck"; +import ok from "./ok"; +import err from "./error"; + +export default function tupleAsync( + checks: [], + lengthError?: string, +): AsyncCheck; +export default function tupleAsync( + checks: [MaybeAsyncCheck], + lengthError?: string, +): AsyncCheck; +export default function tupleAsync( + checks: [MaybeAsyncCheck, MaybeAsyncCheck], + lengthError?: string, +): AsyncCheck; +export default function tupleAsync( + checks: [ + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + ], + lengthError?: string, +): AsyncCheck; +export default function tupleAsync( + checks: [ + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + ], + lengthError?: string, +): AsyncCheck; +export default function tupleAsync( + checks: [ + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + ], + lengthError?: string, +): AsyncCheck; +export default function tupleAsync< + I, + O1, + O2, + O3, + O4, + O5, + O6, + A extends unknown[] +>( + checks: [ + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + MaybeAsyncCheck, + ], + lengthError?: string, +): AsyncCheck; + +/** + * Asynchronous version of [[`tuple`]]. + * + * ```js + * const check = tupleAsync([ + * chainAsync(string(), transformAsync(async value => value.trim())), + * number(), + * ]); + * + * await check([" Jérôme ", 30]); + * // => { + * // isOk: true, + * // value: ["Jérôme", 30], + * // } + * + * await check(["Jérôme", "thirty"]); + * // => { isOk: false, ... } + * + * await check(["Jérôme", 30, "Web Developer"]); + * // => { isOk: false, ... } + * ``` + * + * @param checks An array of async check functions. + * @param lengthError The error to give when the value does not have the right + * length. + * @returns An async check function. + */ +export default function tupleAsync( + checks: Array>, + lengthError = checks.length === 1 + ? "does not have 1 item" + : `does not have ${checks.length} items`, +): AsyncCheck { + return async (input, ...args) => { + if (input.length !== checks.length) { + return err(input, lengthError); + } + + const results = await Promise.all( + checks.map((check, i) => check(input[i], ...args)), + ); + const output = new Array(results.length); + + for (let i = 0; i < results.length; i++) { + const result = results[i]; + + if (result.isOk) { + output[i] = result.value; + } else { + return { + ...result, + path: [i, ...result.path], + }; + } + } + + return ok(output); + }; +} diff --git a/test/tupleAsync.test.ts b/test/tupleAsync.test.ts new file mode 100644 index 0000000..bab6733 --- /dev/null +++ b/test/tupleAsync.test.ts @@ -0,0 +1,117 @@ +import * as check from "../src"; + +test("check succeeds when all child checks succeed", async () => { + const checkValue = check.tupleAsync([ + async () => check.ok(1), + async () => check.ok(2), + ]); + const result = await checkValue(["one", "two"]); + + expect(result).toEqual({ + isOk: true, + value: [1, 2], + }); +}); + +test("check fails when given value does not have the right length", async () => { + const checkValue = check.tupleAsync([ + async () => check.error(1, "is wrong"), + async () => check.error(2, "is wrong"), + ]); + const result = await checkValue(["one"]); + + expect(result).toEqual({ + isOk: false, + error: "does not have 2 items", + invalidValue: ["one"], + path: [], + }); +}); + +test("check fails when at least one child check fails", async () => { + const checkValue = check.tupleAsync([ + async () => check.ok(1), + async () => check.error(2, "is wrong"), + async () => check.error(3, "is wrong"), + ]); + const result = await checkValue(["one", "two", "three"]); + + expect(result).toEqual({ + isOk: false, + error: "is wrong", + invalidValue: 2, + path: [1], + }); +}); + +test("child checks are called with value at corresponding index", async () => { + const checkOne = jest.fn(async () => check.ok(1)); + const checkTwo = jest.fn(async () => check.ok(2)); + const checkValue = check.tupleAsync([checkOne, checkTwo]); + await checkValue(["one", "two"]); + + expect(checkOne).toHaveBeenCalledTimes(1); + expect(checkOne).toHaveBeenCalledWith("one"); + expect(checkTwo).toHaveBeenCalledTimes(1); + expect(checkTwo).toHaveBeenCalledWith("two"); +}); + +test("child checks are called with the additional arguments", async () => { + const checkOne = jest.fn(async () => check.ok(1)); + const checkTwo = jest.fn(async () => check.ok(2)); + const checkValue = check.tupleAsync([ + checkOne, + checkTwo, + ]); + await checkValue([1, 2], "one", "two"); + + expect(checkOne).toHaveBeenCalledTimes(1); + expect(checkOne).toHaveBeenCalledWith(expect.anything(), "one", "two"); + expect(checkTwo).toHaveBeenCalledTimes(1); + expect(checkTwo).toHaveBeenCalledWith(expect.anything(), "one", "two"); +}); + +test("given length error is returned with the invalid result", async () => { + const checkValue = check.tupleAsync( + [async () => check.ok(1), async () => check.ok(2)], + "does not have two items", + ); + const result = await checkValue(["one"]); + + expect(result).toEqual({ + isOk: false, + error: "does not have two items", + invalidValue: ["one"], + path: [], + }); +}); + +test("correct path is returned with the item error", async () => { + const checkValue = check.tupleAsync([ + async () => check.ok(1), + async () => check.error(2, "is wrong", [2]), + ]); + const result = await checkValue(["one", "two"]); + + expect(result).toEqual({ + isOk: false, + error: "is wrong", + invalidValue: 2, + path: [1, 2], + }); +}); + +test("synchronous child checks are handled", async () => { + const checkValue = check.tupleAsync([ + async () => check.ok(1), + () => check.ok(2), + async () => check.ok(3), + () => check.ok(4), + ]); + const result = await checkValue(["one", "two", "three", "four"]); + + expect(result).toEqual({ + isOk: true, + value: [1, 2, 3, 4], + }); +}); From f9cd0cca6fac080dbec20dccb6fb7fca1cece3c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Guyon?= Date: Tue, 18 Aug 2020 18:39:42 +0200 Subject: [PATCH 28/36] Drop support for iterables in items check The overwhelming majority of uses of the items check will be to check an array, which is made slower by the iterable support. --- src/items.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/items.ts b/src/items.ts index 61cfe67..41060b8 100644 --- a/src/items.ts +++ b/src/items.ts @@ -22,17 +22,15 @@ import ok from "./ok"; */ export default function items( check: Check, -): Check, O[], A> { +): Check { return (input, ...args) => { - const output = []; + const output = new Array(input.length); - let i = 0; - for (const value of input) { - const result = check(value, ...args); + for (let i = 0; i < input.length; i++) { + const result = check(input[i], ...args); if (result.isOk) { - output.push(result.value); - i++; + output[i] = result.value; } else { return { ...result, From 9c5c540739e9f3f8d95e45910f01d92bca41023d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Guyon?= Date: Tue, 18 Aug 2020 18:41:33 +0200 Subject: [PATCH 29/36] Fix documentation of ok function --- src/ok.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ok.ts b/src/ok.ts index d6d4735..b71519d 100644 --- a/src/ok.ts +++ b/src/ok.ts @@ -16,7 +16,7 @@ export interface OkResult { * Creates a valid result. * * ```js - * check.ok("value"); + * ok("value"); * // => { * // isOk: true, * // value: "value", From 70e4f54e7f1fbd72f6f54359a4a92bfe402e1df7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Guyon?= Date: Tue, 18 Aug 2020 18:43:05 +0200 Subject: [PATCH 30/36] Fix documentation of items check --- src/items.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/items.ts b/src/items.ts index 41060b8..392bb30 100644 --- a/src/items.ts +++ b/src/items.ts @@ -2,7 +2,7 @@ import Check from "./check"; import ok from "./ok"; /** - * Creates a check function that validates the items of an iterable. + * Creates a check function that validates the items of an array. * * ```js * const check = items(chain(string(), trim())); From 369b41a9a9c06721175242800cd1c0830e8848dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Guyon?= Date: Tue, 18 Aug 2020 18:57:18 +0200 Subject: [PATCH 31/36] Add itemsAsync check --- src/index.ts | 1 + src/itemsAsync.ts | 53 ++++++++++++++++++++++++++++++ test/itemsAsync.test.ts | 73 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 127 insertions(+) create mode 100644 src/itemsAsync.ts create mode 100644 test/itemsAsync.test.ts diff --git a/src/index.ts b/src/index.ts index 0b22390..e4e27fd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -58,6 +58,7 @@ export { default as nullable } from "./nullable"; export { default as nullableAsync } from "./nullableAsync"; export { default as items } from "./items"; +export { default as itemsAsync } from "./itemsAsync"; export { default as shape } from "./shape"; export { default as shapeAsync } from "./shapeAsync"; export { default as tuple } from "./tuple"; diff --git a/src/itemsAsync.ts b/src/itemsAsync.ts new file mode 100644 index 0000000..2678bb0 --- /dev/null +++ b/src/itemsAsync.ts @@ -0,0 +1,53 @@ +import AsyncCheck from "./asyncCheck"; +import MaybeAsyncCheck from "./maybeAsyncCheck"; +import ok from "./ok"; + +/** + * Asynchronous version of [[`items`]]. + * + * ```js + * const check = itemsAsync( + * chainAsync( + * string(), + * transformAsync(async value => value.trim()), + * ), + * ); + * + * await check([" one ", " two "]); + * // => { + * // isOk: true, + * // value: ["one", "two"], + * // } + * + * await check(["one", 2]); + * // => { isOk: false, ... } + * ``` + * + * @param check The async check function to use on the items. + * @returns An async check function. + */ +export default function itemsAsync( + check: MaybeAsyncCheck, +): AsyncCheck { + return async (input, ...args) => { + const results = await Promise.all( + input.map((value) => check(value, ...args)), + ); + const output = new Array(results.length); + + for (let i = 0; i < results.length; i++) { + const result = results[i]; + + if (result.isOk) { + output[i] = result.value; + } else { + return { + ...result, + path: [i, ...result.path], + }; + } + } + + return ok(output); + }; +} diff --git a/test/itemsAsync.test.ts b/test/itemsAsync.test.ts new file mode 100644 index 0000000..1b2ac8f --- /dev/null +++ b/test/itemsAsync.test.ts @@ -0,0 +1,73 @@ +import * as check from "../src"; + +test("check succeeds when child check succeeds on all items", async () => { + const checkValue = check.itemsAsync( + check.transformAsync(async (value: string) => value.trim()), + ); + const result = await checkValue(["one", " two "]); + + expect(result).toEqual({ + isOk: true, + value: ["one", "two"], + }); +}); + +test("check fails when child check fails on at least one item", async () => { + const checkValue = check.itemsAsync( + check.testAsync(async (value) => value === "valid", "is invalid"), + ); + const result = await checkValue(["valid", "invalid one", "invalid two"]); + + expect(result).toEqual({ + isOk: false, + error: "is invalid", + invalidValue: "invalid one", + path: [1], + }); +}); + +test("child check is called with all items", async () => { + const checkItem = jest.fn(async () => check.ok(42)); + const checkValue = check.itemsAsync(checkItem); + await checkValue([1, 2, 3]); + + expect(checkItem).toHaveBeenCalledTimes(3); + expect(checkItem).toHaveBeenCalledWith(1); + expect(checkItem).toHaveBeenCalledWith(2); + expect(checkItem).toHaveBeenCalledWith(3); +}); + +test("child check is called with the additional arguments", async () => { + const checkItem = jest.fn(async () => check.ok(42)); + const checkValue = check.itemsAsync(checkItem); + await checkValue([1, 2, 3], "one", "two"); + + expect(checkItem).toHaveBeenCalledTimes(3); + expect(checkItem).toHaveBeenNthCalledWith(1, expect.anything(), "one", "two"); + expect(checkItem).toHaveBeenNthCalledWith(2, expect.anything(), "one", "two"); + expect(checkItem).toHaveBeenNthCalledWith(3, expect.anything(), "one", "two"); +}); + +test("correct path is returned with the error", async () => { + const checkValue = check.itemsAsync(async (value) => + value === "valid" ? check.ok(value) : check.error(value, "is wrong", [2]), + ); + const result = await checkValue(["valid", "invalid"]); + + expect(result).toEqual({ + isOk: false, + error: "is wrong", + invalidValue: "invalid", + path: [1, 2], + }); +}); + +test("synchronous child checks are handled", async () => { + const checkValue = check.itemsAsync(check.trim()); + const result = await checkValue([" one ", "two ", " three "]); + + expect(result).toEqual({ + isOk: true, + value: ["one", "two", "three"], + }); +}); From ce13edd70386475f9e28f2694e5b77de7f1deb2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Guyon?= Date: Tue, 18 Aug 2020 19:19:19 +0200 Subject: [PATCH 32/36] Fix incomplete documentation of entries check --- src/entries.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/entries.ts b/src/entries.ts index d812f40..9a38cca 100644 --- a/src/entries.ts +++ b/src/entries.ts @@ -25,6 +25,10 @@ import ok from "./ok"; * check({ one: "one" }); * // => { isOk: false, ... } * ``` + * + * @param checkKey The check function to use on the keys. + * @param checkValue The check function to use on the values. + * @returns A check function. */ export default function entries( checkKey: Check, From 3b745f00995a0a32ffb974625daebbf918d3acc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Guyon?= Date: Tue, 18 Aug 2020 19:29:33 +0200 Subject: [PATCH 33/36] Add entriesAsync check --- src/entriesAsync.ts | 73 ++++++++++++++++++++ src/index.ts | 1 + test/entriesAsync.test.ts | 140 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 214 insertions(+) create mode 100644 src/entriesAsync.ts create mode 100644 test/entriesAsync.test.ts diff --git a/src/entriesAsync.ts b/src/entriesAsync.ts new file mode 100644 index 0000000..f62ec63 --- /dev/null +++ b/src/entriesAsync.ts @@ -0,0 +1,73 @@ +import AsyncCheck from "./asyncCheck"; +import MaybeAsyncCheck from "./maybeAsyncCheck"; +import ok from "./ok"; + +/** + * Asynchronous version of [[`entries`]]. + * + * ```js + * const check = entriesAsync( + * pattern(/^[0-9]+$/), + * chainAsync( + * string(), + * transformAsync(async value => value.trim()), + * ), + * ); + * + * await check({ + * 1: " one ", + * 2: " two " + * }); + * // => { + * // isOk: true, + * // value: { + * // 1: "one", + * // 2: "two", + * // }, + * // } + * + * await check({ 1: 1 }); + * // => { isOk: false, ... } + * + * await check({ one: "one" }); + * // => { isOk: false, ... } + * ``` + * + * @param checkKey The async check function to use on the keys. + * @param checkValue The async check function to use on the values. + * @returns An async check function. + */ +export default function entriesAsync( + checkKey: MaybeAsyncCheck, + checkValue: MaybeAsyncCheck, +): AsyncCheck<{ [key: string]: I }, { [key: string]: O }, A> { + return async (input, ...args) => { + const keys = Object.keys(input); + const [keyResults, valueResults] = await Promise.all([ + Promise.all(keys.map((key) => checkKey(key, ...args))), + Promise.all(keys.map((key) => checkValue(input[key], ...args))), + ]); + const output: { [key: string]: O } = {}; + + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const keyResult = keyResults[i]; + const valueResult = valueResults[i]; + + if (keyResult.isOk) { + if (valueResult.isOk) { + output[keyResult.value] = valueResult.value; + } else { + return { + ...valueResult, + path: [key, ...valueResult.path], + }; + } + } else { + return keyResult; + } + } + + return ok(output); + }; +} diff --git a/src/index.ts b/src/index.ts index e4e27fd..dae8cf2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -64,3 +64,4 @@ export { default as shapeAsync } from "./shapeAsync"; export { default as tuple } from "./tuple"; export { default as tupleAsync } from "./tupleAsync"; export { default as entries } from "./entries"; +export { default as entriesAsync } from "./entriesAsync"; diff --git a/test/entriesAsync.test.ts b/test/entriesAsync.test.ts new file mode 100644 index 0000000..32b683b --- /dev/null +++ b/test/entriesAsync.test.ts @@ -0,0 +1,140 @@ +import * as check from "../src"; + +test("check succeeds when child checks succeeds on all key-value pairs", async () => { + const checkValue = check.entriesAsync( + check.chainAsync( + check.transformAsync(async (value) => value.trim()), + check.pattern(/^[0-9]+$/), + ), + check.chainAsync( + check.string(), + check.transformAsync(async (value) => value.trim()), + ), + ); + const result = await checkValue({ + " 1 ": " one ", + " 2": " two ", + }); + + expect(result).toEqual({ + isOk: true, + value: { + 1: "one", + 2: "two", + }, + }); +}); + +test("check fails when child key check fails on at least one key", async () => { + const checkValue = check.entriesAsync( + check.testAsync(async (value) => value === "one", "is invalid"), + async (value) => check.ok(value), + ); + const result = await checkValue({ one: 1, two: 2 }); + + expect(result).toEqual({ + isOk: false, + error: "is invalid", + invalidValue: "two", + path: [], + }); +}); + +test("check fails when child value check fails on at least one value", async () => { + const checkValue = check.entriesAsync( + async (value) => check.ok(value), + check.testAsync(async (value) => value === "one", "is invalid"), + ); + const result = await checkValue({ 1: "one", 2: "two" }); + + expect(result).toEqual({ + isOk: false, + error: "is invalid", + invalidValue: "two", + path: ["2"], + }); +}); + +test("child checks are called with all key-value pairs", async () => { + const checkKey = jest.fn(async () => check.ok("key")); + const checkValue = jest.fn(async () => check.ok("value")); + const checkEntries = check.entriesAsync(checkKey, checkValue); + await checkEntries({ + one: 1, + two: 2, + }); + + expect(checkKey).toHaveBeenCalledTimes(2); + expect(checkKey).toHaveBeenCalledWith("one"); + expect(checkKey).toHaveBeenCalledWith("two"); + expect(checkValue).toHaveBeenCalledTimes(2); + expect(checkValue).toHaveBeenCalledWith(1); + expect(checkValue).toHaveBeenCalledWith(2); +}); + +test("child checks are called with the additional arguments", async () => { + const checkKey = jest.fn(async () => check.ok("key")); + const checkValue = jest.fn(async () => check.ok("value")); + const checkEntries = check.entriesAsync( + checkKey, + checkValue, + ); + await checkEntries( + { + one: 1, + two: 2, + }, + "one", + "two", + ); + + expect(checkKey).toHaveBeenCalledTimes(2); + expect(checkKey).toHaveBeenNthCalledWith(1, expect.anything(), "one", "two"); + expect(checkKey).toHaveBeenNthCalledWith(2, expect.anything(), "one", "two"); + expect(checkValue).toHaveBeenCalledTimes(2); + expect(checkValue).toHaveBeenNthCalledWith( + 1, + expect.anything(), + "one", + "two", + ); + expect(checkValue).toHaveBeenNthCalledWith( + 2, + expect.anything(), + "one", + "two", + ); +}); + +test("correct path is returned with the value error", async () => { + const checkValue = check.entriesAsync( + check.transformAsync(async (value) => value.trim()), + async () => check.error("invalid", "is wrong", ["two"]), + ); + const result = await checkValue({ + " one ": 1, + }); + + expect(result).toEqual({ + isOk: false, + error: "is wrong", + invalidValue: "invalid", + path: [" one ", "two"], + }); +}); + +test("synchronous child checks are handled", async () => { + const checkValue = check.entriesAsync( + () => check.ok("key"), + () => check.ok("value"), + ); + const result = await checkValue({ + one: 1, + two: 2, + }); + + expect(result).toEqual({ + isOk: true, + value: { key: "value" }, + }); +}); From 4a95afbf8930c5d0d116d335ceb9b27a6c45e685 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Guyon?= Date: Tue, 18 Aug 2020 19:32:20 +0200 Subject: [PATCH 34/36] Export async check types --- src/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/index.ts b/src/index.ts index dae8cf2..25d58a0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,8 @@ export { default as Result } from "./result"; export { default as ok, OkResult } from "./ok"; export { default as error, ErrorResult } from "./error"; export { default as Check } from "./check"; +export { default as AsyncCheck } from "./asyncCheck"; +export { default as MaybeAsyncCheck } from "./maybeAsyncCheck"; export { default as pass } from "./pass"; export { default as fail } from "./fail"; From f84ca54a02f43e23bbf6c26ef0dc624ba38bc6c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Guyon?= Date: Tue, 18 Aug 2020 22:31:55 +0200 Subject: [PATCH 35/36] Use kebab case for file names --- src/{asyncCheck.ts => async-check.ts} | 0 src/{chainAsync.ts => chain-async.ts} | 4 +- src/{entriesAsync.ts => entries-async.ts} | 4 +- src/index.ts | 50 +++++++++---------- src/{itemsAsync.ts => items-async.ts} | 4 +- src/{lessThan.ts => less-than.ts} | 0 src/{maxLength.ts => max-length.ts} | 0 ...aybeAsyncCheck.ts => maybe-async-check.ts} | 0 src/{minLength.ts => min-length.ts} | 0 src/{moreThan.ts => more-than.ts} | 0 src/{NaN.ts => not-a-number.ts} | 2 +- src/{notAsync.ts => not-async.ts} | 2 +- src/{nullableAsync.ts => nullable-async.ts} | 4 +- src/{oneOfAsync.ts => one-of-async.ts} | 4 +- src/{oneOf.ts => one-of.ts} | 0 src/{optionalAsync.ts => optional-async.ts} | 4 +- src/{shapeAsync.ts => shape-async.ts} | 4 +- src/{testAsync.ts => test-async.ts} | 2 +- src/{toBoolean.ts => to-boolean.ts} | 0 src/{toDate.ts => to-date.ts} | 0 src/{toLower.ts => to-lower.ts} | 0 src/{toNumber.ts => to-number.ts} | 0 src/{toString.ts => to-string.ts} | 0 src/{toUpper.ts => to-upper.ts} | 0 src/{transformAsync.ts => transform-async.ts} | 2 +- src/{tupleAsync.ts => tuple-async.ts} | 4 +- ...chainAsync.test.ts => chain-async.test.ts} | 0 ...iesAsync.test.ts => entries-async.test.ts} | 0 ...itemsAsync.test.ts => items-async.test.ts} | 0 test/{lessThan.test.ts => less-than.test.ts} | 0 .../{maxLength.test.ts => max-length.test.ts} | 0 .../{minLength.test.ts => min-length.test.ts} | 0 test/{moreThan.test.ts => more-than.test.ts} | 0 test/{NaN.test.ts => not-a-number.test.ts} | 0 test/{notAsync.test.ts => not-async.test.ts} | 0 ...leAsync.test.ts => nullable-async.test.ts} | 0 ...neOfAsync.test.ts => one-of-async.test.ts} | 0 test/{oneOf.test.ts => one-of.test.ts} | 0 ...alAsync.test.ts => optional-async.test.ts} | 0 ...shapeAsync.test.ts => shape-async.test.ts} | 0 .../{testAsync.test.ts => test-async.test.ts} | 0 .../{toBoolean.test.ts => to-boolean.test.ts} | 0 test/{toDate.test.ts => to-date.test.ts} | 0 test/{toLower.test.ts => to-lower.test.ts} | 0 test/{toNumber.test.ts => to-number.test.ts} | 0 test/{toString.test.ts => to-string.test.ts} | 0 test/{toUpper.test.ts => to-upper.test.ts} | 0 ...mAsync.test.ts => transform-async.test.ts} | 0 ...tupleAsync.test.ts => tuple-async.test.ts} | 0 49 files changed, 45 insertions(+), 45 deletions(-) rename src/{asyncCheck.ts => async-check.ts} (100%) rename src/{chainAsync.ts => chain-async.ts} (98%) rename src/{entriesAsync.ts => entries-async.ts} (95%) rename src/{itemsAsync.ts => items-async.ts} (92%) rename src/{lessThan.ts => less-than.ts} (100%) rename src/{maxLength.ts => max-length.ts} (100%) rename src/{maybeAsyncCheck.ts => maybe-async-check.ts} (100%) rename src/{minLength.ts => min-length.ts} (100%) rename src/{moreThan.ts => more-than.ts} (100%) rename src/{NaN.ts => not-a-number.ts} (94%) rename src/{notAsync.ts => not-async.ts} (97%) rename src/{nullableAsync.ts => nullable-async.ts} (88%) rename src/{oneOfAsync.ts => one-of-async.ts} (98%) rename src/{oneOf.ts => one-of.ts} (100%) rename src/{optionalAsync.ts => optional-async.ts} (89%) rename src/{shapeAsync.ts => shape-async.ts} (94%) rename src/{testAsync.ts => test-async.ts} (97%) rename src/{toBoolean.ts => to-boolean.ts} (100%) rename src/{toDate.ts => to-date.ts} (100%) rename src/{toLower.ts => to-lower.ts} (100%) rename src/{toNumber.ts => to-number.ts} (100%) rename src/{toString.ts => to-string.ts} (100%) rename src/{toUpper.ts => to-upper.ts} (100%) rename src/{transformAsync.ts => transform-async.ts} (93%) rename src/{tupleAsync.ts => tuple-async.ts} (97%) rename test/{chainAsync.test.ts => chain-async.test.ts} (100%) rename test/{entriesAsync.test.ts => entries-async.test.ts} (100%) rename test/{itemsAsync.test.ts => items-async.test.ts} (100%) rename test/{lessThan.test.ts => less-than.test.ts} (100%) rename test/{maxLength.test.ts => max-length.test.ts} (100%) rename test/{minLength.test.ts => min-length.test.ts} (100%) rename test/{moreThan.test.ts => more-than.test.ts} (100%) rename test/{NaN.test.ts => not-a-number.test.ts} (100%) rename test/{notAsync.test.ts => not-async.test.ts} (100%) rename test/{nullableAsync.test.ts => nullable-async.test.ts} (100%) rename test/{oneOfAsync.test.ts => one-of-async.test.ts} (100%) rename test/{oneOf.test.ts => one-of.test.ts} (100%) rename test/{optionalAsync.test.ts => optional-async.test.ts} (100%) rename test/{shapeAsync.test.ts => shape-async.test.ts} (100%) rename test/{testAsync.test.ts => test-async.test.ts} (100%) rename test/{toBoolean.test.ts => to-boolean.test.ts} (100%) rename test/{toDate.test.ts => to-date.test.ts} (100%) rename test/{toLower.test.ts => to-lower.test.ts} (100%) rename test/{toNumber.test.ts => to-number.test.ts} (100%) rename test/{toString.test.ts => to-string.test.ts} (100%) rename test/{toUpper.test.ts => to-upper.test.ts} (100%) rename test/{transformAsync.test.ts => transform-async.test.ts} (100%) rename test/{tupleAsync.test.ts => tuple-async.test.ts} (100%) diff --git a/src/asyncCheck.ts b/src/async-check.ts similarity index 100% rename from src/asyncCheck.ts rename to src/async-check.ts diff --git a/src/chainAsync.ts b/src/chain-async.ts similarity index 98% rename from src/chainAsync.ts rename to src/chain-async.ts index 39237d7..3afc021 100644 --- a/src/chainAsync.ts +++ b/src/chain-async.ts @@ -1,5 +1,5 @@ -import AsyncCheck from "./asyncCheck"; -import MaybeAsyncCheck from "./maybeAsyncCheck"; +import AsyncCheck from "./async-check"; +import MaybeAsyncCheck from "./maybe-async-check"; import ok from "./ok"; export default function chainAsync(): AsyncCheck< diff --git a/src/entriesAsync.ts b/src/entries-async.ts similarity index 95% rename from src/entriesAsync.ts rename to src/entries-async.ts index f62ec63..af4bef7 100644 --- a/src/entriesAsync.ts +++ b/src/entries-async.ts @@ -1,5 +1,5 @@ -import AsyncCheck from "./asyncCheck"; -import MaybeAsyncCheck from "./maybeAsyncCheck"; +import AsyncCheck from "./async-check"; +import MaybeAsyncCheck from "./maybe-async-check"; import ok from "./ok"; /** diff --git a/src/index.ts b/src/index.ts index 25d58a0..f3a30dc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,20 +2,20 @@ export { default as Result } from "./result"; export { default as ok, OkResult } from "./ok"; export { default as error, ErrorResult } from "./error"; export { default as Check } from "./check"; -export { default as AsyncCheck } from "./asyncCheck"; -export { default as MaybeAsyncCheck } from "./maybeAsyncCheck"; +export { default as AsyncCheck } from "./async-check"; +export { default as MaybeAsyncCheck } from "./maybe-async-check"; export { default as pass } from "./pass"; export { default as fail } from "./fail"; export { default as test } from "./test"; -export { default as testAsync } from "./testAsync"; +export { default as testAsync } from "./test-async"; export { default as transform } from "./transform"; -export { default as transformAsync } from "./transformAsync"; +export { default as transformAsync } from "./transform-async"; export { default as is } from "./is"; export { default as null } from "./null"; export { default as undefined } from "./undefined"; -export { default as NaN } from "./NaN"; +export { default as NaN } from "./not-a-number"; export { default as boolean } from "./boolean"; export { default as number } from "./number"; export { default as integer } from "./integer"; @@ -25,10 +25,10 @@ export { default as date } from "./date"; export { default as object } from "./object"; export { default as array } from "./array"; -export { default as toBoolean } from "./toBoolean"; -export { default as toNumber } from "./toNumber"; -export { default as toString } from "./toString"; -export { default as toDate } from "./toDate"; +export { default as toBoolean } from "./to-boolean"; +export { default as toNumber } from "./to-number"; +export { default as toString } from "./to-string"; +export { default as toDate } from "./to-date"; export { default as floor } from "./floor"; export { default as ceil } from "./ceil"; @@ -36,34 +36,34 @@ export { default as round } from "./round"; export { default as truncate } from "./truncate"; export { default as min } from "./min"; export { default as max } from "./max"; -export { default as lessThan } from "./lessThan"; -export { default as moreThan } from "./moreThan"; +export { default as lessThan } from "./less-than"; +export { default as moreThan } from "./more-than"; export { default as trim } from "./trim"; -export { default as toLower } from "./toLower"; -export { default as toUpper } from "./toUpper"; +export { default as toLower } from "./to-lower"; +export { default as toUpper } from "./to-upper"; export { default as pattern } from "./pattern"; -export { default as minLength } from "./minLength"; -export { default as maxLength } from "./maxLength"; +export { default as minLength } from "./min-length"; +export { default as maxLength } from "./max-length"; export { default as not } from "./not"; -export { default as notAsync } from "./notAsync"; +export { default as notAsync } from "./not-async"; export { default as chain } from "./chain"; -export { default as chainAsync } from "./chainAsync"; -export { default as oneOf } from "./oneOf"; -export { default as oneOfAsync } from "./oneOfAsync"; +export { default as chainAsync } from "./chain-async"; +export { default as oneOf } from "./one-of"; +export { default as oneOfAsync } from "./one-of-async"; export { default as optional } from "./optional"; -export { default as optionalAsync } from "./optionalAsync"; +export { default as optionalAsync } from "./optional-async"; export { default as nullable } from "./nullable"; -export { default as nullableAsync } from "./nullableAsync"; +export { default as nullableAsync } from "./nullable-async"; export { default as items } from "./items"; -export { default as itemsAsync } from "./itemsAsync"; +export { default as itemsAsync } from "./items-async"; export { default as shape } from "./shape"; -export { default as shapeAsync } from "./shapeAsync"; +export { default as shapeAsync } from "./shape-async"; export { default as tuple } from "./tuple"; -export { default as tupleAsync } from "./tupleAsync"; +export { default as tupleAsync } from "./tuple-async"; export { default as entries } from "./entries"; -export { default as entriesAsync } from "./entriesAsync"; +export { default as entriesAsync } from "./entries-async"; diff --git a/src/itemsAsync.ts b/src/items-async.ts similarity index 92% rename from src/itemsAsync.ts rename to src/items-async.ts index 2678bb0..6d7182d 100644 --- a/src/itemsAsync.ts +++ b/src/items-async.ts @@ -1,5 +1,5 @@ -import AsyncCheck from "./asyncCheck"; -import MaybeAsyncCheck from "./maybeAsyncCheck"; +import AsyncCheck from "./async-check"; +import MaybeAsyncCheck from "./maybe-async-check"; import ok from "./ok"; /** diff --git a/src/lessThan.ts b/src/less-than.ts similarity index 100% rename from src/lessThan.ts rename to src/less-than.ts diff --git a/src/maxLength.ts b/src/max-length.ts similarity index 100% rename from src/maxLength.ts rename to src/max-length.ts diff --git a/src/maybeAsyncCheck.ts b/src/maybe-async-check.ts similarity index 100% rename from src/maybeAsyncCheck.ts rename to src/maybe-async-check.ts diff --git a/src/minLength.ts b/src/min-length.ts similarity index 100% rename from src/minLength.ts rename to src/min-length.ts diff --git a/src/moreThan.ts b/src/more-than.ts similarity index 100% rename from src/moreThan.ts rename to src/more-than.ts diff --git a/src/NaN.ts b/src/not-a-number.ts similarity index 94% rename from src/NaN.ts rename to src/not-a-number.ts index 9df3836..ab44ff8 100644 --- a/src/NaN.ts +++ b/src/not-a-number.ts @@ -21,7 +21,7 @@ import test from "./test"; * @param error The error to given when the value is not NaN. * @returns A check function. */ -export default function NotANumber( +export default function notANumber( error = "is not NaN", ): Check { return test((value): value is number => isNaN(value as any), error); diff --git a/src/notAsync.ts b/src/not-async.ts similarity index 97% rename from src/notAsync.ts rename to src/not-async.ts index 5f60e01..854a6e0 100644 --- a/src/notAsync.ts +++ b/src/not-async.ts @@ -1,4 +1,4 @@ -import AsyncCheck from "./asyncCheck"; +import AsyncCheck from "./async-check"; import ok from "./ok"; import err from "./error"; diff --git a/src/nullableAsync.ts b/src/nullable-async.ts similarity index 88% rename from src/nullableAsync.ts rename to src/nullable-async.ts index 423b5ec..d21046e 100644 --- a/src/nullableAsync.ts +++ b/src/nullable-async.ts @@ -1,5 +1,5 @@ -import AsyncCheck from "./asyncCheck"; -import MaybeAsyncCheck from "./maybeAsyncCheck"; +import AsyncCheck from "./async-check"; +import MaybeAsyncCheck from "./maybe-async-check"; import ok from "./ok"; /** diff --git a/src/oneOfAsync.ts b/src/one-of-async.ts similarity index 98% rename from src/oneOfAsync.ts rename to src/one-of-async.ts index 49e27c8..a96fe58 100644 --- a/src/oneOfAsync.ts +++ b/src/one-of-async.ts @@ -1,5 +1,5 @@ -import AsyncCheck from "./asyncCheck"; -import MaybeAsyncCheck from "./maybeAsyncCheck"; +import AsyncCheck from "./async-check"; +import MaybeAsyncCheck from "./maybe-async-check"; export default function oneOfAsync( ...checks: [MaybeAsyncCheck] diff --git a/src/oneOf.ts b/src/one-of.ts similarity index 100% rename from src/oneOf.ts rename to src/one-of.ts diff --git a/src/optionalAsync.ts b/src/optional-async.ts similarity index 89% rename from src/optionalAsync.ts rename to src/optional-async.ts index 4ed9b6a..6f409a5 100644 --- a/src/optionalAsync.ts +++ b/src/optional-async.ts @@ -1,5 +1,5 @@ -import AsyncCheck from "./asyncCheck"; -import MaybeAsyncCheck from "./maybeAsyncCheck"; +import AsyncCheck from "./async-check"; +import MaybeAsyncCheck from "./maybe-async-check"; import ok from "./ok"; /** diff --git a/src/shapeAsync.ts b/src/shape-async.ts similarity index 94% rename from src/shapeAsync.ts rename to src/shape-async.ts index 477c0f4..1832066 100644 --- a/src/shapeAsync.ts +++ b/src/shape-async.ts @@ -1,5 +1,5 @@ -import AsyncCheck from "./asyncCheck"; -import MaybeAsyncCheck from "./maybeAsyncCheck"; +import AsyncCheck from "./async-check"; +import MaybeAsyncCheck from "./maybe-async-check"; import ok from "./ok"; export default function shapeAsync( diff --git a/src/testAsync.ts b/src/test-async.ts similarity index 97% rename from src/testAsync.ts rename to src/test-async.ts index ecd00c1..00c1ab7 100644 --- a/src/testAsync.ts +++ b/src/test-async.ts @@ -1,4 +1,4 @@ -import AsyncCheck from "./asyncCheck"; +import AsyncCheck from "./async-check"; import ok from "./ok"; import err from "./error"; diff --git a/src/toBoolean.ts b/src/to-boolean.ts similarity index 100% rename from src/toBoolean.ts rename to src/to-boolean.ts diff --git a/src/toDate.ts b/src/to-date.ts similarity index 100% rename from src/toDate.ts rename to src/to-date.ts diff --git a/src/toLower.ts b/src/to-lower.ts similarity index 100% rename from src/toLower.ts rename to src/to-lower.ts diff --git a/src/toNumber.ts b/src/to-number.ts similarity index 100% rename from src/toNumber.ts rename to src/to-number.ts diff --git a/src/toString.ts b/src/to-string.ts similarity index 100% rename from src/toString.ts rename to src/to-string.ts diff --git a/src/toUpper.ts b/src/to-upper.ts similarity index 100% rename from src/toUpper.ts rename to src/to-upper.ts diff --git a/src/transformAsync.ts b/src/transform-async.ts similarity index 93% rename from src/transformAsync.ts rename to src/transform-async.ts index 0743038..8f52da2 100644 --- a/src/transformAsync.ts +++ b/src/transform-async.ts @@ -1,4 +1,4 @@ -import AsyncCheck from "./asyncCheck"; +import AsyncCheck from "./async-check"; import ok from "./ok"; /** diff --git a/src/tupleAsync.ts b/src/tuple-async.ts similarity index 97% rename from src/tupleAsync.ts rename to src/tuple-async.ts index 2d9709c..58a9d0e 100644 --- a/src/tupleAsync.ts +++ b/src/tuple-async.ts @@ -1,5 +1,5 @@ -import AsyncCheck from "./asyncCheck"; -import MaybeAsyncCheck from "./maybeAsyncCheck"; +import AsyncCheck from "./async-check"; +import MaybeAsyncCheck from "./maybe-async-check"; import ok from "./ok"; import err from "./error"; diff --git a/test/chainAsync.test.ts b/test/chain-async.test.ts similarity index 100% rename from test/chainAsync.test.ts rename to test/chain-async.test.ts diff --git a/test/entriesAsync.test.ts b/test/entries-async.test.ts similarity index 100% rename from test/entriesAsync.test.ts rename to test/entries-async.test.ts diff --git a/test/itemsAsync.test.ts b/test/items-async.test.ts similarity index 100% rename from test/itemsAsync.test.ts rename to test/items-async.test.ts diff --git a/test/lessThan.test.ts b/test/less-than.test.ts similarity index 100% rename from test/lessThan.test.ts rename to test/less-than.test.ts diff --git a/test/maxLength.test.ts b/test/max-length.test.ts similarity index 100% rename from test/maxLength.test.ts rename to test/max-length.test.ts diff --git a/test/minLength.test.ts b/test/min-length.test.ts similarity index 100% rename from test/minLength.test.ts rename to test/min-length.test.ts diff --git a/test/moreThan.test.ts b/test/more-than.test.ts similarity index 100% rename from test/moreThan.test.ts rename to test/more-than.test.ts diff --git a/test/NaN.test.ts b/test/not-a-number.test.ts similarity index 100% rename from test/NaN.test.ts rename to test/not-a-number.test.ts diff --git a/test/notAsync.test.ts b/test/not-async.test.ts similarity index 100% rename from test/notAsync.test.ts rename to test/not-async.test.ts diff --git a/test/nullableAsync.test.ts b/test/nullable-async.test.ts similarity index 100% rename from test/nullableAsync.test.ts rename to test/nullable-async.test.ts diff --git a/test/oneOfAsync.test.ts b/test/one-of-async.test.ts similarity index 100% rename from test/oneOfAsync.test.ts rename to test/one-of-async.test.ts diff --git a/test/oneOf.test.ts b/test/one-of.test.ts similarity index 100% rename from test/oneOf.test.ts rename to test/one-of.test.ts diff --git a/test/optionalAsync.test.ts b/test/optional-async.test.ts similarity index 100% rename from test/optionalAsync.test.ts rename to test/optional-async.test.ts diff --git a/test/shapeAsync.test.ts b/test/shape-async.test.ts similarity index 100% rename from test/shapeAsync.test.ts rename to test/shape-async.test.ts diff --git a/test/testAsync.test.ts b/test/test-async.test.ts similarity index 100% rename from test/testAsync.test.ts rename to test/test-async.test.ts diff --git a/test/toBoolean.test.ts b/test/to-boolean.test.ts similarity index 100% rename from test/toBoolean.test.ts rename to test/to-boolean.test.ts diff --git a/test/toDate.test.ts b/test/to-date.test.ts similarity index 100% rename from test/toDate.test.ts rename to test/to-date.test.ts diff --git a/test/toLower.test.ts b/test/to-lower.test.ts similarity index 100% rename from test/toLower.test.ts rename to test/to-lower.test.ts diff --git a/test/toNumber.test.ts b/test/to-number.test.ts similarity index 100% rename from test/toNumber.test.ts rename to test/to-number.test.ts diff --git a/test/toString.test.ts b/test/to-string.test.ts similarity index 100% rename from test/toString.test.ts rename to test/to-string.test.ts diff --git a/test/toUpper.test.ts b/test/to-upper.test.ts similarity index 100% rename from test/toUpper.test.ts rename to test/to-upper.test.ts diff --git a/test/transformAsync.test.ts b/test/transform-async.test.ts similarity index 100% rename from test/transformAsync.test.ts rename to test/transform-async.test.ts diff --git a/test/tupleAsync.test.ts b/test/tuple-async.test.ts similarity index 100% rename from test/tupleAsync.test.ts rename to test/tuple-async.test.ts From 6720df8e19e8d9c390b12e91bfc5f24e932829ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Guyon?= Date: Tue, 18 Aug 2020 22:47:05 +0200 Subject: [PATCH 36/36] Make asynchronicity of function arguments optional --- src/not-async.ts | 7 ++++--- src/test-async.ts | 6 +++--- src/transform-async.ts | 2 +- test/not-async.test.ts | 10 ++++++++++ test/test-async.test.ts | 10 ++++++++++ test/transform-async.test.ts | 10 ++++++++++ 6 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/not-async.ts b/src/not-async.ts index 854a6e0..872122c 100644 --- a/src/not-async.ts +++ b/src/not-async.ts @@ -1,13 +1,14 @@ import AsyncCheck from "./async-check"; +import MaybeAsyncCheck from "./maybe-async-check"; import ok from "./ok"; import err from "./error"; export default function notAsync( - check: AsyncCheck, + check: MaybeAsyncCheck, error?: string, ): AsyncCheck; export default function notAsync( - check: AsyncCheck, + check: MaybeAsyncCheck, error: string, path: unknown[], getInvalidValue: (value: V, ...args: A) => unknown, @@ -33,7 +34,7 @@ export default function notAsync( * @returns An async check function. */ export default function notAsync( - check: AsyncCheck, + check: MaybeAsyncCheck, error: string = "is invalid", path: unknown[] = [], getInvalidValue?: (value: V, ...args: A) => unknown, diff --git a/src/test-async.ts b/src/test-async.ts index 00c1ab7..c70914e 100644 --- a/src/test-async.ts +++ b/src/test-async.ts @@ -3,11 +3,11 @@ import ok from "./ok"; import err from "./error"; export default function testAsync( - predicate: (value: V, ...args: A) => Promise, + predicate: (value: V, ...args: A) => Promise | boolean, error?: string, ): AsyncCheck; export default function testAsync( - predicate: (value: V, ...args: A) => Promise, + predicate: (value: V, ...args: A) => Promise | boolean, error: string, path: unknown[], getInvalidValue: (value: V, ...args: A) => unknown, @@ -34,7 +34,7 @@ export default function testAsync( * @returns An async check function. */ export default function testAsync( - predicate: (value: V, ...args: A) => Promise, + predicate: (value: V, ...args: A) => Promise | boolean, error: string = "is invalid", path: unknown[] = [], getInvalidValue?: (value: V, ...args: A) => unknown, diff --git a/src/transform-async.ts b/src/transform-async.ts index 8f52da2..36d42f4 100644 --- a/src/transform-async.ts +++ b/src/transform-async.ts @@ -18,7 +18,7 @@ import ok from "./ok"; * @returns An async check function. */ export default function transformAsync( - trans: (value: I, ...args: A) => Promise, + trans: (value: I, ...args: A) => Promise | O, ): AsyncCheck { return async (value: I, ...args: A) => { const nextValue = await trans(value, ...args); diff --git a/test/not-async.test.ts b/test/not-async.test.ts index d4d1082..e4ce034 100644 --- a/test/not-async.test.ts +++ b/test/not-async.test.ts @@ -98,3 +98,13 @@ test("given function to get the invalid value is called with the additional argu expect(getInvalidValue).toHaveBeenCalledTimes(1); expect(getInvalidValue).toHaveBeenCalledWith(expect.anything(), "one", "two"); }); + +test("synchronous child check are handled", async () => { + const checkValue = check.notAsync(() => check.error("jerome", "is wrong")); + const result = await checkValue(42); + + expect(result).toEqual({ + isOk: true, + value: 42, + }); +}); diff --git a/test/test-async.test.ts b/test/test-async.test.ts index 16779eb..7865fa2 100644 --- a/test/test-async.test.ts +++ b/test/test-async.test.ts @@ -96,3 +96,13 @@ test("given function to get the invalid value is called with the additional argu expect(getInvalidValue).toHaveBeenCalledTimes(1); expect(getInvalidValue).toHaveBeenCalledWith(expect.anything(), "one", "two"); }); + +test("synchronous predicates are handled", async () => { + const checkValue = check.testAsync(() => true); + const result = await checkValue(42); + + expect(result).toEqual({ + isOk: true, + value: 42, + }); +}); diff --git a/test/transform-async.test.ts b/test/transform-async.test.ts index 98580a5..92e5a6e 100644 --- a/test/transform-async.test.ts +++ b/test/transform-async.test.ts @@ -29,3 +29,13 @@ test("given transform function is called with the additional arguments", async ( expect(transform).toHaveBeenCalledTimes(1); expect(transform).toHaveBeenCalledWith(expect.anything(), "one", "two"); }); + +test("synchronous transforms are handled", async () => { + const checkValue = check.transformAsync(() => "jerome"); + const result = await checkValue(42); + + expect(result).toEqual({ + isOk: true, + value: "jerome", + }); +});