Capture: - Hover/focus states are driven with CDP CSS.forcePseudoState instead of moving the real cursor - transparent full-viewport overlay layers were swallowing every pointer hover, silently capturing zero hover states on hover-rich sites - text-wrap (balance/pretty) captured and emitted (Tailwind text-balance/ text-pretty, arbitrary fallback) - Videos re-run <source> selection per viewport before frame-0 normalization: resize-without-reload kept aspect-gated variants stuck on the load-time choice, poisoning mobile ground-truth screenshots - svg roots capture their computed paint; fill="none" with a computed paint (the fill-current pattern) recovers the real color instead of rendering blank Tokens & naming: - Full CSS-Color-4 parsing (oklab/oklch/lab/lch/hsl) so modern colors cluster and earn semantic roles; visually-equal literals share one token; decoration/gradient/shadow colors consult the palette before minting opaque tokens (ridge: 45 opaque tokens -> 15, anthropic: 12 -> 7) - Expanded role vocabulary (background/foreground/primary/accent/border/ surface/muted) with deterministic tiebreaks and a chroma guard - Section names mine CMS section ids and js-* hooks with hashy-suffix stripping (split_callout_JtTWTt -> split-callout-section) 364 tests pass (42 new), typecheck clean; determinism verified by double-regen byte-comparison on two reference runs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
67 lines
3.0 KiB
TypeScript
67 lines
3.0 KiB
TypeScript
import { describe, it } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { selectVideoSourceIndex, type VideoSourceCandidate } from "../src/capture/capture.js";
|
|
|
|
// Predicate builders for the injected matchMedia / canPlayType.
|
|
const matchesAny = (matching: Set<string>) => (m: string) => matching.has(m);
|
|
const canPlayAll = () => true;
|
|
const canPlayNone = () => false;
|
|
|
|
describe("selectVideoSourceIndex (video <source> resource-selection)", () => {
|
|
it("picks the first source whose media matches; missing media matches unconditionally", () => {
|
|
// Aspect-gated hero: landscape sources first, portrait after, plain fall-through last.
|
|
const sources: VideoSourceCandidate[] = [
|
|
{ media: "(min-aspect-ratio: 21/9)", type: "video/webm" },
|
|
{ media: "(min-aspect-ratio: 16/9)", type: "video/webm" },
|
|
{ media: "(max-aspect-ratio: 4/5)", type: "video/webm" },
|
|
{ media: null, type: "video/webm" }, // plain fall-through
|
|
];
|
|
// Portrait viewport: only the max-aspect-ratio query matches → index 2.
|
|
assert.equal(
|
|
selectVideoSourceIndex(sources, matchesAny(new Set(["(max-aspect-ratio: 4/5)"])), canPlayAll),
|
|
2,
|
|
);
|
|
// Landscape/wide viewport: the min-aspect 16/9 query matches → index 1.
|
|
assert.equal(
|
|
selectVideoSourceIndex(sources, matchesAny(new Set(["(min-aspect-ratio: 16/9)"])), canPlayAll),
|
|
1,
|
|
);
|
|
// Nothing matches → falls through to the plain no-media source (index 3).
|
|
assert.equal(selectVideoSourceIndex(sources, matchesAny(new Set()), canPlayAll), 3);
|
|
});
|
|
|
|
it("skips a source whose type the UA cannot play", () => {
|
|
const sources: VideoSourceCandidate[] = [
|
|
{ media: null, type: "video/webm" }, // unplayable here
|
|
{ media: null, type: "video/mp4" },
|
|
];
|
|
const canPlayMp4 = (t: string) => t === "video/mp4";
|
|
assert.equal(selectVideoSourceIndex(sources, matchesAny(new Set()), canPlayMp4), 1);
|
|
});
|
|
|
|
it("treats a missing/empty type as never disqualifying", () => {
|
|
const sources: VideoSourceCandidate[] = [
|
|
{ media: null, type: "" },
|
|
{ media: null },
|
|
];
|
|
// canPlay is never consulted when type is absent, even if it would reject everything.
|
|
assert.equal(selectVideoSourceIndex(sources, matchesAny(new Set()), canPlayNone), 0);
|
|
});
|
|
|
|
it("returns -1 when no source is eligible", () => {
|
|
const sources: VideoSourceCandidate[] = [
|
|
{ media: "(max-aspect-ratio: 4/5)", type: "video/webm" },
|
|
];
|
|
assert.equal(selectVideoSourceIndex(sources, matchesAny(new Set()), canPlayNone), -1);
|
|
assert.equal(selectVideoSourceIndex([], matchesAny(new Set()), canPlayAll), -1);
|
|
});
|
|
|
|
it("is deterministic and first-match-wins in document order", () => {
|
|
const sources: VideoSourceCandidate[] = [
|
|
{ media: "(min-width: 100px)", type: "video/mp4" },
|
|
{ media: "(min-width: 100px)", type: "video/mp4" }, // also eligible, but later
|
|
];
|
|
assert.equal(selectVideoSourceIndex(sources, matchesAny(new Set(["(min-width: 100px)"])), canPlayAll), 0);
|
|
});
|
|
});
|