Files
oh-my-pi/packages/tui/test/image-render.test.ts
T
can1357 42a05f7b09 fix(coding-agent/modes): allowed transcript rows to re-earn append-only after clean frames
- Changed transcript commit tracking from a volatile boolean to a cooldown counter that decays over clean frames.
- Normalized row equality checks to ignore ANSI and trailing-space noise so equivalent renders do not trigger rewrites.
- Removed the Bun test-runtime exception from Ghostty image paint deferral so normal delay logic always runs.
2026-06-09 19:47:56 +02:00

154 lines
4.7 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it } from "bun:test";
import { Image } from "@oh-my-pi/pi-tui/components/image";
import {
type CellDimensions,
getCellDimensions,
ImageProtocol,
isWindowsTerminalPreviewSixelSupported,
renderImage,
setCellDimensions,
TERMINAL,
} from "@oh-my-pi/pi-tui/terminal-capabilities";
type MutableTerminalInfo = {
imageProtocol: ImageProtocol | null;
};
const terminal = TERMINAL as unknown as MutableTerminalInfo;
const BASE64_DUMMY = "AA==";
const SQUARE_DIMENSIONS = { widthPx: 100, heightPx: 100 };
const BASE64_ONE_PIXEL_PNG =
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVR4nGNgAAAAAgABSK+kcQAAAABJRU5ErkJggg==";
function parseKittyParam(sequence: string, key: "c" | "r"): number | null {
const match = sequence.match(new RegExp(`${key}=(\\d+)`));
if (!match) return null;
return Number.parseInt(match[1], 10);
}
function parseITermWidth(sequence: string): string | null {
const match = sequence.match(/width=([^;:]+)/);
return match?.[1] ?? null;
}
describe("terminal image rendering", () => {
const originalProtocol = TERMINAL.imageProtocol;
let originalCellDims: CellDimensions;
beforeEach(() => {
originalCellDims = { ...getCellDimensions() };
setCellDimensions({ widthPx: 10, heightPx: 10 });
terminal.imageProtocol = null;
});
afterEach(() => {
setCellDimensions(originalCellDims);
terminal.imageProtocol = originalProtocol;
});
it("fits Kitty images within max width and max height while preserving aspect ratio", () => {
terminal.imageProtocol = ImageProtocol.Kitty;
const result = renderImage(BASE64_DUMMY, SQUARE_DIMENSIONS, {
maxWidthCells: 10,
maxHeightCells: 2,
});
expect(result).not.toBeNull();
expect(result?.rows).toBe(2);
expect(parseKittyParam(result?.sequence ?? "", "c")).toBe(2);
expect(parseKittyParam(result?.sequence ?? "", "r")).toBe(2);
});
it("uses intrinsic image size when no bounds are provided", () => {
terminal.imageProtocol = ImageProtocol.Kitty;
const result = renderImage(BASE64_DUMMY, SQUARE_DIMENSIONS);
expect(result).not.toBeNull();
expect(result?.rows).toBe(10);
expect(parseKittyParam(result?.sequence ?? "", "c")).toBe(10);
expect(parseKittyParam(result?.sequence ?? "", "r")).toBe(10);
});
it("transmits stable Kitty images in-band before placement", () => {
terminal.imageProtocol = ImageProtocol.Kitty;
const result = renderImage(BASE64_ONE_PIXEL_PNG, SQUARE_DIMENSIONS, {
imageId: 42,
includeTransmit: true,
});
expect(result).not.toBeNull();
expect(result?.transmit).toBe(`\x1b_Ga=t,f=100,q=2,i=42;${BASE64_ONE_PIXEL_PNG}\x1b\\`);
expect(result?.transmit).not.toContain("t=t");
});
it("reduces iTerm2 width when max height is the limiting bound", () => {
terminal.imageProtocol = ImageProtocol.Iterm2;
const result = renderImage(BASE64_DUMMY, SQUARE_DIMENSIONS, {
maxWidthCells: 10,
maxHeightCells: 2,
});
expect(result).not.toBeNull();
expect(result?.rows).toBe(2);
expect(parseITermWidth(result?.sequence ?? "")).toBe("2");
expect(result?.sequence).toContain("height=auto");
});
it("encodes SIXEL output when protocol is SIXEL", () => {
terminal.imageProtocol = ImageProtocol.Sixel;
const result = renderImage(BASE64_ONE_PIXEL_PNG, SQUARE_DIMENSIONS, {
maxWidthCells: 10,
maxHeightCells: 2,
});
expect(result).not.toBeNull();
expect(result?.rows).toBe(2);
expect((result?.sequence ?? "").startsWith("\x1bP")).toBe(true);
});
it("Image component forwards maxHeightCells to terminal rendering", () => {
terminal.imageProtocol = ImageProtocol.Kitty;
const image = new Image(
BASE64_DUMMY,
"image/png",
{ fallbackColor: text => text },
{ maxWidthCells: 10, maxHeightCells: 2 },
SQUARE_DIMENSIONS,
);
const lines = image.render(20);
expect(lines[0]).toBe("\x1b[0m");
expect(lines).toHaveLength(2);
expect(lines[1]).toContain("\x1b[1A");
expect(lines[1]).toContain("c=2");
expect(lines[1]).toContain("r=2");
});
});
describe("Windows Terminal Preview SIXEL detection", () => {
it("requires Windows platform, WT session, and known version 1.22+", () => {
expect(
isWindowsTerminalPreviewSixelSupported(
{ WT_SESSION: "1", TERM_PROGRAM: "Windows_Terminal", TERM_PROGRAM_VERSION: "1.22.2362.0" },
"win32",
),
).toBe(true);
expect(
isWindowsTerminalPreviewSixelSupported(
{ WT_SESSION: "1", TERM_PROGRAM: "Windows_Terminal", TERM_PROGRAM_VERSION: "1.21.0.0" },
"win32",
),
).toBe(false);
expect(
isWindowsTerminalPreviewSixelSupported({ WT_SESSION: "1", TERM_PROGRAM: "Windows_Terminal" }, "win32"),
).toBe(false);
expect(
isWindowsTerminalPreviewSixelSupported(
{ WT_SESSION: "1", TERM_PROGRAM: "Windows_Terminal", TERM_PROGRAM_VERSION: "1.22.2362.0" },
"linux",
),
).toBe(false);
});
});