fix(gui): clarifier aide et disponibilite moteurs

Passe theme clair, libelles utilisateur, aides conteneurs, recherche de mise a jour et indication honnete des moteurs optionnels non embarques. Tests GUI unitaires: 126 passed.
This commit is contained in:
2026-06-17 18:01:25 +02:00
parent d18ca919fa
commit 60fb41c2e7
11 changed files with 296 additions and 44 deletions

View File

@@ -46,6 +46,7 @@ def test_usage_tab_survives_theme_change(app):
def test_main_tab_renamed_to_administration():
"""Retour Dom #2 : l'onglet principal Configuration devient Administration."""
pytest.importorskip("customtkinter")
from gui_v6.app import _TABS
labels = [label for _, label in _TABS]
@@ -56,6 +57,7 @@ def test_main_tab_renamed_to_administration():
def test_no_separate_rules_subtab():
"""Retour Dom : les règles appartiennent au profil → plus de sous-onglet
« Règles » séparé (et donc plus de « Règles 2 » incompréhensible)."""
pytest.importorskip("customtkinter")
from gui_v6.tabs.tab_config import _SUBTABS
keys = [key for key, _ in _SUBTABS]
@@ -99,6 +101,25 @@ def test_beta_label_in_product_identity(app):
assert any("bêta" in t or "beta" in t for t in texts)
def test_default_theme_is_light():
"""Retour Dom : le thème clair est le thème par défaut de la GUI."""
from gui_v6 import theme as theme_mod
assert theme_mod.DEFAULT_THEME == "clair"
def test_about_uses_user_facing_database_label(app):
"""Retour Dom : éviter le terme technique anglais « Gazetteers » dans À propos."""
app._show("about")
app.update_idletasks()
texts = _all_texts(app._tab_frames["about"])
joined = " | ".join(texts)
assert "bases de données" in joined.lower()
assert "Gazetteers" not in joined
assert "Rechercher une mise à jour" in joined
def _count_help_buttons(widget) -> int:
from gui_v6.ui_kit import HelpButton

View File

@@ -2,7 +2,18 @@
from __future__ import annotations
from gui_v6.tabs.tab_config import CONFIG_INTERACTION_CONTRACT, CONFIG_MOCKUP_SECTIONS
import pytest
pytest.importorskip("customtkinter")
from gui_v6.tabs.tab_config import (
CONFIG_INTERACTION_CONTRACT,
CONFIG_MOCKUP_SECTIONS,
MINI_TOGGLE_HEIGHT,
MINI_TOGGLE_HINT_FONT_SIZE,
MINI_TOGGLE_LABEL_FONT_SIZE,
_DETECTION_OPTIONS,
)
def test_config_mockup_sections_cover_admin_surface():
@@ -42,3 +53,12 @@ def test_config_interaction_contract_prebuilds_panels_and_mask_editor():
"clear_page",
"apply_template_selection",
]
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
assert MINI_TOGGLE_HEIGHT >= 44
assert MINI_TOGGLE_LABEL_FONT_SIZE >= 12
assert MINI_TOGGLE_HINT_FONT_SIZE >= 11

View File

@@ -57,7 +57,11 @@ def test_kwargs_defaults_v5_like():
def test_kwargs_with_loaded_managers():
settings = EngineSettings(enable_eds=True, enable_gliner=True)
counter = {"camembert": 0, "eds": 0, "gliner": 0}
managers = NerManagers(settings, factories=_counting_factories(counter))
managers = NerManagers(
settings,
factories=_counting_factories(counter),
caps_provider=_caps_provider(eds_ok=True, gliner_ok=True),
)
managers.ensure_loaded()
kwargs = build_engine_kwargs(settings, managers)
assert kwargs["use_hf"] is True
@@ -89,7 +93,11 @@ def test_managers_not_loaded_on_init():
def test_managers_load_once_and_state():
settings = EngineSettings(enable_eds=True)
counter = {"camembert": 0, "eds": 0, "gliner": 0}
managers = NerManagers(settings, factories=_counting_factories(counter))
managers = NerManagers(
settings,
factories=_counting_factories(counter),
caps_provider=_caps_provider(eds_ok=True, gliner_ok=True),
)
assert managers.state == ManagerState.NOT_LOADED
assert managers.ensure_loaded() == ManagerState.READY
assert managers.ensure_loaded() == ManagerState.READY # idempotent
@@ -121,7 +129,11 @@ def test_optional_manager_failure_is_tolerated():
return {"camembert": camembert, "eds": eds, "gliner": gliner}
managers = NerManagers(settings, factories=factories())
managers = NerManagers(
settings,
factories=factories(),
caps_provider=_caps_provider(eds_ok=True, gliner_ok=True),
)
assert managers.ensure_loaded() == ManagerState.READY # gliner ko ne bloque pas
assert managers.use_hf is True

