/** * 端到端链路冒烟测试(§17 V1 验收标准)。 * * 验证完整链路: * ReportDocument(core) * → 数据注入 + 布局(layout,含分组/聚合) * → LayoutDocument * → HTML/SVG Renderer(renderer) * → Designer 编辑(designer) */ import { describe, expect, it } from "vitest" import { createDocument, createTableElement } from "@report/core" import { createEstimateMeasurer, createPassthroughFontResolver, layoutDocument } from "@report/layout" import { renderHtml, renderSvg, createPdfRenderer } from "@report/renderer" import { EditorSession, createTextElement } from "@report/designer" const measurer = createEstimateMeasurer() const fontResolver = createPassthroughFontResolver() describe("端到端冒烟(§17)", () => { it("完整链路:建文档 → 布局 → HTML/SVG → Designer 编辑", async () => { // 1. Core:创建文档 + 明细表 const doc = createDocument({ name: "销售单" }) doc.datasets = { sales: { id: "sales" } } const detail = doc.sections.find((s) => s.kind === "detail")! detail.children.push( createTableElement("tbl", { x: 0, y: 0, width: 180, height: 20 }, "sales", [ { id: "c1", width: 90, header: "商品", binding: { field: "name" } }, { id: "c2", width: 90, header: "金额", binding: { field: "amount" }, align: "right" }, ]), ) const header = doc.sections.find((s) => s.kind === "page-header")! header.children.push({ id: "title", type: "text", frame: { x: 0, y: 0, width: 100, height: 8 }, props: { text: "销售单" }, }) const rows = [ { name: "苹果", amount: 100 }, { name: "香蕉", amount: 200 }, { name: "橙子", amount: 300 }, ] // 2. Layout:数据注入 + 布局 const layout = await layoutDocument( doc, { data: { sales: rows }, parameters: { } }, { textMeasurer: measurer, fontResolver }, ) expect(layout.pageCount).toBeGreaterThanOrEqual(1) expect(layout.diagnostics.filter((d) => d.severity === "error")).toHaveLength(0) // 3. Renderer:HTML + SVG 渲染,页数一致 const html = renderHtml(layout) const svg = renderSvg(layout) expect(html.pageCount).toBe(layout.pageCount) expect(svg.pageCount).toBe(layout.pageCount) expect(html.content).toContain("销售单") expect(html.content).toContain("苹果") expect(svg.content).toContain("苹果") // PDF 缺后端 → 诊断而非抛异常 const pdf = createPdfRenderer().render(layout) expect(pdf.target).toBe("pdf") expect(pdf.diagnostics.length).toBeGreaterThan(0) // 4. Designer:编辑(移动 + 撤销) const editor = new EditorSession(doc) editor.select("title") const before = editor.getDocument().sections .find((s) => s.kind === "page-header")!.children[0]!.frame editor.moveElements({ title: { x: 20, y: 0, width: 100, height: 8 } }) const afterMove = editor.getDocument().sections .find((s) => s.kind === "page-header")!.children[0]!.frame expect(afterMove.x).toBe(20) editor.undo() const afterUndo = editor.getDocument().sections .find((s) => s.kind === "page-header")!.children[0]!.frame expect(afterUndo.x).toBe(before.x) // 新增元素 const beforeCount = editor.getDocument().sections .find((s) => s.kind === "detail")!.children.length editor.addElement(detail.id, createTextElement({ x: 0, y: 0, width: 30, height: 8 }, "备注")) const afterAdd = editor.getDocument().sections .find((s) => s.kind === "detail")!.children.length expect(afterAdd).toBe(beforeCount + 1) }) })