feat: release Office Tools 0.1.0

This commit is contained in:
2026-08-31 22:37:01 +02:00
commit a5524e27e3
90 changed files with 16328 additions and 0 deletions
+102
View File
@@ -0,0 +1,102 @@
import { expect, test, type Page } from "@playwright/test";
import {
createMinimalOdp,
createMinimalOds,
createMinimalOdt,
type OdfFixture,
} from "../fixtures/odf";
const entry = "/deep/nested/office/";
async function openFixture(page: Page, fixture: OdfFixture) {
await page.goto(entry);
await page.getByTestId("office-file-input").setInputFiles(fixture);
await expect(page.getByText("Rendered locally", { exact: true })).toBeVisible(
{
timeout: 30_000,
},
);
}
async function find(page: Page, value: string) {
const search = page.getByRole("search");
await search.getByPlaceholder("Find in file").fill(value);
await search.getByRole("button", { name: "Find" }).click();
await expect(search.getByText("1 of 1", { exact: true })).toBeVisible();
}
test("renders and searches a project-authored ODT in an isolated worker", async ({
page,
}) => {
const externalHosts = new Set<string>();
const errors: string[] = [];
const workerScripts: URL[] = [];
page.on("request", (request) => {
const url = new URL(request.url());
if (
(url.protocol === "http:" || url.protocol === "https:") &&
url.hostname !== "127.0.0.1"
)
externalHosts.add(url.hostname);
if (url.pathname.includes("odf.worker")) workerScripts.push(url);
});
page.on("console", (message) => {
if (message.type() === "error") errors.push(message.text());
});
page.on("pageerror", (error) => errors.push(error.message));
await openFixture(page, createMinimalOdt());
await expect(
page.getByRole("heading", { name: "OpenDocument Text Fixture" }),
).toBeVisible();
await expect(
page.getByRole("table", { name: "Fixture table" }),
).toBeVisible();
await expect(
page.getByRole("img", { name: "Project-authored fixture image" }),
).toBeVisible();
await find(page, "ODT Search Marker");
await page.getByRole("button", { name: "Zoom in" }).click();
await expect(page.getByText("110%", { exact: true })).toBeVisible();
expect(externalHosts).toEqual(new Set());
expect(errors).toEqual([]);
expect(workerScripts.length).toBeGreaterThan(0);
expect(
workerScripts.every(
(url) =>
url.origin === "http://127.0.0.1:4173" &&
url.pathname.startsWith("/deep/nested/office/assets/"),
),
).toBe(true);
});
test("renders, searches and switches sheets in a project-authored ODS", async ({
page,
}) => {
await openFixture(page, createMinimalOds());
await expect(page.getByText("sheet 1 of 2", { exact: true })).toBeVisible();
await expect(page.getByRole("grid")).toContainText("Office Tools Workbook");
await find(page, "ODS Search Marker");
await page.getByRole("button", { name: "Next sheet" }).click();
await expect(page.getByText("sheet 2 of 2", { exact: true })).toBeVisible();
await expect(page.getByRole("grid")).toContainText("Second sheet content");
});
test("renders, searches and navigates a project-authored ODP", async ({
page,
}) => {
await openFixture(page, createMinimalOdp());
await expect(page.getByText("slide 1 of 2", { exact: true })).toBeVisible();
await expect(
page.getByRole("heading", { name: "OpenDocument Presentation Fixture" }),
).toBeVisible();
await find(page, "ODP Search Marker");
await expect(page.getByText("slide 2 of 2", { exact: true })).toBeVisible();
await expect(
page.getByRole("heading", { name: "ODP Search Marker" }),
).toBeVisible();
await expect(
page.getByRole("complementary", { name: "Speaker notes" }),
).toContainText("Second speaker note");
});
+38
View File
@@ -0,0 +1,38 @@
import { expect, test } from "@playwright/test";
const entry = "/deep/nested/office/";
test("loads from a nested path without external requests", async ({ page }) => {
const external: string[] = [];
page.on("request", (request) => {
const url = new URL(request.url());
if (url.hostname !== "127.0.0.1") external.push(request.url());
});
await page.goto(entry);
await expect(
page.getByRole("heading", { name: "Open an office file" }),
).toBeVisible();
await expect(page.getByText("No upload")).toBeVisible();
expect(external).toEqual([]);
});
test("publishes valid relocatable metadata", async ({ request }) => {
const manifestResponse = await request.get(`${entry}toolbox-app.json`);
expect(manifestResponse.ok()).toBe(true);
await expect(manifestResponse.json()).resolves.toMatchObject({
schemaVersion: 1,
id: "de.add-ideas.office-tools",
version: "0.1.0",
entry: "./",
icon: "./favicon.svg",
privacy: { processing: "local", fileUploads: false, telemetry: false },
});
const webManifest = await request.get(`${entry}manifest.webmanifest`);
expect(webManifest.ok()).toBe(true);
expect(webManifest.headers()["content-type"]).toContain(
"application/manifest+json",
);
});
+164
View File
@@ -0,0 +1,164 @@
import { expect, test, type Locator, type Page } from "@playwright/test";
import {
createMalformedDocx,
createMinimalDocx,
createMinimalPptx,
createMinimalXlsx,
type OoxmlFixture,
} from "../fixtures/ooxml";
const entry = "/deep/nested/office/";
async function openFixture(page: Page, fixture: OoxmlFixture) {
await page.goto(entry);
await page
.getByTestId("office-file-input")
.setInputFiles(
fixture as unknown as Parameters<Locator["setInputFiles"]>[0],
);
}
async function expectRenderedPackage(
page: Page,
format: "docx" | "xlsx" | "pptx",
) {
await expect(page.getByTestId("opened-file")).toBeVisible();
await expect(page.getByText("Rendered locally", { exact: true })).toBeVisible(
{
timeout: 90_000,
},
);
await expect(
page.locator(`.office-render-stage--${format} canvas`).first(),
).toBeVisible();
const canvas = page.locator(`.office-render-stage--${format} canvas`).first();
const dimensions = await canvas.evaluate((element) => {
const rendered = element as HTMLCanvasElement;
const bounds = rendered.getBoundingClientRect();
return {
bitmapHeight: rendered.height,
bitmapWidth: rendered.width,
cssHeight: bounds.height,
cssWidth: bounds.width,
};
});
expect(dimensions.bitmapWidth).toBeGreaterThan(1);
expect(dimensions.bitmapHeight).toBeGreaterThan(1);
expect(dimensions.cssWidth).toBeGreaterThan(1);
expect(dimensions.cssHeight).toBeGreaterThan(1);
}
async function expectOneSearchMatch(page: Page, text: string) {
await page.getByRole("search").getByPlaceholder("Find in file").fill(text);
await page.getByRole("search").getByRole("button", { name: "Find" }).click();
await expect(page.getByText("1 match", { exact: true })).toBeVisible();
}
test("opens, renders, searches and navigates a project-authored DOCX", async ({
page,
}) => {
const externalHosts = new Set<string>();
const runtimeErrors: string[] = [];
const wasmResponses: Array<{
cacheControl: string;
contentType: string;
url: URL;
}> = [];
page.on("request", (request) => {
const url = new URL(request.url());
if (url.hostname !== "127.0.0.1") externalHosts.add(url.hostname);
});
page.on("response", (response) => {
const url = new URL(response.url());
if (url.pathname.endsWith(".wasm")) {
wasmResponses.push({
cacheControl: response.headers()["cache-control"] ?? "",
contentType: response.headers()["content-type"] ?? "",
url,
});
}
});
page.on("console", (message) => {
if (message.type() === "error") runtimeErrors.push(message.text());
});
page.on("pageerror", (error) => runtimeErrors.push(error.message));
await openFixture(page, createMinimalDocx());
await expectRenderedPackage(page, "docx");
await expect(page.getByText(/page 1 of [2-9]\d*/i)).toBeVisible();
await expectOneSearchMatch(page, "Second Page Search Marker");
await page.getByRole("button", { name: "Next page" }).click();
await expect(page.getByText(/page 2 of [2-9]\d*/i)).toBeVisible();
expect(externalHosts).toEqual(new Set());
expect(wasmResponses.length).toBeGreaterThan(0);
expect(
wasmResponses.every(
({ cacheControl, contentType, url }) =>
url.origin === "http://127.0.0.1:4173" &&
url.pathname.startsWith("/deep/nested/office/assets/") &&
contentType.startsWith("application/wasm") &&
cacheControl.includes("max-age=31536000") &&
cacheControl.includes("immutable"),
),
).toBe(true);
expect(runtimeErrors).toEqual([]);
});
test("opens, renders, searches and switches sheets in a project-authored XLSX", async ({
page,
}) => {
await openFixture(page, createMinimalXlsx());
await expectRenderedPackage(page, "xlsx");
await expect(page.getByText("sheet 1 of 2", { exact: true })).toBeVisible();
await expectOneSearchMatch(page, "Office Tools Workbook Fixture");
await page.getByRole("button", { name: "Next sheet" }).click();
await expect(page.getByText("sheet 2 of 2", { exact: true })).toBeVisible();
});
test("opens, renders, searches and navigates a project-authored PPTX", async ({
page,
}) => {
await openFixture(page, createMinimalPptx());
await expectRenderedPackage(page, "pptx");
await expect(page.getByText("slide 1 of 2", { exact: true })).toBeVisible();
await expectOneSearchMatch(page, "Second Slide Search Marker");
await page.getByRole("button", { name: "Next slide" }).click();
await expect(page.getByText("slide 2 of 2", { exact: true })).toBeVisible();
});
test("fails closed with an inline error for a malformed OOXML package", async ({
page,
}) => {
await openFixture(page, createMalformedDocx());
const alert = page.getByRole("alert");
await expect(alert).toBeVisible({ timeout: 90_000 });
await expect(alert).not.toBeEmpty();
await expect(
page.locator(".file-heading__state .state-dot--error"),
).toBeVisible();
});
test("rejects a valid package whose contents do not match its extension", async ({
page,
}) => {
const workbook = createMinimalXlsx();
await openFixture(page, {
...workbook,
mimeType:
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
name: "workbook-disguised-as-document.docx",
});
const alert = page.getByRole("alert");
await expect(alert).toBeVisible({ timeout: 90_000 });
await expect(alert).not.toBeEmpty();
await expect(
page.locator(".file-heading__state .state-dot--error"),
).toBeVisible();
});