generated from coulomb/repo-seed
Implements full LEVEL3 feature set: cross-references (xref.py), numbered figures (figures.py), auto-diagrams (diagrams.py), bibliography/citations (bibliography.py), LEVEL3 capability detection (level3.py), and structured error/warning records (errors.py). Builder, importer, and differ updated for LEVEL3 round-trip support. REST and MCP interfaces updated with structured warning records. 259 tests passing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
232 lines
8.2 KiB
Python
232 lines
8.2 KiB
Python
"""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
|