feat: release Office Tools 0.1.0
This commit is contained in:
@@ -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");
|
||||
});
|
||||
@@ -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",
|
||||
);
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
Reference in New Issue
Block a user