From 8c2d91576237ebc44cee42e21d4d2b8c2946ffe9 Mon Sep 17 00:00:00 2001 From: Milly Date: Thu, 8 Aug 2024 20:33:41 +0900 Subject: [PATCH 1/3] WIP: implement symbol properties From 2e6d88050b1d303f7dedef6cdd58e36d9bb4bacf Mon Sep 17 00:00:00 2001 From: Milly Date: Thu, 8 Aug 2024 19:46:07 +0900 Subject: [PATCH 2/3] feat[isObjectOf]: discover symbol properties in `predObj` Ref #94, #95 --- is/__snapshots__/object_of_test.ts.snap | 16 ++++ is/object_of.ts | 5 +- is/object_of_test.ts | 116 ++++++++++++++++++++++++ 3 files changed, 136 insertions(+), 1 deletion(-) diff --git a/is/__snapshots__/object_of_test.ts.snap b/is/__snapshots__/object_of_test.ts.snap index 7524ffc..6f49853 100644 --- a/is/__snapshots__/object_of_test.ts.snap +++ b/is/__snapshots__/object_of_test.ts.snap @@ -17,3 +17,19 @@ snapshot[`isObjectOf > returns properly named predicate function 3`] = ` }) })" `; + +snapshot[`isObjectOf > with symbol properties > returns properly named predicate function 1`] = ` +"isObjectOf({ + a: isNumber, + b: isString, + Symbol(s): isBoolean +})" +`; + +snapshot[`isObjectOf > with symbol properties > returns properly named predicate function 2`] = ` +"isObjectOf({ + Symbol(a): isObjectOf({ + Symbol(b): isObjectOf({Symbol(c): isBoolean}) + }) +})" +`; diff --git a/is/object_of.ts b/is/object_of.ts index 16defd6..91c0122 100644 --- a/is/object_of.ts +++ b/is/object_of.ts @@ -48,7 +48,10 @@ export function isObjectOf< Array.isArray(x) ) return false; // Check each values - return Object.keys(predObj).every((k) => predObj[k]((x as T)[k])); + return [ + ...Object.keys(predObj), + ...Object.getOwnPropertySymbols(predObj), + ].every((k) => predObj[k]((x as T)[k])); }, "isObjectOf", predObj, diff --git a/is/object_of_test.ts b/is/object_of_test.ts index 4aadc5c..514ef4b 100644 --- a/is/object_of_test.ts +++ b/is/object_of_test.ts @@ -152,4 +152,120 @@ Deno.test("isObjectOf", async (t) => { }); }, ); + + await t.step("with symbol properties", async (t) => { + const s = Symbol("s"); + const predObj = { + a: is.Number, + b: is.String, + [s]: is.Boolean, + }; + + await t.step("returns properly named predicate function", async (t) => { + await assertSnapshot(t, isObjectOf(predObj).name); + await assertSnapshot( + t, + isObjectOf({ + [Symbol("a")]: isObjectOf({ + [Symbol("b")]: isObjectOf({ [Symbol("c")]: is.Boolean }), + }), + }).name, + ); + }); + + await t.step("returns true on T object", () => { + assertEquals(isObjectOf(predObj)({ a: 0, b: "a", [s]: true }), true); + assertEquals( + isObjectOf(predObj)({ a: 0, b: "a", [s]: true, d: "ignored" }), + true, + "Undefined properties are ignored", + ); + assertEquals( + isObjectOf(predObj)({ + a: 0, + b: "a", + [s]: true, + [Symbol("t")]: "ignored", + }), + true, + "Undefined symbol properties are ignored", + ); + assertEquals( + isObjectOf(predObj)( + Object.assign(() => void 0, { a: 0, b: "a", [s]: true }), + ), + true, + "Function are treated as an object", + ); + }); + + await t.step("returns false on non T object", () => { + assertEquals(isObjectOf(predObj)("a"), false, "Value is not an object"); + assertEquals( + isObjectOf(predObj)({ a: 0, b: "a", [s]: "" }), + false, + "Object have a different type symbol property", + ); + assertEquals( + isObjectOf(predObj)({ a: 0, b: "a" }), + false, + "Object does not have symbol property", + ); + const arrayWithSymbolProp = ["ignored"]; + // deno-lint-ignore no-explicit-any + (arrayWithSymbolProp as any)[s] = true; + assertEquals( + isObjectOf({ [s]: is.Boolean })(arrayWithSymbolProp), + false, + "Value is not an object", + ); + }); + + await t.step("predicated type is correct", () => { + const a = Symbol("a"); + const b = Symbol("b"); + const c = Symbol("c"); + const d = Symbol("d"); + const e = Symbol("e"); + const f = Symbol("f"); + const predObj2 = { + [a]: as.Readonly(as.Optional(is.String)), + [b]: as.Optional(as.Readonly(is.String)), + [c]: as.Readonly(is.String), + [d]: as.Optional(is.String), + [e]: as.Unreadonly(as.Unoptional(as.Readonly(as.Optional(is.String)))), + [f]: as.Unoptional(as.Unreadonly(as.Optional(as.Readonly(is.String)))), + }; + const x: unknown = {}; + + if (isObjectOf(predObj)(x)) { + assertType< + Equal< + typeof x, + { + a: number; + b: string; + [s]: boolean; + } + > + >(true); + } + + if (isObjectOf(predObj2)(x)) { + assertType< + Equal< + typeof x, + { + readonly [a]?: string; + readonly [b]?: string; + readonly [c]: string; + [d]?: string; + [e]: string; + [f]: string; + } + > + >(true); + } + }); + }); }); From 38f2881f5824a4b3c96f4bf69740b2565a64e279 Mon Sep 17 00:00:00 2001 From: Milly Date: Fri, 9 Aug 2024 02:43:01 +0900 Subject: [PATCH 3/3] test[isIntersectionOf]: discover symbol properties in annotated `predObj` --- is/__snapshots__/intersection_of_test.ts.snap | 16 ++++ is/intersection_of_test.ts | 75 +++++++++++++++++++ 2 files changed, 91 insertions(+) diff --git a/is/__snapshots__/intersection_of_test.ts.snap b/is/__snapshots__/intersection_of_test.ts.snap index 56278e4..6e36920 100644 --- a/is/__snapshots__/intersection_of_test.ts.snap +++ b/is/__snapshots__/intersection_of_test.ts.snap @@ -15,3 +15,19 @@ snapshot[`isIntersectionOf > returns properly named predicate function 3`] = isObjectOf({b: isString}) ])" `; + +snapshot[`isIntersectionOf > with symbol properties > returns properly named predicate function 1`] = `"isString"`; + +snapshot[`isIntersectionOf > with symbol properties > returns properly named predicate function 2`] = ` +"isObjectOf({ + a: isNumber, + Symbol(b): isString +})" +`; + +snapshot[`isIntersectionOf > with symbol properties > returns properly named predicate function 3`] = ` +"isIntersectionOf([ + isFunction, + isObjectOf({Symbol(b): isString}) +])" +`; diff --git a/is/intersection_of_test.ts b/is/intersection_of_test.ts index a22c4f7..c0f474b 100644 --- a/is/intersection_of_test.ts +++ b/is/intersection_of_test.ts @@ -77,4 +77,79 @@ Deno.test("isIntersectionOf", async (t) => { >(true); } }); + + await t.step("with symbol properties", async (t) => { + const b = Symbol("b"); + const objPreds = [ + is.ObjectOf({ a: is.Number }), + is.ObjectOf({ [b]: is.String }), + ] as const; + const mixPreds = [ + is.Function, + is.ObjectOf({ [b]: is.String }), + ] as const; + + await t.step("returns properly named predicate function", async (t) => { + assertEquals(typeof isIntersectionOf([is.String]), "function"); + await assertSnapshot(t, isIntersectionOf([is.String]).name); + await assertSnapshot(t, isIntersectionOf(objPreds).name); + await assertSnapshot(t, isIntersectionOf(mixPreds).name); + }); + + await t.step("returns true on all of T", () => { + const f = Object.assign(() => void 0, { [b]: "a" }); + assertEquals(isIntersectionOf([is.String])("a"), true); + assertEquals(isIntersectionOf(objPreds)({ a: 0, [b]: "a" }), true); + assertEquals(isIntersectionOf(mixPreds)(f), true); + }); + + await t.step("returns false on non of T", () => { + const f = Object.assign(() => void 0, { [b]: "a" }); + assertEquals(isIntersectionOf(objPreds)("a"), false); + assertEquals(isIntersectionOf(mixPreds)({ a: 0, [b]: "a" }), false); + assertEquals(isIntersectionOf([is.String])(f), false); + }); + + await t.step("predicated type is correct", () => { + const a: unknown = undefined; + + if (isIntersectionOf([is.String])(a)) { + assertType>(true); + } + + if (isIntersectionOf(objPreds)(a)) { + assertType>(true); + } + + if (isIntersectionOf(mixPreds)(a)) { + assertType< + Equal< + typeof a, + & ((...args: unknown[]) => unknown) + & { [b]: string } + > + >(true); + } + }); + + await t.step("predicated type is correct (#68)", () => { + const a: unknown = undefined; + const pred = isIntersectionOf([ + is.ObjectOf({ id: is.String }), + is.UnionOf([ + is.ObjectOf({ result: is.String }), + is.ObjectOf({ error: is.String }), + ]), + ]); + + if (pred(a)) { + assertType< + Equal< + typeof a, + { id: string } & ({ result: string } | { error: string }) + > + >(true); + } + }); + }); });