Files
edentext/tests/unit/list-indent.test.ts
T
Steffen Becker 0af3488e6e feat: every list level takes its indent from the file
Only the top level's own w:ind w:left (ODF: the label-alignment margin)
was read; every level below it was forced to the editor's 1.27cm step.

Both formats store a level's indent absolutely while the editor nests one
step per level, so import now subtracts the level above and the ODF
export adds the running sum back per level (DOCX already accumulated).

Measured: a 0.5cm/-0.6cm pair puts LibreOffice's two levels at 1.77cm and
2.44cm, and the editor draws them at 1.769 and 2.44. Parity corpus
unchanged.
2026-08-08 17:47:17 +00:00

57 lines
2.2 KiB
TypeScript

// A list level's indent: Word's w:lvl/w:pPr/w:ind w:left, ODF's label-alignment
// fo:margin-left. Both are absolute, the editor nests one step per level, so the attr
// is the step past the level above.
import { describe, it, expect } from 'vitest';
import { buildOdt } from '../../src/lib/export/odt';
import { buildDocx } from '../../src/lib/export/docx';
import { importOdt } from '../../src/lib/import/odt';
import { importDocx } from '../../src/lib/import/docx';
type N = any;
const item = (text: string, sub?: N): N =>
({ type: 'listItem', content: [{ type: 'paragraph', content: [{ type: 'text', text }] }, ...(sub ? [sub] : [])] });
// Level 1 sits 0.5cm right of the base, level 2 0.6cm left of its own base.
const doc: N = {
type: 'doc',
content: [
{ type: 'bulletList', attrs: { indent: 0.5 }, content: [
item('one', { type: 'bulletList', attrs: { indent: -0.6 }, content: [item('nested')] }),
item('two'),
] },
],
};
const margins = { top: 2, bottom: 2, left: 2, right: 2 };
const lists = (d: N) => {
const top = d.content!.find((n: N) => n.type === 'bulletList') as N;
return [top, top.content[0].content[1]];
};
describe('list level indents', () => {
it('round-trips a per-level indent through ODF', async () => {
const [top, nested] = lists((await importOdt(await buildOdt(doc, margins, 'portrait'))).content);
expect(top.attrs?.indent).toBeCloseTo(0.5, 2);
expect(nested.attrs?.indent).toBeCloseTo(-0.6, 2);
});
it('round-trips a per-level indent through DOCX', async () => {
const [top, nested] = lists(importDocx(await buildDocx(doc, margins, 'portrait')).content);
expect(top.attrs?.indent).toBeCloseTo(0.5, 2);
expect(nested.attrs?.indent).toBeCloseTo(-0.6, 2);
});
it('leaves an ordinary list unindented in both formats', async () => {
const plain: N = { type: 'doc', content: [
{ type: 'bulletList', content: [item('one', { type: 'bulletList', content: [item('nested')] })] },
] };
for (const back of [
(await importOdt(await buildOdt(plain, margins, 'portrait'))).content,
importDocx(await buildDocx(plain, margins, 'portrait')).content,
]) {
for (const list of lists(back)) expect(list.attrs?.indent ?? null).toBeNull();
}
});
});