Files
mytoolkit/tests/test_md_to_pdf.py
T
Zhengshou Lai 06704acf60 test: add pytest suite — CLI smoke + config/templates/md_to_pdf units
- tests/conftest.py redirects MYTOOLKIT_HOME to a temp dir before imports
  so the module-level Config singleton never touches the real ~/.mytoolkit
- CLI smoke: every registered command answers --help with exit code 0
- unit tests: Config get/set/migrate, templates_registry override/fallback,
  md_to_pdf pure helpers (pandoc-dependent cases skipif-guarded)
- pyproject: dev dependency group (pytest) + pytest config; Makefile: make test
2026-07-18 11:45:40 +08:00

96 lines
3.1 KiB
Python

"""Tests for pure helpers in mytoolkit.md_to_pdf (no pandoc/typst needed)."""
import shutil
import pytest
from mytoolkit.md_to_pdf import (
_consume_balanced,
_longest_backtick_run,
_odd_fenced_code_before,
_typst_raw_fence,
preprocess_typst_callouts,
resolve_markdown_image_paths,
)
class TestResolveMarkdownImagePaths:
def test_relative_path_resolved_when_file_exists(self, tmp_path):
(tmp_path / "figs").mkdir()
(tmp_path / "figs" / "a.png").write_bytes(b"png")
out = resolve_markdown_image_paths("![alt](figs/a.png)", tmp_path)
assert out == f"![alt]({tmp_path / 'figs' / 'a.png'})"
def test_missing_file_left_unchanged(self, tmp_path):
md = "![alt](figs/nope.png)"
assert resolve_markdown_image_paths(md, tmp_path) == md
@pytest.mark.parametrize(
"src",
["https://x.com/a.png", "http://x.com/a.png", "data:image/png;base64,AA", "#frag"],
)
def test_urls_and_anchors_untouched(self, tmp_path, src):
md = f"![alt]({src})"
assert resolve_markdown_image_paths(md, tmp_path) == md
def test_absolute_path_untouched(self, tmp_path):
md = "![alt](/abs/path/a.png)"
assert resolve_markdown_image_paths(md, tmp_path) == md
def test_html_img_tag_resolved(self, tmp_path):
(tmp_path / "a.png").write_bytes(b"png")
out = resolve_markdown_image_paths('<img src="a.png" width="50%">', tmp_path)
assert out == f'<img src="{tmp_path / "a.png"}" width="50%">'
class TestBacktickFenceHelpers:
@pytest.mark.parametrize(
("s", "expected"),
[("", 0), ("a`b", 1), ("``x```y", 3), ("````", 4)],
)
def test_longest_backtick_run(self, s, expected):
assert _longest_backtick_run(s) == expected
def test_raw_fence_at_least_three(self):
assert _typst_raw_fence("plain") == "```"
def test_raw_fence_exceeds_inner_run(self):
assert _typst_raw_fence("has ``` inside") == "````"
class TestBalancedConsume:
def test_simple(self):
assert _consume_balanced("(a(b)c)", 0, "(", ")") == 7
def test_not_open_char(self):
assert _consume_balanced("x()", 0, "(", ")") is None
def test_unbalanced(self):
assert _consume_balanced("(a(b)", 0, "(", ")") is None
def test_quoted_chars_ignored(self):
assert _consume_balanced('(")")x', 0, "(", ")") == 5
class TestOddFencedCode:
def test_inside_fence(self):
md = "```\ncode\n"
assert _odd_fenced_code_before(md, len(md)) is True
def test_outside_fence(self):
md = "```\ncode\n```\n"
assert _odd_fenced_code_before(md, len(md)) is False
@pytest.mark.skipif(not shutil.which("pandoc"), reason="pandoc not installed")
class TestPreprocessTypstCallouts:
def test_info_box_wrapped_in_raw_typst(self):
md = "#info-box[\nHello **world**\n]\n"
out = preprocess_typst_callouts(md)
assert "{=typst}" in out
assert "#info-box[" in out
def test_callout_inside_code_fence_untouched(self):
md = "```\n#info-box[\nx\n]\n```\n"
assert preprocess_typst_callouts(md) == md