diff --git a/CLAUDE.md b/CLAUDE.md index 48c79d3..8589cab 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -27,7 +27,7 @@ Tests live in `tests/` (outside `src/`, so `svelte-check` ignores them), jsdom v `roundtrip.test.ts` covers the ODF export↔import round trip + a foreign-doc/style-resolver leg; `lo-roundtrip.test.ts` re-saves through LibreOffice and **self-skips** without `soffice`, so `npm test`/CI stay green; `corpus.test.ts` round-trips the committed `tests/corpus/` -documents (authored by `render-parity/make-fixtures.mjs`, never by our own exporter); +documents (by `make-fixtures.mjs` + Word re-saves in `corpus/word/`, never our exporter); `fuzz-roundtrip.test.ts` round-trips seeded random documents (`fuzzDoc.ts` generator); `schema-validation.test.ts` validates the `kitchenSink.ts` exports against the vendored schemas in `tests/schemas/` (self-skips without `xmllint`); `package-lint.test.ts` checks the semantic invariants schemas can't express (unique ids, dangling references); `tests/unit/` holds fast helper tests. All test tooling stays a diff --git a/tests/corpus.test.ts b/tests/corpus.test.ts index 6098966..148b38d 100644 --- a/tests/corpus.test.ts +++ b/tests/corpus.test.ts @@ -58,6 +58,23 @@ describe.skipIf(!files.length)('the authored corpus', () => { }); } + // Word re-saves of the same documents (tests/corpus/word/): Word rewrites the file + // in its own dialect — rsids, proofErr, separator notes, theme docDefaults — which + // neither the docx lib nor LibreOffice produce, so this is the third author here. + const WORD = join(FIX, 'word'); + const wordFiles = existsSync(WORD) + ? readdirSync(WORD).filter((f) => /^\d\d-.+\.docx$/.test(f)).sort() + : []; + for (const f of wordFiles) { + it(`word/${f} reads like the original and survives the round trip`, async () => { + const doc = importDocx(new Uint8Array(readFileSync(join(WORD, f)))).content as N; + expect(outline(doc), 'matches the docx-lib original').toEqual(outline(importAny(f, load(f)))); + const margins = { top: 2, bottom: 2, left: 2, right: 2 }; + const again = importDocx(await buildDocx(doc, margins, 'portrait')); + expect(outline(again.content)).toEqual(outline(doc)); + }); + } + // The same document in both formats, and each one exported as the other: the four // legs a document takes through this editor have to agree on what it says. for (const name of files.filter((f) => f.endsWith('.docx')).map((f) => f.slice(0, -5))) { diff --git a/tests/corpus/14-formulas.docx b/tests/corpus/14-formulas.docx index 0ee68ad..9219439 100644 Binary files a/tests/corpus/14-formulas.docx and b/tests/corpus/14-formulas.docx differ diff --git a/tests/corpus/14-formulas.odt b/tests/corpus/14-formulas.odt index f8687a6..6b9876b 100644 Binary files a/tests/corpus/14-formulas.odt and b/tests/corpus/14-formulas.odt differ diff --git a/tests/corpus/word/01-flow.docx b/tests/corpus/word/01-flow.docx new file mode 100644 index 0000000..7195574 Binary files /dev/null and b/tests/corpus/word/01-flow.docx differ diff --git a/tests/corpus/word/02-blocks.docx b/tests/corpus/word/02-blocks.docx new file mode 100644 index 0000000..b2b2027 Binary files /dev/null and b/tests/corpus/word/02-blocks.docx differ diff --git a/tests/corpus/word/03-fonts.docx b/tests/corpus/word/03-fonts.docx new file mode 100644 index 0000000..ec775f3 Binary files /dev/null and b/tests/corpus/word/03-fonts.docx differ diff --git a/tests/corpus/word/04-table.docx b/tests/corpus/word/04-table.docx new file mode 100644 index 0000000..e023642 Binary files /dev/null and b/tests/corpus/word/04-table.docx differ diff --git a/tests/corpus/word/05-breaks.docx b/tests/corpus/word/05-breaks.docx new file mode 100644 index 0000000..03fca60 Binary files /dev/null and b/tests/corpus/word/05-breaks.docx differ diff --git a/tests/corpus/word/06-calibri.docx b/tests/corpus/word/06-calibri.docx new file mode 100644 index 0000000..fea8661 Binary files /dev/null and b/tests/corpus/word/06-calibri.docx differ diff --git a/tests/corpus/word/07-nowidow.docx b/tests/corpus/word/07-nowidow.docx new file mode 100644 index 0000000..c315be9 Binary files /dev/null and b/tests/corpus/word/07-nowidow.docx differ diff --git a/tests/corpus/word/08-lists.docx b/tests/corpus/word/08-lists.docx new file mode 100644 index 0000000..8be5472 Binary files /dev/null and b/tests/corpus/word/08-lists.docx differ diff --git a/tests/corpus/word/09-headerfooter.docx b/tests/corpus/word/09-headerfooter.docx new file mode 100644 index 0000000..3feb3d1 Binary files /dev/null and b/tests/corpus/word/09-headerfooter.docx differ diff --git a/tests/corpus/word/10-tabs.docx b/tests/corpus/word/10-tabs.docx new file mode 100644 index 0000000..4c8a9be Binary files /dev/null and b/tests/corpus/word/10-tabs.docx differ diff --git a/tests/corpus/word/11-marks.docx b/tests/corpus/word/11-marks.docx new file mode 100644 index 0000000..d2491a5 Binary files /dev/null and b/tests/corpus/word/11-marks.docx differ diff --git a/tests/corpus/word/12-notes.docx b/tests/corpus/word/12-notes.docx new file mode 100644 index 0000000..87a57cb Binary files /dev/null and b/tests/corpus/word/12-notes.docx differ diff --git a/tests/corpus/word/13-images.docx b/tests/corpus/word/13-images.docx new file mode 100644 index 0000000..6eaeb03 Binary files /dev/null and b/tests/corpus/word/13-images.docx differ diff --git a/tests/render-parity/make-fixtures.mjs b/tests/render-parity/make-fixtures.mjs index 9832b2b..c903645 100644 --- a/tests/render-parity/make-fixtures.mjs +++ b/tests/render-parity/make-fixtures.mjs @@ -12,7 +12,7 @@ import { Table, TableRow, TableCell, WidthType, convertMillimetersToTwip, Header, Footer, PageNumber, TabStopType, FootnoteReferenceRun, ImageRun, ExternalHyperlink, UnderlineType, - Math as DocxMath, MathRun, MathFraction, MathNumerator, MathDenominator, MathRadical, + Math as DocxMath, MathRun, MathFraction, MathRadical, } from 'docx'; const OUT = join(dirname(fileURLToPath(import.meta.url)), '..', 'corpus'); @@ -265,9 +265,11 @@ await write('13-images.docx', [{ }]); // 14. OMML formulas: one inline in the sentence, one alone on its line. +// numerator/denominator take the runs directly — the lib mints m:num/m:den itself, +// and a hand-wrapped MathNumerator doubles the element, which Word refuses to open. const frac = () => new MathFraction({ - numerator: [new MathNumerator([new MathRun('a')])], - denominator: [new MathDenominator([new MathRun('b')])], + numerator: [new MathRun('a')], + denominator: [new MathRun('b')], }); await write('14-formulas.docx', [{ properties: { page }, @@ -278,7 +280,7 @@ await write('14-formulas.docx', [{ new TextRun(' in the sentence.'), ] }), new Paragraph({ children: [new DocxMath({ children: [ - new MathRadical({ children: [new MathRun('x')], degree: [] }), new MathRun('+1'), + new MathRadical({ children: [new MathRun('x')], degree: [new MathRun('3')] }), new MathRun('+1'), ] })] }), para(LOREM), ],