feat(gui): onglet Profils éditable (création/modification/persistance)
Retour Dom : remplacer la page vitrine par un vrai éditeur de profils. - gui_v6/profile_editor.py : couche logique (build_profile_spec, profile_is_editable runtime vs defaut, list_profile_choices, slug_for_copy, save/set_default/delete) au-dessus de profile_defaults — persistance dans config/profiles.yml. - gui_v6/editable_list.py : EditableTermList (tableau scrollable de termes, ajout/suppression, pas de pastilles) — reste lisible à 50+ termes. - tab_config : sous-onglet « 👤 Profils » réintroduit comme éditeur — menu déroulant « Profil à modifier », boutons Nouveau / Dupliquer / Enregistrer / Annuler / Définir par défaut, sections Identité, Masquage (require_manual_mask, template), Moteurs (force_disable_vlm), Mots (à masquer/conserver/ignorer éditables), Règles « à venir ». Profils défaut = lecture seule (dupliquer pour modifier). Confirmation non bloquante (pas de modale). - Réglages : bouton « ✏️ Modifier le profil… » → ouvre Profils sur le profil actif. Pas de pastilles inline. Persiste : label, description, require_manual_mask, force_disable_vlm, preferred_manual_mask_template, param_lists (3 listes). 260 tests unit OK (0 régression), self-test OK, nav 5 sous-onglets + thème OK. Préserve 1bbe70a/d30f7b7. Aucun build/push sans GO Dom. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -118,14 +118,78 @@ def test_attach_tooltip_does_not_break_widget(ctk_root):
|
||||
assert lbl.winfo_exists()
|
||||
|
||||
|
||||
def test_subtabs_no_profils_subtab():
|
||||
"""Retour Dom : le sous-onglet Profils (doublon non câblé) est retiré."""
|
||||
def test_subtabs_include_editable_profils():
|
||||
"""Retour Dom : sous-onglet Profils réintroduit (éditeur)."""
|
||||
from gui_v6.tabs.tab_config import _SUBTABS
|
||||
|
||||
keys = [k for k, _ in _SUBTABS]
|
||||
labels = [lbl for _, lbl in _SUBTABS]
|
||||
assert "pro" not in keys
|
||||
assert not any("Profils" in lbl for lbl in labels)
|
||||
assert "pro" in keys
|
||||
assert any("Profils" in lbl for lbl in labels)
|
||||
|
||||
|
||||
def test_profils_editor_creates_and_persists(ctk_root, tmp_path, monkeypatch):
|
||||
"""L'éditeur crée un profil, le rend éditable, et persiste les modifications."""
|
||||
from gui_v6.tabs import tab_config
|
||||
|
||||
monkeypatch.setattr(tab_config, "_app_base_dir", lambda: tmp_path)
|
||||
profiles = tmp_path / "profiles.yml"
|
||||
tab = tab_config.ConfigTab(ctk_root)
|
||||
tab._profiles_path = profiles
|
||||
tab._show_sub("pro")
|
||||
tab.update_idletasks()
|
||||
|
||||
# création d'un profil runtime
|
||||
tab._pro_new()
|
||||
tab.update_idletasks()
|
||||
key = tab._pro_edit_key
|
||||
assert key and key.startswith("nouveau_profil")
|
||||
|
||||
# éditer : nom + un terme à masquer, puis enregistrer
|
||||
tab._pro_label_var.set("Profil cabinet")
|
||||
tab._pro_require_mask_var.set(True)
|
||||
tab._pro_term_lists["blacklist"].add_term("CHUXX")
|
||||
tab._pro_save()
|
||||
tab.update_idletasks()
|
||||
|
||||
from profile_defaults import list_effective_profiles
|
||||
|
||||
saved = list_effective_profiles(profiles)[key]
|
||||
assert saved["label"] == "Profil cabinet"
|
||||
assert saved["require_manual_mask"] is True
|
||||
assert saved["param_lists"]["blacklist_force_mask_terms"] == ["CHUXX"]
|
||||
tab.destroy()
|
||||
|
||||
|
||||
def test_profils_default_profile_is_read_only(ctk_root, tmp_path, monkeypatch):
|
||||
"""Un profil par défaut n'est pas éditable (bouton Enregistrer désactivé)."""
|
||||
from gui_v6.tabs import tab_config
|
||||
|
||||
monkeypatch.setattr(tab_config, "_app_base_dir", lambda: tmp_path)
|
||||
tab = tab_config.ConfigTab(ctk_root)
|
||||
tab._profiles_path = tmp_path / "profiles.yml"
|
||||
tab._show_sub("pro")
|
||||
tab._pro_load("standard_local") # profil défaut
|
||||
tab.update_idletasks()
|
||||
assert str(tab._pro_save_btn.cget("state")) == "disabled"
|
||||
tab.destroy()
|
||||
|
||||
|
||||
def test_editable_term_list_add_remove(ctk_root):
|
||||
from gui_v6 import theme as theme_mod
|
||||
from gui_v6.editable_list import EditableTermList
|
||||
|
||||
p = theme_mod.get_palette(theme_mod.DEFAULT_THEME)
|
||||
lst = EditableTermList(ctk_root, p, title="À masquer", initial=["A", "B"])
|
||||
ctk_root.update_idletasks()
|
||||
assert lst.terms() == ["A", "B"]
|
||||
assert lst.add_term("C") is True
|
||||
assert lst.add_term("C") is False # pas de doublon
|
||||
lst.remove_term("A")
|
||||
assert lst.terms() == ["B", "C"]
|
||||
lst.set_editable(False)
|
||||
assert str(lst._add_btn.cget("state")) == "disabled"
|
||||
lst.destroy()
|
||||
|
||||
|
||||
def _all_texts(widget):
|
||||
|
||||
Reference in New Issue
Block a user