epub2md/tests/test_merge.py
dschlueter 64d0cb0357 fix: Pandoc-Weg – EPUB-Links, Formeln im EPUB, portables TeX (QA3-04 bis QA3-06)
Prüfung aller 5 Beispielbücher über die Befehle der Buch-README (HTML,
EPUB, PDF mit weasyprint und xelatex):

- QA3-04: Der EPUB-Befehl der Buch-README hatte kein --mathml; im
  Mathebuch blieben 1072 Formeln als TeX-Text. Jetzt 4904 als MathML.
- QA3-05: Kapitel-Startanker von epub2md_merge.py begannen mit einer
  Ziffer (039-…). Pandocs EPUB-Writer löst Links darauf nicht auf (C-Buch
  32, Mathebuch 20 tote Links). Jetzt kap-039-…; book.lua erkennt beide.
- QA3-06: portable_tex() passt Verlags-TeX für texmath an (\"u, \O,
  @{…} und \arraycolsep in array, leeres \displaystyle vor Zellenende).
  Mathebuch: 1 statt 9 nicht umsetzbare Formeln.
- Cover ohne Seitenzahl (print.css, book.lua).

Neue Tests: tests/test_pandoc.py (nur mit installiertem pandoc),
Parametertests für portable_tex. Andere Bücher byte-identisch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 21:41:13 +02:00

138 lines
7.2 KiB
Python

