Files
rar-autopass/tests/test_tema.py
Claude Agent 5a964a1a8d feat(5.10): UX trimiteri (pill filtre, paginare, editare) + Mapari in meniu + branding ROMFAST
14 stories TDD prin echipa de workeri (lead orchestreaza, 3 teammates pe valuri cu fisiere disjuncte; routes.py + base.html serializate ca fisiere fierbinti).

- US-001 fix filtrare data (_iso_date_prefix pe garda+comparatie, prinde timestamp cu ora)
- US-002/007 operatie service distincta in payload_view + afisare in detaliu
- US-003 pill-uri categorii (button/aria-pressed; needs_mapping --warn, needs_data/error --err); fara lista ID-uri/dropdown
- US-004 paginare numerotata 25/pag (total ramificat SQL-COUNT vs fetch-all+slice, clamp page, poll pastreaza pagina)
- US-005 VIN block-level sub nr
- US-006/006b editare cod RAR + validare nomenclator + recalcul idempotency (needs_data/needs_mapping via /corecteaza, error via /repune)
- US-008 card eroare 3-niveluri doar pe read-only + rezumat top-of-form
- US-009 Mapari in meniu hamburger; scoatere tab-bar + role=tablist orfan
- US-010/011 pagina Mapari consolidata + butoane icon SVG + dirty-state (fara kebab/emoji)
- US-012/012b header centrat + logo ROMFAST (/static/romfast_logo.png) in header
- US-013 paleta azur ROMFAST (#2E74D6/#1F66C9) + IBM Plex Sans/Mono self-host (woff2 reale)
- US-014 selector tema ciclic Light/Dark/Petrol/Auto + anti-FOUC pe 4 stari

Backend trimitere (worker/masina stari/idempotenta/mapping) + schema NEATINSE (UI/UX pur + 1 fix de filtrare).
VERIFY context curat PASS; /code-review high: 1 finding material reparat (US-006b). Regresie 896 passed, 1 skipped, 0 failed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 20:20:58 +00:00

189 lines
7.6 KiB
Python

"""Teste US-001 + US-002 (PRD 5.3): Light/Dark mode comutator tema.
TDD: testele se scriu INAINTE de implementare (RED), dupa implementare trec (GREEN).
"""
from __future__ import annotations
import os
import re
import tempfile
from pathlib import Path
import pytest
from starlette.testclient import TestClient
@pytest.fixture()
def client(monkeypatch):
tmp = tempfile.mkdtemp()
monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "tema.db"))
from app.config import get_settings
get_settings.cache_clear()
from app.main import app
with TestClient(app) as c:
yield c
get_settings.cache_clear()
def _create_user(email: str = "tema@test.com", password: str = "parolasecreta"):
from app.accounts import create_account
from app.users import create_user
from app.db import get_connection
conn = get_connection()
try:
acct_id = create_account(conn, "Service Tema", active=True)
create_user(conn, acct_id, email, password)
return acct_id
finally:
conn.close()
def _login(client, email: str, password: str = "parolasecreta") -> None:
resp = client.get("/login")
m = re.search(r'name="csrf_token"\s+value="([^"]+)"', resp.text) or \
re.search(r'value="([^"]+)"\s+name="csrf_token"', resp.text)
assert m, "csrf_token negasit in /login"
resp = client.post(
"/login",
data={"email": email, "parola": password, "csrf_token": m.group(1)},
follow_redirects=False,
)
assert resp.status_code == 303, f"Login esuat: {resp.status_code}"
# ── US-001: Tema light ─────────────────────────────────────────────────────────
def test_paleta_light_definita(client):
"""HTML de la GET /login contine un selector [data-theme="light"] care redefineste
cel putin --bg, --card, --ink, --muted, --line."""
resp = client.get("/login")
assert resp.status_code == 200
html = resp.text
assert '[data-theme="light"]' in html, 'Lipseste blocul [data-theme="light"] in HTML'
light_block = re.search(r'\[data-theme=["\']light["\']\]\s*\{([^}]+)\}', html, re.DOTALL)
assert light_block, 'Nu am gasit blocul CSS [data-theme="light"] { ... }'
block = light_block.group(1)
for var in ("--bg", "--card", "--ink", "--muted", "--line"):
assert var in block, f"Variabila {var} lipseste din blocul [data-theme=\"light\"]"
def test_dark_ramane_default(client):
""":root contine paleta dark exacta: --bg:#0f1218, --card:#181c24, --ink:#e6e9ef.
Valorile actualizate la US-013 (PRD 5.10) conform DESIGN.md (accent azur ROMFAST).
"""
resp = client.get("/login")
assert resp.status_code == 200
html = resp.text
assert "--bg:#0f1218" in html, "Paleta dark --bg:#0f1218 a fost modificata sau stearsa"
assert "--card:#181c24" in html, "Paleta dark --card:#181c24 a fost modificata sau stearsa"
assert "--ink:#e6e9ef" in html, "Paleta dark --ink:#e6e9ef a fost modificata sau stearsa"
def test_suprafete_fara_fundal_hardcodat(client):
"""<style> NU mai contine literalii hex dark-fix #241a1a, #201c0f, #16241c
(banner eroare / banner warn / flash facute theme-aware)."""
resp = client.get("/login")
assert resp.status_code == 200
style_match = re.search(r'<style>(.*?)</style>', resp.text, re.DOTALL)
assert style_match, "<style> negasit in HTML"
style = style_match.group(1)
assert "#241a1a" not in style, "Fundalul hardcodat #241a1a (banner eroare) inca in <style>"
assert "#201c0f" not in style, "Fundalul hardcodat #201c0f (banner warn) inca in <style>"
assert "#16241c" not in style, "Fundalul hardcodat #16241c (flash) inca in <style>"
# ── US-002: Comutator tema + anti-FOUC ────────────────────────────────────────
def test_script_antifouc_in_head_inainte_de_style(client):
"""<head> contine un <script> care citeste localStorage (cheia 'theme') si seteaza
data-theme pe document.documentElement, pozitionat INAINTE de <style>."""
resp = client.get("/login")
assert resp.status_code == 200
html = resp.text
head_match = re.search(r'<head>(.*?)</head>', html, re.DOTALL | re.IGNORECASE)
assert head_match, "<head> negasit in HTML"
head = head_match.group(1)
style_pos = head.find('<style>')
assert style_pos >= 0, "<style> negasit in <head>"
head_before_style = head[:style_pos]
assert 'localStorage' in head_before_style, \
"Scriptul anti-FOUC (cu localStorage) trebuie sa fie in <head> INAINTE de <style>"
assert 'theme' in head_before_style, \
"Scriptul anti-FOUC trebuie sa citeasca cheia 'theme' din localStorage"
assert ('data-theme' in head_before_style or 'dataset.theme' in head_before_style), \
"Scriptul anti-FOUC trebuie sa seteze data-theme pe documentElement"
def test_buton_toggle_in_header_cu_eticheta(client):
"""<header> contine un <button> de comutare cu aria-label descriptiv (contine 'tema')."""
resp = client.get("/login")
assert resp.status_code == 200
html = resp.text
header_match = re.search(r'<header>(.*?)</header>', html, re.DOTALL | re.IGNORECASE)
assert header_match, "<header> negasit in HTML"
header = header_match.group(1)
labels = re.findall(r'<button[^>]*aria-label=["\']([^"\']+)["\']', header, re.IGNORECASE)
assert labels, "<button> cu aria-label negasit in <header>"
assert any('tema' in lbl.lower() for lbl in labels), \
f"Niciun <button> in <header> cu aria-label care contine 'tema'. Gasit: {labels}"
def test_toggle_pe_login_si_dashboard(client):
"""Butonul toggle apare atat pe /login (neautentificat) cat si pe dashboard (autentificat)."""
# Pe /login
resp = client.get("/login")
assert resp.status_code == 200
header_match = re.search(r'<header>(.*?)</header>', resp.text, re.DOTALL | re.IGNORECASE)
assert header_match, "<header> negasit pe /login"
assert re.search(
r'<button[^>]*aria-label=["\'][^"\']*tema[^"\']*["\']',
header_match.group(1),
re.IGNORECASE,
), "Butonul toggle lipseste pe /login"
# Pe dashboard (autentificat)
_create_user("tema_dash@test.com", "parolasecreta")
_login(client, "tema_dash@test.com", "parolasecreta")
resp = client.get("/")
assert resp.status_code == 200
header_match = re.search(r'<header>(.*?)</header>', resp.text, re.DOTALL | re.IGNORECASE)
assert header_match, "<header> negasit pe dashboard"
assert re.search(
r'<button[^>]*aria-label=["\'][^"\']*tema[^"\']*["\']',
header_match.group(1),
re.IGNORECASE,
), "Butonul toggle lipseste pe dashboard"
# ── US-003: Fragmente HTMX fara fundal hardcodat ──────────────────────────────
def test_fragmente_fara_fundal_hardcodat():
"""Niciun fisier _*.html din app/web/templates/ nu contine literalii hex dark-fix
#241a1a, #201c0f, #16241c (suprafete banner eroare / warn / flash)."""
templates_dir = Path(__file__).parent.parent / "app" / "web" / "templates"
fragmente = sorted(templates_dir.glob("_*.html"))
assert fragmente, f"Nu am gasit fragmente _*.html in {templates_dir}"
vinovate = []
for f in fragmente:
continut = f.read_text(encoding="utf-8")
for literal in ("#241a1a", "#201c0f", "#16241c"):
if literal in continut:
vinovate.append(f"{f.name}: {literal}")
assert not vinovate, (
"Fragmente cu fundal hardcodat dark (nu adapteaza la tema light):\n"
+ "\n".join(vinovate)
)