import { describe, it } from "node:test"; import assert from "node:assert/strict"; import { mkdtempSync, mkdirSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import type { RawNode, RawChild } from "../src/capture/walker.js"; import { buildIR, isTextChild, type IRNode } from "../src/normalize/ir.js"; const VPS = [375, 1280]; function raw(tag: string, attrs: Record = {}, children: RawChild[] = [], visible = true): RawNode { return { tag, attrs, computed: { display: visible ? "block" : "none", position: "static", visibility: "visible" }, bbox: { x: 0, y: 0, width: visible ? 640 : 0, height: visible ? 360 : 0 }, visible, children, }; } /** Minimal PageSnapshot JSON around a body tree (same tree at every viewport). */ function snapshot(vp: number, root: RawNode): object { return { doc: { url: "https://example.test/page", title: "Fixture", head: { description: "", canonical: "", ogTitle: "", ogDescription: "", ogImage: "", ogType: "", ogSiteName: "", twitterCard: "", themeColor: "" }, lang: "en", charset: "UTF-8", viewportWidth: vp, viewportHeight: 800, scrollWidth: vp, scrollHeight: 800, htmlBg: "rgb(255, 255, 255)", bodyBg: "rgb(255, 255, 255)", bodyColor: "rgb(0, 0, 0)", bodyFont: "Arial", metaViewport: "width=device-width, initial-scale=1", nodeCount: 10, truncated: false, }, root, cssVars: {}, fontFaces: [], cssUrls: [], domAssets: [], keyframes: [], }; } function buildFixtureIR(root: RawNode): IRNode { const sourceDir = mkdtempSync(join(tmpdir(), "ditto-ir-prune-")); mkdirSync(join(sourceDir, "capture"), { recursive: true }); for (const vp of VPS) { writeFileSync(join(sourceDir, "capture", `dom-${vp}.json`), JSON.stringify(snapshot(vp, structuredClone(root)))); } return buildIR(sourceDir, VPS).root; } function findByTag(node: IRNode, tag: string): IRNode | null { if (node.tag === tag) return node; for (const c of node.children) { if (isTextChild(c)) continue; const hit = findByTag(c, tag); if (hit) return hit; } return null; } describe("IR prune keeps media candidates", () => { it("keeps invisible source children of and