Files
jellytau/src/lib/utils/nativeVideoLayers.test.ts
T
dtourolle ad48d89dfe chore(format): run prettier over src/ and scripts/
Formatting was configured but never enforced: `bun run format:check`
reported 199 unformatted files and ran in no workflow and in no git hook,
so .prettierrc (printWidth 100, trailing commas) described an intention
rather than the tree.

This is the one-time sweep that makes the check gateable. Whitespace and
token-reflow only -- no behavioural change: `bun run check` reports 0
errors and all 1053 frontend tests pass before and after.

Kept out of every other commit on purpose. A 199-file diff mixed with
real changes is unreviewable, and the next commit turns format:check
into a hard CI gate so this cannot silently accumulate again.
2026-08-21 17:41:44 +02:00

91 lines
3.7 KiB
TypeScript

/**
* Every opaque layer the native-video CSS claims to clear must actually exist.
*
* TRACES: UR-003, UR-004 | DR-185 | UT-186
*
* The compositing rules in app.css clear the page's painted backgrounds so the
* ExoPlayer SurfaceView behind the WebView can be seen. One of the three
* selectors, `[data-app-shell]`, was written against an attribute that **no
* component ever set** — in any commit — so the app shell went on painting
* `--color-background` across the whole viewport, underneath a player that had
* correctly made itself transparent. The WebView therefore composited opaque
* and the surface could never show through.
*
* That failure is invisible three ways over: the CSS is valid, the selector is
* plausible, and the symptom (black screen, audio fine) is identical to a
* genuine compositing failure — which is how it survived DR-150 through DR-172.
* A rule that matches nothing is the specific defect worth a tripwire, so this
* asserts the relationship rather than the rule: every attribute the block
* targets is set somewhere in the app.
*/
import { describe, it, expect } from "vitest";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const here = path.dirname(fileURLToPath(import.meta.url));
const srcRoot = path.resolve(here, "../..");
function read(file: string): string {
return fs.readFileSync(file, "utf-8");
}
/** Every .svelte file under src/. */
function svelteFiles(dir: string, found: string[] = []): string[] {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) svelteFiles(full, found);
else if (entry.name.endsWith(".svelte")) found.push(full);
}
return found;
}
/**
* The selector list of the `[data-native-video="active"]` rule in app.css.
* Returned verbatim, one selector per entry.
*/
function compositingSelectors(css: string): string[] {
const marker = 'html[data-native-video="active"]';
const start = css.indexOf(marker);
expect(start, "app.css no longer contains the native-video rule").toBeGreaterThan(-1);
const open = css.indexOf("{", start);
return css
.slice(start, open)
.split(",")
.map((s) => s.trim())
.filter(Boolean);
}
describe("native-video compositing layers (DR-185)", () => {
const css = read(path.join(srcRoot, "app.css"));
const selectors = compositingSelectors(css);
const markup = svelteFiles(srcRoot).map(read).join("\n");
it("clears the app shell, which paints over the whole viewport", () => {
// The shell is the layer directly between the player and the WebView; if it
// stays painted, nothing below it can be seen however transparent the
// player and the WebView widget are.
expect(selectors.some((s) => s.includes("[data-app-shell]"))).toBe(true);
expect(markup).toContain("data-app-shell");
});
it("targets no attribute that nothing in the app sets", () => {
const attributes = selectors
.flatMap((selector) => [...selector.matchAll(/\[([a-zA-Z-]+)(?:[=\]])/g)])
.map((match) => match[1])
// data-native-video is set imperatively on <html> by nativeVideo.ts, not
// in markup, so it is verified against that module instead.
.filter((attr) => attr !== "data-native-video");
const unset = [...new Set(attributes)].filter((attr) => !markup.includes(attr));
expect(unset, `app.css targets attributes no component sets: ${unset.join(", ")}`).toEqual([]);
});
it("still sets data-native-video on <html> from the store", () => {
const store = read(path.join(srcRoot, "lib/stores/nativeVideo.ts"));
expect(store).toContain("data-native-video");
expect(store).toContain("documentElement");
});
});