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>
This commit is contained in:
175
tests/test_web_header_branding.py
Normal file
175
tests/test_web_header_branding.py
Normal file
@@ -0,0 +1,175 @@
|
||||
"""Teste US-012 / US-012b (PRD 5.10): Header logo ROMFAST + titlu centrat.
|
||||
|
||||
TDD: testele se scriu INAINTE de implementare (RED), dupa implementare trec (GREEN).
|
||||
|
||||
US-012b (decizie user): logo-ul PNG real (/static/romfast_logo.png) in loc de wordmark text.
|
||||
|
||||
Testeaza:
|
||||
- test_header_contine_by_romfast: <img src="/static/romfast_logo.png" alt="ROMFAST" class="brand-logo">
|
||||
- test_titlu_centrat: titlul e in structura centrata (grila 3 coloane), controale la dreapta
|
||||
"""
|
||||
|
||||
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, "branding.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_header(html: str) -> str:
|
||||
"""Extrage continutul elementului <header>."""
|
||||
m = re.search(r"<header>(.*?)</header>", html, re.DOTALL | re.IGNORECASE)
|
||||
assert m, "<header> negasit in HTML"
|
||||
return m.group(1)
|
||||
|
||||
|
||||
def _get_style(html: str) -> str:
|
||||
"""Extrage continutul primului <style>."""
|
||||
m = re.search(r"<style>(.*?)</style>", html, re.DOTALL)
|
||||
assert m, "<style> negasit in HTML"
|
||||
return m.group(1)
|
||||
|
||||
|
||||
# ── test_header_contine_by_romfast ────────────────────────────────────────────
|
||||
|
||||
def test_header_contine_by_romfast(client):
|
||||
"""Header contine logo-ul ROMFAST ca <img> (US-012b: decizie user — PNG real).
|
||||
|
||||
Verifica:
|
||||
- <img src="/static/romfast_logo.png"> prezent in header
|
||||
- Atribut alt non-gol (ex. alt="ROMFAST") pentru accesibilitate
|
||||
- Imaginea are clasa brand-logo (pentru stilizare CSS)
|
||||
- NU mai exista spanurile text .romfast-rom / .romfast-fast (wordmark text inlocuit)
|
||||
"""
|
||||
resp = client.get("/login")
|
||||
assert resp.status_code == 200
|
||||
header = _get_header(resp.text)
|
||||
|
||||
# Gaseste toate tag-urile <img> din header si cauta logo-ul
|
||||
img_tags = re.findall(r'<img[^>]+>', header, re.IGNORECASE)
|
||||
logo_tag = next(
|
||||
(t for t in img_tags if "romfast_logo.png" in t),
|
||||
None,
|
||||
)
|
||||
|
||||
# 1. <img> cu src="/static/romfast_logo.png" prezent in header
|
||||
assert logo_tag is not None, (
|
||||
"<img> cu 'romfast_logo.png' negasit in header. "
|
||||
"Decizie user (US-012b): logo-ul PNG real trebuie sa apara in header. "
|
||||
f"Header: {header[:500]}"
|
||||
)
|
||||
|
||||
# 2. Atribut alt non-gol pe imaginea logo-ului (accesibilitate)
|
||||
alt_match = re.search(r'alt=["\']([^"\']+)["\']', logo_tag, re.IGNORECASE)
|
||||
assert alt_match and alt_match.group(1).strip(), (
|
||||
"Imaginea logo lipseste atributul alt (sau e gol). "
|
||||
f"Tag gasit: {logo_tag}"
|
||||
)
|
||||
|
||||
# 3. Clasa brand-logo aplicata (pentru controlul inaltimii CSS)
|
||||
assert "brand-logo" in logo_tag, (
|
||||
"class='brand-logo' lipseste de pe <img> logo. "
|
||||
f"Tag gasit: {logo_tag}"
|
||||
)
|
||||
|
||||
# 4. Spanurile text (wordmark vechi .romfast-rom / .romfast-fast) NU mai exista
|
||||
assert "romfast-rom" not in header and "romfast-fast" not in header, (
|
||||
"Clasele .romfast-rom / .romfast-fast (wordmark text) inca prezente in header. "
|
||||
"Trebuie inlocuite complet de <img> logo. "
|
||||
f"Header snippet: {header[:500]}"
|
||||
)
|
||||
|
||||
|
||||
# ── test_titlu_centrat ────────────────────────────────────────────────────────
|
||||
|
||||
def test_titlu_centrat(client):
|
||||
"""Titlul 'Gateway RAR AUTOPASS' e in structura centrata in header (grila 3 coloane).
|
||||
|
||||
Verifica:
|
||||
- CSS contine grid-template-columns cu 3 coloane pe header (1fr auto 1fr sau similar)
|
||||
- Header contine un element cu clasa 'header-center' (sau similar) care contine h1
|
||||
- Controalele (button tema-toggle) sunt la dreapta (in header-right sau margin-left:auto)
|
||||
- Badge-ul env e in grila (header-left sau similar), nu flotant
|
||||
"""
|
||||
resp = client.get("/login")
|
||||
assert resp.status_code == 200
|
||||
html = resp.text
|
||||
header = _get_header(html)
|
||||
style = _get_style(html)
|
||||
|
||||
# 1. CSS header foloseste grid cu 3 coloane
|
||||
header_css = re.search(r"header\s*\{([^}]+)\}", style, re.DOTALL)
|
||||
assert header_css, "Regula CSS 'header { ... }' negasita in <style>"
|
||||
header_block = header_css.group(1)
|
||||
assert "grid" in header_block.lower(), (
|
||||
f"header CSS nu foloseste grid (display:grid). Block: {header_block.strip()}"
|
||||
)
|
||||
assert "grid-template-columns" in style, (
|
||||
"grid-template-columns lipseste din <style> (necesar pentru 3 coloane)"
|
||||
)
|
||||
|
||||
# 2. Element centrat in header contine h1
|
||||
center_div = re.search(
|
||||
r'<div[^>]+class=["\'][^"\']*header-center[^"\']*["\'][^>]*>(.*?)</div>',
|
||||
header,
|
||||
re.DOTALL,
|
||||
)
|
||||
assert center_div, (
|
||||
"Element cu clasa 'header-center' negasit in <header>. "
|
||||
"Titlul trebuie sa fie intr-o celula centrata a grilei. "
|
||||
f"Header snippet: {header[:600]}"
|
||||
)
|
||||
center_content = center_div.group(1)
|
||||
assert "<h1" in center_content, (
|
||||
"h1 nu e in interiorul elementului .header-center. "
|
||||
f"Continut .header-center: {center_content[:300]}"
|
||||
)
|
||||
|
||||
# 3. Controalele (butonul de tema) sunt in header-right
|
||||
right_div = re.search(
|
||||
r'<div[^>]+class=["\'][^"\']*header-right[^"\']*["\'][^>]*>(.*?)</div>',
|
||||
header,
|
||||
re.DOTALL,
|
||||
)
|
||||
assert right_div, (
|
||||
"Element cu clasa 'header-right' negasit in <header>. "
|
||||
"Controalele (tema, versiune, meniu) trebuie sa fie la dreapta. "
|
||||
f"Header snippet: {header[:600]}"
|
||||
)
|
||||
right_content = right_div.group(1)
|
||||
assert "tema-toggle" in right_content, (
|
||||
"Butonul tema-toggle nu e in .header-right. "
|
||||
f"Continut .header-right: {right_content[:300]}"
|
||||
)
|
||||
|
||||
# 4. Badge-ul env e in header-left (nu mai e aruncat dupa h1)
|
||||
left_div = re.search(
|
||||
r'<div[^>]+class=["\'][^"\']*header-left[^"\']*["\'][^>]*>(.*?)</div>',
|
||||
header,
|
||||
re.DOTALL,
|
||||
)
|
||||
assert left_div, (
|
||||
"Element cu clasa 'header-left' negasit in <header>. "
|
||||
"Badge-ul env trebuie sa fie in celula stanga a grilei (echilibru optic). "
|
||||
f"Header snippet: {header[:600]}"
|
||||
)
|
||||
left_content = left_div.group(1)
|
||||
assert 'class="env"' in left_content or "class='env'" in left_content, (
|
||||
"Badge-ul .env nu e in .header-left. "
|
||||
f"Continut .header-left: {left_content[:200]}"
|
||||
)
|
||||
Reference in New Issue
Block a user