mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 22:03:45 +00:00
Changed isImageLine() from using startsWith() to includes() to detect Kitty and iTerm2 image escape sequences anywhere in a line, not just at the start. This prevents TUI width checks from failing on lines containing image data, which could cause crashes when rendering tool results with images (e.g., when reading image files). Also added comprehensive test coverage for isImageLine() including: - Both iTerm2 and Kitty protocols - Regression tests for long lines and terminals without image support - Negative cases to ensure no false positives Fixes crash: 'Rendered line exceeds terminal width' when image escape sequences appear in output.
153 lines
6.3 KiB
TypeScript
153 lines
6.3 KiB
TypeScript
/**
|
|
* Tests for terminal image detection and line handling
|
|
*/
|
|
|
|
import assert from "node:assert";
|
|
import { describe, it } from "node:test";
|
|
import { isImageLine } from "../src/terminal-image.js";
|
|
|
|
describe("isImageLine", () => {
|
|
describe("iTerm2 image protocol", () => {
|
|
it("should detect iTerm2 image escape sequence at start of line", () => {
|
|
// iTerm2 image escape sequence: ESC ]1337;File=...
|
|
const iterm2ImageLine = "\x1b]1337;File=size=100,100;inline=1:base64encodeddata==\x07";
|
|
assert.strictEqual(isImageLine(iterm2ImageLine), true);
|
|
});
|
|
|
|
it("should detect iTerm2 image escape sequence with text before it", () => {
|
|
// Simulating a line that has text then image data (bug scenario)
|
|
const lineWithTextAndImage = "Some text \x1b]1337;File=size=100,100;inline=1:base64data==\x07 more text";
|
|
assert.strictEqual(isImageLine(lineWithTextAndImage), true);
|
|
});
|
|
|
|
it("should detect iTerm2 image escape sequence in middle of long line", () => {
|
|
// Simulate a very long line with image data in the middle
|
|
const longLineWithImage =
|
|
"Text before image..." + "\x1b]1337;File=inline=1:verylongbase64data==" + "...text after";
|
|
assert.strictEqual(isImageLine(longLineWithImage), true);
|
|
});
|
|
|
|
it("should detect iTerm2 image escape sequence at end of line", () => {
|
|
const lineWithImageAtEnd = "Regular text ending with \x1b]1337;File=inline=1:base64data==\x07";
|
|
assert.strictEqual(isImageLine(lineWithImageAtEnd), true);
|
|
});
|
|
|
|
it("should detect minimal iTerm2 image escape sequence", () => {
|
|
const minimalImageLine = "\x1b]1337;File=:\x07";
|
|
assert.strictEqual(isImageLine(minimalImageLine), true);
|
|
});
|
|
});
|
|
|
|
describe("Kitty image protocol", () => {
|
|
it("should detect Kitty image escape sequence at start of line", () => {
|
|
// Kitty image escape sequence: ESC _G
|
|
const kittyImageLine = "\x1b_Ga=T,f=100,t=f,d=base64data...\x1b\\\x1b_Gm=i=1;\x1b\\";
|
|
assert.strictEqual(isImageLine(kittyImageLine), true);
|
|
});
|
|
|
|
it("should detect Kitty image escape sequence with text before it", () => {
|
|
// Bug scenario: text + image data in same line
|
|
const lineWithTextAndKittyImage = "Output: \x1b_Ga=T,f=100;data...\x1b\\\x1b_Gm=i=1;\x1b\\";
|
|
assert.strictEqual(isImageLine(lineWithTextAndKittyImage), true);
|
|
});
|
|
|
|
it("should detect Kitty image escape sequence with padding", () => {
|
|
// Kitty protocol adds padding to escape sequences
|
|
const kittyWithPadding = " \x1b_Ga=T,f=100...\x1b\\\x1b_Gm=i=1;\x1b\\ ";
|
|
assert.strictEqual(isImageLine(kittyWithPadding), true);
|
|
});
|
|
});
|
|
|
|
describe("Bug regression tests", () => {
|
|
it("should detect image sequences in very long lines (304k+ chars)", () => {
|
|
// This simulates the crash scenario: a line with 304,401 chars
|
|
// containing image escape sequences somewhere
|
|
const base64Char = "A".repeat(100); // 100 chars of base64-like data
|
|
const imageSequence = "\x1b]1337;File=size=800,600;inline=1:";
|
|
|
|
// Build a long line with image sequence
|
|
const longLine =
|
|
"Text prefix " +
|
|
imageSequence +
|
|
base64Char.repeat(3000) + // ~300,000 chars
|
|
" suffix";
|
|
|
|
assert.strictEqual(longLine.length > 300000, true);
|
|
assert.strictEqual(isImageLine(longLine), true);
|
|
});
|
|
|
|
it("should detect image sequences when terminal doesn't support images", () => {
|
|
// The bug occurred when getImageEscapePrefix() returned null
|
|
// isImageLine should still detect image sequences regardless
|
|
const lineWithImage = "Read image file [image/jpeg]\x1b]1337;File=inline=1:base64data==\x07";
|
|
assert.strictEqual(isImageLine(lineWithImage), true);
|
|
});
|
|
|
|
it("should detect image sequences with ANSI codes before them", () => {
|
|
// Text might have ANSI styling before image data
|
|
const lineWithAnsiAndImage = "\x1b[31mError output \x1b]1337;File=inline=1:image==\x07";
|
|
assert.strictEqual(isImageLine(lineWithAnsiAndImage), true);
|
|
});
|
|
|
|
it("should detect image sequences with ANSI codes after them", () => {
|
|
const lineWithImageAndAnsi = "\x1b_Ga=T,f=100:data...\x1b\\\x1b_Gm=i=1;\x1b\\\x1b[0m reset";
|
|
assert.strictEqual(isImageLine(lineWithImageAndAnsi), true);
|
|
});
|
|
});
|
|
|
|
describe("Negative cases - lines without images", () => {
|
|
it("should not detect images in plain text lines", () => {
|
|
const plainText = "This is just a regular text line without any escape sequences";
|
|
assert.strictEqual(isImageLine(plainText), false);
|
|
});
|
|
|
|
it("should not detect images in lines with only ANSI codes", () => {
|
|
const ansiText = "\x1b[31mRed text\x1b[0m and \x1b[32mgreen text\x1b[0m";
|
|
assert.strictEqual(isImageLine(ansiText), false);
|
|
});
|
|
|
|
it("should not detect images in lines with cursor movement codes", () => {
|
|
const cursorCodes = "\x1b[1A\x1b[2KLine cleared and moved up";
|
|
assert.strictEqual(isImageLine(cursorCodes), false);
|
|
});
|
|
|
|
it("should not detect images in lines with partial iTerm2 sequences", () => {
|
|
// Similar prefix but missing the complete sequence
|
|
const partialSequence = "Some text with ]1337;File but missing ESC at start";
|
|
assert.strictEqual(isImageLine(partialSequence), false);
|
|
});
|
|
|
|
it("should not detect images in lines with partial Kitty sequences", () => {
|
|
// Similar prefix but missing the complete sequence
|
|
const partialSequence = "Some text with _G but missing ESC at start";
|
|
assert.strictEqual(isImageLine(partialSequence), false);
|
|
});
|
|
|
|
it("should not detect images in empty lines", () => {
|
|
assert.strictEqual(isImageLine(""), false);
|
|
});
|
|
|
|
it("should not detect images in lines with newlines only", () => {
|
|
assert.strictEqual(isImageLine("\n"), false);
|
|
assert.strictEqual(isImageLine("\n\n"), false);
|
|
});
|
|
});
|
|
|
|
describe("Mixed content scenarios", () => {
|
|
it("should detect images when line has both Kitty and iTerm2 sequences", () => {
|
|
const mixedLine = "Kitty: \x1b_Ga=T...\x1b\\\x1b_Gm=i=1;\x1b\\ iTerm2: \x1b]1337;File=inline=1:data==\x07";
|
|
assert.strictEqual(isImageLine(mixedLine), true);
|
|
});
|
|
|
|
it("should detect image in line with multiple text and image segments", () => {
|
|
const complexLine = "Start \x1b]1337;File=img1==\x07 middle \x1b]1337;File=img2==\x07 end";
|
|
assert.strictEqual(isImageLine(complexLine), true);
|
|
});
|
|
|
|
it("should not falsely detect image in line with file path containing keywords", () => {
|
|
// File path might contain "1337" or "File" but without escape sequences
|
|
const filePathLine = "/path/to/File_1337_backup/image.jpg";
|
|
assert.strictEqual(isImageLine(filePathLine), false);
|
|
});
|
|
});
|
|
});
|