Files
siftlode/frontend/src/lib/menuGlyphs.contract.test.ts
T

35 lines
1.5 KiB
TypeScript

import { readdirSync, readFileSync } from "node:fs";
import { describe, expect, it } from "vitest";
// DESIGN "A glyph means one thing": a menu row wears the glyph its action wears everywhere else. The
// "…" menus' Play from the beginning wore the skip arrow ⏮, which beside the player is Previous, while
// the card's own Restart wore RotateCcw — and RotateCcw was the same menu's Reset, the Files menu's
// Redownload. Each row is a `label: t("…")` followed by its `icon`.
const ROW_GLYPHS: Record<string, string> = {
"card.restartTitle": "RotateCcw",
"card.clearProgress": "Eraser",
"files.actions.redownload": "RefreshCw",
};
const root = new URL("../", import.meta.url);
const sources = () =>
readdirSync(root, { recursive: true, encoding: "utf8" })
.map((f) => f.replace(/\\/g, "/"))
.filter((f) => /\.tsx?$/.test(f) && !f.includes(".test.") && !f.startsWith("design-lab/"))
.map((f) => ({ f, src: readFileSync(new URL(f, root), "utf8") }));
describe("a menu row's glyph", () => {
for (const [key, glyph] of Object.entries(ROW_GLYPHS))
it(`${key} wears ${glyph}`, () => {
const row = new RegExp(
`label: t\\("${key.replace(/\./g, "\\.")}"\\),\\s*icon: (?:<|icon\\()(\\w+)`,
"g",
);
const worn = sources().flatMap(({ f, src }) =>
[...src.matchAll(row)].map((m) => `${f}: ${m[1]}`),
);
expect(worn.length, key).toBeGreaterThan(0);
expect(worn.filter((w) => !w.endsWith(`: ${glyph}`))).toEqual([]);
});
});