|
1 | 1 | import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; |
2 | 2 | import { tmpdir } from 'node:os'; |
3 | 3 | 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'; |
5 | 5 | import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; |
6 | 6 | import { createProgram } from '../program'; |
7 | 7 | import { EXIT_SUCCESS } from '../runtime/exit-codes'; |
@@ -39,13 +39,26 @@ async function runCli(args: readonly string[]): Promise<CapturedRun> { |
39 | 39 | return { exitCode: process.exitCode, stdout: stdoutChunks.join(''), stderr: stderrChunks.join('') }; |
40 | 40 | } |
41 | 41 |
|
| 42 | +const SHEET_CELL_TEXT = 'A cell surviving an xlsx metadata patch'; |
| 43 | + |
42 | 44 | beforeAll(async () => { |
43 | 45 | workspace = await mkdtemp(join(tmpdir(), 'document-cli-set-metadata-')); |
44 | 46 | await writeFile(join(workspace, 'source.docx'), buildDocxWithMetadata()); |
45 | 47 | await writeFile(join(workspace, 'extras.docx'), buildDocxWithExtras()); |
46 | 48 | await writeFile(join(workspace, 'source.pdf'), buildPdfWithMetadata()); |
47 | 49 | // 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. |
48 | 50 | 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())); |
49 | 62 | }); |
50 | 63 |
|
51 | 64 | afterAll(async () => { |
@@ -114,11 +127,29 @@ describe('set-metadata', () => { |
114 | 127 | expect(patchedLayout.pages).toStrictEqual(sourceLayout.pages); |
115 | 128 | }); |
116 | 129 |
|
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 () => { |
118 | 149 | const { exitCode, stderr } = await runCli(['set-metadata', join(workspace, 'source.docx'), join(workspace, 'never.xlsx'), '--set-title', 'x']); |
119 | 150 |
|
120 | 151 | 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'); |
122 | 153 | }); |
123 | 154 |
|
124 | 155 | it('rejects a standalone odf formula document as a source, naming the missing write path', async () => { |
|
0 commit comments