From 170ff131e5f292cbe41aca8b13d55a38ee80d678 Mon Sep 17 00:00:00 2001 From: Milly Date: Fri, 9 Aug 2024 15:56:17 +0900 Subject: [PATCH 1/2] fix[isRecordOf]: checks only own properties --- is/record_of.ts | 3 ++- is/record_of_test.ts | 53 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/is/record_of.ts b/is/record_of.ts index 4676f71..60423ce 100644 --- a/is/record_of.ts +++ b/is/record_of.ts @@ -39,7 +39,8 @@ export function isRecordOf( return rewriteName( (x: unknown): x is Record => { if (!isRecord(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_of_test.ts b/is/record_of_test.ts index 91cf7d6..8093ec0 100644 --- a/is/record_of_test.ts +++ b/is/record_of_test.ts @@ -29,6 +29,29 @@ Deno.test("isRecordOf", async (t) => { assertType>>(true); } }); + + await t.step("checks only object's own properties", async (t) => { + await t.step("returns true on T record", () => { + assertEquals( + isRecordOf(is.Number)( + Object.assign(Object.create({ p: "ignore" }), { a: 0 }), + ), + true, + ); + assertEquals( + isRecordOf(is.String)( + Object.assign(Object.create({ p: 0 /* ignore */ }), { a: "a" }), + ), + true, + ); + assertEquals( + isRecordOf(is.Boolean)( + Object.assign(Object.create({ p: "ignore" }), { a: true }), + ), + true, + ); + }); + }); }); Deno.test("isRecordOf", async (t) => { @@ -64,4 +87,34 @@ Deno.test("isRecordOf", async (t) => { assertType>>(true); } }); + + await t.step("checks only object's own properties", async (t) => { + await t.step("returns true on T record", () => { + assertEquals( + isRecordOf(is.Number, is.String)( + Object.assign(Object.create({ p: "ignore" }), { a: 0 }), + ), + true, + ); + assertEquals( + isRecordOf(is.String, is.String)( + Object.assign(Object.create({ p: 0 /* ignore */ }), { a: "a" }), + ), + true, + ); + assertEquals( + isRecordOf(is.Boolean, is.String)( + Object.assign(Object.create({ p: "ignore" }), { a: true }), + ), + true, + ); + assertEquals( + isRecordOf(is.String, is.Number)( + Object.assign(Object.create({ p: "ignore" }), {/* empty */}), + ), + true, + "No own properties", + ); + }); + }); }); From b9074739079e92b67fae9026a0fc346bd9041ba9 Mon Sep 17 00:00:00 2001 From: Milly Date: Fri, 9 Aug 2024 03:59:57 +0900 Subject: [PATCH 2/2] feat[isRecordOf]: checks own symbol key properties --- is/__snapshots__/record_of_test.ts.snap | 4 ++ is/record_of.ts | 5 ++- is/record_of_test.ts | 59 +++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 1 deletion(-) diff --git a/is/__snapshots__/record_of_test.ts.snap b/is/__snapshots__/record_of_test.ts.snap index aa1da92..0874713 100644 --- a/is/__snapshots__/record_of_test.ts.snap +++ b/is/__snapshots__/record_of_test.ts.snap @@ -7,3 +7,7 @@ snapshot[`isRecordOf > returns properly named predicate function 2`] = `"isRe snapshot[`isRecordOf > returns properly named predicate function 1`] = `"isRecordOf(isNumber, isString)"`; snapshot[`isRecordOf > returns properly named predicate function 2`] = `"isRecordOf((anonymous), isString)"`; + +snapshot[`isRecordOf > with symbol properties > returns properly named predicate function 1`] = `"isRecordOf(isNumber, isSymbol)"`; + +snapshot[`isRecordOf > with symbol properties > returns properly named predicate function 2`] = `"isRecordOf((anonymous), isSymbol)"`; diff --git a/is/record_of.ts b/is/record_of.ts index 60423ce..1d96cd2 100644 --- a/is/record_of.ts +++ b/is/record_of.ts @@ -39,7 +39,10 @@ export function isRecordOf( return rewriteName( (x: unknown): x is Record => { if (!isRecord(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_of_test.ts b/is/record_of_test.ts index 8093ec0..a91fc33 100644 --- a/is/record_of_test.ts +++ b/is/record_of_test.ts @@ -52,6 +52,21 @@ Deno.test("isRecordOf", async (t) => { ); }); }); + + await t.step("with symbol properties", async (t) => { + const a = Symbol("a"); + await t.step("returns true on T record", () => { + assertEquals(isRecordOf(is.Number)({ [a]: 0 }), true); + assertEquals(isRecordOf(is.String)({ [a]: "a" }), true); + assertEquals(isRecordOf(is.Boolean)({ [a]: true }), true); + }); + + await t.step("returns false on non T record", () => { + assertEquals(isRecordOf(is.String)({ [a]: 0 }), false); + assertEquals(isRecordOf(is.Number)({ [a]: "a" }), false); + assertEquals(isRecordOf(is.String)({ [a]: true }), false); + }); + }); }); Deno.test("isRecordOf", async (t) => { @@ -89,6 +104,7 @@ Deno.test("isRecordOf", 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( isRecordOf(is.Number, is.String)( @@ -115,6 +131,49 @@ Deno.test("isRecordOf", async (t) => { true, "No own properties", ); + assertEquals( + isRecordOf(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, isRecordOf(is.Number, is.Symbol).name); + await assertSnapshot( + t, + isRecordOf((_x): _x is string => false, is.Symbol).name, + ); + }); + + await t.step("returns true on T record", () => { + assertEquals(isRecordOf(is.Number, is.Symbol)({ [a]: 0 }), true); + assertEquals(isRecordOf(is.String, is.Symbol)({ [a]: "a" }), true); + assertEquals(isRecordOf(is.Boolean, is.Symbol)({ [a]: true }), true); + }); + + await t.step("returns false on non T record", () => { + assertEquals(isRecordOf(is.String, is.Symbol)({ [a]: 0 }), false); + assertEquals(isRecordOf(is.Number, is.Symbol)({ [a]: "a" }), false); + assertEquals(isRecordOf(is.String, is.Symbol)({ [a]: true }), false); + }); + + await t.step("returns false on non K record", () => { + assertEquals(isRecordOf(is.Number, is.String)({ [a]: 0 }), false); + assertEquals(isRecordOf(is.String, is.String)({ [a]: "a" }), false); + assertEquals(isRecordOf(is.Boolean, is.String)({ [a]: true }), false); + }); + + await t.step("predicated type is correct", () => { + const a: unknown = { a: 0 }; + if (isRecordOf(is.Number, is.Symbol)(a)) { + assertType>>(true); + } }); }); });