"""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(" 0 and nav_pos > 0 assert skip_pos < nav_pos, ( "Le skip-link doit précéder le