fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.
This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).
Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
253
backend/tests/test_design_system.py
Normal file
253
backend/tests/test_design_system.py
Normal file
@@ -0,0 +1,253 @@
|
||||
"""
|
||||
Tests fuer das Design-System (Light/Dark Mode, CSS-Variablen).
|
||||
|
||||
Testet:
|
||||
- CSS-Variablen-Definitionen
|
||||
- Theme-Switching (Light/Dark Mode)
|
||||
- Footer-Struktur mit Legal-Links
|
||||
- Design-Konsistenz
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class TestCSSVariables:
|
||||
"""Tests fuer CSS-Variablen im Design-System."""
|
||||
|
||||
@pytest.fixture
|
||||
def studio_css(self):
|
||||
"""Laedt CSS-Variablen fuer Tests.
|
||||
|
||||
Nach dem Refactoring sind die Variablen in modules/base/variables.css.
|
||||
Fallback auf studio.css fuer Abwaertskompatibilitaet.
|
||||
"""
|
||||
# Primaer: Modularisierte Variablen-Datei
|
||||
variables_path = Path(__file__).parent.parent / "frontend" / "static" / "css" / "modules" / "base" / "variables.css"
|
||||
if variables_path.exists():
|
||||
return variables_path.read_text()
|
||||
|
||||
# Fallback: Legacy studio.css
|
||||
css_path = Path(__file__).parent.parent / "frontend" / "static" / "css" / "studio.css"
|
||||
if css_path.exists():
|
||||
return css_path.read_text()
|
||||
return None
|
||||
|
||||
@pytest.fixture
|
||||
def base_py(self):
|
||||
"""Laedt base.py fuer Tests."""
|
||||
base_path = Path(__file__).parent.parent / "frontend" / "components" / "base.py"
|
||||
if base_path.exists():
|
||||
return base_path.read_text()
|
||||
return None
|
||||
|
||||
def test_dark_mode_primary_color_is_teal(self, studio_css):
|
||||
"""Test: Dark Mode Primary ist Teal (#0f766e)."""
|
||||
if studio_css is None:
|
||||
pytest.skip("studio.css nicht gefunden")
|
||||
|
||||
# Suche nach --bp-primary in :root (Dark Mode)
|
||||
root_match = re.search(r':root\s*\{([^}]+)\}', studio_css, re.DOTALL)
|
||||
assert root_match is not None, ":root Block nicht gefunden"
|
||||
|
||||
root_content = root_match.group(1)
|
||||
assert "--bp-primary: #0f766e" in root_content, "Dark Mode Primary sollte Teal (#0f766e) sein"
|
||||
|
||||
def test_dark_mode_accent_color_is_lime_green(self, studio_css):
|
||||
"""Test: Dark Mode Accent ist Lime Green (#22c55e)."""
|
||||
if studio_css is None:
|
||||
pytest.skip("studio.css nicht gefunden")
|
||||
|
||||
root_match = re.search(r':root\s*\{([^}]+)\}', studio_css, re.DOTALL)
|
||||
assert root_match is not None, ":root Block nicht gefunden"
|
||||
|
||||
root_content = root_match.group(1)
|
||||
assert "--bp-accent: #22c55e" in root_content, "Dark Mode Accent sollte Lime Green (#22c55e) sein"
|
||||
|
||||
def test_light_mode_primary_color_is_sky_blue(self, studio_css):
|
||||
"""Test: Light Mode Primary ist Sky Blue (#0ea5e9)."""
|
||||
if studio_css is None:
|
||||
pytest.skip("studio.css nicht gefunden")
|
||||
|
||||
# Suche nach [data-theme="light"] Block
|
||||
light_match = re.search(r'\[data-theme="light"\]\s*\{([^}]+)\}', studio_css, re.DOTALL)
|
||||
assert light_match is not None, "[data-theme='light'] Block nicht gefunden"
|
||||
|
||||
light_content = light_match.group(1)
|
||||
assert "--bp-primary: #0ea5e9" in light_content, "Light Mode Primary sollte Sky Blue (#0ea5e9) sein"
|
||||
|
||||
def test_light_mode_accent_color_is_fuchsia(self, studio_css):
|
||||
"""Test: Light Mode Accent ist Fuchsia (#d946ef)."""
|
||||
if studio_css is None:
|
||||
pytest.skip("studio.css nicht gefunden")
|
||||
|
||||
light_match = re.search(r'\[data-theme="light"\]\s*\{([^}]+)\}', studio_css, re.DOTALL)
|
||||
assert light_match is not None, "[data-theme='light'] Block nicht gefunden"
|
||||
|
||||
light_content = light_match.group(1)
|
||||
assert "--bp-accent: #d946ef" in light_content, "Light Mode Accent sollte Fuchsia (#d946ef) sein"
|
||||
|
||||
def test_no_hardcoded_material_design_grays(self):
|
||||
"""Test: Keine hardcodierten Material Design Grays (#E0E0E0, #F5F5F5, #F8F8F8)."""
|
||||
# Pruefe alle CSS-Dateien im modules-Verzeichnis
|
||||
css_base = Path(__file__).parent.parent / "frontend" / "static" / "css"
|
||||
modules_dir = css_base / "modules"
|
||||
|
||||
if not modules_dir.exists():
|
||||
# Fallback: Pruefe nur studio.css
|
||||
css_path = css_base / "studio.css"
|
||||
if not css_path.exists():
|
||||
pytest.skip("Keine CSS-Dateien gefunden")
|
||||
css_files = [css_path]
|
||||
else:
|
||||
css_files = list(modules_dir.rglob("*.css"))
|
||||
|
||||
# Diese sollten durch CSS-Variablen ersetzt sein
|
||||
# Ausnahme: In Kommentaren oder Variable-Definitionen
|
||||
problem_colors = ['#E0E0E0', '#F5F5F5', '#F8F8F8']
|
||||
|
||||
for css_file in css_files:
|
||||
lines = css_file.read_text().split('\n')
|
||||
for line_num, line in enumerate(lines, 1):
|
||||
# Ueberspringe Kommentare
|
||||
if line.strip().startswith('/*') or line.strip().startswith('*') or line.strip().startswith('//'):
|
||||
continue
|
||||
# Ueberspringe Variable-Definitionen im Light-Mode Block
|
||||
if '--bp-' in line:
|
||||
continue
|
||||
|
||||
for color in problem_colors:
|
||||
if color in line.upper():
|
||||
pytest.fail(f"Hardcodierte Farbe {color} gefunden in {css_file.name}:{line_num}: {line.strip()}")
|
||||
|
||||
def test_light_mode_uses_slate_colors(self, studio_css):
|
||||
"""Test: Light Mode verwendet Slate-Farbpalette."""
|
||||
if studio_css is None:
|
||||
pytest.skip("studio.css nicht gefunden")
|
||||
|
||||
light_match = re.search(r'\[data-theme="light"\]\s*\{([^}]+)\}', studio_css, re.DOTALL)
|
||||
assert light_match is not None, "[data-theme='light'] Block nicht gefunden"
|
||||
|
||||
light_content = light_match.group(1)
|
||||
# Slate-50 fuer Background
|
||||
assert "#f8fafc" in light_content.lower(), "Light Mode sollte Slate-50 (#f8fafc) verwenden"
|
||||
# Slate-200 fuer Border
|
||||
assert "#e2e8f0" in light_content.lower(), "Light Mode sollte Slate-200 (#e2e8f0) fuer Border verwenden"
|
||||
|
||||
def test_base_py_has_website_design_light_mode(self, base_py):
|
||||
"""Test: base.py verwendet Website Design fuer Light Mode."""
|
||||
if base_py is None:
|
||||
pytest.skip("base.py nicht gefunden")
|
||||
|
||||
# Pruefe auf Website Design Kommentar
|
||||
assert "Website Design" in base_py, "base.py sollte Website Design verwenden"
|
||||
assert "Sky Blue" in base_py or "#0ea5e9" in base_py, "base.py sollte Sky Blue fuer Light Mode verwenden"
|
||||
assert "Fuchsia" in base_py or "#d946ef" in base_py, "base.py sollte Fuchsia fuer Light Mode verwenden"
|
||||
|
||||
|
||||
class TestFooterStructure:
|
||||
"""Tests fuer Footer-Struktur mit Legal-Links."""
|
||||
|
||||
@pytest.fixture
|
||||
def studio_html(self):
|
||||
"""Laedt studio.html fuer Tests."""
|
||||
html_path = Path(__file__).parent.parent / "frontend" / "templates" / "studio.html"
|
||||
if html_path.exists():
|
||||
return html_path.read_text()
|
||||
return None
|
||||
|
||||
@pytest.fixture
|
||||
def base_py(self):
|
||||
"""Laedt base.py fuer Tests."""
|
||||
base_path = Path(__file__).parent.parent / "frontend" / "components" / "base.py"
|
||||
if base_path.exists():
|
||||
return base_path.read_text()
|
||||
return None
|
||||
|
||||
def test_footer_has_impressum_link(self, studio_html):
|
||||
"""Test: Footer enthaelt Impressum-Link."""
|
||||
if studio_html is None:
|
||||
pytest.skip("studio.html nicht gefunden")
|
||||
|
||||
assert "Impressum" in studio_html, "Footer sollte Impressum-Link enthalten"
|
||||
|
||||
def test_footer_has_agb_link(self, studio_html):
|
||||
"""Test: Footer enthaelt AGB-Link."""
|
||||
if studio_html is None:
|
||||
pytest.skip("studio.html nicht gefunden")
|
||||
|
||||
assert "AGB" in studio_html, "Footer sollte AGB-Link enthalten"
|
||||
|
||||
def test_footer_has_datenschutz_link(self, studio_html):
|
||||
"""Test: Footer enthaelt Datenschutz-Link."""
|
||||
if studio_html is None:
|
||||
pytest.skip("studio.html nicht gefunden")
|
||||
|
||||
assert "Datenschutz" in studio_html, "Footer sollte Datenschutz-Link enthalten"
|
||||
|
||||
def test_footer_has_cookies_link(self, studio_html):
|
||||
"""Test: Footer enthaelt Cookies-Link."""
|
||||
if studio_html is None:
|
||||
pytest.skip("studio.html nicht gefunden")
|
||||
|
||||
assert "Cookies" in studio_html, "Footer sollte Cookies-Link enthalten"
|
||||
|
||||
def test_footer_has_deine_rechte_link(self, studio_html):
|
||||
"""Test: Footer enthaelt Deine Rechte (GDPR)-Link."""
|
||||
if studio_html is None:
|
||||
pytest.skip("studio.html nicht gefunden")
|
||||
|
||||
assert "Deine Rechte" in studio_html, "Footer sollte 'Deine Rechte' (GDPR)-Link enthalten"
|
||||
|
||||
def test_footer_has_einstellungen_link(self, studio_html):
|
||||
"""Test: Footer enthaelt Einstellungen-Link."""
|
||||
if studio_html is None:
|
||||
pytest.skip("studio.html nicht gefunden")
|
||||
|
||||
assert "Einstellungen" in studio_html, "Footer sollte Einstellungen-Link enthalten"
|
||||
|
||||
def test_base_py_footer_has_all_links(self, base_py):
|
||||
"""Test: base.py Footer enthaelt alle erforderlichen Links."""
|
||||
if base_py is None:
|
||||
pytest.skip("base.py nicht gefunden")
|
||||
|
||||
required_links = ["Impressum", "AGB", "Datenschutz", "Cookies", "Deine Rechte", "Einstellungen"]
|
||||
for link in required_links:
|
||||
assert link in base_py, f"base.py Footer sollte '{link}'-Link enthalten"
|
||||
|
||||
|
||||
class TestThemeSwitching:
|
||||
"""Tests fuer Theme-Switching Funktionalitaet."""
|
||||
|
||||
@pytest.fixture
|
||||
def studio_js(self):
|
||||
"""Laedt studio.js fuer Tests."""
|
||||
js_path = Path(__file__).parent.parent / "frontend" / "static" / "js" / "studio.js"
|
||||
if js_path.exists():
|
||||
return js_path.read_text()
|
||||
return None
|
||||
|
||||
def test_theme_toggle_function_exists(self, studio_js):
|
||||
"""Test: Theme-Toggle Funktion existiert."""
|
||||
if studio_js is None:
|
||||
pytest.skip("studio.js nicht gefunden")
|
||||
|
||||
assert "initThemeToggle" in studio_js or "theme-toggle" in studio_js, \
|
||||
"Theme-Toggle Funktionalitaet sollte existieren"
|
||||
|
||||
def test_theme_saved_to_localstorage(self, studio_js):
|
||||
"""Test: Theme wird in localStorage gespeichert."""
|
||||
if studio_js is None:
|
||||
pytest.skip("studio.js nicht gefunden")
|
||||
|
||||
assert "localStorage" in studio_js, "Theme sollte in localStorage gespeichert werden"
|
||||
assert "bp-theme" in studio_js or "bp_theme" in studio_js, \
|
||||
"Theme-Key sollte 'bp-theme' oder 'bp_theme' sein"
|
||||
|
||||
def test_data_theme_attribute_used(self, studio_js):
|
||||
"""Test: data-theme Attribut wird verwendet."""
|
||||
if studio_js is None:
|
||||
pytest.skip("studio.js nicht gefunden")
|
||||
|
||||
assert "data-theme" in studio_js, "data-theme Attribut sollte fuer Theme-Switching verwendet werden"
|
||||
Reference in New Issue
Block a user