Four editable colors (background/foreground/cursor/selection) via a new ColorPanel modal with built-in presets and live preview. Global default persists to localStorage and syncs across windows; per-pane overrides ride on LeafNode.colorOverride in the workspace tree. Titlebar 🎨 button edits the global theme; per-pane 🎨 chip overrides a single pane. Subsumes the prior uncommitted softened-foreground tweak into lib/theme.ts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
79 lines
2.7 KiB
TypeScript
79 lines
2.7 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import {
|
|
resolvePaneColors,
|
|
toXtermTheme,
|
|
DEFAULT_PANE_COLORS,
|
|
COLOR_PRESETS,
|
|
type PaneColors,
|
|
} from "./theme";
|
|
|
|
describe("resolvePaneColors", () => {
|
|
it("falls back to defaults when nothing is set", () => {
|
|
expect(resolvePaneColors(undefined, undefined)).toEqual(DEFAULT_PANE_COLORS);
|
|
});
|
|
|
|
it("uses global values over defaults", () => {
|
|
const global: PaneColors = { background: "#111111", cursor: "#abcdef" };
|
|
const r = resolvePaneColors(global, undefined);
|
|
expect(r.background).toBe("#111111");
|
|
expect(r.cursor).toBe("#abcdef");
|
|
// Unset fields still come from defaults.
|
|
expect(r.foreground).toBe(DEFAULT_PANE_COLORS.foreground);
|
|
expect(r.selection).toBe(DEFAULT_PANE_COLORS.selection);
|
|
});
|
|
|
|
it("per-pane override wins over global, field by field", () => {
|
|
const global: PaneColors = { background: "#111111", foreground: "#222222" };
|
|
const override: PaneColors = { background: "#999999" };
|
|
const r = resolvePaneColors(global, override);
|
|
expect(r.background).toBe("#999999"); // override wins
|
|
expect(r.foreground).toBe("#222222"); // inherits global
|
|
expect(r.cursor).toBe(DEFAULT_PANE_COLORS.cursor); // inherits default
|
|
});
|
|
|
|
it("always returns all four fields defined", () => {
|
|
const r = resolvePaneColors({}, {});
|
|
expect(Object.keys(r).sort()).toEqual([
|
|
"background",
|
|
"cursor",
|
|
"foreground",
|
|
"selection",
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe("toXtermTheme", () => {
|
|
it("maps resolved colours onto the xterm ITheme shape", () => {
|
|
const theme = toXtermTheme({
|
|
background: "#0c0c0c",
|
|
foreground: "#c5c8c6",
|
|
cursor: "#ffffff",
|
|
selection: "#3a3a3a",
|
|
});
|
|
expect(theme.background).toBe("#0c0c0c");
|
|
expect(theme.foreground).toBe("#c5c8c6");
|
|
expect(theme.cursor).toBe("#ffffff");
|
|
// selection maps to xterm 5.x's renamed property.
|
|
expect(theme.selectionBackground).toBe("#3a3a3a");
|
|
// cursorAccent is pinned to the background for block-cursor legibility.
|
|
expect(theme.cursorAccent).toBe("#0c0c0c");
|
|
});
|
|
|
|
it("keeps the fixed softened white/brightWhite slice", () => {
|
|
const theme = toXtermTheme(DEFAULT_PANE_COLORS);
|
|
expect(theme.white).toBe("#c5c8c6");
|
|
expect(theme.brightWhite).toBe("#e0e0e0");
|
|
});
|
|
});
|
|
|
|
describe("COLOR_PRESETS", () => {
|
|
it("starts with the tiletopia default and every preset is fully specified", () => {
|
|
expect(COLOR_PRESETS[0].name).toBe("Tiletopia Dark");
|
|
expect(COLOR_PRESETS[0].colors).toEqual(DEFAULT_PANE_COLORS);
|
|
for (const p of COLOR_PRESETS) {
|
|
for (const key of ["background", "foreground", "cursor", "selection"] as const) {
|
|
expect(p.colors[key]).toMatch(/^#[0-9a-fA-F]{6}$/);
|
|
}
|
|
}
|
|
});
|
|
});
|