chore(vendor): import pi 1.3.0
Imported as a linear vendor commit so forge rebases do not replay the upstream root tree. git-subtree-dir: third_party/pi git-subtree-split: 71023aec1546d6d677a65669c5665c0927ce0dd0
This commit is contained in:
104
third_party/pi/packages/coding-agent/test/theme-style.test.ts
vendored
Normal file
104
third_party/pi/packages/coding-agent/test/theme-style.test.ts
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { colorToHex, okhslColor, styleText } from "@earendil-works/pi-tui";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { loadThemeFromPath, setTerminalColors } from "../src/modes/interactive/theme/theme.ts";
|
||||
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
type ThemeFile = {
|
||||
name: string;
|
||||
appearance?: "dark" | "light";
|
||||
vars?: Record<string, string | number>;
|
||||
colors: Record<string, string | number>;
|
||||
};
|
||||
|
||||
/** Load a copy of a built-in theme, modified by `edit`. */
|
||||
function loadTheme(base: "dark" | "light", edit: (theme: ThemeFile) => void = () => {}) {
|
||||
const themeJson = JSON.parse(
|
||||
readFileSync(new URL(`../src/modes/interactive/theme/${base}.json`, import.meta.url), "utf8"),
|
||||
) as ThemeFile;
|
||||
edit(themeJson);
|
||||
const dir = mkdtempSync(join(tmpdir(), "pi-theme-style-"));
|
||||
tempDirs.push(dir);
|
||||
const path = join(dir, `${themeJson.name}.json`);
|
||||
writeFileSync(path, JSON.stringify(themeJson));
|
||||
return loadThemeFromPath(path, "truecolor");
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
setTerminalColors({});
|
||||
for (const dir of tempDirs.splice(0)) rmSync(dir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
describe("theme styles", () => {
|
||||
it("renders theme tokens the same as the generic text styler", () => {
|
||||
const theme = loadTheme("dark");
|
||||
expect(theme.style("Ready", { fg: "success", bg: "toolSuccessBg", bold: true })).toBe(
|
||||
styleText("Ready", { fg: theme.colors.success, bg: theme.colors.toolSuccessBg, bold: true }, "truecolor"),
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects unknown tokens and tokens in the wrong slot", () => {
|
||||
const theme = loadTheme("dark");
|
||||
expect(() => theme.style("x", { fg: "notAToken" as never })).toThrow("Unknown theme color: notAToken");
|
||||
// @ts-expect-error background tokens are not foreground colors; use theme.colors.userMessageBg
|
||||
expect(() => theme.style("x", { fg: "userMessageBg" })).toThrow("Unknown theme color: userMessageBg");
|
||||
});
|
||||
|
||||
it("loads OKLCH theme values", () => {
|
||||
const theme = loadTheme("dark", (json) => {
|
||||
json.colors.accent = "oklch(62% 0.1 200)";
|
||||
});
|
||||
expect(theme.colors.accent).toEqual({ kind: "oklch", l: 0.62, c: 0.1, h: 200 });
|
||||
});
|
||||
|
||||
it("loads OKHSL theme values, including through variables", () => {
|
||||
const theme = loadTheme("dark", (json) => {
|
||||
json.vars = { ...json.vars, brand: "okhsl(250 60% 55%)" };
|
||||
json.colors.accent = "brand";
|
||||
json.colors.error = "okhsl(20 90% 60%)";
|
||||
});
|
||||
expect(colorToHex(theme.colors.accent)).toBe(colorToHex(okhslColor(250, 0.6, 0.55)));
|
||||
expect(colorToHex(theme.colors.error)).toBe(colorToHex(okhslColor(20, 0.9, 0.6)));
|
||||
});
|
||||
|
||||
it("detects the appearance unless it is declared", () => {
|
||||
expect(loadTheme("dark").appearance).toBe("dark");
|
||||
expect(loadTheme("light").appearance).toBe("light");
|
||||
// Without a declaration, the appearance is detected from the theme's own colors.
|
||||
for (const base of ["dark", "light"] as const) {
|
||||
expect(loadTheme(base, (json) => delete json.appearance).appearance).toBe(base);
|
||||
}
|
||||
expect(
|
||||
loadTheme("dark", (json) => {
|
||||
json.appearance = "light";
|
||||
}).appearance,
|
||||
).toBe("light");
|
||||
|
||||
// Palette colors 0-15 follow the terminal palette, so such themes follow the terminal background.
|
||||
const paletteOnly = loadTheme("dark", (json) => {
|
||||
delete json.appearance;
|
||||
for (const key of Object.keys(json.colors)) json.colors[key] = key.endsWith("Bg") ? 0 : 7;
|
||||
});
|
||||
expect(paletteOnly.appearance).toBe("dark");
|
||||
setTerminalColors({ background: { r: 250, g: 250, b: 250 } });
|
||||
expect(paletteOnly.appearance).toBe("light");
|
||||
});
|
||||
|
||||
it("renders empty tokens as terminal defaults and reports concrete colors for them", () => {
|
||||
const theme = loadTheme("dark", (json) => {
|
||||
json.colors.text = "";
|
||||
json.colors.userMessageBg = "";
|
||||
});
|
||||
expect(theme.fg("text", "x")).toBe("\x1b[39mx\x1b[39m");
|
||||
expect(theme.bg("userMessageBg", "x")).toBe("\x1b[49mx\x1b[49m");
|
||||
expect(colorToHex(theme.colors.text)).toBe("#e5e5e7");
|
||||
expect(colorToHex(theme.colors.userMessageBg)).toBe("#000000");
|
||||
|
||||
setTerminalColors({ foreground: { r: 200, g: 210, b: 220 }, background: { r: 10, g: 20, b: 30 } });
|
||||
expect(colorToHex(theme.colors.text)).toBe("#c8d2dc");
|
||||
expect(colorToHex(theme.colors.userMessageBg)).toBe("#0a141e");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user