Logik aus dem Jinja-Template (Heuchelei-Marker, Konsistenz-Block, decisive-Outcome-Selection) in app/marker.py extrahiert. Template ruft die drei Helper als Jinja-Globals auf. Damit ist die Logik testbar ohne Render-Kontext. Plus: app/pm_render.py als Python-Spiegelbild des JS-Mini-Markdown- Renderers in aktuelle-themen.html — fuer Tests und potenzielle Server-side-Render-Optionen (z.B. PM-Mail). Tests: - tests/test_marker.py (35 Cases): heuchelei_score, decisive_outcome, consistency_state inkl. Multi-Vote, ambivalente Empfehlung, Edge-Cases. - tests/test_pm_render.py (21 Cases): Bold, Italic, Listen, HTML-Escape, Paragraph-Splitting, snake_case-Schutz. Refs: ADR 0010, ADR 0011 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
138 lines
4.3 KiB
Python
138 lines
4.3 KiB
Python
"""Tests für app.pm_render — Python-Spiegelbild des JS-Mini-Markdown-Renderers."""
|
|
import pytest
|
|
|
|
try:
|
|
from app.pm_render import render_pm_body
|
|
_HAS_RENDER = True
|
|
except ImportError:
|
|
_HAS_RENDER = False
|
|
|
|
pytestmark = pytest.mark.skipif(not _HAS_RENDER, reason="app.pm_render nicht importierbar")
|
|
|
|
|
|
class TestEmptyAndNone:
|
|
def test_empty_returns_empty(self):
|
|
assert render_pm_body("") == ""
|
|
|
|
def test_none_returns_empty(self):
|
|
assert render_pm_body(None) == ""
|
|
|
|
|
|
class TestPlainParagraph:
|
|
def test_single_paragraph(self):
|
|
out = render_pm_body("Ein einzelner Satz.")
|
|
assert out == '<p style="margin:0 0 0.9em;">Ein einzelner Satz.</p>'
|
|
|
|
def test_two_paragraphs(self):
|
|
out = render_pm_body("Erster Satz.\n\nZweiter Satz.")
|
|
assert "Erster Satz" in out
|
|
assert "Zweiter Satz" in out
|
|
assert out.count("<p ") == 2
|
|
|
|
def test_single_newline_becomes_br(self):
|
|
out = render_pm_body("Zeile A\nZeile B")
|
|
assert "Zeile A<br>Zeile B" in out
|
|
|
|
|
|
class TestHtmlEscape:
|
|
def test_lt_gt_escaped(self):
|
|
out = render_pm_body("Vergleich a < b > c.")
|
|
assert "<" in out and ">" in out
|
|
assert "<b>" not in out # nichts wird zu echten Tags
|
|
|
|
def test_amp_escaped(self):
|
|
out = render_pm_body("Tom & Jerry")
|
|
assert "&" in out
|
|
|
|
|
|
class TestBold:
|
|
def test_double_star_to_strong(self):
|
|
out = render_pm_body("Wert: **30 %**")
|
|
assert "<strong>30 %</strong>" in out
|
|
|
|
def test_double_underscore_to_strong(self):
|
|
out = render_pm_body("Wert: __wichtig__")
|
|
assert "<strong>wichtig</strong>" in out
|
|
|
|
def test_no_bold_across_newline(self):
|
|
"""**...** mit Newline dazwischen wird nicht gemarkt."""
|
|
out = render_pm_body("**erste\nZeile**")
|
|
assert "<strong>" not in out
|
|
|
|
|
|
class TestItalic:
|
|
def test_single_star_to_em(self):
|
|
out = render_pm_body("Das ist *betont*.")
|
|
assert "<em>betont</em>" in out
|
|
|
|
def test_single_underscore_to_em(self):
|
|
out = render_pm_body("Das ist _wichtig_.")
|
|
assert "<em>wichtig</em>" in out
|
|
|
|
def test_underscore_in_word_not_em(self):
|
|
"""snake_case darf nicht als em gerendert werden."""
|
|
out = render_pm_body("Variable my_var hier.")
|
|
assert "<em>" not in out
|
|
|
|
def test_star_between_words_not_em(self):
|
|
"""Ein * zwischen Wort-Charaktern ist kein em-Marker."""
|
|
out = render_pm_body("Datei: foo*bar.txt")
|
|
assert "<em>" not in out
|
|
|
|
|
|
class TestBoldAndItalicCombined:
|
|
def test_bold_first_then_italic(self):
|
|
out = render_pm_body("**fett** und *kursiv*.")
|
|
assert "<strong>fett</strong>" in out
|
|
assert "<em>kursiv</em>" in out
|
|
|
|
|
|
class TestLists:
|
|
def test_simple_dash_list(self):
|
|
out = render_pm_body("- Item 1\n- Item 2")
|
|
assert "<ul" in out
|
|
assert "<li>Item 1</li>" in out
|
|
assert "<li>Item 2</li>" in out
|
|
|
|
def test_simple_star_list(self):
|
|
out = render_pm_body("* A\n* B")
|
|
assert "<ul" in out
|
|
assert "<li>A</li>" in out
|
|
assert "<li>B</li>" in out
|
|
|
|
def test_list_with_paragraph_around(self):
|
|
body = "Vor der Liste.\n\n- Item 1\n- Item 2\n\nNach der Liste."
|
|
out = render_pm_body(body)
|
|
assert "<p" in out
|
|
assert "<ul" in out
|
|
assert "<li>Item 1</li>" in out
|
|
|
|
def test_dash_in_word_not_list(self):
|
|
"""Ein Bindestrich mitten im Wort ist kein Bullet."""
|
|
out = render_pm_body("Halb-leer ist halb-voll.")
|
|
assert "<ul" not in out
|
|
|
|
|
|
class TestRealisticPmBody:
|
|
"""End-to-End-Sample wie qwen-max es typischerweise produziert."""
|
|
|
|
def test_sample_with_bold_and_paragraphs(self):
|
|
body = (
|
|
"Mehr junge Menschen entscheiden sich für die Pflege.\n\n"
|
|
"Auszubildende brechen heute zu rund **30 %** ab.\n\n"
|
|
"Wir fordern eine Mindest-Vergütung."
|
|
)
|
|
out = render_pm_body(body)
|
|
# Drei Paragraphen
|
|
assert out.count("<p ") == 3
|
|
# Bold-Markierung gerendert
|
|
assert "<strong>30 %</strong>" in out
|
|
# Keine rohen ** mehr
|
|
assert "**" not in out
|
|
|
|
def test_no_bold_when_unmatched(self):
|
|
body = "Ein *einfacher Stern."
|
|
out = render_pm_body(body)
|
|
# Unmatched * darf nicht als em gerendert werden
|
|
assert "<em>" not in out
|