feat(gui): câbler les 7 toggles catégories au moteur (P1-2)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-26 11:26:47 +02:00
parent daec1f53bd
commit bf832e12f0
5 changed files with 228 additions and 14 deletions

View File

@@ -0,0 +1,121 @@
"""Tests du câblage des 7 toggles « Données à détecter » → moteur (Plan 1b / P1-2).
Sémantique UI : un toggle ON = « détecter cette catégorie » (= masquer).
Un toggle OFF = la catégorie est laissée en clair → elle entre dans
``disabled_kinds`` (set des CATÉGORIES désactivées passé au moteur).
Aucun widget, aucun display : on teste l'état (ConfigState) et le pont
(build_engine_kwargs / make_process_fn) en pur Python.
"""
from __future__ import annotations
from pathlib import Path
from gui_v6.config_state import (
CATEGORY_FIELDS,
DETECTION_CATEGORIES,
ConfigState,
)
from gui_v6.engine_bridge import (
EngineSettings,
NerManagers,
build_engine_kwargs,
make_process_fn,
)
# -- catégories canoniques -------------------------------------------------
def test_seven_categories_match_engine_set():
# Les 7 catégories exposées doivent matcher EXACTEMENT le set moteur.
assert set(DETECTION_CATEGORIES) == {
"NOM",
"DATE_NAISSANCE",
"ETAB",
"ADRESSE",
"NIR",
"TEL",
"ADHERENT",
}
# Un champ booléen par catégorie.
assert set(CATEGORY_FIELDS.values()) == set(DETECTION_CATEGORIES)
# -- disabled_kinds dérivé -------------------------------------------------
def test_disabled_kinds_empty_by_default():
# Défaut : tous les toggles ON ⇒ aucun désactivé (zéro changement vs aujourd'hui).
state = ConfigState()
assert state.disabled_kinds() == frozenset()
def test_disabled_kinds_unchecking_nir_and_etab():
# Décocher « N° sécurité sociale » (NIR) et « Établissements » (ETAB).
state = ConfigState(detect_nir=False, detect_etab=False)
assert state.disabled_kinds() == frozenset({"NIR", "ETAB"})
def test_disabled_kinds_all_off():
state = ConfigState(
detect_nom=False,
detect_date_naissance=False,
detect_etab=False,
detect_adresse=False,
detect_nir=False,
detect_tel=False,
detect_adherent=False,
)
assert state.disabled_kinds() == frozenset(DETECTION_CATEGORIES)
# -- propagation vers EngineSettings --------------------------------------
def test_to_engine_settings_propagates_disabled_kinds():
state = ConfigState(detect_nir=False, detect_tel=False)
settings = state.to_engine_settings()
assert settings.disabled_kinds == frozenset({"NIR", "TEL"})
def test_to_engine_settings_default_empty():
settings = ConfigState().to_engine_settings()
assert settings.disabled_kinds == frozenset()
# -- propagation dans les kwargs moteur -----------------------------------
def test_build_engine_kwargs_includes_disabled_kinds():
settings = EngineSettings(disabled_kinds=frozenset({"NIR", "ETAB"}))
kwargs = build_engine_kwargs(settings, managers=None)
assert kwargs["disabled_kinds"] == frozenset({"NIR", "ETAB"})
def test_build_engine_kwargs_default_empty_disabled_kinds():
# Défaut (set vide) = no-op : la clé est présente mais vide.
kwargs = build_engine_kwargs(EngineSettings(), managers=None)
assert kwargs["disabled_kinds"] == frozenset()
def test_process_fn_threads_disabled_kinds_to_engine(tmp_path):
settings = EngineSettings(
use_local_ner=False, disabled_kinds=frozenset({"ADRESSE"})
)
managers = NerManagers(settings)
captured = {}
def fake_engine(doc_path, out_dir, **kwargs):
captured["kwargs"] = kwargs
return {"status": "ok"}
fn = make_process_fn(settings, managers=managers, engine=fake_engine)
fn(tmp_path / "doc.pdf", tmp_path / "out")
assert captured["kwargs"]["disabled_kinds"] == frozenset({"ADRESSE"})
# -- bout-en-bout : ConfigState → settings → kwargs -----------------------
def test_end_to_end_state_to_kwargs(tmp_path):
state = ConfigState(detect_adherent=False)
settings = state.to_engine_settings(config_path=Path("/tmp/c.yml"))
kwargs = build_engine_kwargs(settings, managers=None)
assert kwargs["disabled_kinds"] == frozenset({"ADHERENT"})

View File

@@ -55,10 +55,26 @@ def test_config_interaction_contract_prebuilds_panels_and_mask_editor():
]
def test_detection_options_fields_match_category_fields():
"""Garde-fou anti-dérive : les champs déclarés dans _DETECTION_OPTIONS doivent
rester alignés (mêmes champs ET même ordre) sur CATEGORY_FIELDS, sinon un
toggle pointerait vers un attribut ConfigState inexistant (AttributeError au
lancement de la GUI au lieu d'un échec de test)."""
from gui_v6.config_state import CATEGORY_FIELDS, ConfigState
fields = [field for _l, _h, field in _DETECTION_OPTIONS]
assert fields == list(CATEGORY_FIELDS) # mêmes champs ET même ordre (ordre UI = ordre catégories)
for f in fields: # chacun est bien un booléen réel de ConfigState
assert isinstance(getattr(ConfigState(), f), bool)
def test_detection_rows_are_readable_in_light_theme():
"""Retour Dom : les sous-labels de la colonne détection doivent rester lisibles."""
assert ("Noms et prénoms", "Annuaire + IA") in _DETECTION_OPTIONS
assert ("Noms et prénoms", "Bases de données + IA") not in _DETECTION_OPTIONS
# Chaque ligne est désormais (libellé, aide, champ ConfigState) ; on ne
# vérifie ici que le couple (libellé, aide) reste lisible.
label_hint = [(label, hint) for label, hint, _field in _DETECTION_OPTIONS]
assert ("Noms et prénoms", "Annuaire + IA") in label_hint
assert ("Noms et prénoms", "Bases de données + IA") not in label_hint
assert MINI_TOGGLE_HEIGHT >= 44
assert MINI_TOGGLE_LABEL_FONT_SIZE >= 12
assert MINI_TOGGLE_HINT_FONT_SIZE >= 11