Skip to content

Commit 8e1bb03

Browse files
test(web): attachment preview omission cases check the prepared document instead of rendering each one (#25562)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 374e57d commit 8e1bb03

1 file changed

Lines changed: 65 additions & 39 deletions

File tree

‎apps/web/src/components/files/AttachmentFilePreview.browser.test.tsx‎

Lines changed: 65 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1259,7 +1259,7 @@ it.each([
12591259
}
12601260
});
12611261

1262-
it.each([
1262+
const EXPANDED_WORD_ROLES = [
12631263
"footnotes",
12641264
"note roots",
12651265
"endnotes",
@@ -1272,8 +1272,9 @@ it.each([
12721272
"style visits",
12731273
"numbering",
12741274
"picture numbering",
1275-
])("keeps the body and warns when omitting expanded %s", async (role) => {
1276-
const { default: JSZip } = await import("jszip");
1275+
] as const;
1276+
1277+
async function expandedWordFixture(role: (typeof EXPANDED_WORD_ROLES)[number]) {
12771278
const archive = new JSZip();
12781279
const ns = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
12791280
const type = role.startsWith("style")
@@ -1289,8 +1290,7 @@ it.each([
12891290
);
12901291
const text = (value: string) => `<w:p><w:r><w:t>${value}</w:t></w:r></w:p>`;
12911292
// Each copy of the 2,000-paragraph payload costs 8,000 markup units, so 20
1292-
// references (or pages) exceed the 100,000-unit expansion budget. More pages
1293-
// only add layout time to the preview.
1293+
// references (or pages) exceed the 100,000-unit expansion budget.
12941294
archive.file(
12951295
"word/document.xml",
12961296
`<w:document xmlns:w="${ns}" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><w:body>${text("body retained")}${Array.from({ length: 20 }, () => `<w:p><w:r><w:footnoteReference w:id="1"/><w:endnoteReference w:id="1"/><w:commentReference w:id="1"/><w:br w:type="page"/></w:r></w:p>`).join("")}<w:sectPr><w:headerReference w:type="default" r:id="aux"/><w:footerReference w:type="default" r:id="aux"/></w:sectPr></w:body></w:document>`,
@@ -1320,47 +1320,73 @@ it.each([
13201320
`<w:footnotes xmlns:w="${ns}" padding="${"x".repeat(32_000)}">${Array.from({ length: 200 }, (_, id) => `<w:footnote w:id="${id}">${text("omitted payload")}</w:footnote>`).join("")}</w:footnotes>`,
13211321
);
13221322
}
1323-
const file = await archive.generateAsync({ type: "blob", compression: "DEFLATE" });
1324-
if (role === "note roots") {
1323+
return archive.generateAsync({ type: "blob", compression: "DEFLATE" });
1324+
}
1325+
1326+
// Rendering starts a layout engine per case, which dominated this file on loaded CI
1327+
// runners. The omission decision lives in prepareOffice, so every role is checked there.
1328+
it.each(EXPANDED_WORD_ROLES)(
1329+
"keeps the body and omits expanded %s from the prepared document",
1330+
async (role) => {
1331+
const file = await expandedWordFixture(role);
13251332
const { SaxesParser } = await import("saxes");
13261333
const { prepareOffice } = await import("./officeArchive");
13271334
const parse = vi.spyOn(SaxesParser.prototype, "write");
13281335
try {
1329-
expect((await prepareOffice(await file.arrayBuffer(), "docx")).truncated).toBe(true);
1330-
expect(
1331-
parse.mock.calls.reduce(
1332-
(sum, [chunk]) => sum + (typeof chunk === "string" ? chunk.length : 0),
1333-
0,
1334-
),
1335-
).toBeLessThan(4 * 1024 * 1024);
1336+
const prepared = await prepareOffice(await file.arrayBuffer(), "docx");
1337+
expect(prepared.truncated).toBe(true);
1338+
if (role === "note roots")
1339+
expect(
1340+
parse.mock.calls.reduce(
1341+
(sum, [chunk]) => sum + (typeof chunk === "string" ? chunk.length : 0),
1342+
0,
1343+
),
1344+
).toBeLessThan(4 * 1024 * 1024);
1345+
const bounded = await JSZip.loadAsync(prepared.bytes);
1346+
const markup = await bounded.file("word/document.xml")!.async("string");
1347+
expect(markup).toContain("body retained");
1348+
expect(bounded.file("word/media/aux.xml")).toBeNull();
1349+
// A dangling header or footer reference makes the engine reject the whole body.
1350+
expect(markup).not.toMatch(/<w:(header|footer)Reference/);
1351+
for (const entry of Object.values(bounded.files))
1352+
if (!entry.dir) expect(await entry.async("string")).not.toContain("omitted payload");
13361353
} finally {
13371354
parse.mockRestore();
13381355
}
1339-
}
1340-
const src = URL.createObjectURL(file);
1341-
try {
1342-
const container = document.createElement("div");
1343-
container.style.cssText = "display:flex;flex-direction:column;width:640px;height:700px";
1344-
await using screen = await render(
1345-
<OfficeFilePreview src={src} name="expanded.docx" kind="docx" sizeBytes={file.size} />,
1346-
{ container },
1347-
);
1348-
await vi.waitFor(
1349-
() => {
1350-
expect(screen.container.querySelector('[role="alert"]')?.textContent ?? "").toBe("");
1351-
expect(screen.container.textContent).toContain("body retained");
1352-
},
1353-
{ timeout: 30_000 },
1354-
);
1355-
expect(screen.container.textContent).toContain("Preview limited");
1356-
expect(screen.container.textContent).not.toContain("omitted payload");
1357-
await vi.waitFor(() =>
1358-
expect(screen.container.querySelector("canvas")!.width).toBeGreaterThan(0),
1359-
);
1360-
} finally {
1361-
URL.revokeObjectURL(src);
1362-
}
1363-
});
1356+
},
1357+
);
1358+
1359+
// Two full renders prove the engine accepts a prepared body whose parts were removed
1360+
// (body references for notes, section references for headers and footers) and show the warning.
1361+
it.each(["footnotes", "footer"] as const)(
1362+
"renders the body and warns when omitting expanded %s",
1363+
async (role) => {
1364+
const file = await expandedWordFixture(role);
1365+
const src = URL.createObjectURL(file);
1366+
try {
1367+
const container = document.createElement("div");
1368+
container.style.cssText = "display:flex;flex-direction:column;width:640px;height:700px";
1369+
await using screen = await render(
1370+
<OfficeFilePreview src={src} name="expanded.docx" kind="docx" sizeBytes={file.size} />,
1371+
{ container },
1372+
);
1373+
await vi.waitFor(
1374+
() => {
1375+
expect(screen.container.querySelector('[role="alert"]')?.textContent ?? "").toBe("");
1376+
expect(screen.container.textContent).toContain("body retained");
1377+
},
1378+
{ timeout: 30_000 },
1379+
);
1380+
expect(screen.container.textContent).toContain("Preview limited");
1381+
expect(screen.container.textContent).not.toContain("omitted payload");
1382+
await vi.waitFor(() =>
1383+
expect(screen.container.querySelector("canvas")!.width).toBeGreaterThan(0),
1384+
);
1385+
} finally {
1386+
URL.revokeObjectURL(src);
1387+
}
1388+
},
1389+
);
13641390

13651391
it.each([
13661392
"ordinary parts",

0 commit comments

Comments
 (0)