Files
siftlode/frontend/src/features/video/mediaPicture.contract.test.ts
T
peter 63a0fecc8b refactor(frontend): the flat components/ folder moves into shell/, modules/<page>/ and features/<name>/
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.
2026-10-22 23:12:45 +02:00

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/);
});
});