The corpus legs pass seven of the exporters' nineteen arguments, so the other twelve — note configuration, page decoration, line and page numbering, the header/footer zones, document properties — were never round-tripped from a file. A new unit test sets all of them and reads them back out of both formats. The kitchen sink had only ever been written, never imported; it now makes both round trips and comes back identical. Two fixture repairs it needed: notes in anchor order, and a section break with sections to open. Caught: a document holding no note yet lost its note configuration on .docx.
49 lines
2.4 KiB
TypeScript
49 lines
2.4 KiB
TypeScript
// Property leg: seeded random documents (fuzzDoc.ts) -> buildOdt -> importOdt must
|
|
// come back unchanged (normalized). A failure names the seed, so it reproduces.
|
|
import { describe, it, expect } from 'vitest';
|
|
import { buildOdt } from '../src/lib/export/odt';
|
|
import { importOdt } from '../src/lib/import/odt';
|
|
import { buildDocx } from '../src/lib/export/docx';
|
|
import { importDocx } from '../src/lib/import/docx';
|
|
import { normalize, firstDiff, stripFontHoist } from './normalize';
|
|
import { genDoc, mulberry32 } from './fuzzDoc';
|
|
|
|
const margins = { top: 2, bottom: 2, left: 2, right: 2 };
|
|
const SEEDS = Number(process.env.FUZZ_SEEDS ?? 50); // FUZZ_SEEDS=500 for a wide sweep
|
|
|
|
describe('fuzz round-trip: editor → buildOdt → importOdt', () => {
|
|
it(`${SEEDS} seeded random documents come back identical`, async () => {
|
|
for (let seed = 1; seed <= SEEDS; seed++) {
|
|
const doc = genDoc(mulberry32(seed));
|
|
const res = importOdt(await buildOdt(doc, margins, 'portrait'));
|
|
expect.soft(res.warnings, `seed ${seed}: warnings`).toEqual([]);
|
|
const diff = firstDiff(stripFontHoist(normalize(doc)), stripFontHoist(normalize(res.content)));
|
|
expect.soft(diff, `seed ${seed} (re-run genDoc(mulberry32(${seed})) to reproduce)`).toBeNull();
|
|
}
|
|
}, 600_000);
|
|
|
|
// ODT leaves default column widths implicit where DOCX always writes w:tcW; ignore
|
|
// them in the cross-format comparison (each format's own leg still covers widths).
|
|
function stripColwidth(node: any): any {
|
|
if (node.attrs?.colwidth) {
|
|
delete node.attrs.colwidth;
|
|
if (!Object.keys(node.attrs).length) delete node.attrs;
|
|
}
|
|
for (const c of node.content ?? []) stripColwidth(c);
|
|
return node;
|
|
}
|
|
|
|
// Cross-format: the same document through both pipelines must import identically —
|
|
// catches one exporter silently losing what the other keeps.
|
|
it(`${SEEDS} seeded documents agree between the ODT and DOCX round-trips`, async () => {
|
|
for (let seed = 1; seed <= SEEDS; seed++) {
|
|
const doc = genDoc(mulberry32(seed));
|
|
const viaOdt = importOdt(await buildOdt(doc, margins, 'portrait')).content;
|
|
const viaDocx = importDocx(await buildDocx(doc, margins, 'portrait')).content;
|
|
const diff = firstDiff(stripColwidth(stripFontHoist(normalize(viaOdt))),
|
|
stripColwidth(stripFontHoist(normalize(viaDocx))));
|
|
expect.soft(diff, `seed ${seed}: ODT vs DOCX import (genDoc(mulberry32(${seed})))`).toBeNull();
|
|
}
|
|
}, 600_000);
|
|
});
|