"""Tests for LEVEL3 auto-diagram support (FR-533, FR-534).""" from __future__ import annotations import textwrap from pathlib import Path LEVEL3_MANIFEST = textwrap.dedent("""\ project: name: diag-test feature_level: level3 family: article sources: - path: doc.md output: dir: ./dist """) def _make_project(tmp_path: Path, markdown: str) -> Path: (tmp_path / "doc.md").write_text(markdown, encoding="utf-8") (tmp_path / "manifest.yaml").write_text(LEVEL3_MANIFEST, encoding="utf-8") return tmp_path # --------------------------------------------------------------------------- # diagrams module helpers # --------------------------------------------------------------------------- class TestDiagramHelpers: def test_is_diagram_info_mermaid(self) -> None: from markidocx.diagrams import is_diagram_info assert is_diagram_info("mermaid") def test_is_diagram_info_graphviz(self) -> None: from markidocx.diagrams import is_diagram_info assert is_diagram_info("graphviz") def test_is_diagram_info_plantuml(self) -> None: from markidocx.diagrams import is_diagram_info assert is_diagram_info("plantuml") def test_is_diagram_info_python_false(self) -> None: from markidocx.diagrams import is_diagram_info assert not is_diagram_info("python") assert not is_diagram_info("") assert not is_diagram_info(None) def test_is_diagram_source_marker(self) -> None: from markidocx.diagrams import is_diagram_source_marker assert is_diagram_source_marker("diagram-source:mermaid\ngraph TD\nA-->B") assert not is_diagram_source_marker("normal text") def test_parse_diagram_source_marker(self) -> None: from markidocx.diagrams import parse_diagram_source_marker source = "graph TD\nA-->B" result = parse_diagram_source_marker(f"diagram-source:mermaid\n{source}") assert result is not None diagram_type, parsed_source = result assert diagram_type == "mermaid" assert parsed_source == source def test_reconstruct_diagram_md(self) -> None: from markidocx.diagrams import reconstruct_diagram_md result = reconstruct_diagram_md("mermaid", "graph TD\nA-->B") assert result.startswith("```mermaid") assert "graph TD" in result assert result.endswith("```") # --------------------------------------------------------------------------- # Builder: diagram blocks → source-only path (no renderer in test env) (FR-533) # --------------------------------------------------------------------------- class TestBuilderDiagrams: def test_build_with_mermaid_block_succeeds(self, tmp_path: Path) -> None: """Mermaid block builds without error (source-only path).""" from markidocx.builder import build_document from markidocx.manifest import load_manifest md = textwrap.dedent("""\ # Document ```mermaid graph TD A --> B --> C ``` Some text. """) _make_project(tmp_path, md) m = load_manifest(tmp_path / "manifest.yaml") result = build_document(m) assert result.success def test_build_emits_warning_for_unavailable_renderer( self, tmp_path: Path, monkeypatch ) -> None: """Warns about missing diagram renderer (FR-538).""" import shutil from markidocx.builder import build_document from markidocx.manifest import load_manifest monkeypatch.setattr(shutil, "which", lambda _cmd: None) md = "```mermaid\ngraph TD\nA-->B\n```" _make_project(tmp_path, md) m = load_manifest(tmp_path / "manifest.yaml") result = build_document(m) assert result.success dep_warnings = [ w for w in result.warning_records if w.reason == "processor-dependency-unavailable" ] assert dep_warnings def test_build_docx_contains_source_marker( self, tmp_path: Path, monkeypatch ) -> None: """DOCX contains diagram-source marker for round-trip.""" import shutil from docx import Document as DocxReader from markidocx.builder import build_document from markidocx.manifest import load_manifest monkeypatch.setattr(shutil, "which", lambda _cmd: None) md = "```mermaid\ngraph TD\nA-->B\n```" _make_project(tmp_path, md) m = load_manifest(tmp_path / "manifest.yaml") result = build_document(m) assert result.success doc = DocxReader(str(result.output_path)) texts = [p.text for p in doc.paragraphs] marker_texts = [t for t in texts if t.startswith("diagram-source:")] assert marker_texts, f"No diagram-source marker found. Paragraphs: {texts}" def test_build_graphviz_block_succeeds(self, tmp_path: Path) -> None: from markidocx.builder import build_document from markidocx.manifest import load_manifest md = "```graphviz\ndigraph G { A -> B }\n```" _make_project(tmp_path, md) m = load_manifest(tmp_path / "manifest.yaml") result = build_document(m) assert result.success def test_non_diagram_code_block_not_warned( self, tmp_path: Path ) -> None: """Python code blocks don't trigger diagram warnings.""" from markidocx.builder import build_document from markidocx.manifest import load_manifest md = "```python\nprint('hello')\n```" _make_project(tmp_path, md) m = load_manifest(tmp_path / "manifest.yaml") result = build_document(m) dep_warnings = [ w for w in result.warning_records if w.reason == "processor-dependency-unavailable" ] # Only level3 diagram types trigger this warning, not python # (may still warn for mmdc/dot if level3 partial check fires, but not for python block) python_warnings = [w for w in dep_warnings if "python" in w.construct] assert not python_warnings # --------------------------------------------------------------------------- # Importer: diagram source-intent marker → fenced block (FR-534) # --------------------------------------------------------------------------- class TestImporterDiagrams: def test_roundtrip_source_only_path(self, tmp_path: Path, monkeypatch) -> None: """Source-only round-trip: diagram source is preserved in reimported MD.""" import shutil from markidocx.builder import build_document from markidocx.importer import import_document from markidocx.manifest import load_manifest monkeypatch.setattr(shutil, "which", lambda _cmd: None) diagram_source = "graph TD\nA --> B --> C" md = f"# Document\n\n```mermaid\n{diagram_source}\n```\n\nText." _make_project(tmp_path, md) m = load_manifest(tmp_path / "manifest.yaml") build_result = build_document(m) assert build_result.success import_result = import_document(m, build_result.output_path) assert import_result.success reimported = import_result.output_files[0].read_text(encoding="utf-8") assert "mermaid" in reimported assert "graph TD" in reimported def test_no_source_discarded(self, tmp_path: Path, monkeypatch) -> None: """Diagram source is never silently dropped (FR-1205).""" import shutil from markidocx.builder import build_document from markidocx.importer import import_document from markidocx.manifest import load_manifest monkeypatch.setattr(shutil, "which", lambda _cmd: None) md = "```plantuml\n@startuml\nAlice -> Bob: Hi\n@enduml\n```" _make_project(tmp_path, md) m = load_manifest(tmp_path / "manifest.yaml") build_result = build_document(m) assert build_result.success import_result = import_document(m, build_result.output_path) assert import_result.success reimported = import_result.output_files[0].read_text(encoding="utf-8") # Source content must be present somewhere in the reimported text assert "plantuml" in reimported or "@startuml" in reimported