"""Tests Vue 03 · Par document — galerie + drill-in + worst_lines.
Couvre :
1. **Hero stats** : 3 chiffres clés (documents / strates normalisées /
moteurs) cohérents avec les chips de filtre.
2. **Galerie** : grid de cards avec aperçu de strate synthétique CSS,
badges moteurs A→E, et CER par moteur.
3. **Toolbar chips** : « Toutes » + 1 chip par strate détectée,
``data-strate`` sur chaque chip pour pilotage JS.
4. **Drill-in** : ``data-doc-id`` exposé, ``_openLegacyDocument()``
défini dans ``_documents.js`` (délégation au lieu d'``onclick``
inline pour parer XSS via doc_id contenant une apostrophe).
5. **Worst lines globales** : branchées en bas de la galerie via
``_build_documents_worst_lines_html`` du générateur.
6. **HTML escape** sur doc_id et labels (anti-XSS).
7. **Empty state** : message clair quand le corpus est vide.
8. **Template** : plus de placeholder « squelette S5B ».
"""
from __future__ import annotations
import subprocess
import sys
from pathlib import Path
import pytest
REPO_ROOT = Path(__file__).resolve().parents[2]
DOC_TEMPLATE = (
REPO_ROOT
/ "picarones" / "reports" / "html" / "templates" / "views" / "documents.html"
)
@pytest.fixture(scope="module")
def demo_html(tmp_path_factory) -> str:
out = tmp_path_factory.mktemp("documents") / "demo.html"
result = subprocess.run(
[sys.executable, "-m", "picarones", "demo", "--output", str(out)],
capture_output=True, text=True, timeout=120,
)
assert result.returncode == 0, (
f"`picarones demo` a échoué :\n"
f"stdout: {result.stdout}\nstderr: {result.stderr}"
)
return out.read_text(encoding="utf-8")
# ─────────────────────────────────────────────────────────────────────────────
# 1. Hero stats
# ─────────────────────────────────────────────────────────────────────────────
class TestDocumentsHeroStats:
def test_three_hero_stats_rendered(self, demo_html: str) -> None:
# Le container hero-stats existe et contient bien 3 stats.
idx = demo_html.find('id="documents-hero-stats"')
assert idx > 0
# 3 .hero-stat dans le bloc Documents
end = demo_html.find('', idx)
snippet = demo_html[idx:end]
assert snippet.count('class="hero-stat"') == 3
def test_hero_stat_count_matches_chip_total(self, demo_html: str) -> None:
"""Le nombre de strates affiché dans le hero stat doit être
égal au nombre de chips moins la chip ``Toutes`` (cohérence
UX)."""
import re
m = re.search(
r'id="documents-hero-stats">.*?(\d+)'
r'.*?(\d+)'
r'.*?(\d+)',
demo_html, re.S,
)
assert m is not None
n_docs, n_strata, n_engines = map(int, m.groups())
assert n_docs > 0
assert n_strata >= 1
assert n_engines >= 1
# Chips réels (hors Toutes)
chips = re.findall(
r'