Redesign Sentinel UI: test_frontend_contract.py

This commit is contained in:
sascha 2026-07-29 15:38:36 +02:00
parent ff46387a43
commit 630f980906

View file

@ -0,0 +1,77 @@
import re
import subprocess
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
HTML = (ROOT / "templates" / "index.html").read_text(encoding="utf-8")
JS = (ROOT / "static" / "js" / "app.js").read_text(encoding="utf-8")
CSS = (ROOT / "static" / "css" / "style.css").read_text(encoding="utf-8")
class FrontendContractTests(unittest.TestCase):
def test_stable_dom_ids_exist_once(self):
required = {
"nav-dashboard", "nav-alerts", "nav-hosts", "nav-config",
"sysStatus", "lastScan", "mOk", "mSize", "mLatest",
"mLatestHost", "mWarn", "mWarnCard", "clusterList",
"liveStream", "chartSvg", "hostGrid", "alertList",
"drawer", "drawerBg", "drawerTitle", "drawerBody",
"modal", "modalBg", "hostForm", "formMode", "formName",
"formKumaUrl", "formEnabled", "toasts",
}
for element_id in required:
count = len(re.findall(rf'id=["\']{re.escape(element_id)}["\']', HTML))
self.assertEqual(count, 1, f"#{element_id} must exist exactly once")
def test_uses_local_styles_without_runtime_css_framework(self):
self.assertIn('/static/css/style.css', HTML)
self.assertNotIn('cdn.tailwindcss.com', HTML)
self.assertNotIn('Material+Symbols', HTML)
self.assertNotIn('backdrop-blur', HTML)
self.assertNotIn('pulse-', HTML)
def test_monitor_surface_has_semantic_landmarks_and_accessible_actions(self):
self.assertRegex(HTML, r'<nav[^>]+aria-label="Hauptnavigation"')
self.assertIn('<main', HTML)
self.assertIn('aria-label="Daten aktualisieren"', HTML)
self.assertIn('aria-label="Host hinzufügen"', HTML)
self.assertIn('aria-live="polite"', HTML)
def test_copy_is_plain_german(self):
expected = [
'Backup-Übersicht', 'Letzte Aktualisierung', 'Heute gesichert',
'Letzte Sicherung', 'Auffälligkeiten', 'Letzte Meldungen',
'Backup-Hosts', 'Einstellungen',
]
for text in expected:
self.assertIn(text, HTML)
forbidden = ['Operational command', 'Real-time surveillance', 'LIVE', 'Vault Overview']
for text in forbidden:
self.assertNotIn(text, HTML)
def test_css_contains_responsive_and_motion_contracts(self):
self.assertIn('@media (max-width: 720px)', CSS)
self.assertIn('@media (prefers-reduced-motion: reduce)', CSS)
self.assertIn(':focus-visible', CSS)
self.assertIn('--status-ok', CSS)
def test_javascript_uses_semantic_status_classes_and_german_labels(self):
self.assertIn('status--ok', JS)
self.assertIn('Betriebsbereit', JS)
self.assertIn('vor ', JS)
self.assertNotIn('material-symbols-outlined', JS)
self.assertNotIn('bg-${color}', JS)
def test_javascript_syntax(self):
result = subprocess.run(
["node", "--check", str(ROOT / "static" / "js" / "app.js")],
capture_output=True,
text=True,
)
self.assertEqual(result.returncode, 0, result.stderr)
if __name__ == "__main__":
unittest.main()