Files
edentext/tests/unit/tab-interval.test.ts
Steffen Becker 7d5bc5e0c9 feat: a run of tabs too wide for its line carries on the next
LibreOffice restarts the tab grid on the continuation line; Chromium
hangs the leftover tabs and starts the next line at the margin, 160mm
away in the repro. The run is walked from the pen before it — the one
thing the fix can't move, so the answer holds whether or not the break
is already in place — and a break widget goes where the line runs out.

Measuring this turned up why the two disagreed in the first place: a
file that declares no tab interval falls back to ODF's own 2cm, not to
LibreOffice's UI default of 1.25, so leaving ours implied on export
rendered every tab elsewhere. Both exports declare it now, and both
imports fall back to their format's value rather than to ours.

Only runs of two or more are walked: reading a block's geometry forces
a reflow, and doing it per tab across 48 pages slowed pagination enough
that the parity harness compared a layout still settling.
2026-08-08 15:58:40 +00:00

52 lines
2.6 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// The document's default tab interval: Word's w:defaultTabStop, ODF's
// style:tab-stop-distance on the paragraph default-style.
import { describe, it, expect } from 'vitest';
import { unzipSync, zipSync, strFromU8 } from 'fflate';
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 { DEFAULT_TAB_INTERVAL_CM, ODF_IMPLIED_TAB_CM, clampTabInterval } from '../../src/lib/storage/tabInterval';
const doc: any = { type: 'doc', content: [{ type: 'paragraph', content: [{ type: 'text', text: 'a\tb' }] }] };
const opts = [undefined, 'portrait', undefined, undefined, 'A4', undefined] as const;
const odt = (cm?: number) => buildOdt(doc, ...(opts as any), cm);
const docx = (cm?: number) => buildDocx(doc, ...(opts as any), cm);
describe('default tab interval', () => {
it('rides an ODF default-style and comes back', async () => {
const back = await importOdt(await odt(1.27));
expect(back.tabIntervalCm).toBeCloseTo(1.27, 3);
});
it('declares the editor default too', async () => {
// Left implied, a file falls back to ODF's own 2cm — measured against LibreOffice,
// which puts a bare A⇥X there and not at its UI default of 1.25.
const styles = strFromU8(unzipSync(await odt(DEFAULT_TAB_INTERVAL_CM))['styles.xml']);
expect(styles).toContain('style:tab-stop-distance="1.25cm"');
expect((await importOdt(await odt(DEFAULT_TAB_INTERVAL_CM))).tabIntervalCm).toBeCloseTo(1.25, 3);
});
it('falls back to ODF’s own implied default', async () => {
const stripped = await odt(1.25).then((b) => {
const files = unzipSync(b);
files['styles.xml'] = new TextEncoder().encode(
strFromU8(files['styles.xml']).replace(/<style:default-style style:family="paragraph">[\s\S]*?<\/style:default-style>/, ''));
return zipSync(files);
});
expect((await importOdt(stripped)).tabIntervalCm).toBeCloseTo(ODF_IMPLIED_TAB_CM, 3);
});
it('rides a DOCX setting and comes back', async () => {
// Word stores twips, so 1.27cm = 720tw is exact and 1.25cm rounds to 709tw.
expect(importDocx(await docx(1.27)).tabIntervalCm).toBeCloseTo(1.27, 2);
expect(importDocx(await docx(DEFAULT_TAB_INTERVAL_CM)).tabIntervalCm).toBeCloseTo(1.25, 2);
});
it('clamps a nonsense value to something renderable', () => {
expect(clampTabInterval(0)).toBe(0.05);
expect(clampTabInterval(999)).toBe(10);
expect(clampTabInterval(NaN)).toBe(DEFAULT_TAB_INTERVAL_CM);
});
});