Ctrl K
ComunidadGuías…

scripts/theme-check.test.mjs

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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/);
});