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:
79
tests/unit/test_gui_v6_profile_editor.py
Normal file
79
tests/unit/test_gui_v6_profile_editor.py
Normal file
@@ -0,0 +1,79 @@
|
||||
"""Couche logique de l'éditeur de profils (persistance via profile_defaults).
|
||||
|
||||
Tests sans display, avec un fichier profiles.yml temporaire.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from gui_v6.profile_editor import (
|
||||
build_profile_spec,
|
||||
list_profile_choices,
|
||||
profile_is_editable,
|
||||
save_profile,
|
||||
slug_for_copy,
|
||||
)
|
||||
|
||||
|
||||
def test_build_profile_spec_structure_and_strip():
|
||||
spec = build_profile_spec(
|
||||
label=" Mon profil ",
|
||||
description="desc",
|
||||
require_manual_mask=True,
|
||||
force_disable_vlm=False,
|
||||
preferred_manual_mask_template="config/mask_templates/x.json",
|
||||
whitelist=[" garder ", "", "garder2"],
|
||||
blacklist=["CHUXX"],
|
||||
stopwords=[],
|
||||
)
|
||||
assert spec["label"] == "Mon profil"
|
||||
assert spec["require_manual_mask"] is True
|
||||
assert spec["force_disable_vlm"] is False
|
||||
assert spec["preferred_manual_mask_template"] == "config/mask_templates/x.json"
|
||||
assert spec["param_lists"]["whitelist_phrases"] == ["garder", "garder2"] # strip + vides retirés
|
||||
assert spec["param_lists"]["blacklist_force_mask_terms"] == ["CHUXX"]
|
||||
assert spec["param_lists"]["additional_stopwords"] == []
|
||||
|
||||
|
||||
def test_save_and_reload_roundtrip(tmp_path):
|
||||
profiles = tmp_path / "profiles.yml"
|
||||
spec = build_profile_spec(
|
||||
label="Test runtime", description="d", require_manual_mask=True,
|
||||
force_disable_vlm=True, preferred_manual_mask_template="",
|
||||
whitelist=["a"], blacklist=["b", "c"], stopwords=["d"],
|
||||
)
|
||||
save_profile("mon_profil", spec, path=profiles)
|
||||
|
||||
from profile_defaults import list_effective_profiles
|
||||
effective = list_effective_profiles(profiles)
|
||||
assert "mon_profil" in effective
|
||||
saved = effective["mon_profil"]
|
||||
assert saved["label"] == "Test runtime"
|
||||
assert saved["require_manual_mask"] is True
|
||||
assert saved["force_disable_vlm"] is True
|
||||
assert saved["param_lists"]["blacklist_force_mask_terms"] == ["b", "c"]
|
||||
|
||||
|
||||
def test_profile_is_editable_runtime_vs_default(tmp_path):
|
||||
profiles = tmp_path / "profiles.yml"
|
||||
save_profile("runtime_one", build_profile_spec(label="R1"), path=profiles)
|
||||
assert profile_is_editable("runtime_one", path=profiles) is True
|
||||
# un profil par défaut (non présent dans l'overlay runtime) n'est pas éditable
|
||||
assert profile_is_editable("standard_local", path=profiles) is False
|
||||
|
||||
|
||||
def test_list_profile_choices_marks_editable(tmp_path):
|
||||
profiles = tmp_path / "profiles.yml"
|
||||
save_profile("runtime_one", build_profile_spec(label="R1"), path=profiles)
|
||||
choices = list_profile_choices(path=profiles)
|
||||
by_key = {c["key"]: c for c in choices}
|
||||
assert by_key["runtime_one"]["editable"] is True
|
||||
assert by_key["runtime_one"]["label"] == "R1"
|
||||
# un profil défaut présent et non éditable
|
||||
assert "standard_local" in by_key
|
||||
assert by_key["standard_local"]["editable"] is False
|
||||
|
||||
|
||||
def test_slug_for_copy_avoids_collision():
|
||||
assert slug_for_copy("std", set()) == "std_copie"
|
||||
assert slug_for_copy("std", {"std_copie"}) == "std_copie_2"
|
||||
assert slug_for_copy("std", {"std_copie", "std_copie_2"}) == "std_copie_3"
|
||||
Reference in New Issue
Block a user