Files
zemion 08b8f84f7b
Verify / verify (push) Canceled after 0s
Release Office Tools 0.2.0
2026-09-02 07:32:31 +02:00

166 lines
5.5 KiB
TypeScript

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")).toHaveCount(0);
await expect(
page.getByRole("heading", { name: "Choose or drop a file" }),
).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();
});