feat(5.12): modal editare + cont obligatoriu la import; design.md + PRD 5.13 revizuit (/autoplan)
5.12 (livrat): editare in modal a randurilor de preview, cont obligatoriu inainte de import, formular editare extras (_form_editare, _editare_preview_modal), plus suita de teste aferenta (preview edit/compact, mapare op, form editare, signup, admin panel). Design + planificare: - docs/design.md: sistem de design (tokeni, breakpoints, scara control, componente, a11y). - docs/prd/prd-5.12-* si prd-5.13-* (5.13 cu raport /autoplan: CEO+Design+Eng, audit trail). Curatare: sterse PNG-urile de test/mockup temporare din radacina. Nota: implementarea CSS 5.13 (responsive compact + sistem butoane) NU e inca facuta — planul revizuit cere refactorul testelor fragile din test_web_responsive.py INAINTE de CSS. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
165
tests/test_web_mapcoloane.py
Normal file
165
tests/test_web_mapcoloane.py
Normal file
@@ -0,0 +1,165 @@
|
||||
"""Teste US-003 — pasul „Potriveste coloanele" arata antet + prima inregistrare.
|
||||
|
||||
Acceptance criteria:
|
||||
- Deasupra/langa randurile de mapare, un mic tabel orizontal:
|
||||
cap de tabel = numele coloanelor din fisier,
|
||||
rand = valorile primei inregistrari (truncate; `title` pe valoarea integrala).
|
||||
- Foloseste .tablewrap pentru scroll orizontal pe mobil.
|
||||
- Fiecare coloana din cap ramane asociata vizual cu select-ul ei de mapare.
|
||||
- Fisier fara randuri de date -> doar capul de tabel + mesaj explicit
|
||||
„antet fara randuri de date" (D#11) + butonul „Continua" dezactivat.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def client(monkeypatch):
|
||||
tmp = tempfile.mkdtemp()
|
||||
monkeypatch.setenv("AUTOPASS_DB_PATH", os.path.join(tmp, "t.db"))
|
||||
monkeypatch.setenv("AUTOPASS_WEB_AUTH_REQUIRED", "false")
|
||||
from app.config import get_settings
|
||||
|
||||
get_settings.cache_clear()
|
||||
from app.main import app
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
with TestClient(app) as c:
|
||||
yield c
|
||||
get_settings.cache_clear()
|
||||
|
||||
|
||||
def _make_xlsx_bytes(rows: list[dict]) -> bytes:
|
||||
"""Construieste un xlsx minimal cu openpyxl."""
|
||||
openpyxl = pytest.importorskip("openpyxl")
|
||||
wb = openpyxl.Workbook()
|
||||
ws = wb.active
|
||||
if not rows:
|
||||
return b""
|
||||
headers = list(rows[0].keys())
|
||||
ws.append(headers)
|
||||
for row in rows:
|
||||
ws.append([row.get(h) for h in headers])
|
||||
buf = io.BytesIO()
|
||||
wb.save(buf)
|
||||
return buf.getvalue()
|
||||
|
||||
|
||||
def _make_xlsx_only_header(headers: list[str]) -> bytes:
|
||||
"""Construieste un xlsx cu doar antet (fara randuri de date)."""
|
||||
openpyxl = pytest.importorskip("openpyxl")
|
||||
wb = openpyxl.Workbook()
|
||||
ws = wb.active
|
||||
ws.append(headers)
|
||||
buf = io.BytesIO()
|
||||
wb.save(buf)
|
||||
return buf.getvalue()
|
||||
|
||||
|
||||
_COLOANE = ["VIN", "Nr inmatriculare", "Data prestatie", "Odometru final", "Operatie"]
|
||||
|
||||
_SAMPLE_ROWS = [
|
||||
{
|
||||
"VIN": "WVWZZZ1KZAW000123",
|
||||
"Nr inmatriculare": "B001TST",
|
||||
"Data prestatie": "15.06.2026",
|
||||
"Odometru final": "123456",
|
||||
"Operatie": "Revizie",
|
||||
},
|
||||
{
|
||||
"VIN": "WVWZZZ1KZAW000456",
|
||||
"Nr inmatriculare": "B002TST",
|
||||
"Data prestatie": "16.06.2026",
|
||||
"Odometru final": "200000",
|
||||
"Operatie": "Revizie",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def _upload_csv_and_get_mapare(client, rows=None, headers=None) -> tuple[int, str]:
|
||||
"""Incarca un xlsx si intoarce (import_id, html_text) din pasul de mapare coloane."""
|
||||
if headers is not None:
|
||||
# Fisier cu doar antet
|
||||
xlsx = _make_xlsx_only_header(headers)
|
||||
else:
|
||||
xlsx = _make_xlsx_bytes(rows or _SAMPLE_ROWS)
|
||||
r = client.post(
|
||||
"/_import/upload",
|
||||
files={"file": ("test.xlsx", xlsx, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")},
|
||||
)
|
||||
return r.status_code, r.text
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# test_mapcoloane_arata_cap_tabel_coloane #
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
def test_mapcoloane_arata_cap_tabel_coloane(client):
|
||||
"""Pasul de mapare coloane afiseaza un tabel orizontal cu antetul fisierului.
|
||||
|
||||
Fiecare coloana din fisier trebuie sa apara ca <th> in capul tabelului.
|
||||
Tabelul trebuie inconjurat de .tablewrap pentru scroll orizontal.
|
||||
"""
|
||||
status, html = _upload_csv_and_get_mapare(client)
|
||||
assert status == 200
|
||||
# Tabelul de preview al antetului trebuie sa fie prezent
|
||||
assert "tablewrap" in html, "Trebuie .tablewrap pentru scroll orizontal"
|
||||
# Fiecare coloana din fisier trebuie sa apara ca <th> in tabel
|
||||
for col in _COLOANE:
|
||||
assert f"<th" in html, "Trebuie elemente <th> in capul tabelului"
|
||||
assert col in html, f"Coloana '{col}' trebuie sa apara in antetul tabelului"
|
||||
# Tabelul de preview (cu class sau id distinct pentru antet preview)
|
||||
assert "preview-antet" in html, "Tabelul de preview trebuie sa aiba class/id 'preview-antet'"
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# test_mapcoloane_arata_valori_prima_inregistrare #
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
def test_mapcoloane_arata_valori_prima_inregistrare(client):
|
||||
"""Pasul de mapare coloane afiseaza valorile primei inregistrari din fisier.
|
||||
|
||||
Valorile din primul rand de date trebuie sa apara in tabel.
|
||||
"""
|
||||
status, html = _upload_csv_and_get_mapare(client, rows=_SAMPLE_ROWS)
|
||||
assert status == 200
|
||||
# Valorile primei inregistrari trebuie sa apara
|
||||
assert "WVWZZZ1KZAW000123" in html, "VIN-ul primei inregistrari trebuie sa apara"
|
||||
assert "B001TST" in html, "Nr inmatriculare al primei inregistrari trebuie sa apara"
|
||||
assert "15.06.2026" in html, "Data prestatiei primei inregistrari trebuie sa apara"
|
||||
assert "123456" in html, "Odometrul primei inregistrari trebuie sa apara"
|
||||
# Valorile celui de-al doilea rand NU trebuie sa apara ca rand de date (numai primul rand)
|
||||
# (Nota: pot aparea ca exemple in alt context, dar randul de date e primul)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# test_mapcoloane_fara_randuri_degradeaza #
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
def test_mapcoloane_fara_randuri_degradeaza(client):
|
||||
"""Fisier xlsx cu antet dar fara randuri de date → degradare grijulie.
|
||||
|
||||
Trebuie sa:
|
||||
- Nu crape (status 200)
|
||||
- Arate capul de tabel cu coloanele
|
||||
- Arate mesajul explicit 'antet fara randuri de date'
|
||||
- Dezactiveze butonul 'Salveaza si continua' (disabled)
|
||||
"""
|
||||
status, html = _upload_csv_and_get_mapare(client, headers=_COLOANE)
|
||||
assert status == 200, f"Status neasteptat: {status}"
|
||||
# Nu trebuie sa crape — formular de mapare afisat
|
||||
assert "Mapare coloane" in html, "Formularul de mapare trebuie afisat"
|
||||
# Capul de tabel cu coloanele trebuie afisat
|
||||
for col in _COLOANE:
|
||||
assert col in html, f"Coloana '{col}' trebuie sa apara in antet chiar si fara date"
|
||||
# Mesaj explicit despre lipsa datelor
|
||||
assert "antet fara randuri de date" in html.lower() or "fara randuri de date" in html.lower(), \
|
||||
"Trebuie mesaj explicit despre lipsa randurilor de date"
|
||||
# Butonul Continua trebuie dezactivat
|
||||
assert "disabled" in html, "Butonul 'Salveaza si continua' trebuie dezactivat cand nu sunt randuri"
|
||||
Reference in New Issue
Block a user