import { describe, expect, it } from "vitest"; import { collectMergeFieldKeys, renderToHtml } from "../src/documents/editor-content-renderer"; /** * Unidade pura, sem app/DB — cobre a porta do serializador Tiptap→HTML do * legado (renderEditorContent.js). documents-pdf.spec.ts cobre o pipeline * completo (geração real via HTTP); aqui é só a árvore de nós, isolada. */ describe("editor-content-renderer", () => { it("renderiza parágrafo com alinhamento e marks (bold/italic/underline/strike/fontSize)", () => { const doc = { type: "doc", content: [ { type: "paragraph", attrs: { textAlign: "center" }, content: [ { type: "text", text: "Olá ", marks: [{ type: "bold" }] }, { type: "text", text: "mundo", marks: [{ type: "italic" }, { type: "underline" }, { type: "strike" }] }, { type: "text", text: " grande", marks: [{ type: "textStyle", attrs: { fontSize: "18px" } }] }, ], }, ], }; const html = renderToHtml(doc); expect(html).toBe( '

Olá mundo grande

', ); }); it("renderiza heading, listas e parágrafo vazio como  ", () => { const doc = { type: "doc", content: [ { type: "heading", attrs: { level: 2 }, content: [{ type: "text", text: "Título" }] }, { type: "paragraph", content: [] }, { type: "bulletList", content: [ { type: "listItem", content: [{ type: "paragraph", content: [{ type: "text", text: "item 1" }] }] }, ], }, { type: "orderedList", content: [ { type: "listItem", content: [{ type: "paragraph", content: [{ type: "text", text: "item A" }] }] }, ], }, ], }; const html = renderToHtml(doc); expect(html).toContain("

Título

"); expect(html).toContain("

 

"); expect(html).toContain(""); expect(html).toContain("
  1. item A

"); }); it("renderiza imagem e quebra de página", () => { const doc = { type: "doc", content: [ { type: "image", attrs: { src: "data:image/png;base64,abc", alt: "logo" } }, { type: "pageBreak" }, ], }; const html = renderToHtml(doc); expect(html).toContain('logo'); expect(html).toContain('style="break-after:page;page-break-after:always;"'); }); it("gera real só quando há tableHeader, mesmo no meio das linhas", () => { const doc = { type: "doc", content: [ { type: "table", content: [ { type: "tableRow", content: [{ type: "tableHeader", attrs: { colspan: 2 }, content: [{ type: "paragraph", content: [] }] }], }, { type: "tableRow", content: [{ type: "tableCell", content: [{ type: "paragraph", content: [{ type: "text", text: "x" }] }] }], }, ], }, ], }; const html = renderToHtml(doc); expect(html).toMatch(/.*/s); expect(html).toMatch(/.*/s); }); it("mergeField vira chip visual sem resolveMergeField, e valor resolvido com resolveMergeField", () => { const doc = { type: "doc", content: [ { type: "paragraph", content: [{ type: "mergeField", attrs: { key: "cliente.razao_social", label: "Razão Social" } }], }, ], }; const chipHtml = renderToHtml(doc); expect(chipHtml).toContain('data-merge-field="cliente.razao_social"'); expect(chipHtml).toContain("Razão Social"); const resolvedHtml = renderToHtml(doc, (key) => (key === "cliente.razao_social" ? "ACME LTDA" : "")); expect(resolvedHtml).toBe("

ACME LTDA

"); expect(resolvedHtml).not.toContain("merge-field-chip"); }); it("conditional sempre renderiza sua linha (avaliação real de condição é trabalho futuro)", () => { const doc = { type: "doc", content: [ { type: "table", content: [ { type: "tableRow", content: [{ type: "tableHeader", content: [{ type: "paragraph", content: [{ type: "text", text: "Coluna" }] }] }], }, { type: "conditional", attrs: { field: "oferta.franquia_minutos", operator: "exists" }, content: [ { type: "tableRow", content: [ { type: "tableCell", content: [{ type: "paragraph", content: [{ type: "text", text: "linha condicional" }] }] }, ], }, ], }, ], }, ], }; const html = renderToHtml(doc); expect(html).toMatch(/.*

Coluna<\/p><\/th>.*<\/thead>/s); expect(html).toContain("

linha condicional

"); expect(html).not.toContain("conditional"); }); it("collectMergeFieldKeys enxerga chaves dentro de um conditional", () => { const doc = { type: "doc", content: [ { type: "conditional", attrs: { field: "x" }, content: [ { type: "tableRow", content: [{ type: "tableCell", content: [{ type: "mergeField", attrs: { key: "a.b" } }] }] }, ], }, ], }; expect(collectMergeFieldKeys(doc)).toEqual(["a.b"]); }); it("escapa HTML de texto e de valores resolvidos (proteção contra XSS)", () => { const doc = { type: "doc", content: [ { type: "paragraph", content: [{ type: "text", text: "" }] }, { type: "paragraph", content: [{ type: "mergeField", attrs: { key: "x" } }] }, ], }; const html = renderToHtml(doc, () => ""); expect(html).not.toContain("