diff --git a/src/saved_views/ui.py b/src/saved_views/ui.py index e253f20..68dcafb 100644 --- a/src/saved_views/ui.py +++ b/src/saved_views/ui.py @@ -1,5 +1,10 @@ +import re + import dash_bootstrap_components as dbc from dash import html +from unidecode import unidecode + +from src.utils import DOMAIN_NAME def bar_style(has_subscription: bool) -> dict: @@ -10,6 +15,30 @@ def clean_view_name(name: str | None) -> str: return (name or "").strip() +def slugify(name: str | None) -> str: + """Slug décoratif en tirets (convention web) : minuscule, translittéré ASCII, + non-alphanumériques → '-', collapse/trim. Ne contient jamais d'underscore + (qui sert de séparateur slug↔jeton dans l'URL).""" + ascii_name = unidecode(name or "").lower() + return re.sub(r"[^a-z0-9]+", "-", ascii_name).strip("-") + + +def build_view_url(name: str, token: str) -> str: + slug = slugify(name) + prefix = f"{slug}_" if slug else "" + return f"https://{DOMAIN_NAME}/tableau?vue={prefix}{token}" + + +def token_from_vue_param(value: str | None) -> str | None: + """Extrait le jeton du paramètre ?vue=. Le jeton base62 ne contient pas d'`_` + et le slug est en tirets, donc le segment après le dernier `_` est le jeton + (`?vue=abc123` sans slug fonctionne aussi).""" + if not value: + return None + token = value.rsplit("_", 1)[-1] + return token or None + + def prepare_view_to_save( has_subscription: bool, name: str | None ) -> tuple[str | None, str | None]: diff --git a/tests/saved_views/test_ui.py b/tests/saved_views/test_ui.py index ad7f7aa..5c062ea 100644 --- a/tests/saved_views/test_ui.py +++ b/tests/saved_views/test_ui.py @@ -58,3 +58,41 @@ def test_views_table_lists_views(): out = ui.views_table([_view(1, "Vue A", "filtres=a")]) text = str(out) assert "Vue A" in text + + +def test_slugify_accents_spaces_case(): + assert ui.slugify("Mes Marchés 2024") == "mes-marches-2024" + + +def test_slugify_special_chars_collapse_and_trim(): + assert ui.slugify(" Éà!! --- test__ok ") == "ea-test-ok" + + +def test_slugify_never_contains_underscore(): + assert "_" not in ui.slugify("a_b c") + + +def test_slugify_empty(): + assert ui.slugify("") == "" + assert ui.slugify(None) == "" + + +def test_build_view_url_dev_domain(monkeypatch): + # DOMAIN_NAME est résolu à l'import ; on patche l'attribut du module ui. + monkeypatch.setattr(ui, "DOMAIN_NAME", "test.colibre.fr") + url = ui.build_view_url("Mes Marchés", "abc123") + assert url == "https://test.colibre.fr/tableau?vue=mes-marches_abc123" + + +def test_build_view_url_empty_slug_omits_prefix(monkeypatch): + monkeypatch.setattr(ui, "DOMAIN_NAME", "test.colibre.fr") + url = ui.build_view_url("!!!", "abc123") + assert url == "https://test.colibre.fr/tableau?vue=abc123" + + +def test_token_from_vue_param(): + assert ui.token_from_vue_param("mes-marches-2024_abc123") == "abc123" + assert ui.token_from_vue_param("zzz_abc123") == "abc123" + assert ui.token_from_vue_param("abc123") == "abc123" + assert ui.token_from_vue_param("") is None + assert ui.token_from_vue_param(None) is None