Constructs, checked
Each construct in its own describe, by its id, held to everything that tries it: its upstream
examples against their oracle, its own examples to their expected output, labelled with the
edge each tries, and the cases generated at its edges from the grammar. A Not supported row is
held the same way to its examples. A red run names the construct first, whatever caught it.
At its edges, a construct's generated cases and their one-character neighbours must each be
read as the grammar reads them, or be settled by a named side rule or a Not supported row, and
between them they must reach all three generated edges: a valid case markz reads, a boundary
neighbour it still reads, and a near miss it doesn't. The two edges the grammar can't write,
ambiguous and unclosed, are its own hand-written examples, and it has one of each or says why
it can't (EDGES).
import { describe, expect, it } from "vite-plus/test";
import { parse } from "../src/index";
import { EDGES, edges } from "./harness/cases";
import { check, examples, type Example } from "./harness/examples";
import { search } from "./harness/generate";
import { CONSTRUCTS } from "./harness/grammar";
import { rows } from "./harness/syntax";
import { expectTree } from "./harness/tree";
const { runs, seed } = search(8);
// A longer search needs longer than a minute: link and element cases are the slowest to judge.
const timeout = Math.max(60_000, runs * 1000);
const label = (e: Example) =>
e.source === "markz"
? [e.id, e.category, e.rule].filter(Boolean).join(" ")
: `${e.id} ${e.upstream} (${e.kind})`;
function examplesOf(section: string) {
const mine = examples.filter((e) => e.section === section);
it("has examples", () => {
expect(mine.length).toBeGreaterThan(0);
});
it.each(mine.map((e) => [label(e), e] as const))("%s", (_, e) => {
expectTree(parse(e.markdown));
const result = check(e);
expect(
result.problem,
result.problem ? `${result.markz}\n${result.oracle ?? e.html}` : "",
).toBe(null);
});
}
describe.each(CONSTRUCTS.map((c) => c.id))("%s", (id) => {
examplesOf(id);
it(
"is read as the grammar reads it at its edges, or a side rule says why not",
() => {
const reached = edges(id, runs, seed);
// The shortest cases show a cause most plainly, and the search settings replay them.
const shortest = [...reached.unsettled].sort((a, b) => a.length - b.length).slice(0, 10);
const replay = `SEARCH=${process.env["SEARCH"] ?? 1} SEED=${seed}`;
expect(
reached.unsettled.length,
`${reached.unsettled.length} unsettled (${replay}), the shortest:\n${shortest.join("\n")}`,
).toBe(0);
expect(reached.missed, "choices no valid case took").toEqual([]);
expect(reached.valid, "valid cases markz reads").toBeGreaterThan(0);
expect(reached.boundary, "boundary cases markz still reads").toBeGreaterThan(0);
expect(reached["near-miss"], "near misses markz doesn't read").toBeGreaterThan(0);
},
timeout,
);
it.each(["ambiguous", "unclosed"] as const)(
"has %s examples, or says why it has none",
(category) => {
const own = examples.filter((e) => e.section === id && e.category === category);
if (EDGES[id]?.[category]) expect(own, "gives a reason and has examples").toEqual([]);
else expect(own.length).toBeGreaterThan(0);
},
);
});
describe.each(rows.map((r) => r.code))("not supported: %s", (code) => {
examplesOf(code);
});