From 8c1d66cff32c41a4135a75b82891d39534a2e0e7 Mon Sep 17 00:00:00 2001 From: Milly Date: Fri, 9 Aug 2024 15:54:54 +0900 Subject: [PATCH 1/2] fix[isRecordObjectOf]: checks only own properties --- is/record_object_of.ts | 3 ++- is/record_object_of_test.ts | 53 +++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/is/record_object_of.ts b/is/record_object_of.ts index dbd05f8..13a7465 100644 --- a/is/record_object_of.ts +++ b/is/record_object_of.ts @@ -39,7 +39,8 @@ export function isRecordObjectOf( return rewriteName( (x: unknown): x is Record => { if (!isRecordObject(x)) return false; - for (const k in x) { + const keys = Object.keys(x); + for (const k of keys) { if (!pred(x[k])) return false; if (predKey && !predKey(k)) return false; } diff --git a/is/record_object_of_test.ts b/is/record_object_of_test.ts index 596ea05..48675fe 100644 --- a/is/record_object_of_test.ts +++ b/is/record_object_of_test.ts @@ -29,6 +29,29 @@ Deno.test("isRecordObjectOf", async (t) => { assertType>>(true); } }); + + await t.step("checks only object's own properties", async (t) => { + await t.step("returns true on T record", () => { + assertEquals( + isRecordObjectOf(is.Number)( + Object.assign(Object.create({ p: "ignore" }), { a: 0 }), + ), + true, + ); + assertEquals( + isRecordObjectOf(is.String)( + Object.assign(Object.create({ p: 0 /* ignore */ }), { a: "a" }), + ), + true, + ); + assertEquals( + isRecordObjectOf(is.Boolean)( + Object.assign(Object.create({ p: "ignore" }), { a: true }), + ), + true, + ); + }); + }); }); Deno.test("isRecordObjectOf", async (t) => { @@ -64,4 +87,34 @@ Deno.test("isRecordObjectOf", async (t) => { assertType>>(true); } }); + + await t.step("checks only object's own properties", async (t) => { + await t.step("returns true on T record", () => { + assertEquals( + isRecordObjectOf(is.Number, is.String)( + Object.assign(Object.create({ p: "ignore" }), { a: 0 }), + ), + true, + ); + assertEquals( + isRecordObjectOf(is.String, is.String)( + Object.assign(Object.create({ p: 0 /* ignore */ }), { a: "a" }), + ), + true, + ); + assertEquals( + isRecordObjectOf(is.Boolean, is.String)( + Object.assign(Object.create({ p: "ignore" }), { a: true }), + ), + true, + ); + assertEquals( + isRecordObjectOf(is.String, is.Number)( + Object.assign(Object.create({ p: "ignore" }), {/* empty */}), + ), + true, + "No own properties", + ); + }); + }); }); From 27f86b1496f8298ee8503d7e22e5196dd71a19bd Mon Sep 17 00:00:00 2001 From: Milly Date: Fri, 9 Aug 2024 16:27:45 +0900 Subject: [PATCH 2/2] feat[isRecordObjectOf]: checks own symbol key properties --- .../record_object_of_test.ts.snap | 4 ++ is/record_object_of.ts | 5 +- is/record_object_of_test.ts | 68 +++++++++++++++++++ 3 files changed, 76 insertions(+), 1 deletion(-) diff --git a/is/__snapshots__/record_object_of_test.ts.snap b/is/__snapshots__/record_object_of_test.ts.snap index 1254e9f..b78c694 100644 --- a/is/__snapshots__/record_object_of_test.ts.snap +++ b/is/__snapshots__/record_object_of_test.ts.snap @@ -7,3 +7,7 @@ snapshot[`isRecordObjectOf > returns properly named predicate function 2`] = snapshot[`isRecordObjectOf > returns properly named predicate function 1`] = `"isRecordObjectOf(isNumber, isString)"`; snapshot[`isRecordObjectOf > returns properly named predicate function 2`] = `"isRecordObjectOf((anonymous), isString)"`; + +snapshot[`isRecordObjectOf > with symbol properties > returns properly named predicate function 1`] = `"isRecordObjectOf(isNumber, isSymbol)"`; + +snapshot[`isRecordObjectOf > with symbol properties > returns properly named predicate function 2`] = `"isRecordObjectOf((anonymous), isSymbol)"`; diff --git a/is/record_object_of.ts b/is/record_object_of.ts index 13a7465..46d22a2 100644 --- a/is/record_object_of.ts +++ b/is/record_object_of.ts @@ -39,7 +39,10 @@ export function isRecordObjectOf( return rewriteName( (x: unknown): x is Record => { if (!isRecordObject(x)) return false; - const keys = Object.keys(x); + const keys = [ + ...Object.keys(x), + ...Object.getOwnPropertySymbols(x), + ]; for (const k of keys) { if (!pred(x[k])) return false; if (predKey && !predKey(k)) return false; diff --git a/is/record_object_of_test.ts b/is/record_object_of_test.ts index 48675fe..7ece23f 100644 --- a/is/record_object_of_test.ts +++ b/is/record_object_of_test.ts @@ -52,6 +52,21 @@ Deno.test("isRecordObjectOf", async (t) => { ); }); }); + + await t.step("with symbol properties", async (t) => { + const a = Symbol("a"); + await t.step("returns true on T record", () => { + assertEquals(isRecordObjectOf(is.Number)({ [a]: 0 }), true); + assertEquals(isRecordObjectOf(is.String)({ [a]: "a" }), true); + assertEquals(isRecordObjectOf(is.Boolean)({ [a]: true }), true); + }); + + await t.step("returns false on non T record", () => { + assertEquals(isRecordObjectOf(is.String)({ [a]: 0 }), false); + assertEquals(isRecordObjectOf(is.Number)({ [a]: "a" }), false); + assertEquals(isRecordObjectOf(is.String)({ [a]: true }), false); + }); + }); }); Deno.test("isRecordObjectOf", async (t) => { @@ -89,6 +104,7 @@ Deno.test("isRecordObjectOf", async (t) => { }); await t.step("checks only object's own properties", async (t) => { + const s = Symbol("s"); await t.step("returns true on T record", () => { assertEquals( isRecordObjectOf(is.Number, is.String)( @@ -115,6 +131,58 @@ Deno.test("isRecordObjectOf", async (t) => { true, "No own properties", ); + assertEquals( + isRecordObjectOf(is.String, is.String)( + Object.assign(Object.create({ [s]: "ignore" }), {/* empty */}), + ), + true, + "No own properties", + ); + }); + }); + + await t.step("with symbol properties", async (t) => { + const a = Symbol("a"); + await t.step("returns properly named predicate function", async (t) => { + await assertSnapshot(t, isRecordObjectOf(is.Number, is.Symbol).name); + await assertSnapshot( + t, + isRecordObjectOf((_x): _x is string => false, is.Symbol).name, + ); + }); + + await t.step("returns true on T record", () => { + assertEquals(isRecordObjectOf(is.Number, is.Symbol)({ [a]: 0 }), true); + assertEquals(isRecordObjectOf(is.String, is.Symbol)({ [a]: "a" }), true); + assertEquals( + isRecordObjectOf(is.Boolean, is.Symbol)({ [a]: true }), + true, + ); + }); + + await t.step("returns false on non T record", () => { + assertEquals(isRecordObjectOf(is.String, is.Symbol)({ [a]: 0 }), false); + assertEquals(isRecordObjectOf(is.Number, is.Symbol)({ [a]: "a" }), false); + assertEquals( + isRecordObjectOf(is.String, is.Symbol)({ [a]: true }), + false, + ); + }); + + await t.step("returns false on non K record", () => { + assertEquals(isRecordObjectOf(is.Number, is.String)({ [a]: 0 }), false); + assertEquals(isRecordObjectOf(is.String, is.String)({ [a]: "a" }), false); + assertEquals( + isRecordObjectOf(is.Boolean, is.String)({ [a]: true }), + false, + ); + }); + + await t.step("predicated type is correct", () => { + const a: unknown = { a: 0 }; + if (isRecordObjectOf(is.Number, is.Symbol)(a)) { + assertType>>(true); + } }); }); });