openclaw / src /agents /modes /interactive /theme /theme.test.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
4c76b0d verified
Raw History Blame Contribute Delete
4.28 kB
import { writeFileSync } from "node:fs";
import { join } from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { useAutoCleanupTempDirTracker } from "../../../../../test/helpers/temp-dir.js";
import darkTheme from "./dark.json" with { type: "json" };
import { loadThemeFromPath } from "./theme.js";
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
function loadColor(color: string | number, mode: "256color" | "truecolor") {
const themePath = join(tempDirs.make("openclaw-theme-"), "theme.json");
writeFileSync(
themePath,
JSON.stringify({
...darkTheme,
colors: { ...darkTheme.colors, accent: color, selectedBg: color },
}),
);
return loadThemeFromPath(themePath, mode);
}
describe("loadThemeFromPath", () => {
it.each([
["#0000ff", 21],
["#2f00ff", 21],
["#3000ff", 57],
["#7200ff", 57],
["#7300ff", 57],
["#7400ff", 93],
["#9a00ff", 93],
["#9b00ff", 93],
["#9c00ff", 129],
["#c200ff", 129],
["#c300ff", 129],
["#c400ff", 165],
["#ea00ff", 165],
["#eb00ff", 165],
["#ec00ff", 201],
["#ff00ff", 201],
["#ff7200", 202],
["#ff7300", 202],
["#ff7400", 208],
["#00ff72", 47],
["#00ff73", 47],
["#00ff74", 48],
["#5f87af", 67],
["#000000", 16],
["#040404", 16],
["#050505", 232],
["#080808", 232],
["#0c0c0c", 232],
["#0d0d0d", 232],
["#0e0e0e", 233],
["#707070", 242],
["#717171", 242],
["#727272", 243],
["#e8e8e8", 254],
["#e9e9e9", 254],
["#eaeaea", 255],
["#eeeeee", 255],
["#ffffff", 231],
["#606060", 59],
["#616161", 241],
["#0c0d0e", 232],
["#0d0d16", 233],
["#0d0d17", 16],
])(
"renders %s as palette index %i with separate foreground/background resets",
(color, index) => {
const theme = loadColor(color, "256color");
expect(theme.fg("accent", "text")).toBe(`\x1b[38;5;${index}mtext\x1b[39m`);
expect(theme.bg("selectedBg", "text")).toBe(`\x1b[48;5;${index}mtext\x1b[49m`);
},
);
it("preserves RGB channels in truecolor output", () => {
const theme = loadColor("#5f87af", "truecolor");
expect(theme.getFgAnsi("accent")).toBe("\x1b[38;2;95;135;175m");
expect(theme.getBgAnsi("selectedBg")).toBe("\x1b[48;2;95;135;175m");
expect(theme.fg("accent", "text")).toBe("\x1b[38;2;95;135;175mtext\x1b[39m");
expect(theme.bg("selectedBg", "text")).toBe("\x1b[48;2;95;135;175mtext\x1b[49m");
});
it.each(["256color", "truecolor"] as const)(
"preserves numeric and reset colors in %s",
(mode) => {
for (const index of [0, 123, 255]) {
const theme = loadColor(index, mode);
expect(theme.getFgAnsi("accent")).toBe(`\x1b[38;5;${index}m`);
expect(theme.getBgAnsi("selectedBg")).toBe(`\x1b[48;5;${index}m`);
expect(theme.fg("accent", "text")).toBe(`\x1b[38;5;${index}mtext\x1b[39m`);
expect(theme.bg("selectedBg", "text")).toBe(`\x1b[48;5;${index}mtext\x1b[49m`);
}
const reset = loadColor("", mode);
expect(reset.getFgAnsi("accent")).toBe("\x1b[39m");
expect(reset.getBgAnsi("selectedBg")).toBe("\x1b[49m");
expect(reset.fg("accent", "text")).toBe("\x1b[39mtext\x1b[39m");
expect(reset.bg("selectedBg", "text")).toBe("\x1b[49mtext\x1b[49m");
},
);
it.each([
["fg", /^Unknown theme color: missing$/],
["getFgAnsi", /^Unknown theme color: missing$/],
["bg", /^Unknown theme background color: missing$/],
["getBgAnsi", /^Unknown theme background color: missing$/],
] as const)("rejects unknown colors through %s", (method, message) => {
const theme = loadColor(123, "256color");
expect(() => Reflect.apply(theme[method], theme, ["missing", "text"])).toThrow(message);
});
it("keeps formatting independent of supplied raw ANSI getter overrides", () => {
const theme = loadColor(123, "256color");
theme.getFgAnsi = () => "\x1b[31m";
theme.getBgAnsi = () => "\x1b[41m";
expect(theme.getFgAnsi("accent")).toBe("\x1b[31m");
expect(theme.getBgAnsi("selectedBg")).toBe("\x1b[41m");
expect(theme.fg("accent", "text")).toBe("\x1b[38;5;123mtext\x1b[39m");
expect(theme.bg("selectedBg", "text")).toBe("\x1b[48;5;123mtext\x1b[49m");
});
});