144 lines
5.3 KiB
Python
144 lines
5.3 KiB
Python
|
|
"""End-to-end behaviour of the `wapdown` command."""
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
from conftest import EXAMPLES
|
||
|
|
from wapdown.cli import main
|
||
|
|
|
||
|
|
|
||
|
|
TWO_SECTIONS = "Intro.\n\n{.card One}\nFirst.\n\n{.card Two}\nSecond.\n"
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.fixture
|
||
|
|
def source(tmp_path):
|
||
|
|
def _write(text: str = TWO_SECTIONS, name: str = "doc.md"):
|
||
|
|
path = tmp_path / name
|
||
|
|
path.write_text(text, encoding="utf-8")
|
||
|
|
return path
|
||
|
|
|
||
|
|
return _write
|
||
|
|
|
||
|
|
|
||
|
|
class TestOutput:
|
||
|
|
def test_writes_to_stdout_by_default(self, source, capsys):
|
||
|
|
assert main([str(source())]) == 0
|
||
|
|
assert capsys.readouterr().out.startswith('<?xml version="1.0"?>')
|
||
|
|
|
||
|
|
def test_writes_to_a_file(self, source, tmp_path):
|
||
|
|
out = tmp_path / "deck.wml"
|
||
|
|
assert main([str(source()), "-o", str(out)]) == 0
|
||
|
|
assert out.read_text(encoding="utf-8").endswith("</wml>\n")
|
||
|
|
|
||
|
|
def test_creates_missing_parent_directories(self, source, tmp_path):
|
||
|
|
out = tmp_path / "nested" / "deep" / "deck.wml"
|
||
|
|
assert main([str(source()), "-o", str(out)]) == 0
|
||
|
|
assert out.exists()
|
||
|
|
|
||
|
|
def test_uses_lf_line_endings(self, source, tmp_path):
|
||
|
|
out = tmp_path / "deck.wml"
|
||
|
|
main([str(source()), "-o", str(out)])
|
||
|
|
assert b"\r" not in out.read_bytes()
|
||
|
|
|
||
|
|
def test_deck_per_card_writes_a_directory(self, source, tmp_path):
|
||
|
|
out = tmp_path / "decks"
|
||
|
|
assert main([str(source()), "--deck-per-card", "-o", str(out)]) == 0
|
||
|
|
assert sorted(p.name for p in out.iterdir()) == [
|
||
|
|
"index.wml",
|
||
|
|
"one.wml",
|
||
|
|
"two.wml",
|
||
|
|
]
|
||
|
|
|
||
|
|
def test_deck_per_card_without_output_is_refused(self, source):
|
||
|
|
# Several files cannot be written to stdout, so say so rather than
|
||
|
|
# silently emitting only one of them.
|
||
|
|
with pytest.raises(SystemExit, match="pass -o DIRECTORY"):
|
||
|
|
main([str(source()), "--deck-per-card"])
|
||
|
|
|
||
|
|
|
||
|
|
class TestOptions:
|
||
|
|
def test_flags_reach_the_renderer(self, source, capsys):
|
||
|
|
main([str(source()), "--menu-style", "select", "--title", "Deck"])
|
||
|
|
out = capsys.readouterr().out
|
||
|
|
assert "<select" in out
|
||
|
|
assert 'title="Deck"' in out
|
||
|
|
|
||
|
|
def test_flag_overrides_frontmatter(self, source, capsys):
|
||
|
|
path = source("---\ntitle: FromFile\n---\n" + TWO_SECTIONS)
|
||
|
|
main([str(path), "--title", "FromFlag"])
|
||
|
|
assert 'title="FromFlag"' in capsys.readouterr().out
|
||
|
|
|
||
|
|
def test_frontmatter_applies_without_a_flag(self, source, capsys):
|
||
|
|
path = source("---\nmenu_style: select\n---\n" + TWO_SECTIONS)
|
||
|
|
main([str(path)])
|
||
|
|
assert "<select" in capsys.readouterr().out
|
||
|
|
|
||
|
|
def test_no_menu_switches_to_linear(self, source, capsys):
|
||
|
|
main([str(source()), "--no-menu"])
|
||
|
|
assert '<card id="menu"' not in capsys.readouterr().out
|
||
|
|
|
||
|
|
def test_frontmatter_is_not_rendered_as_content(self, source, capsys):
|
||
|
|
path = source("---\ntitle: Deck\n---\n" + TWO_SECTIONS)
|
||
|
|
main([str(path)])
|
||
|
|
assert "title: Deck" not in capsys.readouterr().out
|
||
|
|
|
||
|
|
|
||
|
|
class TestErrors:
|
||
|
|
def test_missing_input_file(self, tmp_path, capsys):
|
||
|
|
assert main([str(tmp_path / "nope.md")]) == 2
|
||
|
|
assert "wapdown:" in capsys.readouterr().err
|
||
|
|
|
||
|
|
def test_invalid_option_value(self, source, capsys):
|
||
|
|
assert main([str(source()), "--split-level", "9"]) == 2
|
||
|
|
assert "split_level" in capsys.readouterr().err
|
||
|
|
|
||
|
|
def test_invalid_frontmatter_value(self, source, capsys):
|
||
|
|
path = source("---\nsplit_level: deep\n---\n" + TWO_SECTIONS)
|
||
|
|
assert main([str(path)]) == 2
|
||
|
|
assert "must be a number" in capsys.readouterr().err
|
||
|
|
|
||
|
|
def test_unknown_menu_style_is_rejected_by_argparse(self, source):
|
||
|
|
with pytest.raises(SystemExit) as exc:
|
||
|
|
main([str(source()), "--menu-style", "carousel"])
|
||
|
|
assert exc.value.code == 2
|
||
|
|
|
||
|
|
def test_missing_include_is_reported(self, source, capsys):
|
||
|
|
path = source("Intro.\n\n{.include nowhere.md}\n")
|
||
|
|
assert main([str(path)]) == 2
|
||
|
|
assert "was not found" in capsys.readouterr().err
|
||
|
|
|
||
|
|
|
||
|
|
class TestIncludes:
|
||
|
|
def test_included_file_contributes_content(self, tmp_path, capsys):
|
||
|
|
(tmp_path / "part.md").write_text("Included body.\n", encoding="utf-8")
|
||
|
|
main([str(_write(tmp_path, "Intro.\n\n{.include part.md}\n"))])
|
||
|
|
assert "Included body." in capsys.readouterr().out
|
||
|
|
|
||
|
|
def test_included_frontmatter_is_discarded(self, tmp_path, capsys):
|
||
|
|
(tmp_path / "part.md").write_text(
|
||
|
|
"---\ntitle: Ignored\n---\nIncluded body.\n", encoding="utf-8"
|
||
|
|
)
|
||
|
|
main([str(_write(tmp_path, "Intro.\n\n{.include part.md}\n"))])
|
||
|
|
out = capsys.readouterr().out
|
||
|
|
assert "Included body." in out
|
||
|
|
assert "Ignored" not in out
|
||
|
|
|
||
|
|
def test_circular_include_is_reported(self, tmp_path, capsys):
|
||
|
|
(tmp_path / "a.md").write_text("{.include doc.md}\n", encoding="utf-8")
|
||
|
|
assert main([str(_write(tmp_path, "{.include a.md}\n"))]) == 2
|
||
|
|
assert "Circular include" in capsys.readouterr().err
|
||
|
|
|
||
|
|
|
||
|
|
def _write(directory, text: str, name: str = "doc.md"):
|
||
|
|
path = directory / name
|
||
|
|
path.write_text(text, encoding="utf-8")
|
||
|
|
return path
|
||
|
|
|
||
|
|
|
||
|
|
class TestExamples:
|
||
|
|
@pytest.mark.parametrize(
|
||
|
|
"name", sorted(p.name for p in EXAMPLES.glob("*.md"))
|
||
|
|
)
|
||
|
|
def test_every_example_converts(self, name, tmp_path, capsys):
|
||
|
|
assert main([str(EXAMPLES / name), "-o", str(tmp_path / "out.wml")]) == 0
|