Files
WRNexusJS/packages/viewer/test/optional.test.ts
T

136 lines
6.2 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import { docxToHtml } from "../src/optional/docx.ts";
import { readWorkbook } from "../src/optional/sheet.ts";
import { listZip } from "../src/optional/zip.ts";
import { zipSync, strToU8 } from "fflate";
/**
* These exercise the parsers themselves. The browser wiring — one runtime asset
* per installed package, fetched on first use — is covered in component.test.ts.
*/
describe("zip listing", () => {
const archive = zipSync({
"docs/readme.md": strToU8("hello world\n".repeat(20)),
"src/index.ts": strToU8("export const value = 1;\n"),
});
test("entries are listed with their uncompressed sizes", async () => {
const entries = await listZip(archive);
expect(entries.map((entry) => entry.name).sort()).toEqual(["docs/readme.md", "src/index.ts"]);
expect(entries.find((entry) => entry.name === "docs/readme.md")!.size).toBe(240);
});
test("a corrupt archive rejects rather than resolving empty", async () => {
await expect(listZip(new Uint8Array([0x50, 0x4b, 3, 4, 9, 9, 9]))).rejects.toThrow();
});
});
describe("workbook reading", () => {
// Built with the same shape as examples/basic-app/public/viewer/sample.xlsx.
const NS = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
const R = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
const sheet = (rows: (string | number)[][]) =>
`<?xml version="1.0"?><worksheet xmlns="${NS}"><sheetData>${rows
.map(
(row, rowIndex) =>
`<row r="${rowIndex + 1}">${row
.map((value, index) => {
const ref = `${String.fromCharCode(65 + index)}${rowIndex + 1}`;
return typeof value === "number"
? `<c r="${ref}"><v>${value}</v></c>`
: `<c r="${ref}" t="inlineStr"><is><t>${value}</t></is></c>`;
})
.join("")}</row>`,
)
.join("")}</sheetData></worksheet>`;
const workbook = zipSync({
"[Content_Types].xml": strToU8(
'<?xml version="1.0"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">' +
'<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>' +
'<Default Extension="xml" ContentType="application/xml"/>' +
'<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>' +
'<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>' +
'<Override PartName="/xl/worksheets/sheet2.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>' +
"</Types>",
),
"_rels/.rels": strToU8(
`<?xml version="1.0"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="${R}/officeDocument" Target="xl/workbook.xml"/></Relationships>`,
),
"xl/workbook.xml": strToU8(
`<?xml version="1.0"?><workbook xmlns="${NS}" xmlns:r="${R}"><sheets>` +
'<sheet name="Revenue" sheetId="1" r:id="rId1"/>' +
'<sheet name="Headcount" sheetId="2" r:id="rId2"/>' +
"</sheets></workbook>",
),
"xl/_rels/workbook.xml.rels": strToU8(
`<?xml version="1.0"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">` +
`<Relationship Id="rId1" Type="${R}/worksheet" Target="worksheets/sheet1.xml"/>` +
`<Relationship Id="rId2" Type="${R}/worksheet" Target="worksheets/sheet2.xml"/>` +
"</Relationships>",
),
"xl/worksheets/sheet1.xml": strToU8(
sheet([
["Region", "Q1"],
["North", 15400],
["South", 11200],
]),
),
"xl/worksheets/sheet2.xml": strToU8(
sheet([
["Team", "People"],
["Engineering", 42],
]),
),
});
test("every sheet comes back named, with a header and string cells", () => {
const sheets = readWorkbook(workbook);
expect(sheets.map((item) => item.name)).toEqual(["Revenue", "Headcount"]);
expect(sheets[0]!.header).toEqual(["Region", "Q1"]);
expect(sheets[0]!.rows).toEqual([
["North", "15400"],
["South", "11200"],
]);
expect(sheets[1]!.rows).toEqual([["Engineering", "42"]]);
});
});
describe("document conversion", () => {
const W = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
const R = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
const paragraph = (text: string, style?: string) =>
`<w:p>${style ? `<w:pPr><w:pStyle w:val="${style}"/></w:pPr>` : ""}` +
`<w:r><w:t xml:space="preserve">${text}</w:t></w:r></w:p>`;
const document = zipSync({
"[Content_Types].xml": strToU8(
'<?xml version="1.0"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">' +
'<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>' +
'<Default Extension="xml" ContentType="application/xml"/>' +
'<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>' +
"</Types>",
),
"_rels/.rels": strToU8(
`<?xml version="1.0"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="${R}/officeDocument" Target="word/document.xml"/></Relationships>`,
),
"word/document.xml": strToU8(
`<?xml version="1.0"?><w:document xmlns:w="${W}"><w:body>` +
paragraph("Quarterly summary", "Heading1") +
paragraph("Bodies of text survive; page layout does not.") +
"</w:body></w:document>",
),
});
test("a docx becomes semantic HTML", async () => {
const html = await docxToHtml(document);
expect(html).toContain("<h1>Quarterly summary</h1>");
expect(html).toContain("Bodies of text survive");
// Semantic output only: no document chrome, and nothing executable.
expect(html).not.toContain("<script");
});
test("a file that is not a docx rejects", async () => {
await expect(docxToHtml(strToU8("not a document"))).rejects.toThrow();
});
});