"""Rendered-diagram round-trip regression tests (WP-0005 T05, FR-533, FR-534). Tests are skipped when the required renderer CLI is not on PATH — they are integration tests that require actual renderer tools (mmdc, dot, plantuml). The source-only fallback path is covered by test_level3_diagrams.py and always runs regardless of renderer availability. """ from __future__ import annotations import shutil import textwrap from pathlib import Path import pytest # --------------------------------------------------------------------------- # Fixtures # --------------------------------------------------------------------------- LEVEL3_MANIFEST = textwrap.dedent("""\ project: name: rendered-diag-test feature_level: level3 family: article sources: - path: doc.md output: dir: ./dist """) _FIXTURE_DOC = ( Path(__file__).parent / "level3" / "rendered_diagrams_document.md" ) 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 # --------------------------------------------------------------------------- # Mermaid rendered round-trip (skipped if mmdc not found) # --------------------------------------------------------------------------- @pytest.mark.skipif( shutil.which("mmdc") is None, reason="mmdc (Mermaid CLI) not installed", ) class TestMermaidRenderRoundtrip: def test_mermaid_render_roundtrip(self, tmp_path: Path) -> None: """Mermaid block renders to PNG; round-trip restores source.""" from markidocx.builder import build_document from markidocx.importer import import_document from markidocx.manifest import load_manifest md = textwrap.dedent("""\ # Test ```mermaid graph TD A --> B --> C ``` """) _make_project(tmp_path, md) m = load_manifest(tmp_path / "manifest.yaml") build_result = build_document(m) assert build_result.success, build_result.warning_records # Should have no processor-dependency-unavailable warnings dep_warns = [ w for w in build_result.warning_records if w.reason == "processor-dependency-unavailable" and "mermaid" in w.construct ] assert not dep_warns, f"Unexpected fallback warnings: {dep_warns}" 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_mermaid_no_broken_diagrams_in_differ(self, tmp_path: Path) -> None: """Differ reports zero broken diagrams when mermaid renderer was available.""" from markidocx.builder import build_document from markidocx.differ import compare_documents from markidocx.importer import import_document from markidocx.manifest import load_manifest md = "# Doc\n\n```mermaid\ngraph LR\nX --> Y\n```\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 diff_result = compare_documents(m, build_result.output_path) assert diff_result is not None # --------------------------------------------------------------------------- # Graphviz rendered round-trip (skipped if dot not found) # --------------------------------------------------------------------------- @pytest.mark.skipif( shutil.which("dot") is None, reason="dot (Graphviz) not installed", ) class TestGraphvizRenderRoundtrip: def test_graphviz_render_roundtrip(self, tmp_path: Path) -> None: """Graphviz block renders to PNG; round-trip restores source.""" from markidocx.builder import build_document from markidocx.importer import import_document from markidocx.manifest import load_manifest md = textwrap.dedent("""\ # Test ```graphviz digraph G { A -> B; B -> C; } ``` """) _make_project(tmp_path, md) m = load_manifest(tmp_path / "manifest.yaml") build_result = build_document(m) assert build_result.success, build_result.warning_records dep_warns = [ w for w in build_result.warning_records if w.reason == "processor-dependency-unavailable" and "graphviz" in w.construct ] assert not dep_warns, f"Unexpected fallback warnings: {dep_warns}" 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 "graphviz" in reimported assert "digraph" in reimported # --------------------------------------------------------------------------- # PlantUML rendered round-trip (skipped if plantuml not found) # --------------------------------------------------------------------------- @pytest.mark.skipif( shutil.which("plantuml") is None, reason="plantuml not installed", ) class TestPlantUMLRenderRoundtrip: def test_plantuml_render_roundtrip(self, tmp_path: Path) -> None: """PlantUML block renders to PNG; round-trip restores source.""" from markidocx.builder import build_document from markidocx.importer import import_document from markidocx.manifest import load_manifest md = textwrap.dedent("""\ # Test ```plantuml @startuml Alice -> Bob: Hello @enduml ``` """) _make_project(tmp_path, md) m = load_manifest(tmp_path / "manifest.yaml") build_result = build_document(m) assert build_result.success, build_result.warning_records dep_warns = [ w for w in build_result.warning_records if w.reason == "processor-dependency-unavailable" and "plantuml" in w.construct ] assert not dep_warns, f"Unexpected fallback warnings: {dep_warns}" 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 "plantuml" in reimported assert "@startuml" in reimported # --------------------------------------------------------------------------- # Source-only fallback always passes (verifies fallback path stable) # --------------------------------------------------------------------------- class TestSourceOnlyAlwaysPasses: """Source-only fallback must remain stable regardless of renderer availability.""" def test_all_diagram_types_source_only( self, tmp_path: Path, monkeypatch ) -> None: """All three diagram types build via source-only path when renderers absent.""" 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 = _FIXTURE_DOC.read_text(encoding="utf-8") _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") # All three diagram types must survive the round-trip assert "mermaid" in reimported assert "graphviz" in reimported assert "plantuml" in reimported