Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
lambdalisue committed Aug 13, 2024
1 parent 71d52ab commit ed297c7
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 8 deletions.
29 changes: 23 additions & 6 deletions assert.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Predicate } from "./type.ts";
import type { Notifier, Predicate } from "./type.ts";

/**
* A factory function that generates assertion error messages.
Expand All @@ -7,6 +7,7 @@ export type AssertMessageFactory = (
x: unknown,
pred: Predicate<unknown>,
name?: string,
keys?: PropertyKey[],
) => string;

/**
Expand All @@ -20,9 +21,8 @@ export const defaultAssertMessageFactory: AssertMessageFactory = (
const p = pred.name || "anonymous predicate";
const t = typeof x;
const v = JSON.stringify(x, null, 2);
return `Expected ${
name ?? "a value"
} that satisfies the predicate ${p}, got ${t}: ${v}`;
const n = name ?? "a value";
return `Expected ${n} that satisfies the predicate ${p}, got ${t}: ${v}`;
};

let assertMessageFactory = defaultAssertMessageFactory;
Expand Down Expand Up @@ -94,9 +94,26 @@ export function assert<T>(
pred: Predicate<T>,
options: { message?: string; name?: string } = {},
): asserts x is T {
if (!pred(x)) {
const ns: [
key: PropertyKey,
value: unknown,
pred: Predicate<unknown>,
][] = [];
const notifier: Notifier = (key, value, pred) => {
ns.unshift([key, value, pred]);
};
if (!pred(x, notifier)) {
const cause = ns.length === 0 ? undefined : {
path: ns.map(([k]) => k),
value: ns[0][1],
pred: ns[0][2],
};
throw new AssertError(
options.message ?? assertMessageFactory(x, pred, options.name),
options.message ?? assertMessageFactory(
x,
pred,
options.name,
),
);
}
}
16 changes: 16 additions & 0 deletions assert_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
defaultAssertMessageFactory,
setAssertMessageFactory,
} from "./assert.ts";
import { is } from "./is/mod.ts";

const x: unknown = Symbol("x");

Expand Down Expand Up @@ -61,6 +62,21 @@ Deno.test("assert", async (t) => {
);
},
);

await t.step("throws an `AssertError` on isObjectOf", () => {
const pred = is.ObjectOf({
a: is.ObjectOf({
b: is.ObjectOf({
c: is.String,
}),
}),
});
assertThrows(
() => assert({ a: { b: { c: 0 } } }, pred),
AssertError,
`Expected a value that satisfies the predicate falsePredicate, got symbol: undefined`,
);
});
});

Deno.test("setAssertMessageFactory", async (t) => {
Expand Down
63 changes: 63 additions & 0 deletions is/__snapshots__/object_of_test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,66 @@ snapshot[`isObjectOf<T> > with symbol properties > returns properly named predic
})
})"
`;
snapshot[`isObjectOf<T> > with 'notifier' argument > it is not invoked when x satisfies the predicate 1`] = `[]`;
snapshot[`isObjectOf<T> > with 'notifier' argument > it is invoked when x does not satisfies the predicate (c) 1`] = `
[
[
"c",
0,
[Function: isString],
],
[
"b",
{
c: 0,
},
[Function: isObjectOf({c: isString})],
],
[
"a",
{
b: {
c: 0,
},
},
[Function: isObjectOf({
b: isObjectOf({c: isString})
})],
],
]
`;
snapshot[`isObjectOf<T> > with 'notifier' argument > it is invoked when x does not satisfies the predicate (b) 1`] = `
[
[
"b",
0,
[Function: isObjectOf({c: isString})],
],
[
"a",
{
b: 0,
},
[Function: isObjectOf({
b: isObjectOf({c: isString})
})],
],
]
`;
snapshot[`isObjectOf<T> > with 'notifier' argument > it is invoked when x does not satisfies the predicate (a) 1`] = `
[
[
"a",
0,
[Function: isObjectOf({
b: isObjectOf({c: isString})
})],
],
]
`;
snapshot[`isObjectOf<T> > with 'notifier' argument > it is invoked when x does not satisfies the predicate (<root>) 1`] = `[]`;
9 changes: 7 additions & 2 deletions is/object_of.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,14 @@ export function isObjectOf<
...Object.getOwnPropertySymbols(predObj),
].map((k) => [k, predObj[k]]);
const pred = rewriteName(
(x): x is ObjectOf<T> => {
(x, notifier): x is ObjectOf<T> => {
if (!isRecordT<T>(x)) return false;
return preds.every(([k, pred]) => pred(x[k]));
return preds.every(([k, pred]) => {
const v = x[k];
if (pred(v, notifier)) return true;
notifier?.(k, v, pred);
return false;
});
},
"isObjectOf",
predObj,
Expand Down
71 changes: 71 additions & 0 deletions is/object_of_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { assertEquals } from "@std/assert";
import { assertSnapshot } from "@std/testing/snapshot";
import { assertType } from "@std/testing/types";
import type { Equal } from "../_testutil.ts";
import type { Notifier, Predicate } from "../type.ts";
import { is } from "./mod.ts";
import { as } from "../as/mod.ts";
import { isObjectOf } from "./object_of.ts";
Expand Down Expand Up @@ -268,4 +269,74 @@ Deno.test("isObjectOf<T>", async (t) => {
}
});
});

await t.step("with 'notifier' argument", async (t) => {
const pred = isObjectOf({
a: isObjectOf({
b: isObjectOf({
c: is.String,
}),
}),
});

await t.step(
"it is not invoked when x satisfies the predicate",
async (t) => {
const notified: [PropertyKey, unknown, Predicate<unknown>][] = [];
const notifier: Notifier = (key, value, pred) => {
notified.push([key, value, pred]);
};
assertEquals(pred({ a: { b: { c: "string" } } }, notifier), true);
await assertSnapshot(t, notified);
},
);

await t.step(
"it is not invoked when x does not satisfies the predicate (<root>)",
async (t) => {
const notified: [PropertyKey, unknown, Predicate<unknown>][] = [];
const notifier: Notifier = (key, value, pred) => {
notified.push([key, value, pred]);
};
assertEquals(pred(0, notifier), false);
await assertSnapshot(t, notified);
},
);

await t.step(
"it is invoked when x does not satisfies the predicate (a)",
async (t) => {
const notified: [PropertyKey, unknown, Predicate<unknown>][] = [];
const notifier: Notifier = (key, value, pred) => {
notified.push([key, value, pred]);
};
assertEquals(pred({ a: 0 }, notifier), false);
await assertSnapshot(t, notified);
},
);

await t.step(
"it is invoked when x does not satisfies the predicate (b)",
async (t) => {
const notified: [PropertyKey, unknown, Predicate<unknown>][] = [];
const notifier: Notifier = (key, value, pred) => {
notified.push([key, value, pred]);
};
assertEquals(pred({ a: { b: 0 } }, notifier), false);
await assertSnapshot(t, notified);
},
);

await t.step(
"it is invoked when x does not satisfies the predicate (c)",
async (t) => {
const notified: [PropertyKey, unknown, Predicate<unknown>][] = [];
const notifier: Notifier = (key, value, pred) => {
notified.push([key, value, pred]);
};
assertEquals(pred({ a: { b: { c: 0 } } }, notifier), false);
await assertSnapshot(t, notified);
},
);
});
});

0 comments on commit ed297c7

Please sign in to comment.