A pure move (git mv, history kept): 333 files leave components/, which now holds only the platform layer (ui/) and the legal pages. The shell is the frame (App, header, nav rail, palette, boot); each page lives in modules/<page>/ with its rail, store and tests; what several pages share sits in features/<name>/; the shared-link page is watch/. Domain-free helpers went straight to ui/ (ViewSwitcher, FilterRail, the confirm dialog, the language switcher, the copy rows). Every relative specifier was rewritten (imports, dynamic imports, vi.mock, new URL), and the contract tests that scan sources now scan the whole app source, not only components/ — each glob was counted before and after so none went green over fewer files. The locale-format rule matched `/src/components/` only, so the move would have taken ~300 files out from under its i18n-singleton check; it now covers every folder that holds components, with a test per folder.
68 lines
3.1 KiB
TypeScript
68 lines
3.1 KiB
TypeScript
import { readFileSync } from "node:fs";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import videoCard from "./VideoCard.tsx?raw";
|
|
import { HOVER_REVEAL, HOVER_YIELD } from "../../components/ui/reveal";
|
|
|
|
import {
|
|
MEDIA_BAR,
|
|
MEDIA_HOVER_LAYER,
|
|
MEDIA_PICTURE_CONTAINER,
|
|
MEDIA_RESUME_LAYER,
|
|
mediaPictureBox,
|
|
} from "../../components/ui/mediaCardStyles";
|
|
|
|
// Measured: at text size 1.3 a 242px feed thumbnail put the 63px Play 13px into the 37px hover
|
|
// row (the Play and the row grow with the root font, the picture's height does not). A narrow picture
|
|
// (under 14rem, so the cut moves with the text size) centres what it carries above the row instead.
|
|
describe("a media picture", () => {
|
|
it("is a size container, so what sits on it answers its width", () => {
|
|
expect(mediaPictureBox("wide")).toContain(MEDIA_PICTURE_CONTAINER);
|
|
expect(mediaPictureBox("poster")).toContain(MEDIA_PICTURE_CONTAINER);
|
|
// The video card draws its own box (it is not a mediaPictureBox).
|
|
expect(videoCard).toContain('clsx("relative min-h-0", MEDIA_PICTURE_CONTAINER, className)');
|
|
});
|
|
|
|
it.each([
|
|
["the Play", MEDIA_HOVER_LAYER],
|
|
["the Continue + Restart pair", MEDIA_RESUME_LAYER],
|
|
])("leaves the hover row its band when narrow: %s", (_, layer) => {
|
|
expect(layer.split(" ")).toContain("@max-[14rem]/pic:pb-(--bar-room)");
|
|
});
|
|
|
|
// The lift once applied to every narrow picture, and the feed's row and tile views (a
|
|
// 10rem thumbnail, no hover row) showed their Play 1.25rem above centre over an empty band. The band
|
|
// is now the row's own: set only by a box that holds one.
|
|
it("lifts it only where a hover row is there to clear", () => {
|
|
expect(MEDIA_BAR.split(" ")).toContain("media-bar");
|
|
expect(MEDIA_PICTURE_CONTAINER.split(" ")).toContain("has-[.media-bar]:[--bar-room:2.5rem]");
|
|
for (const layer of [MEDIA_HOVER_LAYER, MEDIA_RESUME_LAYER])
|
|
expect(layer).not.toMatch(/pic:pb-\d/);
|
|
});
|
|
});
|
|
|
|
const indexCss = readFileSync(new URL("../../index.css", import.meta.url), "utf8");
|
|
|
|
// Back from a channel page hands the card its focus again, after a mouse trip: with `focus-within`
|
|
// its hover row stayed up under no pointer until a reload (owner). The chrome answers the pointer and
|
|
// KEYBOARD focus only (`:focus-visible` on the card or inside it); jsdom cannot evaluate either, so
|
|
// the classes are the contract, and the global `.reveal` rule says the same.
|
|
describe("a card's hover chrome", () => {
|
|
it.each([
|
|
["the hover row", HOVER_REVEAL],
|
|
["what yields to it", HOVER_YIELD],
|
|
["the Play layer", MEDIA_HOVER_LAYER],
|
|
["the Continue + Restart layer", MEDIA_RESUME_LAYER],
|
|
])("shows on keyboard focus, never on a focus handed back: %s", (_, cls) => {
|
|
const parts = cls.split(" ");
|
|
expect(parts.some((c) => c.startsWith("group-focus-within:"))).toBe(false);
|
|
expect(parts.some((c) => c.startsWith("group-focus-visible:"))).toBe(true);
|
|
expect(parts.some((c) => c.startsWith("group-has-focus-visible:"))).toBe(true);
|
|
});
|
|
|
|
it("is the same in the global .reveal rule", () => {
|
|
expect(indexCss).not.toMatch(/\.group:focus-within \.reveal/);
|
|
expect(indexCss).toMatch(/\.group:has\(:focus-visible\) \.reveal/);
|
|
});
|
|
});
|