epub2md/tests/test_qa_regressions.py
dschlueter def1d8b5c0 fix: Sprungziel für Notenverweise aus HTML-Tabellen bei GFM-Fußnoten (QA2-03)
Wird dieselbe Anmerkung aus einer als HTML erhaltenen Tabelle und aus
dem Fließtext referenziert, entstand nur eine [^fn-…]-Definition; der
HTML-Verweis <a href="#fn-…"> lief ins Leere. Die Definition erhält in
diesem Fall zusätzlich den Anker <a id="fn-…">.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 23:59:46 +02:00

76 lines
3.6 KiB
Python

"""Regression tests for defects found in the independent QA run (.qa/QA_REPORT.md, IDs QA2-…)."""
import re
from conftest import Book, Doc
def _pipe_rows(text: str) -> list[str]:
return [ln for ln in text.splitlines() if ln.startswith("| ")]
def test_pipe_in_inline_code_does_not_split_gfm_table(convert):
"""QA2-01: GFM splits table cells at every unescaped '|', also inside code spans and link targets."""
body = ("<h1>Operatoren</h1><table><thead><tr><th>Operator</th><th>Bedeutung</th></tr></thead><tbody>"
"<tr><td><code>||</code></td><td>logisches ODER</td></tr>"
"<tr><td><code>a | b</code></td><td><a href=\"https://example.org/a|b\">Link</a></td></tr>"
"</tbody></table>")
run = convert(Book(docs=[Doc("c.xhtml", body)]))
text = run.chapter("001")
for row in _pipe_rows(text):
cells = re.split(r"(?<!\\)\|", row.strip()[1:-1])
assert len(cells) == 2, f"Zeile zerfällt in GFM in {len(cells)} Zellen: {row}"
assert "||" in text and "a | b" in text and "logisches ODER" in text
NOTE_WITH_CODE = ('<h1>K</h1><p>Text<a epub:type="noteref" href="#n1" id="r1">1</a>.</p>'
'<aside epub:type="footnote" id="n1"><p>Note mit Code:</p><pre>code()\n weiter()</pre></aside>'
'<p>Zweite<a epub:type="noteref" href="#n2" id="r2">2</a>.</p>'
'<aside epub:type="footnote" id="n2"><pre>nur_code()</pre></aside>'
"<p>Danach kommt normaler Text.</p>")
def _fences_closed(text: str) -> bool:
"""Every opening ``` fence has a closing line that consists only of backticks."""
open_fence = None
for line in text.splitlines():
s = line.strip()
if open_fence is None:
m = re.match(r"(`{3,})", s)
if m:
open_fence = m.group(1)
elif re.fullmatch(r"`{3,}", s) and len(s) >= len(open_fence):
open_fence = None
return open_fence is None
def test_backlink_after_code_block_does_not_break_fence_commonmark(convert):
"""QA2-02: the ↩ backlink was appended to the closing ``` line, leaving the fence open."""
run = convert(Book(docs=[Doc("c.xhtml", NOTE_WITH_CODE)]), "--format", "commonmark")
text = run.chapter("001")
assert _fences_closed(text), text
assert not re.search(r"\*\*\d+[:.]\*\* ```", text), "Fence hinter Notennummer ist kein Codeblock"
assert "[↩](#fnref-001)" in text
def test_backlink_after_code_block_does_not_break_fence_end_of_book(convert):
run = convert(Book(docs=[Doc("c.xhtml", NOTE_WITH_CODE)]), "--footnotes", "end-of-book")
notes = run.chapter("999")
assert _fences_closed(notes), notes
assert not re.search(r"\*\*\d+[:.]\*\* ```", notes), "Fence hinter Notennummer ist kein Codeblock"
assert "[↩](001-k.md#fnref-001)" in notes
NOTE_IN_HTML_TABLE = ('<h1>K</h1><table><tr><td colspan="2">Zelle<a epub:type="noteref" href="#n1" id="r1">1</a></td></tr>'
"<tr><td>a</td><td>b</td></tr></table>"
'<p>Text<a epub:type="noteref" href="#n1" id="r1b">1</a>.</p>'
'<aside epub:type="footnote" id="n1"><p>Die Note.</p></aside>')
def test_note_referenced_from_html_table_and_text_has_target(convert):
"""QA2-03: GFM: the HTML-table reference pointed to #fn-001, but only a [^fn-001] definition existed."""
run = convert(Book(docs=[Doc("c.xhtml", NOTE_IN_HTML_TABLE)]))
text = run.chapter("001")
assert 'href="#fn-001"' in text and "Text[^fn-001]." in text
assert re.search(r'id="fn-001"', text), text
assert run.report["output_link_check"]["broken_count"] == 0