/** * Unit tests for the image width policy + conservative auto-landscape * (image-policy.ts). Pure HTML-in/HTML-out — no browse daemon. * * The promotion heuristic is deliberately conservative (eng-review P4): * false negatives are cheap (add {page=landscape}), false positives feel * broken. The negative cases here are the load-bearing ones. */ import { describe, expect, test } from "bun:test"; import { applyImageDirectives, applyImagePolicy, parseDirectives, } from "../src/image-policy"; const silent = { warn: () => {} }; // 6.5in content box → threshold = 6.5 × 96 × 2.5 = 1560 CSS px. // Letter landscape content box: 9in wide × 6.5in tall. const LANDSCAPE = { contentWIn: 9, contentHIn: 6.5 }; const OPTS = { contentWidthIn: 6.5, landscape: LANDSCAPE, ...silent }; function img(attrs: string): string { return `
{width=50%}
{not a directive}
set {width=full} in config
`; expect(applyImageDirectives(html)).toBe(html); }); }); // ─── width policy ───────────────────────────────────────────────────── describe("width styles", () => { test("width=full → inline 100% style", () => { const { html } = applyImagePolicy(img(`src="x" data-gstack-width="full"`), OPTS); expect(html).toContain("width: 100%"); }); test("explicit dimension passes through", () => { const { html } = applyImagePolicy(img(`src="x" data-gstack-width="3in"`), OPTS); expect(html).toContain("width: 3in"); }); test("width directive merges with an existing style attribute, preserving it", () => { const { html } = applyImagePolicy( img(`src="x" style="border: 1px solid" data-gstack-width="50%"`), OPTS, ); expect(html).toContain("border: 1px solid"); expect(html).toContain("width: 50%"); }); test("no directive → no inline style (CSS max-width owns the default)", () => { const { html } = applyImagePolicy(img(`src="x" data-gstack-px-width="40" data-gstack-px-height="20"`), OPTS); expect(html).not.toContain("style="); }); }); // ─── landscape promotion ────────────────────────────────────────────── describe("auto-landscape: negative cases (the load-bearing ones)", () => { test("wide screenshot with no alt hint stays portrait", () => { const r = applyImagePolicy( img(`src="x" alt="screenshot of the app" data-gstack-px-width="3000" data-gstack-px-height="900"`), OPTS, ); expect(r.hasLandscape).toBe(false); expect(r.html).not.toContain("page-wide"); }); test("wide banner with hint but below width threshold stays portrait", () => { const r = applyImagePolicy( img(`src="x" alt="chart" data-gstack-px-width="1200" data-gstack-px-height="400"`), OPTS, ); expect(r.hasLandscape).toBe(false); }); test("tall diagram (aspect below 1.8) stays portrait", () => { const r = applyImagePolicy( img(`src="x" alt="architecture diagram" data-gstack-px-width="2000" data-gstack-px-height="1500"`), OPTS, ); expect(r.hasLandscape).toBe(false); }); test("no intrinsic dimensions stays portrait", () => { const r = applyImagePolicy(img(`src="x" alt="diagram"`), OPTS); expect(r.hasLandscape).toBe(false); }); test("page=portrait vetoes everything", () => { const r = applyImagePolicy( img(`src="x" alt="diagram" data-gstack-page="portrait" data-gstack-px-width="4000" data-gstack-px-height="1000"`), OPTS, ); expect(r.hasLandscape).toBe(false); }); test("threshold boundary is deterministic: exactly at threshold stays portrait", () => { // threshold = 6.5 × 96 × 2.5 = 1560 const r = applyImagePolicy( img(`src="x" alt="diagram" data-gstack-px-width="1560" data-gstack-px-height="600"`), OPTS, ); expect(r.hasLandscape).toBe(false); const r2 = applyImagePolicy( img(`src="x" alt="diagram" data-gstack-px-width="1561" data-gstack-px-height="600"`), OPTS, ); expect(r2.hasLandscape).toBe(true); }); }); describe("auto-landscape: positive cases", () => { test("wide + alt hint + over threshold promotes, wraps, and vertically centers", () => { const warnings: string[] = []; const r = applyImagePolicy( img(`src="x" alt="architecture diagram" data-gstack-px-width="2400" data-gstack-px-height="1000"`), { contentWidthIn: 6.5, landscape: LANDSCAPE, warn: (m) => warnings.push(m) }, ); expect(r.hasLandscape).toBe(true); // placed height = 9in × (1000/2400) = 3.75in → margin-top = (6.5−3.75)/2 ≈ 1.38in expect(r.html).toContain('