import re
import yaml
from conftest import PNG, Book, Doc
from epub2md.linkcheck import anchors_in, check_links
from epub2md.merge import main as merge_main
A = ('<h1 id="sec">Kapitel A</h1><p>Siehe <a href="b.xhtml#sec">B</a>, <a href="b.xhtml">Kapitel B</a>, '
'<a href="#sec">hier</a> und Anmerkung<a epub:type="noteref" href="b.xhtml#n1">1</a>.</p>'
'<p><img src="img/a.png" alt="Bild"/></p>'
'<p><a href="b.xhtml"><img src="img/a.png" alt="Bildlink"/></a></p>'
'<pre>[kein](link.md) id="sec" `code`</pre>')
B = ('<h1 id="sec">Kapitel B</h1><p>Zurück zu <a href="a.xhtml#sec">A</a>, nochmal Anmerkung'
'<a epub:type="noteref" href="#n1">1</a>.</p>'
'<aside epub:type="footnote" id="n1"><p>Gemeinsame Anmerkung.</p></aside>')
def book() -> Book:
return Book(title="Merge-Test", docs=[Doc("a.xhtml", A), Doc("b.xhtml", B)], media={"img/a.png": PNG})
def test_merge_complete_book(convert, tmp_path):
run = convert(book())
assert merge_main([str(run.out)]) == 0
target = run.out / f"{run.report['input']['name'][:-5]}.md"
text = target.read_text(encoding="utf-8")
# one book-level frontmatter, no chapter frontmatter
fm = yaml.safe_load(text.split("---\n")[1])
assert fm["title"] == "Merge-Test" and fm["author"] == ["Erika Musterfrau"] and fm["lang"] == "de"
assert text.count("chapter_number") == 0
# reading order
assert text.index("# Kapitel A") < text.index("# Kapitel B")
# links between chapters point into the merged file; colliding id "sec" renamed in chapter B
assert '<a id="sec"></a>' in text and '<a id="c002-sec"></a>' in text
assert "[B](#c002-sec)" in text and "[A](#sec)" in text and "[hier](#sec)" in text
assert "[Kapitel B](#kap-002-kapitel-b)" in text and '<a id="kap-002-kapitel-b"></a>' in text
# media paths relative to the merged file, also inside image links
assert "![Bild](media/images/0001-bild.png)" in text
assert "[![Bildlink](media/images/0001-bild.png)](#kap-002-kapitel-b)" in text
# code untouched
assert '[kein](link.md) id="sec" `code`' in text
# footnote defined in both chapters -> only once in the merged file
assert text.count("[^fn-001]:") == 1
assert check_links({target.as_posix(): text}, {p.as_posix() for p in run.out.rglob("*") if p.is_file()}) == []
prose = re.sub(r"^```.*?^```$", "", text, flags=re.M | re.S)
ids = re.findall(r'id="([^"]+)"', prose)
assert len(ids) == len(set(ids))
def test_merge_refuses_overwrite_without_force(convert):
run = convert(book())
assert merge_main([str(run.out)]) == 0
assert merge_main([str(run.out)]) == 1
assert merge_main([str(run.out), "--force"]) == 0
def test_merge_to_other_location_rewrites_media_paths(convert, tmp_path):
run = convert(book())
(tmp_path / "ziel").mkdir()
target = tmp_path / "ziel" / "buch.md"
assert merge_main([str(run.out), "-o", str(target), "--toc", "--no-frontmatter"]) == 0
text = target.read_text(encoding="utf-8")
assert not text.startswith("---")
assert f"![Bild](../{run.out.name}/media/images/0001-bild.png)" in text
assert "1. [Kapitel A](#kap-001-kapitel-a)" in text
assert "kap-001-kapitel-a" in anchors_in(text)
def test_merge_invalid_directory(tmp_path):
assert merge_main([str(tmp_path / "gibt-es-nicht")]) == 1
(tmp_path / "leer").mkdir()
assert merge_main([str(tmp_path / "leer")]) == 1
def test_merge_shortens_iso_date(convert):
from epub2md.merge import display_date
assert display_date("2025-01-13T08:00:00+00:00", "de") == "13.01.2025"
assert display_date("2025-01-13", "de-DE") == "13.01.2025"
assert display_date("2025-01-13T08:00:00Z", "en") == "2025-01-13"
assert display_date("2025", "de") == "2025" # year only / other formats stay
assert display_date("Januar 2025", "de") == "Januar 2025"
run = convert(book())
assert merge_main([str(run.out)]) == 0
text = (run.out / f"{run.report['input']['name'][:-5]}.md").read_text(encoding="utf-8")
assert yaml.safe_load(text.split("---\n")[1])["date"] == "01.01.2024"
def front_book() -> Book:
docs = [Doc("cover.xhtml", '<div><img src="img/a.png" alt="Cover"/></div>'),
Doc("titel.xhtml", "<p>Erika Musterfrau</p><p>Vorspann</p><p>Roman</p>"),
Doc("impressum.xhtml", "<p>© 2024 Testverlag</p><p>Das Werk ist urheberrechtlich geschützt.</p>"),
Doc("a.xhtml", "<h1>Kapitel A</h1><p>a</p>")]
return Book(title="Vorspann", docs=docs, nav=[("Kapitel A", "a.xhtml")], media={"img/a.png": PNG})
def test_merge_places_toc_placeholder_after_front_matter(convert):
run = convert(front_book())
assert merge_main([str(run.out)]) == 0
text = (run.out / f"{run.report['input']['name'][:-5]}.md").read_text(encoding="utf-8")
cover, title, imprint = text.index("![Cover]"), text.index("\nRoman"), text.index("© 2024")
toc, first = text.index('<div id="epub2md-toc"></div>'), text.index("# Kapitel A")
assert cover < title < imprint < toc < first
assert "epub2md-titlepage" not in text # the book has its own title page
def test_merge_title_placeholder_after_cover_without_own_title_page(convert):
docs = [Doc("cover.xhtml", '<div><img src="img/a.png" alt="Cover"/></div>'), Doc("a.xhtml", "<h1>Kapitel A</h1><p>a</p>")]
run = convert(Book(title="Ohne Titelei", docs=docs, nav=[("Kapitel A", "a.xhtml")], media={"img/a.png": PNG}))
assert merge_main([str(run.out)]) == 0
text = (run.out / f"{run.report['input']['name'][:-5]}.md").read_text(encoding="utf-8")
assert text.index("![Cover]") < text.index('<div id="epub2md-titlepage"></div>') \
< text.index('<div id="epub2md-toc"></div>') < text.index("# Kapitel A")
def test_merge_own_toc_replaces_placeholder(convert):
run = convert(front_book())
assert merge_main([str(run.out), "--toc"]) == 0
text = (run.out / f"{run.report['input']['name'][:-5]}.md").read_text(encoding="utf-8")
assert "epub2md-toc" not in text and text.index("© 2024") < text.index("## Inhalt") < text.index("# Kapitel A")
def test_merge_front_matter_region_with_gaps(convert):
"""Krooss/Blake-like: short untyped pages between title page and imprint; main matter starts at '1 …'."""
docs = [Doc("cover.xhtml", '<div><img src="img/a.png" alt="Cover"/></div>'),
Doc("titel.xhtml", "<p>Erika Musterfrau</p><p>Vorspann</p><p>Roman</p>"),
Doc("auflage.xhtml", "<h1>Vorspann</h1><p>4. Auflage 2025</p>"),
Doc("impressum.xhtml", "<p>© 2024 Testverlag</p><p>Das Werk ist urheberrechtlich geschützt.</p>"),
Doc("brief.xhtml", "<h1>Liebe Leserin, lieber Leser,</h1><p>Viel Spaß.</p>"),
Doc("k1.xhtml", "<h1>1 Der Einstieg</h1><p>a</p>"), Doc("k2.xhtml", "<h1>Zwischenspiel</h1><p>b</p>")]
book = Book(title="Vorspann", docs=docs, nav=[("1 Der Einstieg", "k1.xhtml"), ("Zwischenspiel", "k2.xhtml")],
media={"img/a.png": PNG})
run = convert(book)
assert merge_main([str(run.out)]) == 0
text = (run.out / f"{run.report['input']['name'][:-5]}.md").read_text(encoding="utf-8")
toc = text.index('<div id="epub2md-toc"></div>')
assert text.index("© 2024") < text.index("Viel Spaß.") < toc < text.index("# 1 Der Einstieg")
assert text.count("epub2md-toc") == 1 and "epub2md-titlepage" not in text