Release Office Tools 0.2.0
Verify / verify (push) Canceled after 0s

This commit is contained in:
2026-09-02 07:32:31 +02:00
parent a5524e27e3
commit 08b8f84f7b
50 changed files with 2196 additions and 165 deletions
+37
View File
@@ -0,0 +1,37 @@
import { describe, expect, it } from "vitest";
import { sheetToCsv } from "../../src/office/export";
import { parseOds } from "../../src/office/odf";
import { makeOdf, odsContent } from "./odf/fixtures";
describe("bounded OpenDocument exports", () => {
it("exports cached sheet displays with repeats and CSV quoting", () => {
const document = parseOds(
makeOdf(
"ods",
odsContent(`<table:table table:name="Data">
<table:table-row table:number-rows-repeated="2">
<table:table-cell office:value-type="string"><text:p>A, B</text:p></table:table-cell>
<table:table-cell table:number-columns-repeated="2" office:value-type="float" office:value="42"><text:p>42</text:p></table:table-cell>
</table:table-row>
</table:table>`),
),
);
expect(sheetToCsv(document.sheets[0]!)).toBe(
'"A, B",42,42\r\n"A, B",42,42',
);
});
it("refuses unbounded expanded CSV rows", () => {
const document = parseOds(
makeOdf(
"ods",
odsContent(`<table:table table:name="Large">
<table:table-row table:number-rows-repeated="50001">
<table:table-cell office:value-type="string"><text:p>x</text:p></table:table-cell>
</table:table-row>
</table:table>`),
),
);
expect(() => sheetToCsv(document.sheets[0]!)).toThrow(/50,000 rows/u);
});
});
+22
View File
@@ -7,11 +7,15 @@ import {
familyForFormat,
formatBytes,
formatLabel,
validateOfficePackagePrefix,
} from "../../src/office/formats";
describe("office format handling", () => {
it.each([
["REPORT.DOCX", "docx"],
["REPORT.DOCM", "docx"],
["template.xltx", "xlsx"],
["show.ppsm", "pptx"],
["notes.odt", "odt"],
["budget.xlsx", "xlsx"],
["budget.ODS", "ods"],
@@ -21,6 +25,24 @@ describe("office format handling", () => {
expect(detectOfficeFormat({ name, size: 42 })).toBe(expected);
});
it("rejects renamed compound-binary, RTF and non-package signatures", () => {
expect(() =>
validateOfficePackagePrefix(
Uint8Array.from([0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1]),
"docx",
),
).toThrow(/OLE Compound File/u);
expect(() =>
validateOfficePackagePrefix(new TextEncoder().encode("{\\rtf1"), "docx"),
).toThrow(/Rich Text Format/u);
expect(() =>
validateOfficePackagePrefix(new TextEncoder().encode("not zip!"), "xlsx"),
).toThrow(/ZIP-based/u);
expect(() =>
validateOfficePackagePrefix(Uint8Array.from([0x50, 0x4b, 3, 4]), "pptx"),
).not.toThrow();
});
it("rejects empty, oversized, unsupported and legacy inputs explicitly", () => {
const cases = [
[{ name: "empty.docx", size: 0 }, "empty-file"],
+50
View File
@@ -254,4 +254,54 @@ describe("OpenDocument normalized models", () => {
expect(document.format).toBe("odt");
expect(document.warnings).toContain("Package has no META-INF/manifest.xml");
});
it("retains page geometry, default styles, comments and spreadsheet column metadata", () => {
const styles = `<?xml version="1.0"?><office:document-styles ${XMLNS}>
<office:styles><style:default-style style:family="paragraph"><style:text-properties fo:color="#123456"/></style:default-style></office:styles>
<office:automatic-styles><style:page-layout style:name="pm1"><style:page-layout-properties fo:page-width="21cm" fo:page-height="29.7cm"/></style:page-layout></office:automatic-styles>
</office:document-styles>`;
const odt = parseOdt(
makeOdf(
"odt",
odtContent(
`<text:p>Reviewed<office:annotation office:name="c1"><dc:creator>Ada</dc:creator><dc:date>2026-09-01</dc:date><text:p>Looks good</text:p></office:annotation></text:p>`,
),
{ styles },
),
);
expect(odt.pageWidth).toMatchObject({ value: 21, unit: "cm" });
expect(odt.styles).toEqual(
expect.arrayContaining([
expect.objectContaining({ isDefault: true, family: "paragraph" }),
]),
);
expect(odt.annotations[0]).toMatchObject({
id: "c1",
creator: "Ada",
createdAt: "2026-09-01",
blocks: [expect.objectContaining({ text: "Looks good" })],
});
const ods = parseOds(
makeOdf(
"ods",
odsContent(`<table:table table:name="Columns" table:visibility="collapse">
<table:table-column table:number-columns-repeated="3" table:style-name="Wide" table:default-cell-style-name="Money"/>
<table:table-row table:default-cell-style-name="RowDefault"><table:table-cell office:value-type="string"><text:p>x</text:p></table:table-cell></table:table-row>
</table:table>`),
),
);
expect(ods.sheets[0]).toMatchObject({
visibility: "collapse",
columns: [
{
index: 0,
repeat: 3,
styleName: "Wide",
defaultCellStyleName: "Money",
},
],
rows: [expect.objectContaining({ defaultCellStyleName: "RowDefault" })],
});
});
});