57dc91585d
Internal SmartGift build of a Claude Code monitoring dashboard. Lanes: a durable unit of parallel agent work, one per working directory, tracked across session restarts. Managed lanes are git worktrees the dashboard provisions and can reset or remove behind a three-check destroy guard and a counted preflight; adopted lanes are directories you already own and are never destroyable. Pipelines: a lane moves through pipeline stages. A stage the agent declares with evidence renders green; a stage inferred from the tool-event stream renders dashed amber and never counts as done. Detection is forward-only within a 30-minute window, and never writes the declared stage. Workspace: one page at /run with a lane grid, the selected lane's pipeline, and a full Claude console behind a disclosure.
112 lines
3.6 KiB
TypeScript
112 lines
3.6 KiB
TypeScript
/**
|
|
* @file highlight.test.ts
|
|
* @description Unit tests for the syntax highlighter used by the conversation viewer.
|
|
* Ensures token classification stays stable for languages we actually render.
|
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
*/
|
|
|
|
import { describe, it, expect } from "vitest";
|
|
import { canonicalLang, highlight, tokenClass } from "../highlight";
|
|
|
|
function joinByType(tokens: { type: string; text: string }[], type: string): string {
|
|
return tokens
|
|
.filter((t) => t.type === type)
|
|
.map((t) => t.text)
|
|
.join("|");
|
|
}
|
|
|
|
describe("highlight()", () => {
|
|
it("preserves the original source verbatim when concatenated", () => {
|
|
const sources = [
|
|
"const x = 42;\nconsole.log(x);",
|
|
"def foo(a, b):\n return a + b\n",
|
|
'{"a": 1, "b": [true, null]}',
|
|
"echo $HOME && ls -la",
|
|
];
|
|
for (const src of sources) {
|
|
const langs = ["js", "python", "json", "bash"];
|
|
for (const lang of langs) {
|
|
const out = highlight(src, lang)
|
|
.map((t) => t.text)
|
|
.join("");
|
|
expect(out).toBe(src);
|
|
}
|
|
}
|
|
});
|
|
|
|
it("classifies JS keywords, strings, numbers, comments", () => {
|
|
const tokens = highlight('const x = "hi"; // greet\nreturn 42;', "ts");
|
|
expect(joinByType(tokens, "keyword")).toContain("const");
|
|
expect(joinByType(tokens, "keyword")).toContain("return");
|
|
expect(joinByType(tokens, "string")).toContain('"hi"');
|
|
expect(joinByType(tokens, "comment")).toContain("// greet");
|
|
expect(joinByType(tokens, "number")).toContain("42");
|
|
});
|
|
|
|
it("classifies JSON keys as property and bare strings as string", () => {
|
|
const tokens = highlight('{"name": "Son", "age": 30}', "json");
|
|
expect(joinByType(tokens, "property")).toContain('"name"');
|
|
expect(joinByType(tokens, "property")).toContain('"age"');
|
|
expect(joinByType(tokens, "string")).toContain('"Son"');
|
|
expect(joinByType(tokens, "number")).toContain("30");
|
|
});
|
|
|
|
it("classifies bash builtins and variables", () => {
|
|
const tokens = highlight("echo $USER", "bash");
|
|
expect(joinByType(tokens, "builtin")).toContain("echo");
|
|
expect(joinByType(tokens, "variable")).toContain("$USER");
|
|
});
|
|
|
|
it("returns plain tokens for unknown languages", () => {
|
|
const tokens = highlight("anything goes here", "klingon");
|
|
expect(tokens).toHaveLength(1);
|
|
expect(tokens[0]!.type).toBe("plain");
|
|
});
|
|
|
|
it("classifies diff lines", () => {
|
|
const src = "diff --git a b\n+++ b\n--- a\n+added\n-removed\n unchanged";
|
|
const tokens = highlight(src, "diff");
|
|
expect(joinByType(tokens, "diff-add")).toContain("+added");
|
|
expect(joinByType(tokens, "diff-del")).toContain("-removed");
|
|
expect(joinByType(tokens, "diff-meta")).toContain("diff --git a b");
|
|
});
|
|
});
|
|
|
|
describe("canonicalLang()", () => {
|
|
it("maps common aliases to canonical keys", () => {
|
|
expect(canonicalLang("JavaScript")).toBe("js");
|
|
expect(canonicalLang("tsx")).toBe("ts");
|
|
expect(canonicalLang("py")).toBe("python");
|
|
expect(canonicalLang("zsh")).toBe("bash");
|
|
expect(canonicalLang("yml")).toBe("yaml");
|
|
expect(canonicalLang("")).toBe("plain");
|
|
});
|
|
});
|
|
|
|
describe("tokenClass()", () => {
|
|
it("returns a non-empty class for every token type", () => {
|
|
const types = [
|
|
"plain",
|
|
"comment",
|
|
"string",
|
|
"number",
|
|
"keyword",
|
|
"builtin",
|
|
"function",
|
|
"operator",
|
|
"punctuation",
|
|
"property",
|
|
"tag",
|
|
"attr",
|
|
"variable",
|
|
"boolean",
|
|
"diff-add",
|
|
"diff-del",
|
|
"diff-meta",
|
|
] as const;
|
|
for (const t of types) {
|
|
expect(tokenClass(t).length).toBeGreaterThan(0);
|
|
}
|
|
});
|
|
});
|