spb/khaelor Public
KHAELOR — a terminal-native autonomous engineering agent powered by Anthropic.
TypeScript 82.9%
HTML 14.9%
CSS 1.1%
JavaScript 0.7%
1/**2 * KHAELOR3 * File: tests/tui/theme.test.ts4 * Description: Theme tests — capability ladder, NO_COLOR monochrome purity, light/dark variants.5 *6 * Author: Simon-Pierre Boucher7 * Contact: contact@spboucher.ai8 */910import { describe, expect, it } from "vitest";11import { detectColorDepth } from "../../src/tui/renderer/capabilities.js";12import { resolveTheme } from "../../src/tui/theme.js";13import type { StyleRole } from "../../src/tui/theme.js";1415const ALL_ROLES: StyleRole[] = [16 "text",17 "dim",18 "accent",19 "accentDim",20 "success",21 "warning",22 "error",23 "code",24 "violet",25 "cyan",26 "magenta",27 "teal",28 "orange",29 "bold",30 "italic",31 "strike",32 "underline",33 "invert",34 "synKeyword",35 "synString",36 "synComment",37 "synNumber",38 "synFunction",39 "synType",40];4142describe("resolveTheme", () => {43 it("mono emits no SGR codes for any role (NO_COLOR honored absolutely)", () => {44 const mono = resolveTheme({ colorDepth: "mono" });45 for (const role of ALL_ROLES) {46 expect(mono.paint(role, "state")).toBe("state");47 }48 });4950 it("truecolor emits 24-bit foreground and closes with default-fg", () => {51 const dark = resolveTheme({ colorDepth: "truecolor" });52 const painted = dark.paint("accent", "x");53 expect(painted).toMatch(/^\x1b\[38;2;\d+;\d+;\d+mx\x1b\[39m$/);54 });5556 it("ansi256 uses indexed colors, ansi16 uses basic codes", () => {57 expect(resolveTheme({ colorDepth: "ansi256" }).paint("error", "x")).toContain("38;5;");58 expect(resolveTheme({ colorDepth: "ansi16" }).paint("error", "x")).toContain("\x1b[31m");59 });6061 it("text role in ansi16 stays the terminal default (no SGR)", () => {62 expect(resolveTheme({ colorDepth: "ansi16" }).paint("text", "x")).toBe("x");63 });6465 it("format roles use targeted close codes, never a full reset", () => {66 const dark = resolveTheme({ colorDepth: "truecolor" });67 expect(dark.paint("bold", "x")).toBe("\x1b[1mx\x1b[22m");68 expect(dark.paint("underline", "x")).toBe("\x1b[4mx\x1b[24m");69 expect(dark.paint("bold", "x")).not.toContain("\x1b[0m");70 });7172 it("selects the light variant for light backgrounds", () => {73 const light = resolveTheme({ colorDepth: "truecolor", background: "light" });74 expect(light.name).toBe("khaelor-light");75 expect(light.paint("text", "x")).not.toBe(76 resolveTheme({ colorDepth: "truecolor" }).paint("text", "x"),77 );78 });7980 it("painting an empty string emits nothing", () => {81 const dark = resolveTheme({ colorDepth: "truecolor" });82 expect(dark.paint("accent", "")).toBe("");83 });84});8586describe("gradients and tints", () => {87 it("brand gradient interpolates per character in truecolor and strips clean", () => {88 const dark = resolveTheme({ colorDepth: "truecolor" });89 const painted = dark.paintGradient("brand", "KHAELOR");90 const codes = painted.match(/\x1b\[38;2;\d+;\d+;\d+m/g) ?? [];91 expect(codes.length).toBe(7); // one 24-bit color per character92 expect(new Set(codes).size).toBeGreaterThan(1); // actually a gradient93 expect(painted.replace(/\x1b\[[0-9;]*m/g, "")).toBe("KHAELOR");94 });9596 it("gradient degrades to a solid brand color on ansi256 and to identity on mono", () => {97 const c256 = resolveTheme({ colorDepth: "ansi256" }).paintGradient("brand", "KHAELOR");98 expect(c256.match(/38;5;/g)?.length).toBe(1);99 expect(resolveTheme({ colorDepth: "mono" }).paintGradient("brand", "KHAELOR")).toBe("KHAELOR");100 });101102 it("line tints are truecolor-only backgrounds that close with bg-reset", () => {103 const dark = resolveTheme({ colorDepth: "truecolor" });104 expect(dark.tintLine("add", "x")).toMatch(/^\x1b\[48;2;\d+;\d+;\d+mx\x1b\[49m$/);105 expect(resolveTheme({ colorDepth: "ansi256" }).tintLine("add", "x")).toBe("x");106 expect(resolveTheme({ colorDepth: "mono" }).tintLine("del", "x")).toBe("x");107 });108});109110describe("detectColorDepth", () => {111 it("NO_COLOR and TERM=dumb force mono; non-TTY is mono", () => {112 expect(detectColorDepth({ NO_COLOR: "1", COLORTERM: "truecolor" }, true)).toBe("mono");113 expect(detectColorDepth({ TERM: "dumb" }, true)).toBe("mono");114 expect(detectColorDepth({ COLORTERM: "truecolor", TERM: "xterm" }, false)).toBe("mono");115 });116117 it("walks the ladder: truecolor → 256 → 16", () => {118 expect(detectColorDepth({ COLORTERM: "truecolor", TERM: "xterm-256color" }, true)).toBe(119 "truecolor",120 );121 expect(detectColorDepth({ TERM: "xterm-256color" }, true)).toBe("ansi256");122 expect(detectColorDepth({ TERM: "xterm" }, true)).toBe("ansi16");123 });124});125