Files
oh-my-pi/packages/coding-agent/test/tools/render-utils.test.ts
T
can1357 4f85ebe472 fix(coding-agent): corrected gutter marker alignment in diff rendering
- Added `CodeFrameMarker` and `formatCodeFrameLine()` to centralize code-frame gutter formatting.
- Extended diff rendering to preserve `|` and `│` separators, aligning gutter markers and line numbers.
- Reworked AST, grep, hashline, Vim, and diff renderers to use shared line formatting with computed `lineNumberWidth`.
- Updated atom editing flow and tests, including `resolveAtomEntryPaths` migration and new loc-based/edge-case coverage.
- Adjusted benchmark runner early-stop configuration by passing `buildEarlyStop` through prompt collection.
2026-04-26 03:00:51 +02:00

187 lines
5.2 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,
formatCodeFrameLine,
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)");
});
});
describe("formatCodeFrameLine", () => {
it("pads markers as part of the gutter", () => {
expect(formatCodeFrameLine(" ", 447, "context", 3)).toBe(" 447│context");
expect(formatCodeFrameLine("*", 448, "match", 3)).toBe("*448│match");
expect(formatCodeFrameLine("+", 11, "added", 3)).toBe(" +11│added");
expect(formatCodeFrameLine("+", 235, "added", 3)).toBe("+235│added");
});
});