From 2e6d88050b1d303f7dedef6cdd58e36d9bb4bacf Mon Sep 17 00:00:00 2001 From: Milly Date: Thu, 8 Aug 2024 19:46:07 +0900 Subject: [PATCH] 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); + } + }); + }); });