Skip to content
This repository was archived by the owner on Aug 20, 2026. It is now read-only.

Commit 049d18e

Browse files
committed
test: prove xlsx now round-trips through from-package and set-metadata
documents.js's own xlsx content codec (DOCUMENT_FORMAT_CODECS.xlsx.content) made buildDocumentBytes and setDocumentMetadata stop rejecting xlsx outright, which broke two tests asserting the old categorical-rejection behaviour. Both are replaced with tests proving the real round trip: a spreadsheet-kind DocumentPackage now builds real xlsx bytes via from-package, and set-metadata patches an xlsx file's own title in place while leaving its cells untouched. set-metadata still rejects a cross-format request into xlsx, since it only patches metadata and never converts format.
1 parent 2d37a67 commit 049d18e

2 files changed

Lines changed: 65 additions & 11 deletions

File tree

src/commands/from-package.test.ts

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
22
import { tmpdir } from 'node:os';
33
import { join } from 'node:path';
4-
import { createDocx, openDocx } from 'documents.js';
4+
import { createDocx, createOds, decodeDocumentPackage, openDocx, readOdsContent, xlsxToOds } from 'documents.js';
55
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
66
import { createProgram } from '../program';
77
import { EXIT_SUCCESS } from '../runtime/exit-codes';
@@ -33,6 +33,7 @@ async function runCli(args: readonly string[]): Promise<CapturedRun> {
3333
}
3434

3535
const PARAGRAPH_TEXT = 'A paragraph dumped to a DocumentPackage and read back again';
36+
const SHEET_CELL_TEXT = 'A cell dumped to a DocumentPackage and rebuilt as xlsx';
3637

