Файли
openidea/src/platform/colorScheme.test.ts
T
l0ng-aiтаbghtyu 24330192c0 feat(themes): Solarized and One theme plugins (#92)
Solarized Light/Dark and One Light/Dark ship as theme plugins (plugins/theme-solarized, plugins/theme-one) with complete UI tokens, editor schemes and terminal colours, and the original palettes' MIT notices. Installing a plugin now also copies its LICENSE/NOTICE/COPYING.

Theme handling that benefits every plugin theme:
- The last plugin theme is restored before first paint (splash, workbench); the native window background follows the theme.
- HSL / color-mix() / var() colours are resolved to RGB for native windows, the Settings window and Mermaid.
- Mermaid, the terminal and editor schemes follow switches between two themes of the same kind.
- An explicitly chosen editor scheme wins over a plugin theme's editor colours.

Supersedes #85.

Co-authored-by: bghtyu <[email protected]>
Claude-Session: https://claude.ai/code/session_01VUYsdXA2nkF5v6zsC93dpn
2026-10-27 17:53:07 +08:00

111 рядки
5.7 KiB
TypeScript

import { readFileSync } from "node:fs";
import { beforeAll, describe, expect, it } from "vitest";
import { _setSchemeSource, schemeCss, schemeTokens } from "./colorScheme";
import { fontWeightVars, validateEditorExtras } from "./editorSettings";
import type { Settings } from "./ipc";
import { useProject } from "./project";
import { installColorScheme } from "@/editor/core/colorSchemeRuntime";
describe("color schemes", () => {
beforeAll(() => _setSchemeSource(readFileSync("src/styles/tokens.css", "utf8")));
it("reads resolved scheme tokens from tokens.css", () => {
expect(schemeTokens("light").get("--syn-keyword")).toBe("#0033b3");
expect(schemeTokens("dark").get("--syn-keyword")).toBe("#cf8e6d");
expect(schemeTokens("light").get("--bg-editor")).not.toMatch(/var\(/);
});
it("emits scheme and override declarations", () => {
const root = document.documentElement;
root.dataset.theme = "light";
expect(schemeCss("theme-default", undefined, root)).toBe("");
const dark = schemeCss("dark", undefined, root);
expect(dark).toContain("--syn-keyword: #cf8e6d !important");
const over = schemeCss("theme-default", { light: { keyword: { foreground: "#ff0000", bold: true } } }, root);
expect(over).toContain("--syn-keyword: #ff0000 !important");
// Explicit Bold / regular follow Typography Settings' bold / main weight.
expect(over).toContain("--syn-keyword-bold: var(--fw-code-bold, 700) !important");
const regular = schemeCss("theme-default", { light: { keyword: { bold: false } } }, root);
expect(regular).toContain("--syn-keyword-bold: var(--fw-code, 400) !important");
const upright = schemeCss("theme-default", { light: { staticSymbol: { italic: false } } }, root);
expect(upright).toContain("--syn-static-italic: normal !important");
const pythonItalic = schemeCss("theme-default", { light: { pyStaticSymbol: { italic: true } } }, root);
expect(pythonItalic).toContain("--syn-python-static-italic: italic !important");
});
it("applies an explicit scheme over a plugin theme of the same base", () => {
const root = document.documentElement;
root.dataset.theme = "dark";
root.dataset.pluginTheme = "theme-one/one-dark";
try {
// "Theme default" keeps the plugin theme's own editor colours.
expect(schemeCss("theme-default", undefined, root)).toBe("");
expect(schemeCss("dark", undefined, root)).toContain("--syn-keyword: #cf8e6d !important");
delete root.dataset.pluginTheme;
expect(schemeCss("dark", undefined, root)).toBe("");
} finally {
delete root.dataset.pluginTheme;
}
});
it("re-applies the scheme when a plugin theme of the same base is selected", async () => {
const root = document.documentElement;
root.dataset.theme = "dark";
const previous = useProject.getState().settings;
useProject.setState({ settings: { editor: { colorScheme: "dark" } } as unknown as Settings });
const dispose = installColorScheme();
const sheet = () => document.getElementById("openidea-editor-color-scheme")?.textContent ?? "";
try {
expect(sheet()).toBe("");
root.dataset.pluginTheme = "theme-one/one-dark";
await Promise.resolve();
expect(sheet()).toContain("--syn-keyword: #cf8e6d !important");
} finally {
dispose();
delete root.dataset.pluginTheme;
useProject.setState({ settings: previous });
}
});
it("keeps editor weights out of the schemes", () => {
for (const id of ["light", "dark", "dark-hc"] as const) {
expect(schemeTokens(id).has("--fw-code")).toBe(false);
expect(schemeTokens(id).has("--syn-keyword-bold")).toBe(false);
}
// Dark schemes bold matched braces with the configured bold weight, even under the light theme.
expect(schemeTokens("dark").get("--matched-brace-weight")).toBe("var(--fw-code-bold)");
expect(schemeTokens("light").get("--matched-brace-weight")).toBe("inherit");
const root = document.documentElement;
root.dataset.theme = "light";
expect(schemeCss("dark", undefined, root)).toContain("--matched-brace-weight: var(--fw-code-bold) !important");
});
it("sets the configured weights on the root and follows settings changes", () => {
const root = document.documentElement;
useProject.setState({ settings: { editor: { fontWeight: 300, boldFontWeight: 800 } } as unknown as Settings });
const dispose = installColorScheme();
expect([root.style.getPropertyValue("--fw-code"), root.style.getPropertyValue("--fw-code-bold")]).toEqual(["300", "800"]);
useProject.setState({ settings: { editor: {} } as unknown as Settings });
expect([root.style.getPropertyValue("--fw-code"), root.style.getPropertyValue("--fw-code-bold")]).toEqual(["400", "700"]);
dispose();
expect(root.style.getPropertyValue("--fw-code")).toBe("");
});
it("validates Typography Settings weights", () => {
const d = validateEditorExtras({});
expect([d.fontWeight, d.boldFontWeight]).toEqual([400, 700]);
const v = validateEditorExtras({ fontWeight: 349, boldFontWeight: 1200 });
expect([v.fontWeight, v.boldFontWeight]).toEqual([350, 900]);
expect(validateEditorExtras({ fontWeight: "bold" }).fontWeight).toBe(400);
expect(fontWeightVars(500, undefined)).toEqual({ "--fw-code": "500", "--fw-code-bold": "700" });
expect(fontWeightVars(450, 650)).toEqual({ "--fw-code": "450", "--fw-code-bold": "650" });
});
it("coerces editor settings to their defaults' shape", () => {
const v = validateEditorExtras({ tabs: { tabLimit: "x", placement: "none" }, codeStyle: { languages: { go: { useTab: true } } } });
expect(v.tabs.tabLimit).toBe(30);
expect(v.tabs.placement).toBe("none");
expect(v.codeStyle.languages.go).toEqual({ useTab: true });
expect(v.general.softWrapFiles).toContain("*.md");
});
});