The watched toggle weighed its own click at click time against marks stamped when the server answered, so the late answer of its previous click overruled the newer one; it now holds its word while any of its writes is on the way, is stamped at the answer, and drops a refused write. The hosts return the write. In a live search the feed's own answered writes stand down over the rows, which carry the marks, so a newer mark from the related list reaches a card the feed touched earlier. A lit mode cycle got the hover layer, and a variant's hover colour outranked the pressed and lit colours: the state layer stands down on [data-lit] and those colours are important. The menus' Play from the beginning wears RotateCcw like the card's Restart, Reset wears Eraser and Redownload the retry arrow (DESIGN decision log). Tests pin the toggle's orders, the Watch later mark at its call sites, the live-search overrides, the hover exemptions and the menu glyphs.
35 lines
1.5 KiB
TypeScript
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.resetState": "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([]);
|
|
});
|
|
});
|