feat: render LaTeX, custom HTML and diagrams in the transcript (#19)
This commit was merged in pull request #19.
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { renderInlineContent, renderModelOutput } from "../lib/render";
|
||||
|
||||
describe("renderModelOutput", () => {
|
||||
it("renders inline maths in a sentence", () => {
|
||||
expect(renderModelOutput("The answer is $x^2 + 1$, obviously.")).toBe("The answer is x² + 1, obviously.");
|
||||
expect(renderModelOutput("So \\(x^2\\) holds.")).toBe("So x² holds.");
|
||||
});
|
||||
|
||||
it("leaves money and shell variables alone", () => {
|
||||
expect(renderModelOutput("Costs $5 and $10 today.")).toBe("Costs $5 and $10 today.");
|
||||
expect(renderModelOutput("cd $HOME && echo $PATH")).toBe("cd $HOME && echo $PATH");
|
||||
});
|
||||
|
||||
it("puts a display formula on a paragraph of its own", () => {
|
||||
expect(renderModelOutput("$$\\frac{a}{b} = c$$")).toBe("\na/b = c\n");
|
||||
expect(renderModelOutput("$$\nx = 1\n$$")).toBe("\nx = 1\n");
|
||||
expect(renderModelOutput("\\[\\sum_{i=1}^{n} i\\]")).toBe("\n∑ᵢ₌₁ⁿ i\n");
|
||||
});
|
||||
|
||||
it("does not let a stray marker swallow the rest of the answer", () => {
|
||||
// An opening `$$` with no closer only counts when what follows reads as a
|
||||
// formula, so a price is not a formula and the paragraphs after it survive.
|
||||
expect(renderModelOutput("The price is\n$$5 per unit.\n\nThat is cheap.")).toBe(
|
||||
"The price is\n$$5 per unit.\n\nThat is cheap.",
|
||||
);
|
||||
});
|
||||
|
||||
it("renders a maths fence as a formula rather than as code", () => {
|
||||
expect(renderModelOutput("```math\n\\sum_{i=1}^{n} i\n```")).toBe("\n∑ᵢ₌₁ⁿ i\n");
|
||||
expect(renderModelOutput("```latex\nx^2\n```")).toBe("\nx²\n");
|
||||
});
|
||||
|
||||
it("leaves maths inside a fence alone", () => {
|
||||
const markdown = "```ts\nconst x = $a$;\n```";
|
||||
expect(renderModelOutput(markdown)).toBe(markdown);
|
||||
});
|
||||
|
||||
it("leaves a dollar inside a code span alone", () => {
|
||||
expect(renderModelOutput("`$x^2$` stays.")).toBe("`$x^2$` stays.");
|
||||
});
|
||||
|
||||
it("renames a fence language the renderer would print but not colour", () => {
|
||||
expect(renderModelOutput("```console\nls\n```")).toBe("```bash\nls\n```");
|
||||
expect(renderModelOutput("```terminal\nls\n```")).toBe("```bash\nls\n```");
|
||||
});
|
||||
|
||||
it("drops a fence label that carries no information", () => {
|
||||
expect(renderModelOutput("```text\nplain\n```")).toBe("```\nplain\n```");
|
||||
});
|
||||
|
||||
it("leaves a label that does resolve exactly as written", () => {
|
||||
const markdown = "```python\nprint(1)\n```";
|
||||
expect(renderModelOutput(markdown)).toBe(markdown);
|
||||
});
|
||||
|
||||
it("fences a diagram so its alignment survives", () => {
|
||||
expect(renderModelOutput("src/\n├── a.ts\n└── b.ts")).toBe("```\nsrc/\n├── a.ts\n└── b.ts\n```");
|
||||
});
|
||||
|
||||
it("leaves a real table to the table renderer", () => {
|
||||
const table = "| a | b |\n| --- | --- |\n| 1 | 2 |";
|
||||
expect(renderModelOutput(table)).toBe(table);
|
||||
});
|
||||
|
||||
it("leaves a diagram inside a fence alone", () => {
|
||||
const markdown = "```text\n├── a.ts\n└── b.ts\n```";
|
||||
expect(renderModelOutput(markdown)).toBe("```\n├── a.ts\n└── b.ts\n```");
|
||||
});
|
||||
|
||||
it("filters HTML in the flow of an answer", () => {
|
||||
expect(renderModelOutput("<script>alert(1)</script>still here")).toBe("still here");
|
||||
expect(renderModelOutput('<span style="color:#ff0000;position:fixed">x</span>')).toBe(
|
||||
'<span style="color:#ff0000">x</span>',
|
||||
);
|
||||
expect(renderModelOutput('Read <a href="https://example.com">the docs</a>.')).toBe(
|
||||
"Read [the docs](https://example.com).",
|
||||
);
|
||||
});
|
||||
|
||||
it("leaves the markup inside a fence as the code it is", () => {
|
||||
const markdown = "```html\n<b>x</b>\n```";
|
||||
expect(renderModelOutput(markdown)).toBe(markdown);
|
||||
});
|
||||
|
||||
it("hands a block of HTML to the engine as a paragraph", () => {
|
||||
// A line that starts with a block tag is an HTML *block*, and Vicinae's block
|
||||
// path keeps only the text of one. Wrapped in a span it is a paragraph, which
|
||||
// is the path that keeps raw HTML.
|
||||
expect(renderModelOutput("<table><tr><td>a</td><td>b</td></tr></table>")).toBe(
|
||||
"<span><table><tr><td>a</td><td>b</td></tr></table></span>",
|
||||
);
|
||||
});
|
||||
|
||||
it("joins a block of HTML onto one line", () => {
|
||||
// `class` is dropped, because there is no stylesheet for it to name, and the
|
||||
// style attribute is the only thing the renderer reads.
|
||||
expect(renderModelOutput('<div class="card">\n<b>bold</b>\n</div>')).toBe(
|
||||
"<span><div> <b>bold</b> </div></span>",
|
||||
);
|
||||
});
|
||||
|
||||
it("leaves markup that does not balance where the text it carries still shows", () => {
|
||||
expect(renderModelOutput("<div>hello")).toBe("<div>hello");
|
||||
});
|
||||
|
||||
it("leaves a block holding an image alone, because that path loads it", () => {
|
||||
expect(renderModelOutput('<div><img src="https://x/y.png"></div>')).toBe(
|
||||
'<div><img src="https://x/y.png"></div>',
|
||||
);
|
||||
});
|
||||
|
||||
it("leaves a lone inline tag alone, because it is not a block", () => {
|
||||
const markdown = "Try <b>bold</b> for that.";
|
||||
expect(renderModelOutput(markdown)).toBe(markdown);
|
||||
});
|
||||
|
||||
it("keeps an answer that has nothing to render exactly as written", () => {
|
||||
const plain = "Just a sentence, with a [link](https://example.com) and `code`.";
|
||||
expect(renderModelOutput(plain)).toBe(plain);
|
||||
});
|
||||
|
||||
it("repairs fences in model output but not in what a person typed", () => {
|
||||
// A lone command is a fence when a model writes it, and a question about it
|
||||
// is left as the person typed it.
|
||||
expect(renderModelOutput("`pwd`")).toBe("```bash\npwd\n```");
|
||||
expect(renderInlineContent("`pwd`")).toBe("`pwd`");
|
||||
});
|
||||
|
||||
it("still renders maths and markup in what a person typed", () => {
|
||||
expect(renderInlineContent("what is $x^2$?")).toBe("what is x²?");
|
||||
expect(renderInlineContent("<script>alert(1)</script>hi")).toBe("hi");
|
||||
expect(renderInlineContent("```ts\nconst x = $a$;\n```")).toBe("```ts\nconst x = $a$;\n```");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user