inline.test.ts
The inline pass on what an example's HTML can't show: node data (math and expression values,
link expressions, text values), exact ranges, and heading ids. What the inline constructs write
is in test/examples/markz/. Every parsed document is also held to the tree invariants.
import { describe, expect, it } from "vite-plus/test";
import { html, parse, type Document, type NodeId, type NodeType } from "./index";
import { expectTree } from "../test/harness/tree";
function parsed(source: string): Document {
const doc = parse(source);
expectTree(doc);
return doc;
}
const messages = (source: string) => parsed(source).warnings.map((d) => d.message);
function first(doc: Document, type: NodeType): NodeId {
for (let n = 0; n < doc.size; n++) if (doc.type(n) === type) return n;
throw new Error(`no ${type}`);
}
const text = (doc: Document, node: NodeId) => doc.source.slice(doc.start(node), doc.end(node));
describe("math", () => {
it("keeps the raw TeX and its range", () => {
const doc = parsed("a $\\frac{1}{2}$");
const math = first(doc, "math");
expect(doc.data(math, "math")).toMatchObject({ block: false, value: "\\frac{1}{2}" });
expect(text(doc, math)).toBe("$\\frac{1}{2}$");
});
});
describe("expressions", () => {
it("bind tighter than emphasis and keep their code", () => {
const doc = parsed("_a ${x * y * z} b_");
const node = first(doc, "expression");
expect(doc.data(node, "expression").code).toBe("x * y * z");
expect(html(doc)).toBe(
'<p><em>a <code class="language-js expression">x * y * z</code> b</em></p>\n',
);
});
it("keep their lines as a paragraph does, without the container prefix", () => {
const doc = parsed("> a ${f(\n> x\n> )} b");
expect(doc.data(first(doc, "expression"), "expression").code).toBe("f(\nx\n)");
expect(html(doc)).toBe(
'<blockquote>\n<p>a <code class="language-js expression">f(\nx\n)</code> b</p>\n</blockquote>\n',
);
});
it("skip braces in strings, templates and comments", () => {
const doc = parsed('${f("}", `${"}"}`, /* } */ {a: 1})} after');
expect(doc.data(first(doc, "expression"), "expression").code).toBe(
'f("}", `${"}"}`, /* } */ {a: 1})',
);
});
it("sit in link destinations", () => {
const doc = parsed("[x](/u/${id}/edit)");
const link = doc.data(first(doc, "link"), "link");
expect(link.destination).toBe("/u/${id}/edit");
expect(link.expressions.map((r) => doc.source.slice(r.start, r.end))).toEqual(["${id}"]);
});
});
describe("text", () => {
it("reads \\ before a space as a non-breaking space", () => {
const doc = parsed("10\\ km");
expect(doc.data(first(doc, "text"), "text").value).toBe("10 km");
});
it("maps every text node back to the characters typed", () => {
const doc = parsed('> a © \\*b\\*\n> "c"');
const nodes = [...Array(doc.size).keys()].filter((n) => doc.type(n) === "text");
expect(nodes.map((n) => [text(doc, n), doc.data(n, "text").value])).toEqual([
["a © \\*b\\*\n", "a © *b*\n"],
['"c"', '"c"'],
]);
});
});
describe("heading ids", () => {
const ids = (source: string) => {
const doc = parsed(source);
return [...doc.children(doc.root)]
.filter((n) => doc.type(n) === "heading")
.map((n) => doc.data(n, "heading").id);
};
it("follow the golden table", () => {
expect(
ids(
[
"## Foo",
"## Foo",
"## Foo 1",
"## Café au lait",
"## शुरुआत करें",
"## 日本語の見出し",
"## 1. Rename",
"## See [docs](https://example.com)",
"## a - b",
"## 😄 Smile",
"## ???",
].join("\n\n"),
),
).toEqual([
"foo",
"foo-1",
"foo-1-1",
"café-au-lait",
"शुरुआत-करें",
"日本語の見出し",
"1-rename",
"see-docs",
"a---b",
"-smile",
"section",
]);
});
it("number past a heading whose own text looks like a suffix", () => {
expect(ids("# foo-1\n\n# foo\n\n# foo")).toEqual(["foo-1", "foo", "foo-2"]);
});
it("slug apostrophes the same straight or curled", () => {
expect(ids("## Don't\n\n## Don’t")).toEqual(["dont", "dont-1"]);
});
it("take an explicit id as written, and report one already used", () => {
expect(ids("{#top}\n# A\n\n# Pricing\n\n{#pricing}\n# B")).toEqual([
"top",
"pricing",
"pricing",
]);
expect(messages("# Pricing\n\n{#pricing}\n# B")).toEqual([
"id `pricing` is already used by an earlier heading",
]);
});
});