Skip to content

Commit 8010e65

Browse files
committed
test: make docx/odt exporter tests network-independent
The docx and odt exporter tests export a testDocument containing image blocks pointing at a remote placeholder URL (placehold.co), which the default exporter file resolver fetches over the network. This made the tests flaky and caused 'TypeError: fetch failed' (ETIMEDOUT/ENETUNREACH) in sandboxed CI without outbound network access. Add a test-only resolveFileUrl that returns an in-memory 332x322 JPEG (matching the remote image dimensions so existing snapshots are preserved) for the placeholder URL, and wire it into the docx and odt exporter tests.
1 parent 17bf418 commit 8010e65

4 files changed

Lines changed: 68 additions & 3 deletions

File tree

packages/xl-docx-exporter/src/docx/__snapshots__/withCustomOptions/document.xml.rels

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,6 @@
1313
<Relationship Id="FAKE-ID" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink" Target="https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3" TargetMode="External"/>
1414
<Relationship Id="FAKE-ID" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink" Target="" TargetMode="External"/>
1515
<Relationship Id="FAKE-ID" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink" Target="https://www.blocknotejs.org" TargetMode="External"/>
16-
<Relationship Id="FAKE-ID" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/c0c1a1a0abe0fdd4533c468319977db996162c95.gif"/>
16+
<Relationship Id="FAKE-ID" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/50e4c971936c813eeeacc22f2ae97b4bc2a023f0.gif"/>
1717
<Relationship Id="FAKE-ID" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable" Target="fontTable.xml"/>
1818
</Relationships>

packages/xl-docx-exporter/src/docx/docxExporter.test.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { docxDefaultSchemaMappings } from "./defaultSchema/index.js";
1818
import { DOCXExporter } from "./docxExporter.js";
1919
import { ColumnBlock, ColumnListBlock } from "@blocknote/xl-multi-column";
2020
import { partialBlocksToBlocksForTesting } from "@shared/formatConversionTestUtil.js";
21+
import { testResolveFileUrl } from "@shared/util/testFileResolver.js";
2122

