Files
QOffice/src/io/wordOfficeImport.test.ts
T
2026-06-02 08:11:00 +03:00

403 lines
16 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { exportDocxBlob, htmlToWordBlocks, importDocxFile } from "./wordOffice";
const tinyPngBase64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII=";
const tinyPngDataUri = `data:image/png;base64,${tinyPngBase64}`;
describe("wordOffice DOCX import", () => {
it("imports DOCX Heading 3-6 as h3-h6", async () => {
const blob = await exportDocxBlob("Headings.docx", "<h3>Quality gate</h3><h4>Scenario</h4><h5>Step</h5><h6>Detail</h6>");
const file = new File([blob], "Headings.docx", { type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document" });
const imported = await importDocxFile(file);
expect(imported.html).toContain("<h3>Quality gate</h3>");
expect(imported.html).toContain("<h4>Scenario</h4>");
expect(imported.html).toContain("<h5>Step</h5>");
expect(imported.html).toContain("<h6>Detail</h6>");
expect(htmlToWordBlocks(imported.html)).toEqual([
{ type: "heading", level: 3, text: "Quality gate", runs: [{ text: "Quality gate" }] },
{ type: "heading", level: 4, text: "Scenario", runs: [{ text: "Scenario" }] },
{ type: "heading", level: 5, text: "Step", runs: [{ text: "Step" }] },
{ type: "heading", level: 6, text: "Detail", runs: [{ text: "Detail" }] }
]);
});
it("imports DOCX Quote style as blockquote", async () => {
const blob = await exportDocxBlob("Quote.docx", "<blockquote>Quoted fragment</blockquote>");
const file = new File([blob], "Quote.docx", { type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document" });
const imported = await importDocxFile(file);
expect(imported.html).toContain("<blockquote>Quoted fragment</blockquote>");
expect(htmlToWordBlocks(imported.html)).toEqual([
{
type: "paragraph",
text: "Quoted fragment",
runs: [{ text: "Quoted fragment" }],
quote: true
}
]);
});
it("imports DOCX text color and highlight from OOXML", async () => {
const blob = await exportDocxBlob(
"Color.docx",
'<p><span style="color: #0f5fae; background-color: #fff2cc;">Color fragment</span></p>'
);
const file = new File([blob], "Color.docx", { type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document" });
const imported = await importDocxFile(file);
expect(imported.html).toContain("color: #0F5FAE;");
expect(imported.html).toContain("background-color: #FFF2CC;");
expect(htmlToWordBlocks(imported.html)).toEqual([
{
type: "paragraph",
text: "Color fragment",
runs: [{ text: "Color fragment", color: "0F5FAE", highlightColor: "FFF2CC" }]
}
]);
});
it("imports DOCX paragraph alignment from OOXML", async () => {
const blob = await exportDocxBlob(
"Alignment.docx",
'<p style="text-align: center;">Centered</p><p align="right">Right</p><p style="text-align: justify;">Justified</p>'
);
const file = new File([blob], "Alignment.docx", {
type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
});
const imported = await importDocxFile(file);
expect(imported.html).toContain('style="text-align: center;"');
expect(imported.html).toContain('style="text-align: right;"');
expect(imported.html).toContain('style="text-align: justify;"');
expect(htmlToWordBlocks(imported.html)).toEqual([
{ type: "paragraph", text: "Centered", runs: [{ text: "Centered" }], alignment: "center" },
{ type: "paragraph", text: "Right", runs: [{ text: "Right" }], alignment: "right" },
{ type: "paragraph", text: "Justified", runs: [{ text: "Justified" }], alignment: "both" }
]);
});
it("imports DOCX font size from OOXML", async () => {
const blob = await exportDocxBlob("FontSize.docx", '<p><span style="font-size: 18pt;">Large text</span></p>');
const file = new File([blob], "FontSize.docx", {
type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
});
const imported = await importDocxFile(file);
expect(imported.html).toContain("font-size: 18pt;");
expect(htmlToWordBlocks(imported.html)).toEqual([
{
type: "paragraph",
text: "Large text",
runs: [{ text: "Large text", fontSize: 18 }]
}
]);
});
it("imports DOCX font family from OOXML", async () => {
const blob = await exportDocxBlob("FontFamily.docx", '<p><span style="font-family: Arial;">Arial text</span></p>');
const file = new File([blob], "FontFamily.docx", {
type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
});
const imported = await importDocxFile(file);
expect(imported.html).toContain("font-family: 'Arial';");
expect(htmlToWordBlocks(imported.html)).toEqual([
{
type: "paragraph",
text: "Arial text",
runs: [{ text: "Arial text", fontFamily: "Arial" }]
}
]);
});
it("imports DOCX strikethrough from OOXML", async () => {
const blob = await exportDocxBlob("Strike.docx", "<p><s>Deleted text</s></p>");
const file = new File([blob], "Strike.docx", {
type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
});
const imported = await importDocxFile(file);
expect(imported.html).toContain("<s>Deleted text</s>");
expect(htmlToWordBlocks(imported.html)).toEqual([
{
type: "paragraph",
text: "Deleted text",
runs: [{ text: "Deleted text", strike: true }]
}
]);
});
it("imports DOCX subscript and superscript from OOXML", async () => {
const blob = await exportDocxBlob("Indexes.docx", "<p>H<sub>2</sub>O m<sup>2</sup></p>");
const file = new File([blob], "Indexes.docx", {
type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
});
const imported = await importDocxFile(file);
expect(imported.html).toContain("<sub>2</sub>");
expect(imported.html).toContain("<sup>2</sup>");
expect(htmlToWordBlocks(imported.html)).toEqual([
{
type: "paragraph",
text: "H2O m2",
runs: [
{ text: "H" },
{ text: "2", subscript: true },
{ text: "O m" },
{ text: "2", superscript: true }
]
}
]);
});
it("imports DOCX line breaks from OOXML", async () => {
const blob = await exportDocxBlob("LineBreak.docx", "<p>Line 1<br>Line 2</p>");
const file = new File([blob], "LineBreak.docx", {
type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
});
const imported = await importDocxFile(file);
expect(imported.html).toContain("Line 1<br>Line 2");
expect(htmlToWordBlocks(imported.html)).toEqual([
{
type: "paragraph",
text: "Line 1 Line 2",
runs: [{ text: "Line 1\nLine 2" }]
}
]);
});
it("imports DOCX page breaks from OOXML", async () => {
const blob = await exportDocxBlob("PageBreak.docx", '<p>Before<span data-qoffice-page-break="true"></span>After</p>');
const file = new File([blob], "PageBreak.docx", {
type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
});
const imported = await importDocxFile(file);
expect(imported.html).toContain('data-qoffice-page-break="true"');
expect(htmlToWordBlocks(imported.html)).toEqual([
{
type: "paragraph",
text: "Before After",
runs: [{ text: "Before\fAfter" }]
}
]);
});
it("imports DOCX tabs from OOXML", async () => {
const blob = await exportDocxBlob("Tab.docx", '<p>Before<span data-qoffice-tab="true" style="white-space: pre;">\t</span>After</p>');
const file = new File([blob], "Tab.docx", {
type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
});
const imported = await importDocxFile(file);
expect(imported.html).toContain('data-qoffice-tab="true"');
expect(htmlToWordBlocks(imported.html)).toEqual([
{
type: "paragraph",
text: "Before After",
runs: [{ text: "Before\tAfter" }]
}
]);
});
it("imports DOCX external hyperlinks from OOXML relationships", async () => {
const blob = await exportDocxBlob("Hyperlink.docx", '<p>Open <a href="https://example.com/report"><strong>report</strong></a>.</p>');
const file = new File([blob], "Hyperlink.docx", {
type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
});
const imported = await importDocxFile(file);
expect(imported.html).toContain('<a href="https://example.com/report">');
expect(htmlToWordBlocks(imported.html)).toEqual([
{
type: "paragraph",
text: "Open report.",
runs: [
{ text: "Open " },
{ text: "report", bold: true, href: "https://example.com/report" },
{ text: "." }
]
}
]);
});
it("imports DOCX internal hyperlink anchors from OOXML", async () => {
const blob = await exportDocxBlob("InternalHyperlink.docx", '<p>Open <a href="#Section1"><strong>section</strong></a>.</p>');
const file = new File([blob], "InternalHyperlink.docx", {
type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
});
const imported = await importDocxFile(file);
expect(imported.html).toContain('<a href="#Section1">');
expect(htmlToWordBlocks(imported.html)).toEqual([
{
type: "paragraph",
text: "Open section.",
runs: [{ text: "Open " }, { text: "section", bold: true, href: "#Section1" }, { text: "." }]
}
]);
});
it("imports DOCX bookmarks as HTML ids", async () => {
const blob = await exportDocxBlob("Bookmark.docx", '<h1 id="Section1">Section</h1><p><a href="#Section1">Open section</a></p>');
const file = new File([blob], "Bookmark.docx", {
type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
});
const imported = await importDocxFile(file);
expect(imported.html).toContain('<h1 id="Section1">Section</h1>');
expect(imported.html).toContain('<a href="#Section1">Open section</a>');
expect(htmlToWordBlocks(imported.html)).toEqual([
{ type: "heading", level: 1, text: "Section", runs: [{ text: "Section" }], bookmark: "Section1" },
{ type: "paragraph", text: "Open section", runs: [{ text: "Open section", href: "#Section1" }] }
]);
});
it("imports simple DOCX tables from OOXML", async () => {
const blob = await exportDocxBlob(
"Table.docx",
"<p>Before table</p><table><tr><th>Stage</th><th>Owner</th></tr><tr><td>Plan</td><td>Anna</td></tr></table><p>After table</p>"
);
const file = new File([blob], "Table.docx", {
type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
});
const imported = await importDocxFile(file);
expect(imported.html).toContain("<table>");
expect(imported.html).toContain("<td><p>Stage</p></td>");
expect(imported.html).toContain("<td><p>Anna</p></td>");
expect(htmlToWordBlocks(imported.html)).toEqual([
{ type: "paragraph", text: "Before table", runs: [{ text: "Before table" }] },
{
type: "table",
rows: [
["Stage", "Owner"],
["Plan", "Anna"]
]
},
{ type: "paragraph", text: "After table", runs: [{ text: "After table" }] }
]);
});
it("imports DOCX bullet and numbered lists from OOXML numbering", async () => {
const blob = await exportDocxBlob(
"Lists.docx",
'<ul><li>Bullet <strong>one</strong></li><li><span style="color: #0f5fae;">Bullet two</span></li></ul><ol><li>Step one</li><li>Step two</li></ol>'
);
const file = new File([blob], "Lists.docx", {
type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
});
const imported = await importDocxFile(file);
expect(imported.html).toContain("<ul>");
expect(imported.html).toContain("<ol>");
expect(imported.html).toContain("color: #0F5FAE;");
expect(htmlToWordBlocks(imported.html)).toEqual([
{
type: "list",
ordered: false,
items: [
{ text: "Bullet one", runs: [{ text: "Bullet " }, { text: "one", bold: true }] },
{ text: "Bullet two", runs: [{ text: "Bullet two", color: "0F5FAE" }] }
]
},
{
type: "list",
ordered: true,
items: [
{ text: "Step one", runs: [{ text: "Step one" }] },
{ text: "Step two", runs: [{ text: "Step two" }] }
]
}
]);
});
it("imports styled DOCX content when the document also contains images", async () => {
const blob = await exportDocxBlob(
"Mixed.docx",
`<p><span style="color: #0f5fae;">Color fragment</span></p><img src="${tinyPngDataUri}" alt="Logo" width="32" height="32"><table><tr><td>Stage</td><td>Done</td></tr></table>`
);
const file = new File([blob], "Mixed.docx", {
type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
});
const imported = await importDocxFile(file);
const blocks = htmlToWordBlocks(imported.html);
expect(imported.html).toContain("color: #0F5FAE;");
expect(imported.html).toContain("<table>");
expect(imported.html).toContain(`src="${tinyPngDataUri}"`);
expect(blocks[0]).toEqual({
type: "paragraph",
text: "Color fragment",
runs: [{ text: "Color fragment", color: "0F5FAE" }]
});
expect(blocks[1]).toMatchObject({
type: "image",
src: tinyPngDataUri,
alt: "Logo",
width: 32,
height: 32
});
expect(blocks[2]).toEqual({
type: "table",
rows: [["Stage", "Done"]]
});
});
it("imports DOCX paragraph indentation from OOXML", async () => {
const blob = await exportDocxBlob(
"Indent.docx",
'<p style="margin-left: 36pt; text-indent: 18pt;">Indented</p><p style="margin-left: 48px; text-indent: -12pt;">Hanging</p>'
);
const file = new File([blob], "Indent.docx", {
type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
});
const imported = await importDocxFile(file);
expect(imported.html).toContain("margin-left: 36pt; text-indent: 18pt;");
expect(imported.html).toContain("margin-left: 36pt; text-indent: -12pt;");
expect(htmlToWordBlocks(imported.html)).toEqual([
{ type: "paragraph", text: "Indented", runs: [{ text: "Indented" }], indent: { left: 720, firstLine: 360 } },
{ type: "paragraph", text: "Hanging", runs: [{ text: "Hanging" }], indent: { left: 720, hanging: 240 } }
]);
});
it("imports DOCX paragraph spacing from OOXML", async () => {
const blob = await exportDocxBlob(
"Spacing.docx",
'<h2 style="margin-top: 6pt; margin-bottom: 10pt;">Title spacing</h2><p style="margin-top: 12pt; margin-bottom: 18pt;">Paragraph spacing</p>'
);
const file = new File([blob], "Spacing.docx", {
type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
});
const imported = await importDocxFile(file);
expect(imported.html).toContain("margin-top: 6pt; margin-bottom: 10pt;");
expect(imported.html).toContain("margin-top: 12pt; margin-bottom: 18pt;");
expect(htmlToWordBlocks(imported.html)).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("imports DOCX paragraph line spacing from OOXML", async () => {
const blob = await exportDocxBlob(
"LineSpacing.docx",
'<p style="line-height: 1.5;">Auto line height</p><p style="line-height: 24pt;">Exact line height</p>'
);
const file = new File([blob], "LineSpacing.docx", {
type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
});
const imported = await importDocxFile(file);
expect(imported.html).toContain("line-height: 1.5;");
expect(imported.html).toContain("line-height: 24pt;");
expect(htmlToWordBlocks(imported.html)).toEqual([
{ type: "paragraph", text: "Auto line height", runs: [{ text: "Auto line height" }], spacing: { line: 360, lineRule: "auto" } },
{ type: "paragraph", text: "Exact line height", runs: [{ text: "Exact line height" }], spacing: { line: 480, lineRule: "exact" } }
]);
});
});