From efdfdd036cf657f39090befe564f3307928b68a1 Mon Sep 17 00:00:00 2001 From: Vignesh Natarajan Date: Sat, 14 Feb 2026 20:01:01 -0800 Subject: [PATCH] test (tui): cover ANSI-safe searchable select matching --- .../components/searchable-select-list.test.ts | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/src/tui/components/searchable-select-list.test.ts b/src/tui/components/searchable-select-list.test.ts index 35bada1f09..b651e78a83 100644 --- a/src/tui/components/searchable-select-list.test.ts +++ b/src/tui/components/searchable-select-list.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { visibleWidth } from "../../terminal/ansi.js"; +import { stripAnsi, visibleWidth } from "../../terminal/ansi.js"; import { SearchableSelectList, type SearchableSelectListTheme } from "./searchable-select-list.js"; const mockTheme: SearchableSelectListTheme = { @@ -103,6 +103,45 @@ describe("SearchableSelectList", () => { } }); + it("ignores ANSI escape codes in search matching", () => { + const items = [ + { value: "styled", label: "\u001b[32mopenai/gpt-4\u001b[0m", description: "Styled label" }, + { value: "plain", label: "plain-item", description: "Plain label" }, + ]; + const list = new SearchableSelectList(items, 5, mockTheme); + + for (const ch of "32m") { + list.handleInput(ch); + } + + const output = list.render(80); + expect(output.some((line) => line.includes("No matches"))).toBe(true); + }); + + it("does not corrupt ANSI sequences when highlighting multiple tokens", () => { + const ansiTheme: SearchableSelectListTheme = { + selectedPrefix: (t) => t, + selectedText: (t) => t, + description: (t) => t, + scrollInfo: (t) => t, + noMatch: (t) => t, + searchPrompt: (t) => t, + searchInput: (t) => t, + matchHighlight: (t) => `\u001b[31m${t}\u001b[0m`, + }; + const items = [{ value: "gpt-model", label: "gpt-model" }]; + const list = new SearchableSelectList(items, 5, ansiTheme); + + for (const ch of "gpt m") { + list.handleInput(ch); + } + + const renderedLine = list.render(80).find((line) => stripAnsi(line).includes("gpt-model")); + expect(renderedLine).toBeDefined(); + const highlightOpens = renderedLine ? renderedLine.split("\u001b[31m").length - 1 : 0; + expect(highlightOpens).toBe(2); + }); + it("filters items when typing", () => { const list = new SearchableSelectList(testItems, 5, mockTheme);