// A Word TOC field regenerates its rows from the file's `toc 1`…`toc 9` styles. The // cached rows are skipped on import, so the styles are read straight off styles.xml — // without them the index falls back to the editor's own 0.5cm-per-level indent. import { describe, it, expect } from 'vitest'; import { zipSync, strToU8 } from 'fflate'; import { importDocx } from '../../src/lib/import/docx'; import { buildDocx } from '../../src/lib/export/docx'; import { unzipSync, strFromU8 } from 'fflate'; const W = 'xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"'; const CT = ` `; const RELS = ` `; const DOC_RELS = ` `; // Word's own ids are localized (Verzeichnis1 in a German file); w:name is what names the level. const STYLES = ` `; // The field's cached rows sit between `separate` and `end` and are skipped on import. const DOCUMENT = ` TOC \\o "1-3" \\h Abstract Radar Abstract `; const FILE = zipSync({ '[Content_Types].xml': strToU8(CT), '_rels/.rels': strToU8(RELS), 'word/_rels/document.xml.rels': strToU8(DOC_RELS), 'word/styles.xml': strToU8(STYLES), 'word/document.xml': strToU8(DOCUMENT), }); const imported = () => importDocx(FILE); describe('a TOC field', () => { it('names the file’s entry style for each level it lists', () => { const doc = imported().content as unknown as { content: { type: string; attrs?: Record }[] }; const toc = doc.content.find((b) => b.type === 'tableOfContents'); expect(toc?.attrs?.levelStyles).toEqual(['Contents 1', 'Contents 2']); }); it('brings those styles into the registry with their own indent and spacing', () => { const sheet = imported().styles; expect(sheet.paragraph['Contents 1'].para).toEqual({ spaceAfter: 5 }); expect(sheet.paragraph['Contents 2'].para).toEqual({ spaceAfter: 5, indent: 0.39 }); }); it('writes the rows back under those styles, not the fallback indent', async () => { const r = imported(); const toc = (r.content as never as { content: { type: string; attrs: Record }[] }) .content.find((b) => b.type === 'tableOfContents')!; toc.attrs.entries = [{ text: 'Radar', level: 2, page: 3 }]; const files = unzipSync(await buildDocx(r.content as never, undefined, 'portrait', undefined, null, 'A4', r.styles)); expect(strFromU8(files['word/document.xml'])).toContain(''); expect(strFromU8(files['word/styles.xml'])).toContain('w:styleId="Contents2"'); }); });