fix(plex): every small Plex picture asks for its card-size cut
The playlist rows, the search box and the collection picker still fetched the 400px poster for a 32-80px picture. A contract test pins the set of bare Plex pictures, and the srcset widths to imgcache.SIZES.
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
import { cardImage } from "./plexImage";
|
||||
|
||||
// A card's srcset names each Plex cut by its width, and the browser picks one by that width. The
|
||||
// widths are the image proxy's (backend `app/plex/imgcache.py` SIZES), with no wire format between
|
||||
// the two: a backend-only change would have the browser pick a cut by a width it does not have (E31
|
||||
// S5). Read from source, as plexFinishMargin.contract does.
|
||||
const source = readFileSync(
|
||||
new URL("../../../backend/app/plex/imgcache.py", import.meta.url),
|
||||
"utf8",
|
||||
);
|
||||
const backendWidth = (variant: string) => {
|
||||
const m = new RegExp(`"${variant}": \\((\\d+), \\d+\\)`).exec(source);
|
||||
// If this fails the variant was renamed or moved: re-point this test, the invariant stayed.
|
||||
expect(m, `${variant} not in imgcache.SIZES`).not.toBeNull();
|
||||
return Number(m![1]);
|
||||
};
|
||||
|
||||
const srcsetWidths = (url: string) =>
|
||||
Object.fromEntries(
|
||||
cardImage(url)
|
||||
.srcSet!.split(", ")
|
||||
.map((c) => {
|
||||
const [u, w] = c.split(" ");
|
||||
return [
|
||||
new URL(u!, "http://x").searchParams.get("variant") ?? "thumb",
|
||||
Number(w!.slice(0, -1)),
|
||||
];
|
||||
}),
|
||||
);
|
||||
|
||||
it("names each poster cut by the width the proxy cuts it at", () => {
|
||||
expect(srcsetWidths("/api/plex/image/1")).toEqual({
|
||||
thumb_sm: backendWidth("thumb_sm"),
|
||||
thumb: backendWidth("thumb"),
|
||||
});
|
||||
});
|
||||
|
||||
it("names each backdrop cut by the width the proxy cuts it at", () => {
|
||||
expect(srcsetWidths("/api/plex/image/1?variant=art")).toEqual({
|
||||
art_sm: backendWidth("art_sm"),
|
||||
art: backendWidth("art"),
|
||||
});
|
||||
});
|
||||
|
||||
// Every small Plex picture asks for its card-size cut through cardImage (E31 S4e, swept in S5: the
|
||||
// playlist rows, the search box and the collection picker still fetched the 400px poster for a 32px
|
||||
// picture). A bare `src={…thumb}` is allowed only where the full cut is the point or the URL is not
|
||||
// the proxy's; the SET is pinned, so a new bare one fails here.
|
||||
const plexFiles = import.meta.glob(
|
||||
[
|
||||
"../components/{Plex,plex}*.tsx",
|
||||
"../components/{Home,PosterTile,UpNextCard}.tsx",
|
||||
"!../components/**/*.test.tsx",
|
||||
],
|
||||
{ query: "?raw", import: "default", eager: true },
|
||||
);
|
||||
const ALLOWED = new Set([
|
||||
"plexCards.tsx c.thumb", // cast photos: the person-image route, not the image proxy
|
||||
"PlexInfo.tsx c.thumb", // the same cast photos on the detail page
|
||||
"PlexInfo.tsx detail.thumb", // the detail page's own poster: the full cut is the point
|
||||
"plexDetailUi.tsx thumb", // the show/season header poster: the same
|
||||
"Home.tsx card.thumb", // the hero (full width) and the shelf's own tiles (YouTube channel art)
|
||||
]);
|
||||
|
||||
it("leaves no small Plex picture on the full cut", () => {
|
||||
expect(Object.keys(plexFiles).length).toBeGreaterThan(5);
|
||||
const bare = new Set<string>();
|
||||
for (const [path, src] of Object.entries(plexFiles))
|
||||
for (const m of (src as string).matchAll(/<img\s+src=\{([^}]*thumb[^}]*)\}/g))
|
||||
bare.add(`${path.split("/").pop()} ${m[1]!.trim()}`);
|
||||
expect([...bare].filter((b) => !ALLOWED.has(b))).toEqual([]);
|
||||
});
|
||||
Reference in New Issue
Block a user