Files
edentext/tests/corpus.test.ts
T
Steffen Becker bc3c25ece1 test: take Word re-saves of the corpus as a third author
corpus/word/ holds each document saved again by Word — rsids,
proofErr, separator notes, theme docDefaults — asserted to read like
the docx-lib original and survive our round trip. The formula fixture
is reauthored: a hand-wrapped MathNumerator doubled m:num/m:den, which
Word refuses to open, and the radical gets a visible degree.
2026-08-30 10:06:44 +00:00

93 lines
4.2 KiB
TypeScript

// The committed corpus (`tests/corpus/`: documents the `docx` lib writes, plus the ODT
// twins LibreOffice converts them into) read as documents rather than as renderer
// fixtures — a file this editor did not write has to import, survive our own export,
// and read back the same.
import { describe, it, expect } from 'vitest';
import { readFileSync, readdirSync, existsSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
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';
type N = any;
const FIX = join(dirname(fileURLToPath(import.meta.url)), 'corpus');
// `make-fixtures.mjs` writes them; a real-world document stays out of the repo, in the
// gitignored parity fixture directory, and out of this run.
const files = existsSync(FIX)
? readdirSync(FIX).filter((f) => /^\d\d-.+\.(docx|odt)$/.test(f)).sort()
: [];
const load = (f: string) => new Uint8Array(readFileSync(join(FIX, f)));
const importAny = (f: string, bytes: Uint8Array): N =>
(f.endsWith('.odt') ? importOdt(bytes) : importDocx(bytes)).content;
// A block's type and its text, flattened depth-first: the shape a round trip must keep.
function outline(node: N, out: string[] = []): string[] {
for (const child of node.content ?? []) {
if (child.type === 'text') continue;
out.push(`${child.type}:${textOf(child).replace(/\s+/g, ' ').trim()}`);
outline(child, out);
}
return out;
}
function textOf(node: N): string {
if (node.type === 'text') return node.text ?? '';
return (node.content ?? []).map(textOf).join('');
}
describe.skipIf(!files.length)('the authored corpus', () => {
it('has both formats of every document', () => {
const docx = files.filter((f) => f.endsWith('.docx')).map((f) => f.slice(0, -5));
const odt = files.filter((f) => f.endsWith('.odt')).map((f) => f.slice(0, -4));
expect(odt).toEqual(docx);
});
for (const f of files) {
it(`${f} imports and survives its own round trip`, async () => {
const doc = importAny(f, load(f));
expect(outline(doc).length).toBeGreaterThan(0);
const margins = { top: 2, bottom: 2, left: 2, right: 2 };
const again = f.endsWith('.odt')
? importOdt(await buildOdt(doc, margins, 'portrait'))
: importDocx(await buildDocx(doc, margins, 'portrait'));
expect(outline(again.content)).toEqual(outline(doc));
});
}
// 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))) {
it(`${name} reads the same out of either format`, async () => {
const fromDocx = importAny(`${name}.docx`, load(`${name}.docx`));
const fromOdt = importAny(`${name}.odt`, load(`${name}.odt`));
expect(outline(fromOdt)).toEqual(outline(fromDocx));
const margins = { top: 2, bottom: 2, left: 2, right: 2 };
expect(outline(importOdt(await buildOdt(fromDocx, margins, 'portrait')).content))
.toEqual(outline(fromDocx));
expect(outline(importDocx(await buildDocx(fromOdt, margins, 'portrait')).content))
.toEqual(outline(fromOdt));
});
}
});