// @vitest-environment happy-dom import { describe, expect, it } from "vitest"; import { applyMediaBackfillCandidates, calculateMediaBackfillScale, findMediaBackfillCandidates, getMediaBackfillIdsInDocumentOrder, prepareMediaBackfillBlocks } from "../src/media-page-backfill.js"; import { getPrecedingMediaTitleHeight } from "../src/diagram-page-fit.js"; function rect(width: number, height: number) { return { x: 0, y: 0, top: 0, left: 0, right: width, bottom: height, width, height, toJSON: () => ({}) }; } function positionedRect( top: number, width: number, height: number ) { return { ...rect(width, height), y: top, top, bottom: top + height }; } describe("媒体分页空白回填", () => { it("全页适配时保留前置标题高度并避免标题孤行", () => { const root = document.createElement("div"); root.innerHTML = `

媒体标题

`; const title = root.querySelector("h2")!; const block = root.querySelector(".mermaid")!; title.getBoundingClientRect = () => positionedRect(100, 600, 40); expect(getPrecedingMediaTitleHeight(block)).toBe(40); expect(title.style.getPropertyValue("break-after")).toBe( "avoid-page" ); expect(title.style.getPropertyPriority("break-after")).toBe( "important" ); }); it("按限宽后的基准高度计算额外缩放率", () => { expect( calculateMediaBackfillScale({ remainingHeight: 420, fixedHeight: 30, baselineVisualHeight: 430 }) ).toBeCloseTo(388 / 430, 6); }); it("缩小超过阈值或无需缩小时不回填", () => { expect( calculateMediaBackfillScale({ remainingHeight: 300, fixedHeight: 30, baselineVisualHeight: 430 }) ).toBeUndefined(); expect( calculateMediaBackfillScale({ remainingHeight: 500, fixedHeight: 30, baselineVisualHeight: 430 }) ).toBeUndefined(); }); it("记录图片限宽后的实际基准尺寸", () => { const root = document.createElement("div"); root.innerHTML = `
标题
`; const image = root.querySelector("img")!; image.getBoundingClientRect = () => rect(640, 480); expect(prepareMediaBackfillBlocks(root)).toBe(1); const figure = root.querySelector("figure")!; expect(figure.dataset.mediaBackfillKind).toBe("image"); expect(figure.dataset.mediaBaselineWidth).toBe("640"); expect(figure.dataset.mediaBaselineHeight).toBe("480"); }); it("保持媒体元素在源文档中的串行处理顺序", () => { const root = document.createElement("div"); root.innerHTML = `
`; expect(getMediaBackfillIdsInDocumentOrder(root)).toEqual([ "media-2", "media-1" ]); }); it("根据上一页剩余区域生成一次回填候选", () => { const root = document.createElement("div"); root.innerHTML = `
上一页内容

图片标题

`; const pageContents = root.querySelectorAll( ".pagedjs_page_content" ); const previousFlow = pageContents[0]!.firstElementChild as HTMLElement; const title = root.querySelector("h2")!; const block = root.querySelector("figure")!; const image = root.querySelector("img")!; pageContents[0]!.getBoundingClientRect = () => positionedRect(100, 700, 1_000); previousFlow.getBoundingClientRect = () => positionedRect(100, 700, 550); pageContents[1]!.getBoundingClientRect = () => positionedRect(100, 700, 1_000); title.getBoundingClientRect = () => positionedRect(100, 600, 40); block.getBoundingClientRect = () => positionedRect(150, 600, 330); image.getBoundingClientRect = () => positionedRect(150, 600, 300); expect(findMediaBackfillCandidates(root)).toEqual([ { id: "media-1", scale: 0.92 } ]); }); it("显式分页的媒体块不会回填到上一页", () => { const root = document.createElement("div"); root.innerHTML = `

图片标题

`; const pageContents = root.querySelectorAll( ".pagedjs_page_content" ); const title = root.querySelector("h2")!; const block = root.querySelector("figure")!; const image = root.querySelector("img")!; pageContents[0]!.getBoundingClientRect = () => positionedRect(100, 700, 1_000); pageContents[1]!.getBoundingClientRect = () => positionedRect(100, 700, 1_000); title.getBoundingClientRect = () => positionedRect(100, 600, 40); block.getBoundingClientRect = () => positionedRect(150, 600, 330); image.getBoundingClientRect = () => positionedRect(150, 600, 300); expect(findMediaBackfillCandidates(root)).toEqual([]); }); it("忽略媒体之前其他内容的显式分页规则", () => { const root = document.createElement("div"); root.innerHTML = `
上一页内容

本页章节

图片标题

`; const pageContents = root.querySelectorAll( ".pagedjs_page_content" ); const previousFlow = root.querySelector( "[data-ref='previous-content']" )!; const earlierHeading = root.querySelector("h1")!; const title = root.querySelector("h2")!; const block = root.querySelector("figure")!; const image = root.querySelector("img")!; pageContents[0]!.getBoundingClientRect = () => positionedRect(100, 700, 1_000); previousFlow.getBoundingClientRect = () => positionedRect(100, 700, 550); pageContents[1]!.getBoundingClientRect = () => positionedRect(100, 700, 1_000); earlierHeading.getBoundingClientRect = () => positionedRect(100, 600, 40); title.getBoundingClientRect = () => positionedRect(150, 600, 40); block.getBoundingClientRect = () => positionedRect(200, 600, 330); image.getBoundingClientRect = () => positionedRect(200, 600, 300); expect(findMediaBackfillCandidates(root)).toEqual([ { id: "media-1", scale: 0.92 } ]); }); it("将候选缩放应用到图片和 ECharts 主体", () => { const root = document.createElement("div"); root.innerHTML = `

图片标题保持原尺寸

`; expect( applyMediaBackfillCandidates(root, [ { id: "media-1", scale: 0.9 }, { id: "media-2", scale: 0.88 }, { id: "media-3", scale: 0.9 } ]) ).toBe(3); const title = root.querySelector("h2")!; const image = root.querySelector( "img.md-document-image" )!; const host = root.querySelector(".md-echarts-host")!; const svg = root.querySelector("svg")!; const mermaidSvg = root.querySelector( ".mermaid svg" )!; expect(title.getAttribute("style")).toBeNull(); expect(image.style.width).toBe("720px"); expect(image.style.height).toBe("540px"); expect(host.style.width).toBe("616px"); expect(host.style.height).toBe("440px"); expect(svg.style.width).toBe("616px"); expect(svg.style.height).toBe("440px"); expect(mermaidSvg.style.width).toBe("540px"); expect(mermaidSvg.style.height).toBe("360px"); }); });