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>
164 lines
5.9 KiB
Python
164 lines
5.9 KiB
Python
"""Teste US-014 (PRD 5.10): Selector de tema ciclic Light/Dark/Petrol/Auto.
|
|
|
|
TDD: testele se scriu INAINTE de implementare (RED), dupa implementare trec (GREEN).
|
|
|
|
Testeaza:
|
|
- test_petrol_theme_definit: [data-theme="petrol"] definit cu valorile din DESIGN.md
|
|
- test_buton_cicleaza_temele: buton ciclic + anti-FOUC extins + aria-live
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import re
|
|
import tempfile
|
|
|
|
import pytest
|
|
from starlette.testclient import TestClient
|
|
|
|
|
|
@pytest.fixture()
|
|
def client(monkeypatch):
|
|
tmp = tempfile.mkdtemp()
|
|
monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "selector.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 _get_style(html: str) -> str:
|
|
m = re.search(r"<style>(.*?)</style>", html, re.DOTALL)
|
|
assert m, "<style> negasit in HTML"
|
|
return m.group(1)
|
|
|
|
|
|
def _get_head(html: str) -> str:
|
|
m = re.search(r"<head>(.*?)</head>", html, re.DOTALL | re.IGNORECASE)
|
|
assert m, "<head> negasit in HTML"
|
|
return m.group(1)
|
|
|
|
|
|
# ── test_petrol_theme_definit ─────────────────────────────────────────────────
|
|
|
|
def test_petrol_theme_definit(client):
|
|
"""[data-theme="petrol"] definit in CSS cu valorile din DESIGN.md:
|
|
--bg:#0e1416, --card:#161e20, --ink:#e6e9ef, --muted:#8b93a7,
|
|
--line:#232c2e, --accent:#0E7C7B, --ok:#2FBF8F, --warn:#E0A93B, --err:#E05D5D
|
|
"""
|
|
resp = client.get("/login")
|
|
assert resp.status_code == 200
|
|
style = _get_style(resp.text)
|
|
|
|
# Bloc CSS [data-theme="petrol"] prezent
|
|
petrol_m = re.search(
|
|
r'\[data-theme=["\']petrol["\']\]\s*\{([^}]+)\}',
|
|
style,
|
|
re.DOTALL,
|
|
)
|
|
assert petrol_m, (
|
|
'[data-theme="petrol"] { ... } negasit in <style>. '
|
|
"Tema Petrol trebuie definita conform DESIGN.md."
|
|
)
|
|
petrol_block = petrol_m.group(1)
|
|
|
|
petrol_vars = {
|
|
"--accent": "#0E7C7B",
|
|
"--bg": "#0e1416",
|
|
"--card": "#161e20",
|
|
"--ink": "#e6e9ef",
|
|
"--line": "#232c2e",
|
|
"--ok": "#2FBF8F",
|
|
"--warn": "#E0A93B",
|
|
"--err": "#E05D5D",
|
|
}
|
|
for var, val in petrol_vars.items():
|
|
assert val.lower() in petrol_block.lower(), (
|
|
f"Variabila {var}:{val} lipseste din [data-theme=\"petrol\"]. "
|
|
f"Block petrol: {petrol_block.strip()}"
|
|
)
|
|
|
|
|
|
# ── test_buton_cicleaza_temele ────────────────────────────────────────────────
|
|
|
|
def test_buton_cicleaza_temele(client):
|
|
"""Butonul de tema cicleaza Light->Dark->Petrol->Auto si are accesibilitate completa.
|
|
|
|
Verifica:
|
|
1. Anti-FOUC extins: cunoaste 'petrol' si 'auto'; fallback definit pt. valori legacy
|
|
2. JS-ul ciclului contine toate cele 4 teme in ordinea corecta
|
|
3. aria-label pe buton include 'Tema:' + tema curenta + urmatoarea
|
|
4. Regiune aria-live="polite" prezenta pentru anuntarea schimbarii
|
|
"""
|
|
resp = client.get("/login")
|
|
assert resp.status_code == 200
|
|
html = resp.text
|
|
head = _get_head(html)
|
|
|
|
# 1. Anti-FOUC cunoaste 'petrol' si 'auto' (script in <head>, inainte de <style>)
|
|
style_pos = head.find('<style>')
|
|
assert style_pos >= 0, "<style> negasit in <head>"
|
|
head_before_style = head[:style_pos]
|
|
|
|
assert 'petrol' in head_before_style, (
|
|
"Scriptul anti-FOUC nu cunoaste tema 'petrol'. "
|
|
"Trebuie extins sa enumere toate cele 4 teme (light/dark/petrol/auto)."
|
|
)
|
|
assert 'auto' in head_before_style, (
|
|
"Scriptul anti-FOUC nu cunoaste tema 'auto'. "
|
|
"Trebuie sa rezolve 'auto' la light/dark inainte de primul paint."
|
|
)
|
|
|
|
# 2. Anti-FOUC are fallback pentru valori legacy/necunoscute (un set de valide)
|
|
# Acceptam: un obiect/array VALID, sau un if care verifica valorile cunoscute
|
|
has_valid_guard = (
|
|
'VALID' in head_before_style
|
|
or re.search(r'light.*dark.*petrol.*auto', head_before_style, re.DOTALL)
|
|
or 'indexOf' in head_before_style
|
|
)
|
|
assert has_valid_guard, (
|
|
"Anti-FOUC lipseste de un guard pentru valori legacy/necunoscute. "
|
|
"O valoare 'localStorage.theme' necunoscuta trebuie sa cada pe 'auto' sau 'dark'."
|
|
)
|
|
|
|
# 3. JS-ul din <body> contine ciclul complet Light->Dark->Petrol->Auto
|
|
# Cautam in tot HTML-ul (nu doar head) prezenta tuturor celor 4 teme in JS
|
|
# Acceptam: array explicit ['light','dark','petrol','auto'] sau logic echivalent
|
|
cycle_match = re.search(
|
|
r"['\"]light['\"].*['\"]dark['\"].*['\"]petrol['\"].*['\"]auto['\"]",
|
|
html,
|
|
re.DOTALL,
|
|
)
|
|
assert cycle_match, (
|
|
"Ciclul Light->Dark->Petrol->Auto negasit in JS. "
|
|
"Asteptat: array sau secventa cu toate cele 4 teme in ordine."
|
|
)
|
|
|
|
# 4. aria-label pe butonul tema-toggle include 'Tema:' (format 'Tema: X, apasa pentru Y')
|
|
tema_btn = re.search(
|
|
r'<button[^>]+id=["\']tema-toggle["\'][^>]*>',
|
|
html,
|
|
re.IGNORECASE,
|
|
)
|
|
assert tema_btn, "Butonul #tema-toggle negasit in HTML"
|
|
btn_tag = tema_btn.group(0)
|
|
# Acceptam aria-label setat initial in HTML SAU prin JS (aria-label din tag poate fi
|
|
# placeholder; testam ca JS-ul contine formatul corect)
|
|
has_tema_label = (
|
|
'Tema:' in html
|
|
or 'tema:' in html.lower()
|
|
or re.search(r'aria-label[^>]*[Tt]ema', html)
|
|
)
|
|
assert has_tema_label, (
|
|
"aria-label cu formatul 'Tema: ...' negasit in HTML/JS. "
|
|
"Butonul trebuie sa anunte tema curenta + urmatoarea."
|
|
)
|
|
|
|
# 5. Regiune aria-live="polite" prezenta (pentru anuntarea schimbarii de tema)
|
|
assert 'aria-live="polite"' in html or "aria-live='polite'" in html, (
|
|
'Regiune aria-live="polite" negasita in HTML. '
|
|
"Necesara pentru a anunta schimbarea temei catre screen-readers."
|
|
)
|