Skip to content

Commit

Permalink
feat[isObjectOf]: discover symbol properties in predObj
Browse files Browse the repository at this point in the history
Ref #94, #95
  • Loading branch information
Milly committed Aug 8, 2024
1 parent 0a12716 commit 2e6d880
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 1 deletion.
16 changes: 16 additions & 0 deletions is/__snapshots__/object_of_test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,19 @@ snapshot[`isObjectOf<T> > returns properly named predicate function 3`] = `
})
})"
`;
snapshot[`isObjectOf<T> > with symbol properties > returns properly named predicate function 1`] = `
"isObjectOf({
a: isNumber,
b: isString,
Symbol(s): isBoolean
})"
`;
snapshot[`isObjectOf<T> > with symbol properties > returns properly named predicate function 2`] = `
"isObjectOf({
Symbol(a): isObjectOf({
Symbol(b): isObjectOf({Symbol(c): isBoolean})
})
})"
`;
5 changes: 4 additions & 1 deletion is/object_of.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
116 changes: 116 additions & 0 deletions is/object_of_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,4 +152,120 @@ Deno.test("isObjectOf<T>", 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);
}
});
});
});

0 comments on commit 2e6d880

Please sign in to comment.