Files
2026-03-12 13:40:20 +01:00

196 lines
5.9 KiB
TypeScript

import { expect, test } from "vitest";
import {
collectNodesOfType,
findNodeOfType,
renderToText,
} from "@silverbulletmd/silverbullet/lib/tree";
import { buildExtendedMarkdownLanguage } from "./parser.ts";
import { parse } from "./parse_tree.ts";
test("Custom inline syntax - basic", () => {
const lang = buildExtendedMarkdownLanguage({
LatexInline: {
name: "LatexInline",
startMarker: "\\$",
endMarker: "\\$",
mode: "inline",
},
});
const tree = parse(lang, "Hello $E=mc^2$ world");
const nodes = collectNodesOfType(tree, "LatexInline");
expect(nodes.length).toEqual(1);
// Body between markers
const body = findNodeOfType(nodes[0], "LatexInlineBody");
expect(body!.children![0].text).toEqual("E=mc^2");
// Markers are present
const marks = collectNodesOfType(nodes[0], "LatexInlineMark");
expect(marks.length).toEqual(2);
// Round-trip
expect(renderToText(tree)).toEqual("Hello $E=mc^2$ world");
});
test("Custom block syntax - basic", () => {
const lang = buildExtendedMarkdownLanguage({
LatexBlock: {
name: "LatexBlock",
startMarker: "^\\$\\$$",
endMarker: "^\\$\\$$",
mode: "block",
},
});
const tree = parse(lang, "$$\nE=mc^2\n$$");
const nodes = collectNodesOfType(tree, "LatexBlock");
expect(nodes.length).toEqual(1);
const body = findNodeOfType(nodes[0], "LatexBlockBody");
expect(body!.children![0].text).toContain("E=mc^2");
expect(renderToText(tree)).toEqual("$$\nE=mc^2\n$$");
});
test("Custom inline syntax - unclosed marker", () => {
const lang = buildExtendedMarkdownLanguage({
LatexInline: {
name: "LatexInline",
startMarker: "\\$",
endMarker: "\\$",
mode: "inline",
},
});
const tree = parse(lang, "Hello $unclosed");
const nodes = collectNodesOfType(tree, "LatexInline");
expect(nodes.length).toEqual(0);
});
test("Custom inline syntax - empty body", () => {
const lang = buildExtendedMarkdownLanguage({
LatexInline: {
name: "LatexInline",
startMarker: "\\$",
endMarker: "\\$",
mode: "inline",
},
});
const tree = parse(lang, "Hello $$ world");
const nodes = collectNodesOfType(tree, "LatexInline");
expect(nodes.length).toEqual(1);
});
test("Multiple custom syntax extensions", () => {
const lang = buildExtendedMarkdownLanguage({
LatexInline: {
name: "LatexInline",
startMarker: "\\$",
endMarker: "\\$",
mode: "inline",
},
CustomHighlight: {
name: "CustomHighlight",
startMarker: "%%",
endMarker: "%%",
mode: "inline",
},
});
const tree = parse(lang, "Hello $math$ and %%highlight%%");
expect(collectNodesOfType(tree, "LatexInline").length).toEqual(1);
expect(collectNodesOfType(tree, "CustomHighlight").length).toEqual(1);
});
test("Custom block syntax - multi-line", () => {
const lang = buildExtendedMarkdownLanguage({
LatexBlock: {
name: "LatexBlock",
startMarker: "^\\$\\$$",
endMarker: "^\\$\\$$",
mode: "block",
},
});
const tree = parse(lang, "Before\n\n$$\nline1\nline2\n$$\n\nAfter");
const nodes = collectNodesOfType(tree, "LatexBlock");
expect(nodes.length).toEqual(1);
});
test("Custom syntax doesn't break existing syntax", () => {
const lang = buildExtendedMarkdownLanguage({
LatexInline: {
name: "LatexInline",
startMarker: "\\$",
endMarker: "\\$",
mode: "inline",
},
});
const tree = parse(lang, "A [[wiki link]] and $math$");
expect(collectNodesOfType(tree, "WikiLink").length).toEqual(1);
expect(collectNodesOfType(tree, "LatexInline").length).toEqual(1);
});
test("Custom block syntax - unclosed block", () => {
const lang = buildExtendedMarkdownLanguage({
LatexBlock: {
name: "LatexBlock",
startMarker: "^\\$\\$$",
endMarker: "^\\$\\$$",
mode: "block",
},
});
const tree = parse(lang, "$$\nunclosed content");
const nodes = collectNodesOfType(tree, "LatexBlock");
expect(nodes.length).toEqual(0);
});
test("Custom inline syntax - escaped end marker", () => {
const lang = buildExtendedMarkdownLanguage({
LatexInline: {
name: "LatexInline",
startMarker: "\\$",
endMarker: "\\$",
mode: "inline",
},
});
const tree = parse(lang, "Hello $a\\$b$ world");
const nodes = collectNodesOfType(tree, "LatexInline");
expect(nodes.length).toEqual(1);
const body = findNodeOfType(nodes[0], "LatexInlineBody");
expect(body!.children![0].text).toEqual("a\\$b");
expect(renderToText(tree)).toEqual("Hello $a\\$b$ world");
});
test("Custom inline syntax - escaped backslash before end marker", () => {
const lang = buildExtendedMarkdownLanguage({
LatexInline: {
name: "LatexInline",
startMarker: "\\$",
endMarker: "\\$",
mode: "inline",
},
});
const tree = parse(lang, "Hello $a\\\\$ world");
const nodes = collectNodesOfType(tree, "LatexInline");
expect(nodes.length).toEqual(1);
const body = findNodeOfType(nodes[0], "LatexInlineBody");
expect(body!.children![0].text).toEqual("a\\\\");
expect(renderToText(tree)).toEqual("Hello $a\\\\$ world");
});
test("Custom inline syntax - $ with lookahead doesn't clash with ${expr}", () => {
const lang = buildExtendedMarkdownLanguage({
LatexInline: {
name: "LatexInline",
startMarker: "\\$(?!\\{)",
endMarker: "\\$(?!\\{)",
mode: "inline",
},
});
const tree = parse(lang, "Hello $math ${expr} more$ world");
const nodes = collectNodesOfType(tree, "LatexInline");
expect(nodes.length).toEqual(1);
const body = findNodeOfType(nodes[0], "LatexInlineBody");
expect(body!.children![0].text).toEqual("math ${expr} more");
expect(renderToText(tree)).toEqual("Hello $math ${expr} more$ world");
});
test("No custom extensions returns default language", () => {
const lang1 = buildExtendedMarkdownLanguage();
const lang2 = buildExtendedMarkdownLanguage({});
// Both should return the same static instance
expect(lang1).toBe(lang2);
});