2223
const getZIPEntryContent = (entries: Entry[], fileName: string) => {
2324
const entry = entries.find((entry) => {
@@ -40,6 +41,7 @@ describe("exporter", () => {
4041
},
4142
}),
4243
docxDefaultSchemaMappings,
44+
{ resolveFileUrl: testResolveFileUrl },
4345
);
4446
const doc = await exporter.toDocxJsDocument(testDocument, {
4547
sectionOptions: {},
@@ -73,6 +75,7 @@ describe("exporter", () => {
7375
},
7476
}),
7577
docxDefaultSchemaMappings,
78+
{ resolveFileUrl: testResolveFileUrl },
7679
);
7780

7881
const doc = await exporter.toDocxJsDocument(testDocument, {
@@ -145,7 +148,9 @@ describe("exporter", () => {
145148
columnList: ColumnListBlock,
146149
},
147150
});
148-
const exporter = new DOCXExporter(schema, docxDefaultSchemaMappings);
151+
const exporter = new DOCXExporter(schema, docxDefaultSchemaMappings, {
152+
resolveFileUrl: testResolveFileUrl,
153+
});
149154
const doc = await exporter.toDocxJsDocument(
150155
partialBlocksToBlocksForTesting(schema, [
151156
{
@@ -227,6 +232,7 @@ describe("exporter", () => {
227232
},
228233
}),
229234
docxDefaultSchemaMappings,
235+
{ resolveFileUrl: testResolveFileUrl },
230236
);
231237
const doc = await exporter.toDocxJsDocument(testDocument, {
232238
sectionOptions: {},

packages/xl-odt-exporter/src/odt/odtExporter.test.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { odtDefaultSchemaMappings } from "./defaultSchema/index.js";
1111
import { ODTExporter } from "./odtExporter.js";
1212
import { ColumnBlock, ColumnListBlock } from "@blocknote/xl-multi-column";
1313
import { partialBlocksToBlocksForTesting } from "@shared/formatConversionTestUtil.js";
14+
import { testResolveFileUrl } from "@shared/util/testFileResolver.js";
1415

1516
beforeAll(async () => {
1617
// @ts-expect-error - Blob polyfill for Node test environment
@@ -27,6 +28,7 @@ describe("exporter", () => {
2728
},
2829
}),
2930
odtDefaultSchemaMappings,
31+
{ resolveFileUrl: testResolveFileUrl },
3032
);
3133
const odt = await exporter.toODTDocument(testDocument);
3234
await testODTDocumentAgainstSnapshot(odt, {
@@ -47,6 +49,7 @@ describe("exporter", () => {
4749
},
4850
}),
4951
odtDefaultSchemaMappings,
52+
{ resolveFileUrl: testResolveFileUrl },
5053
);
5154

5255
const odt = await exporter.toODTDocument(testDocument, {
@@ -76,7 +79,9 @@ describe("exporter", () => {
7679
columnList: ColumnListBlock,
7780
},
7881
});
79-
const exporter = new ODTExporter(schema, odtDefaultSchemaMappings);
82+
const exporter = new ODTExporter(schema, odtDefaultSchemaMappings, {
83+
resolveFileUrl: testResolveFileUrl,
84+
});
8085
const odt = await exporter.toODTDocument(
8186
partialBlocksToBlocksForTesting(schema, [
8287
{

shared/util/testFileResolver.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* Test-only file resolver for exporter tests.
3+
*
4+
* The `testDocument` used across exporter tests contains image blocks that
5+
* point at a remote placeholder URL (https://placehold.co/332x322.jpg). The
6+
* default exporter file resolver fetches these over the network (via a CORS
7+
* proxy), which makes the tests flaky and causes failures in sandboxed CI
8+
* environments without outbound network access (`TypeError: fetch failed`).
9+
*
10+
* To keep the tests deterministic and network-independent, this module returns
11+
* a tiny in-memory JPEG with the exact same dimensions as the remote image
12+
* (332x322). Preserving the dimensions is important: the exported snapshots
13+
* embed the image size (e.g. `cx="3162300" cy="3067050"` = 332x322 * 9525
14+
* EMU/px), so any substitute must report identical dimensions to avoid
15+
* breaking existing snapshots.
16+
*/
17+
18+
// A minimal valid baseline JPEG whose SOF0 marker declares a 332x322 image.
19+
// Only the header is meaningful (image-meta reads dimensions from SOF0); the
20+
// pixel data is intentionally empty as exporters never decode the contents.
21+
const PLACEHOLDER_332x322_JPEG_BASE64 =
22+
"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAALCAFCAUwBAREA/9oACAEBAAA/AP/Z";
23+
24+
function base64ToArrayBuffer(base64: string): ArrayBuffer {
25+
if (typeof atob === "function") {
26+
const binary = atob(base64);
27+
const bytes = new Uint8Array(binary.length);
28+
for (let i = 0; i < binary.length; i++) {
29+
bytes[i] = binary.charCodeAt(i);
30+
}
31+
return bytes.buffer;
32+
}
33+
// Node.js fallback
34+
const buf = Buffer.from(base64, "base64");
35+
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
36+
}
37+
38+
/**
39+
* A {@link ExporterOptions.resolveFileUrl} implementation for tests that avoids
40+
* any network access. It returns a local 332x322 JPEG blob for the placeholder
41+
* image URL used in `testDocument`, and falls back to fetching for any other
42+
* URL.
43+
*/
44+
export async function testResolveFileUrl(url: string): Promise<string | Blob> {
45+
if (url.includes("placehold.co")) {
46+
return new Blob([base64ToArrayBuffer(PLACEHOLDER_332x322_JPEG_BASE64)], {
47+
type: "image/jpeg",
48+
});
49+
}
50+
51+
// For any non-image/unknown URL, return it unchanged so the caller can fetch
52+
// it. In practice exporter tests only resolve the placeholder image above.
53+
return url;
54+
}

0 commit comments

Comments
 (0)