367 lines
14 KiB
TypeScript
367 lines
14 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 } }
|
|
]);
|
|
});
|
|
});
|