generated from coulomb/repo-seed
Deterministic ops layer and cli
This commit is contained in:
159
tests/test_ops_transform_compose_include.py
Normal file
159
tests/test_ops_transform_compose_include.py
Normal file
@@ -0,0 +1,159 @@
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from click.testing import CliRunner
|
||||
|
||||
from markitect_tool.cli import main
|
||||
from markitect_tool.ops import (
|
||||
IncludeError,
|
||||
compose_files,
|
||||
resolve_includes,
|
||||
transform_markdown,
|
||||
)
|
||||
|
||||
|
||||
def test_transform_sets_frontmatter_and_shifts_headings():
|
||||
markdown = """---
|
||||
title: Original
|
||||
---
|
||||
|
||||
# Intro
|
||||
|
||||
## Detail
|
||||
"""
|
||||
|
||||
result = transform_markdown(
|
||||
markdown,
|
||||
set_frontmatter={"status": "draft", "nested": {"owner": "Docs"}},
|
||||
heading_delta=1,
|
||||
)
|
||||
|
||||
assert "title: Original" in result.markdown
|
||||
assert "status: draft" in result.markdown
|
||||
assert "owner: Docs" in result.markdown
|
||||
assert "## Intro" in result.markdown
|
||||
assert "### Detail" in result.markdown
|
||||
assert result.operations == ["set_frontmatter", "shift_headings:1"]
|
||||
|
||||
|
||||
def test_transform_extracts_selector_text():
|
||||
markdown = """# Doc
|
||||
|
||||
## Keep
|
||||
|
||||
This section should remain.
|
||||
|
||||
## Drop
|
||||
|
||||
This section should not remain.
|
||||
"""
|
||||
|
||||
result = transform_markdown(markdown, extract_selector="sections[heading=Keep]")
|
||||
|
||||
assert result.markdown == "## Keep\n\nThis section should remain."
|
||||
assert "Drop" not in result.markdown
|
||||
|
||||
|
||||
def test_compose_files_adds_title_and_separators(tmp_path: Path):
|
||||
one = tmp_path / "one.md"
|
||||
two = tmp_path / "two.md"
|
||||
one.write_text("# One\n\nText one.", encoding="utf-8")
|
||||
two.write_text("---\ntitle: Two\n---\n\n# Two\n\nText two.", encoding="utf-8")
|
||||
|
||||
result = compose_files([one, two], title="Combined", heading_delta=1)
|
||||
|
||||
assert result.sources == [str(one), str(two)]
|
||||
assert result.markdown.startswith("# Combined")
|
||||
assert "## One" in result.markdown
|
||||
assert "## Two" in result.markdown
|
||||
assert "title: Two" not in result.markdown
|
||||
assert "\n\n---\n\n" in result.markdown
|
||||
|
||||
|
||||
def test_resolve_includes_supports_comment_marker_selector_and_heading_shift(tmp_path: Path):
|
||||
partial = tmp_path / "partial.md"
|
||||
partial.write_text(
|
||||
"""# Partial
|
||||
|
||||
## Keep
|
||||
|
||||
Selected text.
|
||||
|
||||
## Drop
|
||||
|
||||
Nope.
|
||||
""",
|
||||
encoding="utf-8",
|
||||
)
|
||||
markdown = '<!-- mkt:include path="partial.md" selector="sections[heading=Keep]" heading_delta="1" -->'
|
||||
|
||||
result = resolve_includes(markdown, base_dir=tmp_path)
|
||||
|
||||
assert result.included_paths == [str(partial.resolve())]
|
||||
assert "### Keep" in result.markdown
|
||||
assert "Selected text" in result.markdown
|
||||
assert "Drop" not in result.markdown
|
||||
|
||||
|
||||
def test_resolve_includes_supports_brace_shorthand(tmp_path: Path):
|
||||
partial = tmp_path / "partial.md"
|
||||
partial.write_text("Included body.", encoding="utf-8")
|
||||
|
||||
result = resolve_includes("Before\n\n{{include:partial.md}}\n\nAfter", base_dir=tmp_path)
|
||||
|
||||
assert "Before" in result.markdown
|
||||
assert "Included body." in result.markdown
|
||||
assert "After" in result.markdown
|
||||
|
||||
|
||||
def test_resolve_includes_rejects_cycles(tmp_path: Path):
|
||||
one = tmp_path / "one.md"
|
||||
two = tmp_path / "two.md"
|
||||
one.write_text("{{include:two.md}}", encoding="utf-8")
|
||||
two.write_text("{{include:one.md}}", encoding="utf-8")
|
||||
|
||||
with pytest.raises(IncludeError, match="Circular include"):
|
||||
resolve_includes(one.read_text(encoding="utf-8"), base_dir=tmp_path, current_path=one)
|
||||
|
||||
|
||||
def test_resolve_includes_rejects_paths_outside_base_dir(tmp_path: Path):
|
||||
outside = tmp_path.parent / "outside.md"
|
||||
outside.write_text("Nope", encoding="utf-8")
|
||||
|
||||
with pytest.raises(IncludeError, match="escapes base directory"):
|
||||
resolve_includes("{{include:../outside.md}}", base_dir=tmp_path)
|
||||
|
||||
|
||||
def test_mkt_transform_writes_markdown(tmp_path: Path):
|
||||
source = tmp_path / "doc.md"
|
||||
source.write_text("# One\n", encoding="utf-8")
|
||||
|
||||
result = CliRunner().invoke(
|
||||
main, ["transform", str(source), "--heading-delta", "1", "--set", "status=draft"]
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "status: draft" in result.output
|
||||
assert "## One" in result.output
|
||||
|
||||
|
||||
def test_mkt_compose_writes_output_file(tmp_path: Path):
|
||||
one = tmp_path / "one.md"
|
||||
output = tmp_path / "out.md"
|
||||
one.write_text("# One\n", encoding="utf-8")
|
||||
|
||||
result = CliRunner().invoke(main, ["compose", str(one), "--title", "Combined", "--output", str(output)])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert result.output == ""
|
||||
assert output.read_text(encoding="utf-8").startswith("# Combined")
|
||||
|
||||
|
||||
def test_mkt_include_reports_errors(tmp_path: Path):
|
||||
source = tmp_path / "doc.md"
|
||||
source.write_text("{{include:missing.md}}", encoding="utf-8")
|
||||
|
||||
result = CliRunner().invoke(main, ["include", str(source)])
|
||||
|
||||
assert result.exit_code == 1
|
||||
assert "Included file not found" in result.output
|
||||
Reference in New Issue
Block a user