"""Tests Sprint A6 — accessibilité WCAG niveau A bloquant.
Items B-9, B-10, m-3, m-4 de l'audit institutional-readiness-2026-05.
Ce fichier valide le **socle a11y bloquant** pour une déclaration de
conformité RGAA / WCAG 2.1 niveau A :
- WCAG 2.4.1 (Bypass Blocks) — skip-to-content link (B-10)
- WCAG 1.1.1 (Non-text Content) — Canvas charts → aria-label + table
jumelle accessible aux AT (B-9)
- WCAG 1.3.1 (Info and Relationships) — ``scope="col"`` sur les
``
`` (m-4)
- Pas de chaîne hardcodée FR/EN dans la nav (m-3)
Ces tests se contentent de vérifier la présence des marqueurs HTML
attendus dans le rapport généré. L'audit sémantique complet (NVDA /
JAWS / VoiceOver) reste manuel et tracé dans
``docs/audits/external-audits-2026/`` (Sprint A15).
"""
from __future__ import annotations
import re
import pytest
from picarones.evaluation.synthetic import generate_sample_benchmark
from picarones.reports.html.generator import ReportGenerator
@pytest.fixture(scope="module")
def demo_html(tmp_path_factory) -> str:
"""Rapport démo (FR) généré une fois pour tous les tests du module."""
out = tmp_path_factory.mktemp("a11y") / "report.html"
bench = generate_sample_benchmark(n_docs=4)
ReportGenerator(bench, lang="fr").generate(out)
return out.read_text(encoding="utf-8")
@pytest.fixture(scope="module")
def demo_html_en(tmp_path_factory) -> str:
"""Rapport démo (EN) — pour vérifier que les libellés a11y sont
bilingues."""
out = tmp_path_factory.mktemp("a11y_en") / "report_en.html"
bench = generate_sample_benchmark(n_docs=4)
ReportGenerator(bench, lang="en").generate(out)
return out.read_text(encoding="utf-8")
# ---------------------------------------------------------------------------
# B-10 — Skip-to-content (WCAG 2.4.1)
# ---------------------------------------------------------------------------
def test_skip_link_present(demo_html: str) -> None:
"""Un lien ``href="#main"`` avec class ``skip-link`` doit exister."""
assert 'class="skip-link"' in demo_html
assert 'href="#main"' in demo_html
def test_skip_link_first_focusable_in_body(demo_html: str) -> None:
"""Le skip-link doit être le **premier** élément focusable du body
(sinon Tab depuis l'URL bar atteint d'abord la nav, ce qui défait
le but)."""
body_start = demo_html.find("")
assert body_start > 0
body_part = demo_html[body_start : body_start + 1500]
skip_pos = body_part.find('class="skip-link"')
nav_pos = body_part.find("