7e9d568346
- Applied tab sanitization to LSP diagnostic parsing, fallback entries, and rendered diagnostics output. - Updated tool diagnostics formatting to strip tabs from parsed file, source, message, code, and summary fields. - Added regression tests confirming rendered diagnostics contain no tab characters and preserve message text after sanitization.
177 lines
4.8 KiB
TypeScript
177 lines
4.8 KiB
TypeScript
import { describe, expect, it } from "bun:test";
|
|
import * as os from "node:os";
|
|
import * as path from "node:path";
|
|
import { getThemeByName } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
|
|
import {
|
|
dedupeParseErrors,
|
|
formatDiagnostics,
|
|
formatParseErrors,
|
|
formatScreenshot,
|
|
} from "@oh-my-pi/pi-coding-agent/tools/render-utils";
|
|
|
|
describe("parse error formatting", () => {
|
|
it("deduplicates parse errors while preserving order", () => {
|
|
const errors = [
|
|
"foo.ts: parse error (syntax tree contains error nodes)",
|
|
"foo.ts: parse error (syntax tree contains error nodes)",
|
|
"bar.ts: parse error (syntax tree contains error nodes)",
|
|
"foo.ts: parse error (syntax tree contains error nodes)",
|
|
];
|
|
|
|
expect(dedupeParseErrors(errors)).toEqual([
|
|
"foo.ts: parse error (syntax tree contains error nodes)",
|
|
"bar.ts: parse error (syntax tree contains error nodes)",
|
|
]);
|
|
});
|
|
|
|
it("formats deduplicated parse errors", () => {
|
|
const formatted = formatParseErrors([
|
|
"foo.ts: parse error (syntax tree contains error nodes)",
|
|
"foo.ts: parse error (syntax tree contains error nodes)",
|
|
"bar.ts: parse error (syntax tree contains error nodes)",
|
|
]);
|
|
|
|
expect(formatted).toEqual([
|
|
"Parse issues:",
|
|
"- foo.ts: parse error (syntax tree contains error nodes)",
|
|
"- bar.ts: parse error (syntax tree contains error nodes)",
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe("formatScreenshot", () => {
|
|
function fakeResized(
|
|
overrides?: Partial<{
|
|
width: number;
|
|
height: number;
|
|
originalWidth: number;
|
|
originalHeight: number;
|
|
wasResized: boolean;
|
|
buffer: Uint8Array;
|
|
mimeType: string;
|
|
}>,
|
|
): {
|
|
buffer: Uint8Array;
|
|
mimeType: string;
|
|
originalWidth: number;
|
|
originalHeight: number;
|
|
width: number;
|
|
height: number;
|
|
wasResized: boolean;
|
|
get data(): string;
|
|
} {
|
|
const buf = overrides?.buffer ?? new Uint8Array(2048);
|
|
return {
|
|
buffer: buf,
|
|
mimeType: overrides?.mimeType ?? "image/webp",
|
|
originalWidth: overrides?.originalWidth ?? 800,
|
|
originalHeight: overrides?.originalHeight ?? 600,
|
|
width: overrides?.width ?? 800,
|
|
height: overrides?.height ?? 600,
|
|
wasResized: overrides?.wasResized ?? false,
|
|
get data() {
|
|
return Buffer.from(buf).toString("base64");
|
|
},
|
|
};
|
|
}
|
|
|
|
it("formats full-res save with home-relative path", () => {
|
|
const filePath = path.join(os.homedir(), "screenshots", "capture.png");
|
|
const resized = fakeResized({ mimeType: "image/webp", buffer: new Uint8Array(1024) });
|
|
|
|
expect(
|
|
formatScreenshot({
|
|
saveFullRes: true,
|
|
savedMimeType: "image/png",
|
|
savedByteLength: 2048,
|
|
dest: filePath,
|
|
resized,
|
|
}),
|
|
).toEqual([
|
|
"Screenshot captured",
|
|
"Saved: image/png (2.00 KB) to ~/screenshots/capture.png",
|
|
"Model: image/webp (1.00 KB, 800x600)",
|
|
]);
|
|
});
|
|
|
|
it("formats non-home path without tilde", () => {
|
|
const resized = fakeResized({ mimeType: "image/webp", buffer: new Uint8Array(1024) });
|
|
|
|
expect(
|
|
formatScreenshot({
|
|
saveFullRes: true,
|
|
savedMimeType: "image/png",
|
|
savedByteLength: 2048,
|
|
dest: "/tmp/capture.png",
|
|
resized,
|
|
}),
|
|
).toEqual([
|
|
"Screenshot captured",
|
|
"Saved: image/png (2.00 KB) to /tmp/capture.png",
|
|
"Model: image/webp (1.00 KB, 800x600)",
|
|
]);
|
|
});
|
|
|
|
it("formats temp-only screenshot without save line", () => {
|
|
const resized = fakeResized({ mimeType: "image/webp", buffer: new Uint8Array(3072) });
|
|
|
|
expect(
|
|
formatScreenshot({
|
|
saveFullRes: false,
|
|
savedMimeType: "image/webp",
|
|
savedByteLength: 3072,
|
|
dest: "/tmp/omp-sshots-123.png",
|
|
resized,
|
|
}),
|
|
).toEqual(["Screenshot captured", "Format: image/webp (3.00 KB)", "Dimensions: 800x600"]);
|
|
});
|
|
|
|
it("appends dimension note when image was resized", () => {
|
|
const resized = fakeResized({
|
|
wasResized: true,
|
|
originalWidth: 1600,
|
|
originalHeight: 1200,
|
|
width: 800,
|
|
height: 600,
|
|
});
|
|
|
|
const lines = formatScreenshot({
|
|
saveFullRes: false,
|
|
savedMimeType: "image/webp",
|
|
savedByteLength: 2048,
|
|
dest: "/tmp/shot.png",
|
|
resized,
|
|
});
|
|
|
|
expect(lines).toContain(
|
|
"[Image: original 1600x1200, displayed at 800x600. Multiply coordinates by 2.00 to map to original image.]",
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("formatDiagnostics", () => {
|
|
it("replaces tabs in rendered diagnostic text", async () => {
|
|
const theme = await getThemeByName("dark");
|
|
expect(theme).toBeDefined();
|
|
|
|
const formatted = formatDiagnostics(
|
|
{
|
|
errored: true,
|
|
summary: "1\terror(s)",
|
|
messages: [
|
|
"src/example.go:183:41 [error] [compiler] too many\targuments in call (WrongArgCount)",
|
|
"\tunparsed diagnostic\tmessage",
|
|
],
|
|
},
|
|
true,
|
|
theme!,
|
|
() => "go",
|
|
);
|
|
|
|
expect(formatted).not.toContain("\t");
|
|
expect(formatted.replace(/\s+/g, " ")).toContain("too many arguments in call");
|
|
expect(formatted.replace(/\s+/g, " ")).toContain("unparsed diagnostic message");
|
|
expect(formatted.replace(/\s+/g, " ")).toContain("1 error(s)");
|
|
});
|
|
});
|