sahil-12kumar/IL_CMS_Tools
1
1"""Shared fixtures: build .docx files in a temp dir rather than committing binaries,2so the OMML each test exercises is visible in the test that needs it.3"""4import io5import sys6from pathlib import Path7 8import pytest9from lxml import etree10 11sys.path.insert(0, str(Path(__file__).resolve().parent.parent / 'backend'))12 13M_NS = 'http://schemas.openxmlformats.org/officeDocument/2006/math'14 15 16@pytest.fixture(scope='session')17def app_mod():18 import app19 return app20 21 22def mrun(text):23 """An OMML text run."""24 return f'<m:r><m:t>{text}</m:t></m:r>'25 26 27def omath(inner):28 """Wrap OMML fragments in the <m:oMath> element Word puts in a paragraph."""29 return etree.fromstring(30 f'<m:oMath xmlns:m="{M_NS}">{inner}</m:oMath>')31 32 33# ── OMML fragments, written the way Word itself writes them ──────────────────34# A square root: Word marks the (empty) degree hidden rather than omitting it,35# which is what makes the stylesheet emit <msqrt> instead of an empty <mroot>.36SQRT_2GH = (f'<m:rad><m:radPr><m:degHide m:val="1"/></m:radPr><m:deg/>'37 f'<m:e>{mrun("2gh")}</m:e></m:rad>')38B_SQUARED = f'<m:sSup><m:e>{mrun("b")}</m:e><m:sup>{mrun("2")}</m:sup></m:sSup>'39H2O = f'<m:sSub><m:e>{mrun("H")}</m:e><m:sub>{mrun("2")}</m:sub></m:sSub>{mrun("O")}'40HALF = f'<m:f><m:num>{mrun("1")}</m:num><m:den>{mrun("2")}</m:den></m:f>'41# Word's "boxed formula" and its diagonal-strike (cancel) variant.42BOXED = (f'<m:borderBox><m:borderBoxPr><m:hideTop m:val="0"/></m:borderBoxPr>'43 f'<m:e>{mrun("E=mc")}</m:e></m:borderBox>')44CANCELLED = (f'<m:borderBox><m:borderBoxPr><m:strikeBLTR m:val="1"/></m:borderBoxPr>'45 f'<m:e>{mrun("x")}</m:e></m:borderBox>')46 47 48@pytest.fixture49def png_bytes():50 """A small but genuine PNG."""51 from PIL import Image52 im = Image.new('RGB', (120, 60), 'white')53 buf = io.BytesIO()54 im.save(buf, 'PNG')55 return buf.getvalue()56 57 58@pytest.fixture59def make_docx(tmp_path):60 """build([...]) -> path to a .docx. Each item is either a string (a plain61 paragraph) or a list of parts: strings, ('math', omml_str) or ('img', bytes).62 """63 from docx import Document64 from docx.shared import Inches65 66 counter = {'n': 0}67 68 def build(items):69 doc = Document()70 for item in items:71 if isinstance(item, str):72 doc.add_paragraph(item)73 continue74 p = doc.add_paragraph()75 for part in item:76 if isinstance(part, str):77 p.add_run(part)78 elif part[0] == 'math':79 p._p.append(omath(part[1]))80 elif part[0] == 'img':81 p.add_run().add_picture(io.BytesIO(part[1]), width=Inches(1.5))82 counter['n'] += 183 out = tmp_path / f'q{counter["n"]}.docx'84 doc.save(str(out))85 return str(out)86 87 return build88 89 90@pytest.fixture91def no_omml_stylesheet(app_mod, monkeypatch):92 """Simulate the Docker image: no Office, so OMML2MML.XSL is nowhere to be found."""93 monkeypatch.setattr(app_mod, 'OMML2MML_PATHS', [r'C:\nonexistent\OMML2MML.XSL'])94 monkeypatch.setattr(app_mod, '_omml2mml', None)95 yield96 app_mod._omml2mml = None97 98 99@pytest.fixture(autouse=True)100def _reset_stylesheet_cache(app_mod):101 """get_omml2mml() memoises into a module global; keep tests independent."""102 yield103 app_mod._omml2mml = None104 