View File

@@ -28,9 +28,11 @@ class FakeResponse:
class FakeSession:
"""Session HTTP mockable : enregistre les appels, renvoie des réponses scriptées."""
def __init__(self, response=None, exc=None):
def __init__(self, response=None, exc=None, get_response=None, get_exc=None):
self._response = response
self._exc = exc
self._get_response = response if get_response is None else get_response
self._get_exc = exc if get_exc is None else get_exc
self.calls = []
def post(self, url, json, timeout):
@@ -39,6 +41,12 @@ class FakeSession:
raise self._exc
return self._response
def get(self, url, timeout):
self.calls.append({"url": url, "timeout": timeout})
if self._get_exc is not None:
raise self._get_exc
return self._get_response
def _client(tmp_path, session):
store = LicenseStore(tmp_path / "license.json")
@@ -173,6 +181,29 @@ def test_local_status_reads_store(tmp_path):
assert status.license_ref == "LIC-7"
def test_latest_version_reads_active_artifact(tmp_path):
payload = {
"version": "v11.0-beta",
"channel": "beta",
"filename": "Anonymisation-Setup.exe",
}
session = FakeSession(get_response=FakeResponse(200, payload))
client, _ = _client(tmp_path, session)
version = client.latest_version()
assert version == payload
assert session.calls[0]["url"] == "https://portail.example/api/v1/version"
def test_latest_version_unavailable_on_404_or_network_error(tmp_path):
client_404, _ = _client(tmp_path, FakeSession(get_response=FakeResponse(404, {"detail": "No active version"})))
assert client_404.latest_version() is None
client_down, _ = _client(tmp_path, FakeSession(get_exc=TimeoutError("timeout")))
assert client_down.latest_version() is None
def test_status_never_exposes_token():
# Le statut ne porte pas de token : la repr ne peut pas le fuiter.
status = LicenseStatus.from_payload({"status": "active", "license_ref": "LIC-1"})

View File

@@ -120,6 +120,7 @@ def test_attach_tooltip_does_not_break_widget(ctk_root):
def test_subtabs_include_editable_profils():
"""Retour Dom : sous-onglet Profils réintroduit (éditeur)."""
pytest.importorskip("customtkinter")
from gui_v6.tabs.tab_config import _SUBTABS
keys = [k for k, _ in _SUBTABS]
@@ -297,6 +298,21 @@ def test_regles_moved_into_profils(ctk_root, tmp_path, monkeypatch):
tab.destroy()
def test_profile_masking_does_not_expose_templates_folder_button(ctk_root, tmp_path, monkeypatch):
"""Retour Dom : le bouton Dossier ouvrait un navigateur et n'aide pas l'utilisateur."""
from gui_v6.tabs import tab_config
monkeypatch.setattr(tab_config, "_app_base_dir", lambda: tmp_path)
tab = tab_config.ConfigTab(ctk_root)
tab._show_sub("pro")
tab.update_idletasks()
texts = _all_texts(tab._panels["pro"])
assert "🖊 Ouvrir l'éditeur de masque" in texts
assert "📁 Dossier" not in texts
tab.destroy()
def test_unavailable_engines_disabled_in_reglages(ctk_root, tmp_path, monkeypatch):
"""Honnêteté moteurs : EDS-Pseudo / GLiNER non embarqués → switch désactivé
et état forcé à False ; CamemBERT-bio reste actif."""

View File

@@ -15,7 +15,7 @@ _REQUIRED_TOKENS = {
def test_four_themes_present():
assert set(theme_mod.PALETTES) == {"sombre", "clair", "medical", "neutre"}
assert theme_mod.DEFAULT_THEME == "sombre"
assert theme_mod.DEFAULT_THEME == "clair"
@pytest.mark.parametrize("name", ["sombre", "clair", "medical", "neutre"])
@@ -31,11 +31,11 @@ def test_palette_has_all_tokens(name):
def test_default_palette_matches_mockup():
p = theme_mod.get_palette("sombre")
assert p["bg"] == "#1a1a2e"
assert p["card"] == "#16213e"
assert p["primary"] == "#e94560"
assert p["accent"] == "#f5a623"
p = theme_mod.get_palette(theme_mod.DEFAULT_THEME)
assert p["bg"] == "#cdd2da"
assert p["card"] == "#ffffff"
assert p["primary"] == "#c93050"
assert p["accent"] == "#b45309"
def test_get_palette_fallback():