import { stripVTControlCharacters } from "node:util"; import { Theme } from "@earendil-works/pi-coding-agent"; import { visibleWidth } from "@earendil-works/pi-tui"; import { describe, expect, it } from "vitest"; import { renderBlocksBar } from "../progress.ts"; function makeTheme(dim: string | number, faint = false): Theme { // Supply renderer tokens and optional-token fallbacks through the public API. return new Theme( { dim, warning: 3, muted: 8, text: "", thinkingXhigh: 8, } as ConstructorParameters[0], { selectedBg: "" } as ConstructorParameters[1], "truecolor", { appearance: "dark", dim: faint ? ["dim"] : [] }, ); } describe("footer progress styling", () => { it.each([ ["terminal default", "", true], ["indexed palette", 8, false], ["RGB palette", "#666666", false], ] as const)("paints a concrete track with %s colors", (_name, dim, faint) => { const bar = renderBlocksBar(50, 2, makeTheme(dim, faint), "warning"); expect(stripVTControlCharacters(bar)).toBe("█ "); expect(visibleWidth(bar)).toBe(2); expect(bar).toContain("\x1b[48;"); expect(bar).toContain("\x1b[49m"); expect(bar).not.toContain("\x1b[2m"); }); it("closes faint foreground styling before subsequent footer text", () => { const bar = renderBlocksBar(100, 1, makeTheme("", true), "dim"); expect(bar).toContain("\x1b[2m"); expect(bar).toMatch(/\x1b\[[\d;]*22(?:;\d+)*m/u); expect(bar.lastIndexOf("22")).toBeGreaterThan(bar.indexOf("\x1b[2m")); expect(stripVTControlCharacters(`${bar} next`)).toBe("█ next"); }); it.each([ [-10, " "], [0, " "], [25, "▄ "], [50, "█ "], [100, "██"], [110, "██"], ] as const)("preserves block levels at %s%%", (percent, expected) => { const bar = renderBlocksBar(percent, 2, makeTheme(8), "warning"); expect(stripVTControlCharacters(bar)).toBe(expected); }); });