111 lines
4.0 KiB
Python
111 lines
4.0 KiB
Python
"""Tests for heading and paragraph renderers — verify the EFFECT (text + style land)."""
|
|
import zipfile
|
|
import xml.etree.ElementTree as ET
|
|
from pathlib import Path
|
|
|
|
from docx import Document
|
|
from bs4 import BeautifulSoup
|
|
|
|
from md2doc import STYLES, _render_heading, _render_paragraph
|
|
|
|
W_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
|
|
|
|
|
def get_doc_xml(doc):
|
|
tmp = Path(__file__).parent / "_tmp_render.docx"
|
|
doc.save(tmp)
|
|
try:
|
|
with zipfile.ZipFile(tmp) as z:
|
|
return ET.fromstring(z.read("word/document.xml"))
|
|
finally:
|
|
tmp.unlink()
|
|
|
|
|
|
def test_render_h1_uses_heading1_style_and_text():
|
|
doc = Document()
|
|
style = STYLES["elegant"]
|
|
node = BeautifulSoup("<h1>My Title</h1>", "html.parser").find("h1")
|
|
_render_heading(doc, node, level=1, style=style)
|
|
root = get_doc_xml(doc)
|
|
# pStyle is an attribute, not text content
|
|
pStyle_vals = [p.get(f"{{{W_NS}}}val") for p in root.iter(f"{{{W_NS}}}pStyle")]
|
|
assert "Heading1" in pStyle_vals
|
|
texts = [t.text for t in root.iter(f"{{{W_NS}}}t")]
|
|
assert "My Title" in texts
|
|
|
|
|
|
def test_render_h1_has_bottom_border():
|
|
doc = Document()
|
|
style = STYLES["elegant"]
|
|
node = BeautifulSoup("<h1>Title</h1>", "html.parser").find("h1")
|
|
_render_heading(doc, node, level=1, style=style)
|
|
root = get_doc_xml(doc)
|
|
bottoms = list(root.iter(f"{{{W_NS}}}bottom"))
|
|
assert len(bottoms) >= 1, "h1 should have bottom border"
|
|
|
|
|
|
def test_render_h1_applies_style_heading_color():
|
|
"""Heading color should come from STYLES[style]['heading_color'] (case-insensitive)."""
|
|
doc = Document()
|
|
style = STYLES["report"]
|
|
node = BeautifulSoup("<h1>Title</h1>", "html.parser").find("h1")
|
|
_render_heading(doc, node, level=1, style=style)
|
|
root = get_doc_xml(doc)
|
|
colors = [c.get(f"{{{W_NS}}}val").lower() for c in root.iter(f"{{{W_NS}}}color")]
|
|
assert "1e3a8a" in colors, f"expected heading color 1e3a8a from 'report' style, got {colors}"
|
|
|
|
|
|
def test_render_heading_applies_keep_with_next():
|
|
"""COMMON['keep_with_next_headings'] should set paragraph_format.keep_with_next=True."""
|
|
doc = Document()
|
|
style = STYLES["elegant"]
|
|
node = BeautifulSoup("<h2>Section</h2>", "html.parser").find("h2")
|
|
_render_heading(doc, node, level=2, style=style)
|
|
root = get_doc_xml(doc)
|
|
keep_next = list(root.iter(f"{{{W_NS}}}keepNext"))
|
|
assert len(keep_next) >= 1, "expected w:keepNext for heading"
|
|
|
|
|
|
def test_render_h2_no_bottom_border():
|
|
"""Only h1 gets border; h2/h3/h4 don't."""
|
|
doc = Document()
|
|
style = STYLES["elegant"]
|
|
node = BeautifulSoup("<h2>Subtitle</h2>", "html.parser").find("h2")
|
|
_render_heading(doc, node, level=2, style=style)
|
|
root = get_doc_xml(doc)
|
|
bottoms = list(root.iter(f"{{{W_NS}}}bottom"))
|
|
assert len(bottoms) == 0
|
|
|
|
|
|
def test_render_paragraph_emits_text():
|
|
doc = Document()
|
|
style = STYLES["elegant"]
|
|
node = BeautifulSoup("<p>Hello world</p>", "html.parser").find("p")
|
|
_render_paragraph(doc, node, style=style)
|
|
root = get_doc_xml(doc)
|
|
texts = [t.text for t in root.iter(f"{{{W_NS}}}t")]
|
|
assert "Hello world" in texts
|
|
|
|
|
|
def test_render_paragraph_handles_bold_inline():
|
|
doc = Document()
|
|
style = STYLES["elegant"]
|
|
node = BeautifulSoup("<p>This is <strong>bold</strong> text</p>", "html.parser").find("p")
|
|
_render_paragraph(doc, node, style=style)
|
|
root = get_doc_xml(doc)
|
|
b_el = list(root.iter(f"{{{W_NS}}}b"))
|
|
assert len(b_el) >= 1
|
|
|
|
|
|
def test_render_paragraph_handles_inline_code_with_shading():
|
|
"""Inline <code> should have monospace font AND run-level shading."""
|
|
doc = Document()
|
|
style = STYLES["elegant"]
|
|
node = BeautifulSoup("<p>See <code>foo()</code> here</p>", "html.parser").find("p")
|
|
_render_paragraph(doc, node, style=style)
|
|
root = get_doc_xml(doc)
|
|
rPr_list = list(root.iter(f"{{{W_NS}}}rPr"))
|
|
shd_in_runs = [r.find(f"{{{W_NS}}}shd") for r in rPr_list
|
|
if r.find(f"{{{W_NS}}}shd") is not None]
|
|
assert len(shd_in_runs) >= 1, "expected w:shd inside a w:rPr for inline code"
|