import { describe, expect, it } from "vitest";
import {
collectOdfTransferables,
parseOdp,
parseOds,
parseOdt,
parseOpenDocument,
} from "../../../src/office/odf";
import { makeOdf, odpContent, odsContent, odtContent, XMLNS } from "./fixtures";
const PIXEL = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]);
describe("OpenDocument normalized models", () => {
it("parses ODT metadata, styles, rich text, lists, tables, images, and notes", () => {
const content = odtContent(`
A heading
Helloworld!
1
Footnote body
First
Second
Cell
PixelA tiny pixel
`);
const meta = `
Test documentAda
localoffice
`;
const styles = `
`;
const result = parseOdt(
makeOdf("odt", content, {
extra: { "Pictures/pixel.png": PIXEL },
meta,
styles,
}),
{ fileName: "example.odt" },
);
expect(result.format).toBe("odt");
expect(result.metadata).toMatchObject({
title: "Test document",
creator: "Ada",
keywords: ["local", "office"],
statistics: { wordCount: 8, pageCount: 1 },
});
expect(result.styles).toEqual(
expect.arrayContaining([
expect.objectContaining({
name: "Body",
family: "paragraph",
properties: { "paragraph-properties.fo:text-align": "start" },
}),
expect.objectContaining({ name: "InlineBold", family: "text" }),
]),
);
expect(result.blocks[0]).toMatchObject({
kind: "heading",
level: 2,
text: "A heading",
});
expect(result.blocks[1]).toMatchObject({
kind: "paragraph",
text: expect.stringContaining("Hello world\t!"),
runs: expect.arrayContaining([
expect.objectContaining({ text: "world", styleName: "InlineBold" }),
]),
});
expect(result.blocks[2]).toMatchObject({ kind: "list", items: [{}, {}] });
expect(result.blocks[3]).toMatchObject({
kind: "table",
name: "Data",
rows: [{ rowRepeat: 2, cells: [{ columnSpan: 2 }, { covered: true }] }],
});
expect(result.blocks[4]).toMatchObject({
kind: "image",
assetId: "asset-1",
alt: "A tiny pixel",
title: "Pixel",
width: { value: 3, unit: "cm" },
});
expect(result.notes).toHaveLength(1);
expect(result.notes[0]).toMatchObject({ id: "note-1", citation: "1" });
expect(result.assets[0]).toMatchObject({
id: "asset-1",
path: "Pictures/pixel.png",
mediaType: "image/png",
byteLength: PIXEL.byteLength,
});
expect(new Uint8Array(result.assets[0]!.data)).toEqual(PIXEL);
expect(collectOdfTransferables(result)).toEqual([result.assets[0]!.data]);
});
it("parses ODS sheets, typed values, formulas, repeats, merges, and annotations", () => {
const content = odsContent(`
Name
42.50
Cached value only
TRUE
`);
const result = parseOds(makeOdf("ods", content));
const sheet = result.sheets[0]!;
expect(sheet).toMatchObject({
name: "Budget",
styleName: "SheetStyle",
protected: true,
expandedRowCount: 2,
expandedCellCount: 10,
});
expect(sheet.rows[0]).toMatchObject({
index: 0,
rowRepeat: 2,
styleName: "RowStyle",
});
expect(sheet.rows[0]!.cells).toEqual(
expect.arrayContaining([
expect.objectContaining({ column: 0, value: "Name", display: "Name" }),
expect.objectContaining({
column: 1,
columnRepeat: 2,
value: 42.5,
formula: "of:=SUM([.B1:.C1])",
display: "42.50",
annotation: [
expect.objectContaining({
kind: "paragraph",
text: "Cached value only",
}),
],
}),
expect.objectContaining({
column: 3,
columnSpan: 2,
rowSpan: 2,
value: true,
}),
expect.objectContaining({ column: 4, kind: "covered" }),
]),
);
});
it("trims office-suite full-grid tail repeats without expanding them", () => {
const content = odsContent(`
Only value
`);
const sheet = parseOds(makeOdf("ods", content)).sheets[0]!;
expect(sheet.expandedRowCount).toBe(1);
expect(sheet.expandedCellCount).toBe(1);
expect(sheet.rows).toHaveLength(1);
expect(sheet.rows[0]!.cells).toHaveLength(1);
});
it("parses ODP slides, coordinates, text, shapes, images, tables, groups, and notes", () => {
const content = odpContent(`
Local Office
Box
A1
Speaker note
`);
const result = parseOdp(
makeOdf("odp", content, { extra: { "Pictures/pixel.png": PIXEL } }),
);
const slide = result.slides[0]!;
expect(slide).toMatchObject({
name: "Opening",
styleName: "SlideStyle",
masterPageName: "Master",
layoutName: "TitleLayout",
notes: [
expect.objectContaining({ kind: "paragraph", text: "Speaker note" }),
],
});
expect(slide.shapes).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: "title",
type: "text-box",
x: { value: 1, unit: "cm", raw: "1cm" },
blocks: [
expect.objectContaining({
kind: "paragraph",
text: "Local Office",
}),
],
}),
expect.objectContaining({ type: "rectangle", name: "Accent" }),
expect.objectContaining({
id: "picture",
type: "image",
image: expect.objectContaining({ assetId: "asset-1" }),
}),
expect.objectContaining({
id: "grid",
type: "table",
table: expect.objectContaining({ name: "Slide table" }),
}),
expect.objectContaining({
id: "group",
type: "group",
children: [
expect.objectContaining({ id: "circle", type: "ellipse" }),
],
}),
]),
);
});
it("auto-detects formats without parsing the package twice and warns on a missing manifest", () => {
const document = parseOpenDocument(
makeOdf("odt", odtContent("Minimal"), {
manifest: false,
}),
);
expect(document.format).toBe("odt");
expect(document.warnings).toContain("Package has no META-INF/manifest.xml");
});
});