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 33e9508be7678a6299c01a8af5e548f3b03d9395 Mon Sep 17 00:00:00 2001 From: Milly Date: Fri, 9 Aug 2024 02:34:13 +0900 Subject: [PATCH 3/3] feat[isPartialOf]: discover symbol properties in `pred.predObj` --- is/__snapshots__/partial_of_test.ts.snap | 24 +++++++++++ is/partial_of.ts | 11 +++-- is/partial_of_test.ts | 51 ++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 3 deletions(-) diff --git a/is/__snapshots__/partial_of_test.ts.snap b/is/__snapshots__/partial_of_test.ts.snap index 6f96fc2..7e9cb3f 100644 --- a/is/__snapshots__/partial_of_test.ts.snap +++ b/is/__snapshots__/partial_of_test.ts.snap @@ -23,3 +23,27 @@ snapshot[`isPartialOf > returns properly named predicate function 2`] = ` d: asOptional(asReadonly(isString)) })" `; + +snapshot[`isPartialOf > with symbol properties > returns properly named predicate function 1`] = ` +"isObjectOf({ + a: asOptional(isNumber), + Symbol(b): asOptional(isUnionOf([ + isString, + isUndefined + ])), + Symbol(c): asOptional(isBoolean), + Symbol(c): asOptional(asReadonly(isString)) +})" +`; + +snapshot[`isPartialOf > with symbol properties > returns properly named predicate function 2`] = ` +"isObjectOf({ + a: asOptional(isNumber), + Symbol(b): asOptional(isUnionOf([ + isString, + isUndefined + ])), + Symbol(c): asOptional(isBoolean), + Symbol(c): asOptional(asReadonly(isString)) +})" +`; diff --git a/is/partial_of.ts b/is/partial_of.ts index 440db32..56e1395 100644 --- a/is/partial_of.ts +++ b/is/partial_of.ts @@ -42,9 +42,14 @@ export function isPartialOf< ): & Predicate>> & IsPredObj

{ - const predObj = Object.fromEntries( - Object.entries(pred.predObj).map(([k, v]) => [k, asOptional(v)]), - ) as Record>; + const keys = [ + ...Object.keys(pred.predObj), + ...Object.getOwnPropertySymbols(pred.predObj), + ]; + const predObj: Record> = { ...pred.predObj }; + for (const key of keys) { + predObj[key] = asOptional(predObj[key]); + } return isObjectOf(predObj) as & Predicate>> & IsPredObj

; diff --git a/is/partial_of_test.ts b/is/partial_of_test.ts index de0a584..e0433a9 100644 --- a/is/partial_of_test.ts +++ b/is/partial_of_test.ts @@ -47,4 +47,55 @@ Deno.test("isPartialOf", async (t) => { >(true); } }); + + await t.step("with symbol properties", async (t) => { + const b = Symbol("b"); + const c = Symbol("c"); + const d = Symbol("c"); + const pred = is.ObjectOf({ + a: is.Number, + [b]: is.UnionOf([is.String, is.Undefined]), + [c]: as.Optional(is.Boolean), + [d]: as.Readonly(is.String), + }); + + await t.step("returns properly named predicate function", async (t) => { + await assertSnapshot(t, isPartialOf(pred).name); + await assertSnapshot(t, isPartialOf(isPartialOf(pred)).name); + }); + + await t.step("returns true on Partial object", () => { + assertEquals( + isPartialOf(pred)({ a: undefined, [b]: undefined, [c]: undefined }), + true, + ); + assertEquals(isPartialOf(pred)({}), true); + }); + + await t.step("returns false on non Partial object", () => { + assertEquals(isPartialOf(pred)("a"), false, "Value is not an object"); + assertEquals( + isPartialOf(pred)({ a: 0, [b]: "a", [c]: "" }), + false, + "Object have a different type property", + ); + }); + + await t.step("predicated type is correct", () => { + const a: unknown = { a: 0, [b]: "a", [c]: true }; + if (isPartialOf(pred)(a)) { + assertType< + Equal< + typeof a, + Partial<{ + a: number; + [b]: string; + [c]: boolean; + readonly [d]: string; + }> + > + >(true); + } + }); + }); });