Files
lam-agent-vicinae/src/__tests__/code.test.ts
T

479 lines
17 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, expect, it } from "vitest";
import {
fenceAsciiArt,
inferLanguage,
labelCodeFences,
mapOutsideCodeSpans,
normalizeFenceLabels,
promoteStandaloneCodeSpans,
repairCodeBlocks,
restoreUrlLinks,
separateTables,
} from "../lib/code";
describe("inferLanguage", () => {
it("returns nothing for empty or whitespace-only code", () => {
expect(inferLanguage("")).toBeUndefined();
expect(inferLanguage(" \n ")).toBeUndefined();
});
describe("shell", () => {
it("recognises a command as the first word", () => {
expect(inferLanguage("pwd")).toBe("bash");
expect(inferLanguage("git status")).toBe("bash");
expect(inferLanguage("brew install ripgrep")).toBe("bash");
});
it("recognises an explicit prompt marker", () => {
expect(inferLanguage("$ git status")).toBe("bash");
expect(inferLanguage("> ls -la")).toBe("bash");
});
it("recognises a home-relative path", () => {
expect(inferLanguage("~/Documents/Code")).toBe("bash");
});
it("recognises a pipeline between known commands", () => {
expect(inferLanguage("git log --oneline | head -20")).toBe("bash");
});
it("ignores comments when looking at the first word", () => {
expect(inferLanguage("# check the branch\ngit branch --show-current")).toBe("bash");
});
});
describe("json", () => {
it("recognises a parseable object", () => {
expect(inferLanguage('{\n "model": "gpt-oss"\n}')).toBe("json");
});
it("recognises a parseable array", () => {
expect(inferLanguage('["a", "b"]')).toBe("json");
});
it("does not claim malformed braces", () => {
expect(inferLanguage("{ not actually json")).not.toBe("json");
});
});
describe("typescript", () => {
it("recognises type annotations", () => {
expect(inferLanguage("function add(a: number, b: number) {\n return a + b;\n}")).toBe("ts");
});
it("recognises interfaces and type aliases", () => {
expect(inferLanguage("interface User {\n id: string;\n}")).toBe("ts");
expect(inferLanguage("type Id = string | number;")).toBe("ts");
});
it("does not label plain JavaScript, which is ambiguous", () => {
// Guessing wrong costs the highlighting, so this stays unlabelled.
expect(inferLanguage("console.log('hi')")).toBeUndefined();
expect(inferLanguage("const x = [1, 2, 3].map((n) => n * 2);")).toBeUndefined();
});
});
describe("python", () => {
it("recognises a function definition", () => {
expect(inferLanguage("def greet(name):\n return f'hi {name}'")).toBe("python");
});
it("recognises imports and self", () => {
expect(inferLanguage("from pathlib import Path")).toBe("python");
expect(inferLanguage("class A:\n def go(self):\n pass")).toBe("python");
});
});
it("leaves genuinely ambiguous content alone", () => {
expect(inferLanguage("some plain text\nacross two lines")).toBeUndefined();
});
});
describe("promoteStandaloneCodeSpans", () => {
it("promotes a paragraph that is one inline span", () => {
// The reported case: asking for a command and getting a lone chip back.
expect(promoteStandaloneCodeSpans("`pwd`")).toBe("```bash\npwd\n```");
});
it("promotes the same span on its own line within a larger answer", () => {
const promoted = promoteStandaloneCodeSpans("Here you go:\n\n`pwd`\n\nThat prints the path.");
expect(promoted).toContain("```bash\npwd\n```");
expect(promoted).toContain("Here you go:");
expect(promoted).toContain("That prints the path.");
});
it("tolerates a sentence-ending period", () => {
expect(promoteStandaloneCodeSpans("`pwd`.")).toBe("```bash\npwd\n```");
});
it("leaves a span embedded in a sentence alone", () => {
const input = "Run `pwd` to see where you are.";
expect(promoteStandaloneCodeSpans(input)).toBe(input);
});
it("leaves a bulleted span alone", () => {
const input = "- `pwd -P` resolves symlinks";
expect(promoteStandaloneCodeSpans(input)).toBe(input);
});
it("never rewrites a span-like line inside an existing fence", () => {
const input = "```bash\n`not-a-span`\n```";
expect(promoteStandaloneCodeSpans(input)).toBe(input);
});
it("fences a span whose language cannot be inferred, without guessing one", () => {
expect(promoteStandaloneCodeSpans("`Get-Location`")).toBe("```\nGet-Location\n```");
});
it("is idempotent", () => {
const once = promoteStandaloneCodeSpans("`pwd`");
expect(promoteStandaloneCodeSpans(once)).toBe(once);
});
it("is a no-op on ordinary prose", () => {
const input = "There is no code here at all.";
expect(promoteStandaloneCodeSpans(input)).toBe(input);
});
});
describe("restoreUrlLinks", () => {
it("unwraps a fence that is nothing but URLs", () => {
// The reported case: "here are the links" arriving as a code block, so the
// one thing a URL is for, being clicked, does not work.
const input = [
"```",
"https://github.com/jasonkneen/openclicky",
"https://github.com/unn-Known1/clickyX",
"```",
].join("\n");
expect(restoreUrlLinks(input)).toBe(
[
"[https://github.com/jasonkneen/openclicky](https://github.com/jasonkneen/openclicky)",
"[https://github.com/unn-Known1/clickyX](https://github.com/unn-Known1/clickyX)",
].join("\n"),
);
});
it("unwraps a URL fence even when the model labelled it", () => {
expect(restoreUrlLinks("```bash\nhttps://example.com\n```")).toBe(
"[https://example.com](https://example.com)",
);
});
it("links a bare URL standing alone on a line", () => {
expect(restoreUrlLinks("https://example.com")).toBe("[https://example.com](https://example.com)");
});
it("links a lone inline span holding a URL", () => {
expect(restoreUrlLinks("`https://example.com`")).toBe(
"[https://example.com](https://example.com)",
);
});
it("keeps a bulleted list of links a bulleted list", () => {
const input = "- https://a.example\n- https://b.example";
expect(restoreUrlLinks(input)).toBe(
"- [https://a.example](https://a.example)\n- [https://b.example](https://b.example)",
);
});
it("keeps an ordered list marker", () => {
expect(restoreUrlLinks("1. https://example.com")).toBe(
"1. [https://example.com](https://example.com)",
);
});
it("drops sentence punctuation that is not part of the address", () => {
expect(restoreUrlLinks("See this:\n\nhttps://example.com.")).toContain(
"[https://example.com](https://example.com)",
);
});
it("brackets a destination that contains parentheses", () => {
const url = "https://en.wikipedia.org/wiki/Foo_(bar)";
expect(restoreUrlLinks(url)).toBe(`[${url}](<${url}>)`);
});
it("escapes brackets in the label, which an IPv6 literal contains", () => {
// Unescaped, the `]` in the label ends the link text early and the rest of
// the address is left as prose outside the link.
expect(restoreUrlLinks("http://[::1]:8080/")).toBe(
"[http://\\[::1\\]:8080/](http://[::1]:8080/)",
);
});
it("never touches a fence holding real code", () => {
const input = "```bash\ncurl https://example.com | jq .\n```";
expect(restoreUrlLinks(input)).toBe(input);
});
it("never touches a fence when one line is not a URL", () => {
const input = "```\nhttps://example.com\nnot a url\n```";
expect(restoreUrlLinks(input)).toBe(input);
});
it("leaves a URL embedded in a sentence alone", () => {
const input = "The docs at https://example.com cover it.";
expect(restoreUrlLinks(input)).toBe(input);
});
it("leaves a markdown link the model already wrote alone", () => {
const input = "[OpenClickly](https://github.com/jasonkneen/openclicky)";
expect(restoreUrlLinks(input)).toBe(input);
});
it("preserves a fence that is never closed and is real code", () => {
expect(restoreUrlLinks("```\ngit log")).toBe("```\ngit log");
});
it("is idempotent", () => {
const once = restoreUrlLinks("```\nhttps://example.com\n```");
expect(restoreUrlLinks(once)).toBe(once);
});
it("is a no-op on ordinary prose", () => {
const input = "There are no addresses in this sentence.";
expect(restoreUrlLinks(input)).toBe(input);
});
});
describe("separateTables", () => {
/** The table from the answer that prompted this, verbatim. */
const table = [
"| snip | source | aspect | dead space before | box after |",
"| --- | --- | --- | --- | --- |",
"| `…153223` | 626×184 | 3.40 | **~82px** | 400×118 |",
].join("\n");
it("gives a table the blank line it needs to be a table", () => {
// GFM tables cannot interrupt a paragraph, so glued to the line above they
// render as a paragraph of pipes and dashes.
const repaired = separateTables(`Here are the numbers:\n${table}`);
expect(repaired).toBe(`Here are the numbers:\n\n${table}`);
});
it("leaves a table alone when it already has one", () => {
const already = `Here are the numbers:\n\n${table}`;
expect(separateTables(already)).toBe(already);
});
it("leaves a table alone when it opens the answer", () => {
expect(separateTables(table)).toBe(table);
});
it("leaves a table alone when it follows a fence", () => {
const input = `\`\`\`bash\npwd\n\`\`\`\n${table}`;
expect(separateTables(input)).toBe(input);
});
it("does not touch a table inside a fence", () => {
// A table shown as example code has to stay exactly as written, blank line
// or not.
const fenced = `\`\`\`markdown\n${table}\n\`\`\``;
expect(separateTables(fenced)).toBe(fenced);
});
it("leaves prose containing a pipe alone", () => {
const input = "Use the pipe character:\nls | grep foo";
expect(separateTables(input)).toBe(input);
});
it("does not insert a blank line before a data row", () => {
// Only the header starts a table. A blank line inside one would split it.
const input = `text\n${table}\n| a | b | c | d | e |`;
expect(separateTables(input)).toBe(`text\n\n${table}\n| a | b | c | d | e |`);
});
it("recognises a one-dash delimiter, which GFM allows", () => {
const minimal = "| a | b |\n| - | - |\n| 1 | 2 |";
expect(separateTables(`text\n${minimal}`)).toBe(`text\n\n${minimal}`);
});
it("recognises alignment colons", () => {
const aligned = "| a | b |\n|:---|:---:|\n| 1 | 2 |";
expect(separateTables(`text\n${aligned}`)).toBe(`text\n\n${aligned}`);
});
it("is idempotent", () => {
const once = separateTables(`text\n${table}`);
expect(separateTables(once)).toBe(once);
});
});
describe("repairCodeBlocks", () => {
it("promotes a lone command and labels it in one pass", () => {
expect(repairCodeBlocks("`pwd`")).toBe("```bash\npwd\n```");
});
it("leaves a table exactly as the model wrote it", () => {
// The whole repair pass runs over every answer, and a table has to survive
// it untouched: pipes are not fences, not links, and not lone spans.
const table = [
"| snip | source | aspect | box after |",
"| --- | --- | --- | --- |",
"| `…153223` | 626×184 | 3.40 | 400×118 |",
"| [docs](https://example.com) | `pwd` | https://example.com | 400×60 |",
].join("\n");
expect(repairCodeBlocks(table)).toBe(table);
});
it("does not fence a lone URL", () => {
// Restoring links runs before promotion, so a recovered URL never becomes a
// fence again. Fencing it here was the bug the user saw.
const repaired = repairCodeBlocks("`https://example.com`");
expect(repaired).not.toContain("```");
expect(repaired).toBe("[https://example.com](https://example.com)");
});
it("unwraps a fenced list of links and leaves no fence behind", () => {
const repaired = repairCodeBlocks("```\nhttps://a.example\nhttps://b.example\n```");
expect(repaired).not.toContain("```");
expect(repaired).toContain("[https://a.example](https://a.example)");
});
it("still labels a bare multi-line fence", () => {
expect(repairCodeBlocks("```\ngit status\n```")).toBe("```bash\ngit status\n```");
});
it("handles a promoted block and a plain fence in the same answer", () => {
const repaired = repairCodeBlocks("First:\n\n`pwd`\n\nThen:\n\n```\nls -la\n```");
expect(repaired).toContain("```bash\npwd\n```");
expect(repaired).toContain("```bash\nls -la\n```");
});
});
describe("labelCodeFences", () => {
it("labels a bare fence", () => {
const labelled = labelCodeFences("```\ngit status\n```");
expect(labelled).toBe("```bash\ngit status\n```");
});
it("leaves an already labelled fence exactly as the model wrote it", () => {
expect(labelCodeFences("```rust\nfn main() {}\n```")).toBe("```rust\nfn main() {}\n```");
});
it("labels each block independently", () => {
const labelled = labelCodeFences(["```", "pwd", "```", "", "```", '{"a": 1}', "```"].join("\n"));
expect(labelled).toContain("```bash");
expect(labelled).toContain("```json");
});
it("leaves an unlabellable block bare rather than guessing", () => {
const input = "```\njust some words\n```";
expect(labelCodeFences(input)).toBe(input);
});
it("preserves surrounding prose", () => {
const labelled = labelCodeFences("Before:\n\n```\nls\n```\n\nAfter.");
expect(labelled).toContain("Before:");
expect(labelled).toContain("After.");
expect(labelled).toContain("```bash");
});
it("handles a fence that is never closed", () => {
expect(labelCodeFences("```\ngit log")).toContain("```bash");
});
it("is a no-op on markdown with no fences", () => {
const input = "Just a paragraph with `inline code` in it.";
expect(labelCodeFences(input)).toBe(input);
});
it("is idempotent", () => {
const once = labelCodeFences("```\nls\n```");
expect(labelCodeFences(once)).toBe(once);
});
it("does not mistake a closing fence for a new opening one", () => {
// A closing ``` and an opening bare ``` are identical lines. Treating the
// closing one as an opening fence swallowed everything after it, so the
// second block never got labelled.
const input = "```bash\npwd\n```\n\n```\nls -la\n```";
expect(labelCodeFences(input)).toContain("```bash\nls -la\n```");
});
it("labels a later block even when an earlier one is already labelled", () => {
const input = "```rust\nfn main() {}\n```\n\n```\npwd\n```";
const labelled = labelCodeFences(input);
expect(labelled).toContain("```rust");
expect(labelled).toContain("```bash\npwd\n```");
});
it("preserves a language the model chose, including multi-word freeform tags", () => {
expect(labelCodeFences("```c++\nint main() {}\n```")).toContain("```c++");
expect(labelCodeFences("```objective-c\nint x;\n```")).toContain("```objective-c");
});
it("preserves indentation of a nested fence", () => {
expect(labelCodeFences(" ```\n ls\n ```")).toContain(" ```bash");
});
});
describe("mapOutsideCodeSpans", () => {
it("applies the transform everywhere a span is not", () => {
expect(mapOutsideCodeSpans("a `b` c", (text) => text.toUpperCase())).toBe("A `b` C");
});
it("matches a delimiter of the same length only", () => {
// A single-backtick span inside a double-backtick span is content, not a
// closer, so the inner one is left exactly as written.
expect(mapOutsideCodeSpans("`` `a` ``", (text) => text.toUpperCase())).toBe("`` `a` ``");
});
it("treats an unclosed run as text and carries on after it", () => {
expect(mapOutsideCodeSpans("a ` b", (text) => text.toUpperCase())).toBe("A ` B");
});
});
describe("normalizeFenceLabels", () => {
it("names a language that resolves in place of one that does not", () => {
expect(normalizeFenceLabels("```console\nls\n```")).toBe("```bash\nls\n```");
});
it("drops a label that carries no information", () => {
expect(normalizeFenceLabels("```text\nplain\n```")).toBe("```\nplain\n```");
});
it("leaves a language the model chose alone", () => {
const input = "```rust\nfn main() {}\n```";
expect(normalizeFenceLabels(input)).toBe(input);
});
});
describe("fenceAsciiArt", () => {
it("fences a tree so its columns stay monospaced", () => {
expect(fenceAsciiArt("src/\n├── a.ts\n└── b.ts")).toBe("```\nsrc/\n├── a.ts\n└── b.ts\n```");
});
it("fences an ASCII table drawn with borders", () => {
const art = "+---+---+\n| a | b |\n+---+---+";
expect(fenceAsciiArt(art)).toBe("```\n" + art + "\n```");
});
it("leaves a real GFM table to the table renderer", () => {
const table = "| a | b |\n| --- | --- |\n| 1 | 2 |";
expect(fenceAsciiArt(table)).toBe(table);
});
it("leaves prose that contains one pipe alone", () => {
const prose = "Here is what it does:\n| a | b |";
expect(fenceAsciiArt(prose)).toBe(prose);
});
it("leaves a diagram that is already fenced alone", () => {
const fenced = "```\n├── a.ts\n└── b.ts\n```";
expect(fenceAsciiArt(fenced)).toBe(fenced);
});
});