Spaces:
Running
Running
File size: 7,399 Bytes
eb547cb d109222 eb547cb 9011070 eb547cb 9011070 eb547cb 9011070 eb547cb 9011070 eb547cb ff7895c eb547cb 9011070 eb547cb 9011070 eb547cb 9011070 eb547cb e45d507 9011070 eb547cb | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | """Tests Sprint 30 — polish, accessibilité et DX.
Sprint 30 livre quatre durcissements transverses :
1. ``picarones/i18n.py`` : chargement thread-safe via verrou explicite,
``lru_cache`` sur ``get_labels``, ``reload_translations()`` exposé.
2. ``BaseOCREngine._safe_version()`` log la stacktrace en DEBUG au
lieu de swallow vers ``"unknown"`` silencieusement.
3. Badges CER WCAG : icône unicode + pattern de bordure + ``aria-label``
contextuel — la couleur n'est plus la seule info visuelle.
4. Pre-commit hooks : ``.pre-commit-config.yaml`` + section
``CONTRIBUTING.md``.
5. ``CHANGELOG.md`` rattrapé Sprints 10-30.
6. ``SPECS.md`` annexé d'un addendum couvrant Sprints 16-30.
"""
from __future__ import annotations
from pathlib import Path
ROOT = Path(__file__).parent.parent.parent
# ---------------------------------------------------------------------------
# 1. i18n thread-safe + lru_cache
# ---------------------------------------------------------------------------
class TestI18nCache:
def test_get_labels_returns_dict(self):
from picarones.reports.i18n import get_labels
labels = get_labels("fr")
assert isinstance(labels, dict)
assert len(labels) > 5
def test_get_labels_unknown_falls_back_to_fr(self):
from picarones.reports.i18n import get_labels
fr = get_labels("fr")
unknown = get_labels("xx-pas-existante")
# Le fallback doit être le contenu fr
assert unknown == fr
def test_get_labels_cached(self):
from picarones.reports import i18n
i18n.reload_translations()
# Premier appel — peuple le cache
i18n.get_labels("fr")
# Inspection : le cache lru a un hit
# (lru_cache expose cache_info() sur la fonction wrappée)
info_before = i18n._get_labels_cached.cache_info()
i18n.get_labels("fr")
info_after = i18n._get_labels_cached.cache_info()
assert info_after.hits > info_before.hits
def test_reload_translations_clears_cache(self):
from picarones.reports import i18n
i18n.get_labels("fr")
info_before = i18n._get_labels_cached.cache_info()
assert info_before.currsize >= 1
i18n.reload_translations()
info_after = i18n._get_labels_cached.cache_info()
assert info_after.currsize == 0
# Section retirée au sprint H.2.d : ``BaseOCREngine._safe_version()``
# n'existe plus (BaseOCREngine supprimé avec ``adapters/legacy_engines/``).
# Le contrat équivalent côté canonique (``BaseOCRAdapter`` n'a pas de
# ``version()``) est testé dans
# ``tests/app/test_sprint_h2b_canonical_in_runner.py`` via
# ``test_canonical_adapter_version_unknown``.
# ---------------------------------------------------------------------------
# 3. Badges CER WCAG (rapport HTML)
# ---------------------------------------------------------------------------
class TestBadgesAccessibility:
def test_app_js_exposes_tier_helpers(self):
path = ROOT / "picarones" / "reports" / "html" / "templates" / "_app.js"
src = path.read_text(encoding="utf-8")
for fn in ("cerTier", "cerTierIcon", "cerTierLabel"):
assert f"function {fn}" in src, (
f"_app.js doit exposer ``function {fn}`` (Sprint 30 a11y)"
)
def test_styles_define_tier_patterns(self):
path = ROOT / "picarones" / "reports" / "html" / "templates" / "_styles.css"
src = path.read_text(encoding="utf-8")
for tier in ("excellent", "acceptable", "mediocre", "critical"):
assert f'data-cer-tier="{tier}"' in src, (
f"_styles.css doit définir un pattern pour le tier {tier!r}"
)
# Au moins quatre styles de bordure différents
assert "border: 1.5px solid" in src
assert "border: 1.5px dashed" in src
assert "border: 1.5px dotted" in src
assert "border: 1.5px double" in src
def test_main_badge_carries_data_attr_and_aria(self):
path = ROOT / "picarones" / "reports" / "html" / "templates" / "_app.js"
src = path.read_text(encoding="utf-8")
assert "setAttribute('data-cer-tier'" in src
assert "setAttribute('aria-label'" in src
# ---------------------------------------------------------------------------
# 4. Pre-commit + CONTRIBUTING
# ---------------------------------------------------------------------------
class TestPreCommitInfra:
def test_pre_commit_config_exists(self):
path = ROOT / ".pre-commit-config.yaml"
assert path.exists()
text = path.read_text(encoding="utf-8")
# Doit référencer ruff (alignement avec le job CI ``lint``)
assert "ruff" in text.lower()
def test_pre_commit_yaml_is_well_formed(self):
import yaml
path = ROOT / ".pre-commit-config.yaml"
data = yaml.safe_load(path.read_text(encoding="utf-8"))
assert isinstance(data, dict)
assert "repos" in data
assert isinstance(data["repos"], list)
assert any(
"ruff" in (repo.get("repo") or "").lower()
for repo in data["repos"]
)
def test_contributing_documents_pre_commit(self):
path = ROOT / "CONTRIBUTING.md"
text = path.read_text(encoding="utf-8")
assert "pre-commit" in text.lower()
assert "pre-commit install" in text
# ---------------------------------------------------------------------------
# 5. Documentation rattrapée
# ---------------------------------------------------------------------------
class TestChangelogAndSpecsUpdated:
def test_changelog_mentions_recent_sprints(self):
text = (ROOT / "CHANGELOG.md").read_text(encoding="utf-8")
# Backport Sprints 10-22 et 23-30 doivent être mentionnés
for sprint in ("Sprint 11", "Sprint 17", "Sprint 19", "Sprint 22",
"Sprint 24", "Sprint 27", "Sprint 30"):
assert sprint in text, (
f"CHANGELOG.md doit mentionner {sprint} (Sprint 30 backport)"
)
def test_specs_addendum_present(self):
text = (ROOT / "SPECS.md").read_text(encoding="utf-8")
assert "Addendum" in text
# Au moins quatre des nouvelles fonctionnalités annexées
for keyword in ("narrative", "Pareto", "glossaire", "snapshots"):
assert keyword in text.lower() or keyword in text, (
f"SPECS.md addendum doit couvrir {keyword!r}"
)
# ---------------------------------------------------------------------------
# 6. Intégration : un rapport généré porte les attributs WCAG dans son JS
# ---------------------------------------------------------------------------
class TestGeneratedReportCarriesA11y:
def test_generated_html_embeds_tier_helpers(self, tmp_path):
from picarones.evaluation import synthetic as fixtures
from picarones.reports.html.generator import ReportGenerator
b = fixtures.generate_sample_benchmark(n_docs=4)
out = tmp_path / "rapport.html"
ReportGenerator(b, lang="fr").generate(out)
html = out.read_text(encoding="utf-8")
# Les fonctions JS doivent figurer dans le bundle inline
assert "cerTier" in html
assert "cerTierIcon" in html
# Les règles CSS pour les patterns aussi
assert 'data-cer-tier="excellent"' in html
|