"""Artifact-level coverage for structured Rich Text Word export.""" import json import subprocess import tempfile import zipfile from pathlib import Path ROOT = Path(__file__).resolve().parents[1] DOC_JS = (ROOT / "static/js/document.js").read_text(encoding="utf-8") def test_rich_docx_converter_maps_editor_structure_instead_of_raw_html(): converter = DOC_JS.split("function _docxHexColor", 1)[1].split( "async function exportAsDocx", 1 )[0] exporter = DOC_JS.split("async function exportAsDocx", 1)[1].split( "/** Delete the active document", 1 )[0] for helper in ( "_docxImageFallbackRun", "_docxImageWidth", "_docxImagePngData", "_docxPrepareImages", "_docxInlineChildren", "_docxParagraphFromElement", "_docxListBlocks", "_docxTableBlock", "_docxFigureBlocks", "_docxBlocksFromNodes", "_richTextToDocxChildren", "_markdownToDocxChildren", ): assert f"function {helper}" in DOC_JS assert "new docx.ExternalHyperlink" in converter assert "new docx.ImageRun" in converter assert "new docx.PageBreak" in converter assert "new docx.Paragraph({ thematicBreak: true })" in converter assert "new docx.Table(" in converter assert "new docx.TableRow(" in converter assert "new docx.TableCell(" in converter assert "docx.VerticalAlign?.CENTER" in converter assert "docx.HeadingLevel.HEADING_6" in converter assert "_isRichTextLang(lang)" in exporter assert "_richTextToDocxChildren(text, window.docx)" in exporter assert "_markdownToDocxChildren(text, window.docx)" in exporter def test_browser_word_export_contains_native_rich_docx_ooxml(): with tempfile.NamedTemporaryFile(suffix=".docx", delete=False) as output: output_path = Path(output.name) script = rf""" import {{ chromium }} from 'playwright'; const browser = await chromium.launch({{ headless: true }}); const page = await browser.newPage({{ viewport: {{ width: 900, height: 700 }}, acceptDownloads: true, }}); await page.goto('http://127.0.0.1:7011/static/js/documentStats.js'); await page.route('**/api/upload/docx-image-test', route => route.fulfill({{ status: 200, contentType: 'image/png', body: Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=', 'base64'), }})); await page.setContent('
'); await page.evaluate(async () => {{ const mod = await import('/static/js/document.js?v=20260831richtexttools91&docx-export-test=1'); mod.init('/api'); mod.injectFreshDoc({{ id: 'rich-docx-export-doc', title: 'Rich export', language: 'richtext', version_count: 1, current_content: '

Project brief

Bold, italic, underlined, struck, and linked.

  1. First item
  2. Second item
Export diagram
Figure caption
NameStatus
AlphaReady


Second page

', }}); await new Promise(resolve => setTimeout(resolve, 450)); }}); await page.locator('#doc-footer-export-btn').click(); await page.waitForSelector('#doc-export-menu'); const downloadPromise = page.waitForEvent('download'); await page.locator('#doc-export-menu .doc-overflow-item').filter({{ hasText: 'Export as Word' }}).click(); const download = await downloadPromise; await download.saveAs({json.dumps(str(output_path))}); console.log(JSON.stringify({{ filename: download.suggestedFilename(), failure: await download.failure(), }})); await browser.close(); """ result = subprocess.run( ["node", "--input-type=module", "-e", script], cwd=ROOT, check=False, capture_output=True, text=True, ) try: assert result.returncode == 0, result.stderr data = json.loads(result.stdout) assert data["failure"] is None assert data["filename"].endswith(".docx") assert output_path.stat().st_size > 5000 with zipfile.ZipFile(output_path) as package: document_xml = package.read("word/document.xml").decode("utf-8") numbering_xml = package.read("word/numbering.xml").decode("utf-8") relationships_xml = package.read("word/_rels/document.xml.rels").decode("utf-8") media_names = [ name for name in package.namelist() if name.startswith("word/media/") and not name.endswith("/") ] media = package.read(media_names[0]) if media_names else b"" assert '<h1>' not in document_xml assert '' in document_xml assert '' in document_xml for text in ( "Project brief", "Bold", "italic", "underlined", "struck", "linked", "First item", "Second item", "[x] ", "Finished task", "Name", "Status", "Figure caption", "Alpha", "Ready", "Second page", ): assert text in document_xml assert "" in document_xml assert "" in document_xml assert '' in document_xml assert "" in document_xml assert "" in document_xml assert '' in numbering_xml assert "" in document_xml assert "" in document_xml assert '' in document_xml assert "" in document_xml assert '' in document_xml assert len(media_names) == 1 assert media.startswith(b"\x89PNG\r\n\x1a\n") assert "relationships/image" in relationships_xml assert "" in document_xml assert '' in document_xml assert '' in document_xml assert 'descr="Export diagram"' in document_xml assert "[Image: Export diagram]" not in document_xml finally: output_path.unlink(missing_ok=True) def test_browser_markdown_word_export_keeps_heading_and_inline_formatting(): with tempfile.NamedTemporaryFile(suffix=".docx", delete=False) as output: output_path = Path(output.name) script = rf""" import {{ chromium }} from 'playwright'; const browser = await chromium.launch({{ headless: true }}); const page = await browser.newPage({{ viewport: {{ width: 900, height: 700 }}, acceptDownloads: true, }}); await page.goto('http://127.0.0.1:7011/static/js/documentStats.js'); await page.setContent('
'); await page.evaluate(async () => {{ const mod = await import('/static/js/document.js?v=20260831richtexttools91&markdown-docx-export-test=1'); mod.init('/api'); mod.injectFreshDoc({{ id: 'markdown-docx-export-doc', title: 'Markdown export', language: 'markdown', version_count: 1, current_content: '# Markdown title\n\nA **bold phrase** and *italic phrase*.', }}); await new Promise(resolve => setTimeout(resolve, 450)); }}); await page.locator('#doc-footer-export-btn').click(); await page.waitForSelector('#doc-export-menu'); const downloadPromise = page.waitForEvent('download'); await page.locator('#doc-export-menu .doc-overflow-item').filter({{ hasText: 'Export as Word' }}).click(); const download = await downloadPromise; await download.saveAs({json.dumps(str(output_path))}); console.log(JSON.stringify({{ failure: await download.failure() }})); await browser.close(); """ result = subprocess.run( ["node", "--input-type=module", "-e", script], cwd=ROOT, check=False, capture_output=True, text=True, ) try: assert result.returncode == 0, result.stderr assert json.loads(result.stdout)["failure"] is None with zipfile.ZipFile(output_path) as package: document_xml = package.read("word/document.xml").decode("utf-8") assert '' in document_xml assert "Markdown title" in document_xml assert "bold phrase" in document_xml assert "italic phrase" in document_xml assert "" in document_xml assert "" in document_xml finally: output_path.unlink(missing_ok=True)