Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
91 commits
Select commit Hold shift + click to select a range
849bb6e
test(documents.js): raise findCellRegions' 200k-row test timeout
Mearman Sep 13, 2026
92d44de
test(documents.js): assert every rich ODS fixture cell's displayText …
Mearman Sep 14, 2026
18581b9
test(documents.js): remove four dead test-support fixture wrappers
Mearman Sep 14, 2026
e6faa7c
test(documents.js): cover DocxTableCell.borders getter and heightPt u…
Mearman Sep 14, 2026
ab623f3
test(documents.js): cover the replace-existing-value path for colSpan…
Mearman Sep 14, 2026
38fb8d0
test(documents.js): assert every literal in the empty docx scaffold
Mearman Sep 14, 2026
d4b6d1e
test(documents.js): cover ensureColumnDefaultWidth/ensureRowDefaultHe…
Mearman Sep 14, 2026
bf9a7b7
test(documents.js): add a print-settings.ts test suite and simplify i…
Mearman Sep 14, 2026
c9e9b8c
test(documents.js): assert every literal in the empty ods scaffold
Mearman Sep 14, 2026
331d593
fix(documents.js): narrow the undefined manifest:file-entry lookup in…
Mearman Sep 14, 2026
5fc01b6
fix(documents.js): drop the unobservable empty-string user-agent fall…
Mearman Sep 14, 2026
a874e3c
test(documents.js): cover applyOdfGeometry and buildTransformAttr dir…
Mearman Sep 14, 2026
f704f6f
test(documents.js): assert the .name of three named error classes
Mearman Sep 14, 2026
8d36215
test(documents.js): cover the PptEditor/writePptContent non-presentat…
Mearman Sep 14, 2026
98171b2
test(documents.js): assert collectDocumentFormulas' presentation loca…
Mearman Sep 14, 2026
dc60a3e
test(documents.js): assert odbTablesToSpreadsheetDocument's row-sizin…
Mearman Sep 14, 2026
134d154
test(documents.js): cover odbReportGroupChain's held-no-group guard
Mearman Sep 14, 2026
8205920
test(documents.js): assert findChildElement rejects a non-matching tag
Mearman Sep 14, 2026
72c0829
test(documents.js): cover CONTENT_READERS.markdown/readDocumentLayout…
Mearman Sep 14, 2026
1b97dfa
test(documents.js): cover DOCUMENT_FORMAT_CODECS.xls/pdf write-half g…
Mearman Sep 14, 2026
67662ac
fix(documents.js): drop dataCell's redundant empty-field inference guard
Mearman Sep 14, 2026
5d6980e
test(documents.js): assert SvgInvalidUtf8Error's exact name and message
Mearman Sep 14, 2026
457727b
fix(documents.js): drop formatPathNumber's redundant zero/-0 special …
Mearman Sep 14, 2026
7c5148b
test(documents.js): assert treeEmbeddedFontsOf returns undefined for …
Mearman Sep 14, 2026
0ff54ad
test(documents.js): cover markdownBlock's table-recursion and formula…
Mearman Sep 14, 2026
525d540
test(documents.js): extract groupVectorsByShapePosition for direct te…
Mearman Sep 14, 2026
da70f32
test(documents.js): assert decodeLegacyEmbeddedObject's isCompoundFil…
Mearman Sep 14, 2026
26db90c
test(documents.js): export slidePathsInOrder and cover its two malfor…
Mearman Sep 14, 2026
2889d7b
test(documents.js): assert standardFontDocxBytes genuinely requests A…
Mearman Sep 14, 2026
d71ebcf
test(documents.js): export promoteBlock and cover its style/join boun…
Mearman Sep 14, 2026
0de812a
test(documents.js): pin richMarkdownText's own block boundaries exactly
Mearman Sep 14, 2026
3ea66fd
test(documents.js): assert the root element tag of every createEmptyD…
Mearman Sep 14, 2026
f3c697a
test(documents.js): cover strike's default value and buildRun's remai…
Mearman Sep 14, 2026
ae7473e
test(documents.js): add direct coverage for spliceOut and registerIma…
Mearman Sep 14, 2026
1d3864d
test(documents.js): cover XlsEditor's two constructor guards
Mearman Sep 14, 2026
ccdfda5
test(documents.js): pin the Gregorian century-correction and tick-to-…
Mearman Sep 14, 2026
0e82c71
test(documents.js): combine translateVector's identical rect/ellipse/…
Mearman Sep 14, 2026
dec4c3c
test(documents.js): assert compareCellKeys' tie-breaking is a strict …
Mearman Sep 14, 2026
a3f2964
test(documents.js): drop syncOdfManifest's dead root-content.xml comp…
Mearman Sep 14, 2026
894a561
test(documents.js): export nextMediaIndex and cover its prefix/escapi…
Mearman Sep 14, 2026
05d6359
test(documents.js): export nextPictureIndex and cover it, mirroring o…
Mearman Sep 14, 2026
d7a47de
test(documents.js): export allocateRelationshipId and assert the crea…
Mearman Sep 14, 2026
77b53eb
test(documents.js): assert the created Types root's tag/xmlns and the…
Mearman Sep 14, 2026
2465f60
test(documents.js): add direct coverage for shiftItems, drop its dead…
Mearman Sep 14, 2026
81f1ba6
test(documents.js): assert sourcePath on odp's recovered formula and …
Mearman Sep 14, 2026
4296c04
test(documents.js): spy on odf.js's decodeOdfText to assert the wrapp…
Mearman Sep 14, 2026
88ff801
test(documents.js): cover the table cell kind filter, width division,…
Mearman Sep 14, 2026
8f36df0
test(documents.js): add direct coverage for the bin.ts launcher entry…
Mearman Sep 14, 2026
cc2439f
test(documents.js): cover hasWritableMetadataOverride's four presence…
Mearman Sep 14, 2026
fda10dc
test(documents.js): assert an empty keywords array omits cp:keywords
Mearman Sep 14, 2026
c188f5e
test(documents.js): cover insertAfter's mid-container and reference-n…
Mearman Sep 14, 2026
f453696
test(documents.js): add direct coverage for buildVectorShape's nvSpPr…
Mearman Sep 14, 2026
a0bd6fa
test(documents.js): pin parseSvgLengthPt/parseSvgViewBox's trim, fini…
Mearman Sep 14, 2026
44133c1
test(documents.js): add direct coverage for decimalToRational and red…
Mearman Sep 14, 2026
d530af4
test(documents.js): add direct coverage for the mathml node-walking p…
Mearman Sep 14, 2026
22b6881
fix(documents.js): drop readFirebirdBackup's unreachable schema-looku…
Mearman Sep 14, 2026
56d8439
test(documents.js): add direct coverage for OdgPage.remove and its po…
Mearman Sep 14, 2026
e3cfa33
test(documents.js): cover buildRelativeTarget from a root-level part
Mearman Sep 14, 2026
5ce0d02
fix(documents.js): make nextObjectIndex's running max unconditional
Mearman Sep 14, 2026
737569b
test(documents.js): cover shapeDetail's fallback label and a path's e…
Mearman Sep 14, 2026
59f9946
fix(documents.js): stop chaining a non-optional call off an optional …
Mearman Sep 14, 2026
883dd83
test(documents.js): cover CONTENT_READERS.rtf's abort-signal forwarding
Mearman Sep 14, 2026
319c18e
fix(documents.js): make localName branchless to remove an equivalent …
Mearman Sep 14, 2026
01d4a86
test(documents.js): cover gcd(0, 0) and a leading-sign literal rejection
Mearman Sep 14, 2026
6bb13ff
test(documents.js): cover mergeMetadata preserving subject and keywords
Mearman Sep 14, 2026
9a4deaf
fix(documents.js): compare cell keys less-than-first to avoid equival…
Mearman Sep 14, 2026
6f40ebd
test(documents.js): pin nextPictureIndex's Pictures/ prefix check
Mearman Sep 14, 2026
0cf84ec
fix(documents.js): drop readOdpContent's redundant empty-groups guard
Mearman Sep 14, 2026
d9114cd
test(documents.js): pin ensureDefaultContentType's tag check
Mearman Sep 14, 2026
75d792f
test(documents.js): pin nextMediaIndex's escaping and prefix check
Mearman Sep 14, 2026
ac498a2
fix(documents.js): remove insertInSchemaOrder's redundant not-in-orde…
Mearman Sep 14, 2026
6fcb657
fix(documents.js): drop the space-run scan's redundant length bound
Mearman Sep 14, 2026
2151f15
test(documents.js): pin ods scaffold's root tags and namespace/styles…
Mearman Sep 14, 2026
8fed334
test(documents.js): pin markdownBlock's inline vs display formula mar…
Mearman Sep 14, 2026
c52131e
test(documents.js): pin core-properties' declaration and xmlns attrib…
Mearman Sep 14, 2026
a623958
fix(documents.js): use one combined bound in buildRelativeTarget's scan
Mearman Sep 14, 2026
2b13b2c
test(documents.js): cover MarkdownEditor's guards, defaults, and geom…
Mearman Sep 14, 2026
22006ee
test(documents.js): cover from-pdf's signal/images forwarding and out…
Mearman Sep 14, 2026
b2f254f
test(documents.js): cover local converter's odf route guard and font-…
Mearman Sep 14, 2026
9dcf762
test(documents.js): distinguish escaping a regex-special extension ch…
Mearman Sep 15, 2026
5d2cef6
test(documents.js): assert ensureContentTypeOverride's scan checks ta…
Mearman Sep 15, 2026
d4f5111
fix(documents.js): drop buildRelativeTarget's redundant explicit loop…
Mearman Sep 15, 2026
ab43a69
test(documents.js): assert insertInSchemaOrder places a same-rank sib…
Mearman Sep 15, 2026
948dc96
fix(documents.js): drop encodeOdfText's equivalent past-the-end loop …
Mearman Sep 15, 2026
a71a485
test(documents.js): spy on readPdf and reconstructWordprocessing to a…
Mearman Sep 15, 2026
fe93a07
test(documents.js): cover the missing-face font substitution message
Mearman Sep 15, 2026
79c7d36
test(documents.js): assert odfFormulaBytes' own zip layout and annota…
Mearman Sep 15, 2026
8a07462
test(documents.js): cover collectDrawingMlVectors' throw path and its…
Mearman Sep 15, 2026
197a727
fix(documents.js): replace rotationsOf's equivalent kind check with a…
Mearman Sep 15, 2026
ce253b7
test(documents.js): cover glyphOfSymbolText, mintedSymbolId, and Symb…
Mearman Sep 15, 2026
11fbdbe
fix(documents.js): resolve PptxSlide.remove's slide entry by relation…
Mearman Sep 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions packages/documents.js/src/bin-dispatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@ const RUNNERS: Readonly<Record<PackageManager, RunnerSpec>> = {
};

