import { describe, expect, it } from "vitest"; import JSZip from "jszip"; import { exportDocxBlob, htmlToWordBlocks, normalizeImportedDocxHtml, wordInlineRunsToTextRunOptions } from "./wordOffice"; const tinyPngDataUri = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII="; describe("wordOffice helpers", () => { it("разбирает заголовки, абзацы и списки", () => { const blocks = htmlToWordBlocks(`
Первый абзац.
Итог
'); expect(blocks).toEqual([ { type: "heading", level: 1, text: "Раздел", runs: [{ text: "Раздел" }], bookmark: "Section1" }, { type: "paragraph", text: "Итог", runs: [{ text: "Итог" }], bookmark: "Summary" } ]); }); it("разбирает HTML blockquote как цитату QWord", () => { const blocks = htmlToWordBlocks('Цитата важная'); expect(blocks).toEqual([ { type: "paragraph", text: "Цитата важная", runs: [{ text: "Цитата " }, { text: "важная", bold: true }], bookmark: "Quote1", quote: true } ]); }); it("сохраняет базовое inline-форматирование текста для DOCX", () => { const blocks = htmlToWordBlocks(`
Обычный полужирный курсив подчеркнутый все стили
`); expect(blocks).toEqual([ { type: "paragraph", text: "Обычный полужирный курсив подчеркнутый все стили", runs: [ { text: "Обычный " }, { text: "полужирный", bold: true }, { text: " ", }, { text: "курсив", italics: true }, { text: " " }, { text: "подчеркнутый", underline: true }, { text: " " }, { text: "все стили", bold: true, italics: true, underline: true, color: "0F5FAE", highlightColor: "FFF2CC" } ] } ]); }); it("сохраняет HTML-переносы строк как inline runs для DOCX", () => { const blocks = htmlToWordBlocks("Line 1
Line 2
Line 3
BeforeAfter
'); expect(blocks).toEqual([ { type: "paragraph", text: "Before After", runs: [{ text: "Before\fAfter" }] } ]); }); it("сохраняет служебные HTML-табы как inline runs для DOCX", () => { const blocks = htmlToWordBlocks('Before\tAfter
'); expect(blocks).toEqual([ { type: "paragraph", text: "Before After", runs: [{ text: "Before\tAfter" }] } ]); }); it("preserves HTML subscript and superscript as inline runs for DOCX", () => { const blocks = htmlToWordBlocks('H2O m2 x
'); expect(blocks).toEqual([ { type: "paragraph", text: "H2O m2 x", runs: [ { text: "H" }, { text: "2", subscript: true }, { text: "O m" }, { text: "2", superscript: true }, { text: " " }, { text: "x", superscript: true } ] } ]); }); it("сохраняет выравнивание абзацев и заголовков для DOCX", () => { const blocks = htmlToWordBlocks(`Центрированный абзац
Текст по ширине
`); expect(blocks).toEqual([ { type: "heading", level: 1, text: "Итоги", runs: [{ text: "Итоги" }], alignment: "right" }, { type: "paragraph", text: "Центрированный абзац", runs: [{ text: "Центрированный абзац" }], alignment: "center" }, { type: "paragraph", text: "Текст по ширине", runs: [{ text: "Текст по ширине" }], alignment: "both" } ]); }); it("сохраняет HTML-гиперссылки как inline runs", () => { const blocks = htmlToWordBlocks(`Откройте отчет, раздел и небезопасную ссылку.
`); expect(blocks).toEqual([ { type: "paragraph", text: "Откройте отчет, раздел и небезопасную ссылку.", runs: [ { text: "Откройте " }, { text: "отчет", bold: true, href: "https://example.com/report" }, { text: ", " }, { text: "раздел", href: "#Section1" }, { text: " и небезопасную ссылку." } ] } ]); }); it("разбирает изображения из HTML как отдельные блоки", () => { const blocks = htmlToWordBlocks(`Перед После
Жирный Курсив Подчеркнутый
" ); const zip = await JSZip.loadAsync(await blob.arrayBuffer()); const documentXml = await zip.file("word/document.xml")?.async("string"); expect(documentXml).toContain("Line 1
Line 2
Важная цитата"); const zip = await JSZip.loadAsync(await blob.arrayBuffer()); const documentXml = await zip.file("word/document.xml")?.async("string"); expect(documentXml).toMatch(/
BeforeAfter
'); const zip = await JSZip.loadAsync(await blob.arrayBuffer()); const documentXml = await zip.file("word/document.xml")?.async("string"); expect(documentXml).toMatch(/Before\tAfter
'); const zip = await JSZip.loadAsync(await blob.arrayBuffer()); const documentXml = await zip.file("word/document.xml")?.async("string"); expect(documentXml).toContain("Цветной фрагмент
' ); const zip = await JSZip.loadAsync(await blob.arrayBuffer()); const documentXml = await zip.file("word/document.xml")?.async("string"); expect(documentXml).toContain('w:color w:val="0F5FAE"'); expect(documentXml).toContain('w:fill="FFF2CC"'); }); it("записывает выравнивание абзацев в XML экспортированного DOCX", async () => { const blob = await exportDocxBlob("Выравнивание.docx", 'Центр
Право
'); const zip = await JSZip.loadAsync(await blob.arrayBuffer()); const documentXml = await zip.file("word/document.xml")?.async("string"); expect(documentXml).toContain('См. раздел.
'); const zip = await JSZip.loadAsync(await blob.arrayBuffer()); const documentXml = await zip.file("word/document.xml")?.async("string"); const relationshipsXml = await zip.file("word/_rels/document.xml.rels")?.async("string"); expect(documentXml).toMatch(/Логотип
| Этап | Ответственный |
|---|---|
| Планирование | Анна |
Normal Large Pixel size
`); expect(blocks).toEqual([ { type: "paragraph", text: "Normal Large Pixel size", runs: [ { text: "Normal " }, { text: "Large", fontSize: 18 }, { text: " " }, { text: "Pixel size", fontSize: 18 } ] } ]); }); it("преобразует размер шрифта inline runs в half-point значение docx", () => { expect(wordInlineRunsToTextRunOptions([{ text: "Размер", fontSize: 18 }])).toEqual([{ text: "Размер", size: 36 }]); }); it("преобразует семейство шрифта inline runs в параметр docx font", () => { expect(wordInlineRunsToTextRunOptions([{ text: "Arial", fontFamily: "Arial" }])).toEqual([{ text: "Arial", font: "Arial" }]); }); it("записывает размер шрифта в XML экспортированного DOCX", async () => { const blob = await exportDocxBlob("Размер.docx", 'Крупный текст
'); const zip = await JSZip.loadAsync(await blob.arrayBuffer()); const documentXml = await zip.file("word/document.xml")?.async("string"); expect(documentXml).toContain('w:sz w:val="36"'); }); it("сохраняет семейство шрифта из HTML inline-стилей для DOCX", () => { const blocks = htmlToWordBlocks('Arial text
'); expect(blocks).toEqual([ { type: "paragraph", text: "Arial text", runs: [{ text: "Arial text", fontFamily: "Arial" }] } ]); }); it("записывает семейство шрифта в XML экспортированного DOCX", async () => { const blob = await exportDocxBlob("Шрифт.docx", 'Текст Arial
'); const zip = await JSZip.loadAsync(await blob.arrayBuffer()); const documentXml = await zip.file("word/document.xml")?.async("string"); expect(documentXml).toContain('w:ascii="Arial"'); }); it("сохраняет зачеркнутый текст из HTML для DOCX", () => { const blocks = htmlToWordBlocks('Deleted Styled
H2O m2
"); const zip = await JSZip.loadAsync(await blob.arrayBuffer()); const documentXml = await zip.file("word/document.xml")?.async("string"); expect(documentXml).toMatch(/Удаленный текст
Indented paragraph
`); expect(blocks).toEqual([ { type: "heading", level: 1, text: "Title", runs: [{ text: "Title" }], indent: { left: 720, firstLine: 360 } }, { type: "paragraph", text: "Indented paragraph", runs: [{ text: "Indented paragraph" }], indent: { left: 720, hanging: 240 } } ]); }); it("сохраняет HTML-интервалы абзацев и заголовков для DOCX", () => { const blocks = htmlToWordBlocks(`Paragraph spacing
`); expect(blocks).toEqual([ { type: "heading", level: 2, text: "Title spacing", runs: [{ text: "Title spacing" }], spacing: { before: 120, after: 200 } }, { type: "paragraph", text: "Paragraph spacing", runs: [{ text: "Paragraph spacing" }], spacing: { before: 240, after: 360 } } ]); }); it("записывает отступы абзацев в w:ind экспортированного DOCX", async () => { const blob = await exportDocxBlob( "Indent.docx", 'Indented
Hanging
' ); const zip = await JSZip.loadAsync(await blob.arrayBuffer()); const documentXml = await zip.file("word/document.xml")?.async("string"); expect(documentXml).toMatch(/Paragraph spacing
' ); const zip = await JSZip.loadAsync(await blob.arrayBuffer()); const documentXml = await zip.file("word/document.xml")?.async("string"); expect(documentXml).toMatch(/Готово
\n")).toBe("Готово
"); }); });