3738
function docxWithParagraph(): Uint8Array<ArrayBuffer> {
3839
const editor = createDocx();
@@ -104,14 +105,36 @@ describe('from-package', () => {
104105
expect(stderr).toContain('requires a');
105106
});
106107

107-
it('rejects xlsx as a target outright, naming the ods-to-xlsx workaround', async () => {
108-
const packagePath = join(workspace, 'dumped-for-xlsx.package.json');
109-
await runCli(['docx-to-pdf', join(workspace, 'source.docx'), join(workspace, 'unused3.pdf'), '--dump-package', packagePath]);
110-
111-
const { exitCode, stderr } = await runCli(['from-package', packagePath, join(workspace, 'never-written.xlsx')]);
108+
it('builds a real xlsx from a spreadsheet-kind DocumentPackage now that documents.js wires a real xlsx content codec', async () => {
109+
// xlsx used to be rejected outright here -- documents.js's own DOCUMENT_FORMAT_CODECS registry gained a real xlsx content codec (wrapping ooxml.js's readXlsxContent/buildXlsxPackage) this session, and buildDocumentBytes was simplified to dispatch through it like every other format instead of naming xlsx as a special exception.
110+
const sheetPath = join(workspace, 'source-for-xlsx.ods');
111+
const editor = createOds();
112+
// createOds() already starts with one default sheet -- reuse it rather than addSheet('Sheet1'), which would create a second, identically-named sheet and leave the first (empty) one at sheets[0].
113+
const sheet = editor.sheets()[0];
114+
if (sheet === undefined) {
115+
throw new Error('createOds() did not produce a default sheet');
116+
}
117+
sheet.cell(0, 0).value = { kind: 'string', value: SHEET_CELL_TEXT };
118+
// A cell()-materialized column/row otherwise reads back with no width/height style at all (widthPt/heightPt 0), which fails DocumentPackage's own schema validation once the dumped package round-trips through JSON below.
119+
sheet.setColumnWidth(0, 72);
120+
sheet.setRowHeight(0, 14);
121+
await writeFile(sheetPath, editor.toBytes());
112122

113-
expect(exitCode).not.toBe(EXIT_SUCCESS);
114-
expect(stderr).toContain("'xlsx' cannot be built from a DocumentPackage directly");
123+
const packagePath = join(workspace, 'dumped-for-xlsx.package.json');
124+
await runCli(['ods-to-pdf', sheetPath, join(workspace, 'unused3.pdf'), '--dump-package', packagePath]);
125+
126+
const xlsxPath = join(workspace, 'rebuilt.xlsx');
127+
const { exitCode } = await runCli(['from-package', packagePath, xlsxPath]);
128+
expect(exitCode).toBe(EXIT_SUCCESS);
129+
130+
// Round-trips the rebuilt xlsx back through the real xlsx-to-ods bridge to prove the bytes are a genuine, readable xlsx workbook carrying the original cell, not just a file that happened to get written.
131+
const xlsxBytes = new Uint8Array(await readFile(xlsxPath));
132+
const odsBackBytes = xlsxToOds(xlsxBytes);
133+
const content = readOdsContent(decodeDocumentPackage('ods', odsBackBytes));
134+
if (content.kind !== 'spreadsheet') {
135+
throw new Error(`expected a spreadsheet ContentDocument, got ${content.kind}`);
136+
}
137+
expect(content.sheets[0]?.cells[0]?.value).toEqual({ kind: 'string', value: SHEET_CELL_TEXT });
115138
});
116139

117140
it('rejects a plain JSON file with no recognised $schema', async () => {

src/commands/set-metadata.test.ts

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
22
import { tmpdir } from 'node:os';
33
import { join } from 'node:path';
4-
import { decodePackage, readDocxContent, readDocxExtras, readPdf } from 'documents.js';
4+
import { createOds, decodeDocumentPackage, decodePackage, odsToXlsx, readDocxContent, readDocxExtras, readOdsContent, readPdf, xlsxToOds } from 'documents.js';
55
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
66
import { createProgram } from '../program';
77
import { EXIT_SUCCESS } from '../runtime/exit-codes';
@@ -39,13 +39,26 @@ async function runCli(args: readonly string[]): Promise<CapturedRun> {
3939
return { exitCode: process.exitCode, stdout: stdoutChunks.join(''), stderr: stderrChunks.join('') };
4040
}
4141

42+
const SHEET_CELL_TEXT = 'A cell surviving an xlsx metadata patch';
43+
4244
beforeAll(async () => {
4345
workspace = await mkdtemp(join(tmpdir(), 'document-cli-set-metadata-'));
4446
await writeFile(join(workspace, 'source.docx'), buildDocxWithMetadata());
4547
await writeFile(join(workspace, 'extras.docx'), buildDocxWithExtras());
4648
await writeFile(join(workspace, 'source.pdf'), buildPdfWithMetadata());
4749
// setDocumentMetadata (documents.js) validates its own source/target format pair internally rather than the CLI pre-checking it, so the input file is now genuinely read before that rejection fires -- unlike a placeholder path, this needs to exist. Its content is never parsed: the rejection below fires purely on the '.odf' extension, before any real ODF decoding is attempted.
4850
await writeFile(join(workspace, 'formula.odf'), new Uint8Array([0]));
51+
52+
const odsEditor = createOds();
53+
const sheet = odsEditor.sheets()[0];
54+
if (sheet === undefined) {
55+
throw new Error('createOds() did not produce a default sheet');
56+
}
57+
sheet.cell(0, 0).value = { kind: 'string', value: SHEET_CELL_TEXT };
58+
// See from-package.test.ts's own identical note: a cell()-materialized column/row otherwise reads back with no width/height style at all, which is irrelevant here but kept for consistency with the other xlsx fixture.
59+
sheet.setColumnWidth(0, 72);
60+
sheet.setRowHeight(0, 14);
61+
await writeFile(join(workspace, 'source.xlsx'), odsToXlsx(odsEditor.toBytes()));
4962
});
5063

5164
afterAll(async () => {
@@ -114,11 +127,29 @@ describe('set-metadata', () => {
114127
expect(patchedLayout.pages).toStrictEqual(sourceLayout.pages);
115128
});
116129

117-
it('rejects xlsx as a target outright, naming the ods-to-xlsx workaround', async () => {
130+
it('patches an xlsx file in place now that documents.js wires a real xlsx content codec, leaving its cells untouched', async () => {
131+
// xlsx used to be rejected outright here -- documents.js's own DOCUMENT_FORMAT_CODECS registry gained a real xlsx content codec this session, and setDocumentMetadata now rebuilds xlsx through the identical readXContent -> buildXPackage shape every other REBUILD_FORMATS member already used.
132+
const outputPath = join(workspace, 'rebuilt.xlsx');
133+
const { exitCode, stderr } = await runCli(['set-metadata', join(workspace, 'source.xlsx'), outputPath, '--set-title', 'New xlsx title', '--quiet']);
134+
135+
expect(stderr).toBe('');
136+
expect(exitCode).toBe(EXIT_SUCCESS);
137+
138+
// Round-trips the patched xlsx back through the real xlsx-to-ods bridge to prove the bytes are a genuine, readable xlsx workbook carrying both the new title and the original cell.
139+
const odsBackBytes = xlsxToOds(new Uint8Array(await readFile(outputPath)));
140+
const content = readOdsContent(decodeDocumentPackage('ods', odsBackBytes));
141+
if (content.kind !== 'spreadsheet') {
142+
throw new Error(`expected a spreadsheet ContentDocument, got ${content.kind}`);
143+
}
144+
expect(content.metadata.title).toBe('New xlsx title');
145+
expect(content.sheets[0]?.cells[0]?.value).toEqual({ kind: 'string', value: SHEET_CELL_TEXT });
146+
});
147+
148+
it('still rejects a cross-format request into xlsx -- set-metadata patches metadata in place, it does not convert format', async () => {
118149
const { exitCode, stderr } = await runCli(['set-metadata', join(workspace, 'source.docx'), join(workspace, 'never.xlsx'), '--set-title', 'x']);
119150

120151
expect(exitCode).not.toBe(EXIT_SUCCESS);
121-
expect(stderr).toContain("'xlsx' is not a supported setDocumentMetadata source or target");
152+
expect(stderr).toContain('does not convert format');
122153
});
123154

124155
it('rejects a standalone odf formula document as a source, naming the missing write path', async () => {

0 commit comments

Comments
 (0)