function detectPackageManager(userAgent: string | undefined): PackageManager {
const ua = userAgent ?? "";
if (ua.startsWith("yarn/")) {
// No npm_config_user_agent at all (Deno never sets it; running the bin via bare `node` sets nothing) falls back to npm exactly like every other unrecognised value below -- handled as its own branch, rather than defaulting `userAgent` to an empty string first, so there is no fallback string literal whose own value is unobservable (every one of the startsWith checks below is false for it) and therefore untestable.
if (userAgent === undefined) return "npm";

if (userAgent.startsWith("yarn/")) {
// Yarn classic (1.x) has no `dlx` subcommand -- it is Yarn Berry (2+) only -- so classic is treated as npm and runs through npx rather than a command that fails.
return ua.startsWith("yarn/1.") ? "npm" : "yarn";
return userAgent.startsWith("yarn/1.") ? "npm" : "yarn";
}
if (ua.startsWith("pnpm/")) return "pnpm";
if (ua.startsWith("bun/")) return "bun";
if (userAgent.startsWith("pnpm/")) return "pnpm";
if (userAgent.startsWith("bun/")) return "bun";
// npm, and any agent that doesn't identify itself (Deno doesn't set this env var at all; running the bin via bare `node` sets nothing), falls back to npx.
return "npm";
}
Expand Down
83 changes: 83 additions & 0 deletions packages/documents.js/src/bin.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { describe, expect, it, vi } from "vitest";

// bin.ts is a real executable entry point: importing it runs its top-level code immediately, which spawns a child process and calls process.exit. Every test here mocks node:child_process's spawnSync and stubs process.exit/argv/env before a fresh dynamic import, then restores them.

interface SpawnSyncCall {
readonly command: string;
readonly args: readonly string[];
readonly options: unknown;
}

async function runBin(
argv: readonly string[],
userAgent: string | undefined,
status: number | null,
): Promise<{ readonly call: SpawnSyncCall; readonly exitCode: unknown }> {
vi.resetModules();
let call: SpawnSyncCall | undefined;
vi.doMock("node:child_process", () => ({
spawnSync: (command: string, args: readonly string[], options: unknown) => {
call = { command, args, options };
return { status };
},
}));
const exitSpy = vi.spyOn(process, "exit").mockImplementation(() => {
return undefined as never;
});
const originalArgv = process.argv;
const originalUserAgent = process.env.npm_config_user_agent;
process.argv = ["node", "documents.js", ...argv];
if (userAgent === undefined) {
delete process.env.npm_config_user_agent;
} else {
process.env.npm_config_user_agent = userAgent;
}
try {
await import("./bin");
} finally {
process.argv = originalArgv;
if (originalUserAgent === undefined) {
delete process.env.npm_config_user_agent;
} else {
process.env.npm_config_user_agent = originalUserAgent;
}
}
if (call === undefined) {
throw new Error("expected spawnSync to have been called");
}
const exitCode = exitSpy.mock.calls[0]?.[0];
exitSpy.mockRestore();
vi.doUnmock("node:child_process");
return { call, exitCode };
}

describe("bin", () => {
it("strips the node/script argv[0..1] before resolving dispatch, not the full argv", () => {
return runBin(["mcp"], "npm/10.2.4 node/v20", 0).then(({ call }) => {
// Without process.argv.slice(2), argv[0] would be "node" (not "mcp"), never triggering the mcp dispatch path -- this only resolves to document-mcp because the strip happened.
expect(call.command).toBe("npx");
expect(call.args).toEqual(["-y", "document-mcp"]);
});
});

it("spawns with the exact resolved args array and { stdio: 'inherit' } options", () => {
return runBin(["convert", "a.docx"], "npm/10.2.4 node/v20", 0).then(
({ call }) => {
expect(call.args).toEqual(["-y", "document-cli", "convert", "a.docx"]);
expect(call.options).toEqual({ stdio: "inherit" });
},
);
});

it("exits with the spawned process's own non-zero status, not always the same code", () => {
return runBin([], "npm/10.2.4 node/v20", 2).then(({ exitCode }) => {
expect(exitCode).toBe(2);
});
});

it("exits with 1 when spawnSync reports no status at all (e.g. killed by a signal)", () => {
return runBin([], "npm/10.2.4 node/v20", null).then(({ exitCode }) => {
expect(exitCode).toBe(1);
});
});
});
62 changes: 62 additions & 0 deletions packages/documents.js/src/codecs/read.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { describe, expect, it, vi } from "vitest";
import { CONTENT_READERS, readDocumentLayout } from "./read";
import { encodeMarkdownText } from "../markdown/text";

describe("CONTENT_READERS.markdown", () => {
it("forwards the images resolver through to readMarkdownContent", () => {
const resolver = vi.fn(() => undefined);
CONTENT_READERS.markdown(encodeMarkdownText("![alt](img.png)"), {
images: resolver,
});
expect(resolver).toHaveBeenCalledWith("img.png", expect.anything());
});

it("forwards the abort signal through to readMarkdownContent, which checks it before parsing", () => {
const controller = new AbortController();
controller.abort();
let caught: unknown;
try {
CONTENT_READERS.markdown(encodeMarkdownText("hi"), {
signal: controller.signal,
});
} catch (error) {
caught = error;
}
expect(caught).toBeInstanceOf(DOMException);
expect((caught as DOMException).name).toBe("AbortError");
});
});

describe("CONTENT_READERS.rtf", () => {
it("forwards the abort signal through to readRtfContent, which checks it before tokenizing", () => {
const controller = new AbortController();
controller.abort();
let caught: unknown;
try {
CONTENT_READERS.rtf(new TextEncoder().encode("{\\rtf1 hi}"), {
signal: controller.signal,
});
} catch (error) {
caught = error;
}
expect(caught).toBeInstanceOf(DOMException);
expect((caught as DOMException).name).toBe("AbortError");
});
});

describe("readDocumentLayout", () => {
it("forwards the signal option through to readPdf, which checks it before parsing", () => {
const controller = new AbortController();
controller.abort();
// A real "%PDF-" header but otherwise garbage bytes: readPdf checks the header first, then the abort signal, before it ever opens the document -- if the signal were not forwarded (an empty options object), this would fail trying to parse the document instead.
const bytes = new TextEncoder().encode("%PDF-1.4\n%garbage");
let caught: unknown;
try {
readDocumentLayout(bytes, { signal: controller.signal });
} catch (error) {
caught = error;
}
expect(caught).toBeInstanceOf(DOMException);
expect((caught as DOMException).name).toBe("AbortError");
});
});
24 changes: 23 additions & 1 deletion packages/documents.js/src/codecs/registry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type {
import { PAGE_SIZE_LETTER } from "document-schema.js";
import { describe, expect, it } from "vitest";
import type { XlsContentDocument } from "xls-codec";
import { odsToXlsx } from "../convert/convert";
import { docxToPdf, odsToXlsx } from "../convert/convert";
import { readOdfFormulaContent } from "../odf/formula/read";
import { FRACTION_FORMULA, odfFormulaBytes } from "../test-support/odf";
import { minimalDocxBytes } from "../test-support/docx";
Expand Down Expand Up @@ -300,6 +300,18 @@ describe("DOCUMENT_FORMAT_CODECS: content read/write round trips", () => {
expect(roundTripped).toEqual(expected);
});

it("xls: content.write refuses a non-spreadsheet ContentDocument by name", () => {
const codec = requireContentCodec("xls");
const wordprocessing: ContentDocument = {
kind: "wordprocessing",
metadata: {},
sections: [],
};
expect(() => codec.write!(wordprocessing)).toThrow(
"DOCUMENT_FORMAT_CODECS.xls.content.write: expected a spreadsheet ContentDocument",
);
});

// Mirrors ppt-codec's own write.test.ts fixture shape. The writer's own scope is text-box slides only (see that package's README scope note); like pptx/odp above, a black-box substantive-text check is the right-scoped proof of wiring here rather than exact equality -- ppt-codec's own reader always reports PowerPoint's fixed default text insets (0.1in left/right, 0.05in top/bottom) regardless of what a shape actually carries, since it does not yet read a shape's own OfficeArtFOPT inset override (see read.ts's own DEFAULT_INSET_LEFT_RIGHT_PT/DEFAULT_INSET_TOP_BOTTOM_PT comment), a pre-existing, documented gap this registry wiring did not introduce.
it("ppt: read -> write -> read carries the source slide text through", () => {
const codec = requireContentCodec("ppt");
Expand Down Expand Up @@ -353,6 +365,16 @@ describe("DOCUMENT_FORMAT_CODECS: pdf has a layout codec, not a content codec",
expect(DOCUMENT_FORMAT_CODECS.pdf.content).toBeUndefined();
expect(DOCUMENT_FORMAT_CODECS.pdf.layout).toBeDefined();
});

it("layout.write forwards the abort signal through to writePdf's own per-page check", () => {
const codec = DOCUMENT_FORMAT_CODECS.pdf.layout!;
const layout = codec.read(docxToPdf(minimalDocxBytes()));
const controller = new AbortController();
controller.abort();
expect(() => {
codec.write(layout, { signal: controller.signal });
}).toThrow(DOMException);
});
});

describe("DOCUMENT_FORMAT_CODECS: xlsx has a content codec, no layout codec", () => {
Expand Down
22 changes: 22 additions & 0 deletions packages/documents.js/src/convert/bridges.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,20 @@ describe("ods <-> xlsx: ods -> xlsx (one hop, the character-width-unit conversio
const original = odsContentOf(richOdsBytes());
const originalSheet = original.sheets[0]!;

// The source ODS fixture's own header row and every cell's rendered displayText: buildRichFixturePackage (test-support/ods.ts) writes a distinct text:p run for every cell alongside its office:value, and none of it is exercised by any assertion below (those check only the CONVERTED xlsx side's `.value`) -- so a header cell silently losing its label, or a cell's displayText silently losing its rendered text, would go undetected without checking the source fixture directly.
expect(cellAt(originalSheet, 0, 0)?.displayText).toBe("Name");
expect(cellAt(originalSheet, 0, 1)?.displayText).toBe("Amount");
expect(cellAt(originalSheet, 0, 2)?.displayText).toBe("Active");
expect(cellAt(originalSheet, 1, 0)?.displayText).toBe("Widget");
expect(cellAt(originalSheet, 1, 1)?.displayText).toBe("42.5");
expect(cellAt(originalSheet, 1, 2)?.displayText).toBe("TRUE");
expect(cellAt(originalSheet, 2, 0)?.displayText).toBe("15%");
expect(cellAt(originalSheet, 2, 1)?.displayText).toBe("$9.99");
expect(cellAt(originalSheet, 2, 2)?.displayText).toBe("2026-01-15");
expect(cellAt(originalSheet, 3, 0)?.displayText).toBe("14:30");
expect(cellAt(originalSheet, 3, 1)?.displayText).toBe("85");
expect(cellAt(originalSheet, 4, 0)?.displayText).toBe("Merged Cell");

const xlsxBytes = odsToXlsx(richOdsBytes());
const xlsx = xlsxContentOf(xlsxBytes);
const sheet = xlsx.sheets[0]!;
Expand All @@ -624,6 +638,14 @@ describe("ods <-> xlsx: ods -> xlsx (one hop, the character-width-unit conversio
kind: "string",
value: "Name",
});
expect(cellAt(sheet, 0, 1)?.value).toEqual({
kind: "string",
value: "Amount",
});
expect(cellAt(sheet, 0, 2)?.value).toEqual({
kind: "string",
value: "Active",
});
expect(cellAt(sheet, 1, 0)?.value).toEqual({
kind: "string",
value: "Widget",
Expand Down
20 changes: 19 additions & 1 deletion packages/documents.js/src/convert/convert-fonts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import type { FontSubstitution } from "pdf-codec";
import { createStandardFontMeasurer, loadMathFont, writePdf } from "pdf-codec";
const mathMetricsAt = (sizePt: number) => loadMathFont().metricsAt(sizePt);
import { decodePackage as decodeOdfPackage } from "odf.js";
import { encodePackage as encodeOoxmlPackage } from "ooxml.js";
import {
decodePackage as decodeOoxmlPackage,
encodePackage as encodeOoxmlPackage,
} from "ooxml.js";
import { openDocx } from "../edit/docx/editor";
import { openPptx } from "../edit/pptx/editor";
import { buildDocumentBytes } from "./from-package";
Expand Down Expand Up @@ -146,6 +149,21 @@ describe("X -> PDF: caller-supplied faces", () => {
});
expect(substitutions).toEqual([]);
});

it("standardFontDocxBytes genuinely requests Arial, not merely a request no vendored substitute happens to claim", () => {
const content = readDocxContent(
decodeOoxmlPackage(standardFontDocxBytes()),
);
if (content.kind !== "wordprocessing") {
throw new Error("expected a wordprocessing ContentDocument");
}
const paragraph = content.sections[0]?.blocks[0];
expect(
paragraph?.kind === "paragraph"
? paragraph.runs[0]?.fontFamily
: undefined,
).toBe("Arial");
});
});

// The backward-compatibility guarantee this phase had to keep: wiring a FontRegistry into all six conversions must not change a single byte of output for a document that embeds no fonts and asks for no family a vendored substitute claims. Each reference below reproduces the exact pre-registry pipeline -- createStandardFontMeasurer() into the format's own layout engine, then writePdf with no `fonts` option at all -- so this is a genuine before/after byte comparison rather than a self-consistency check of the new code against itself.
Expand Down
1 change: 1 addition & 0 deletions packages/documents.js/src/convert/document-fonts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,5 +72,6 @@ describe("extractSourceFontsForFormat", () => {
);
}
expect(caught.format).toBe("xlsx");
expect(caught.name).toBe("UnsupportedFontSourceFormatError");
});
});
Loading