scripts/theme-check.test.mjs
Rama main · réplica de lectura del repositorio original.
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import test from "node:test";
const script = resolve(import.meta.dirname, "theme-check.mjs");
function theme(dir, name, { manifest = {}, css }) {
const root = join(dir, name);
mkdirSync(root, { recursive: true });
writeFileSync(join(root, "manifest.json"), JSON.stringify({ apiVersion: 1, id: name, name, ...manifest }));
writeFileSync(join(root, "theme.css"), css);
return root;
}
function run(...args) {
const result = spawnSync(process.execPath, [script, ...args], { encoding: "utf8" });
return { status: result.status, stdout: result.stdout, stderr: result.stderr };
}
test("passes a good theme and fails a bad one, with every problem listed", (t) => {
const dir = mkdtempSync(join(tmpdir(), "theme-check-"));
t.after(() => rmSync(dir, { recursive: true, force: true }));
const good = theme(dir, "paper", { css: ".theme-light { --radius: 0.25rem; --page-canvas: #fffdf8; }\n" });
const bad = theme(dir, "leaky", {
css: [
".theme-light { --muted-foreground: #bbbbbb; --font-ui: Inter, sans-serif; --sidebar-width: 1px; }",
"body { background: url(https://evil.test/?q=1); }",
].join("\n"),
});
const passing = run(good);
assert.equal(passing.status, 0, passing.stdout + passing.stderr);
assert.match(passing.stdout, /✓ paper \(paper\)/);
const failing = run(good, bad, "--json");
assert.equal(failing.status, 1);
const [paper, leaky] = JSON.parse(failing.stdout);
assert.equal(paper.ok, true);
assert.equal(leaky.ok, false);
const messages = leaky.issues.map((issue) => issue.message).join("\n");
assert.match(messages, /theme\.css line 2: url\("https:\/\/evil\.test\/\?q=1"\)/);
assert.match(messages, /--sidebar-width is not a public variable/);
assert.match(messages, /--font-ui names sans-serif/);
assert.match(messages, /--muted-foreground is below 4\.5:1 on .*--surface \(\d\.\d\d:1\)/);
});
test("fails a theme on an option value other than the default", (t) => {
const dir = mkdtempSync(join(tmpdir(), "theme-check-"));
t.after(() => rmSync(dir, { recursive: true, force: true }));
const screens = theme(dir, "screens", {
manifest: {
options: [
{ id: "screen", type: "select", label: "Screen", default: "prompt", choices: [
{ value: "prompt", label: "Prompt" },
{ value: "amber", label: "Amber" },
] },
],
},
css: ':root[data-theme-option-screen="amber"].theme-dark { --muted-foreground: #5a3e00; }\n',
});
const text = run(screens);
assert.equal(text.status, 1, text.stdout + text.stderr);
assert.match(text.stdout, /Contrast, dark mode \(Screen = Amber\): --muted-foreground is below 4\.5:1/);
assert.match(text.stdout, /pairs checked at the default options and 1 other option value, \d+ below WCAG AA\. Lowest: --muted-foreground on .*, dark \(Screen = Amber\)/);
const [report] = JSON.parse(run(screens, "--json").stdout);
const failing = report.contrast.filter((c) => c.ratio < c.required);
assert.ok(failing.length > 0);
assert.ok(failing.every((c) => c.variant?.option === "screen" && c.variant.value === "amber"));
});
test("writes a --json report larger than a pipe buffer in full", (t) => {
const dir = mkdtempSync(join(tmpdir(), "theme-check-"));
t.after(() => rmSync(dir, { recursive: true, force: true }));
const choices = Array.from({ length: 8 }, (_, i) => ({ value: `s${i}`, label: `Screen ${i}` }));
const screens = theme(dir, "screens", {
manifest: { options: [{ id: "screen", type: "select", label: "Screen", default: "s0", choices }] },
css: ".theme-light { --radius: 0.25rem; }\n",
});
const result = run(screens, "--json");
assert.equal(result.status, 0, result.stderr);
assert.ok(result.stdout.length > 65536, `the report is only ${result.stdout.length} bytes`);
const [report] = JSON.parse(result.stdout);
assert.equal(report.ok, true);
});
test("explains a path that is not a theme", () => {
const result = run(join(tmpdir(), "no-such-theme-folder"));
assert.equal(result.status, 1);
assert.match(result.stdout, /no-such-theme-folder/);
});