gwoe-antragspruefer/tests/test_inline_styles_baseline.py
Dotty Dotter 57e11b3da7 chore(ci): Anti-Regression-Wache gegen neue Inline-Styles (#184)
Nicht alle 1322 Inline-Style-Vorkommen in einer Sitzung migrieren — aber
zumindest verhindern, dass es mehr werden. Drei Bausteine:

1. ``tools/audit_inline_styles.py`` — CLI für Audit (Cluster, Top-Files)
   und Baseline-Erzeugung (--baseline → JSON {file: count}).

2. ``tools/inline_styles_baseline.json`` — eingefrorene IST-Zählung pro
   Template-Datei. Wird vom Test als obere Schranke gelesen.

3. ``tests/test_inline_styles_baseline.py`` (3 Tests) — pro Datei
   und global: aktuelle Anzahl <= Baseline. Schlägt Alarm bei neuen
   Inline-Styles und auch bei neuen Templates mit Inline-Styles, die
   noch nicht in der Baseline stehen.

Workflow für künftige Migrationen: Inline-Styles in einer Datei nach
benannten Klassen überführen, Baseline neu einfrieren via
``python3 tools/audit_inline_styles.py --baseline > tools/inline_styles_baseline.json``.

Cluster-Verteilung der 1322 Treffer:
- layout: 625, typography: 323, color: 262, sonstige: 112.

Top-Brennpunkte: index.html (463, Classic-UI Legacy),
auswertungen.html (125), antrag_detail.html v2 (119),
aktuelle-themen.html (82).

1220/1220 Tests grün.
2026-05-09 02:32:35 +02:00

83 lines
3.0 KiB
Python

"""Anti-Regression-Wache gegen neue Inline-Styles in Templates (#184).
Die Baseline wurde am Tag der Einführung dieser Wache eingefroren —
``tools/inline_styles_baseline.json``. Der Test prüft pro Datei:
**aktuelle Anzahl <= Baseline**. Damit verhindert der Test neue
Inline-Styles, ohne existierende sofort migrieren zu müssen.
Bei der Migration eines Files (Inline-Styles → benannte Klassen) sinkt
die Anzahl. Der Test bleibt grün, aber die Baseline driftet — sie kann
einfach mit ``python3 tools/audit_inline_styles.py --baseline >
tools/inline_styles_baseline.json`` neu eingefroren werden, damit
die Wache nicht versehentlich auf den alten Stand zurückgesetzt wird.
"""
import json
import re
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parent.parent
TEMPLATES = ROOT / "app" / "templates"
BASELINE = ROOT / "tools" / "inline_styles_baseline.json"
PAT = re.compile(r'style="([^"]+)"')
def _count_per_file() -> dict[str, int]:
out: dict[str, int] = {}
for f in sorted(set(TEMPLATES.rglob("*.html"))):
rel = str(f.relative_to(ROOT))
n = sum(1 for _ in PAT.finditer(f.read_text(errors="replace")))
if n > 0:
out[rel] = n
return out
@pytest.fixture(scope="module")
def baseline() -> dict[str, int]:
return json.loads(BASELINE.read_text())
@pytest.fixture(scope="module")
def current() -> dict[str, int]:
return _count_per_file()
def test_baseline_file_exists():
assert BASELINE.exists(), (
f"Baseline {BASELINE} fehlt. Erst-Erzeugung: "
"python3 tools/audit_inline_styles.py --baseline > tools/inline_styles_baseline.json"
)
def test_no_new_inline_styles_per_file(current: dict[str, int], baseline: dict[str, int]):
"""Pro Datei darf die aktuelle Anzahl die Baseline nicht überschreiten."""
over = []
for path, current_count in current.items():
base = baseline.get(path)
if base is None:
# Neue Datei mit Inline-Styles: nicht erlaubt — keine neuen Inline-Styles.
over.append(f" {path}: NEU mit {current_count} Inline-Styles, "
"Baseline kennt diese Datei noch nicht")
continue
if current_count > base:
over.append(f" {path}: {current_count} > Baseline {base}")
assert not over, (
"Inline-Styles-Regression in folgenden Templates:\n"
+ "\n".join(over)
+ "\n\nFix: Inline-Styles in benannte Klassen überführen, oder "
"(falls Migration in einer separaten PR läuft) Baseline aktualisieren:\n"
" python3 tools/audit_inline_styles.py --baseline > tools/inline_styles_baseline.json"
)
def test_baseline_total_does_not_grow(current: dict[str, int], baseline: dict[str, int]):
"""Globale Wache: Gesamtsumme darf nicht steigen."""
cur_total = sum(current.values())
base_total = sum(baseline.values())
assert cur_total <= base_total, (
f"Inline-Styles total: {cur_total} > Baseline {base_total}. "
"Pro Datei prüfen via test_no_new_inline_styles_per_file."
)