diff --git a/src/pages/tableau.py b/src/pages/tableau.py index 3fccde0..4844aaf 100644 --- a/src/pages/tableau.py +++ b/src/pages/tableau.py @@ -330,7 +330,6 @@ layout = [ ), html.Div( id="saved-views-bar", - style={"display": "none"}, className="d-inline-flex align-items-center gap-2", children=[ dbc.Button( @@ -338,7 +337,10 @@ layout = [ id="btn-save-view", color="secondary", size="sm", - title="Enregistrer les filtres, tris et colonnes actuels sous un nom", + # Grisé/désactivé pour les non-abonnés (le callback + # toggle_saved_views_controls affine au chargement). + disabled=True, + title="Enregistrer les filtres, tris et colonnes actuels sous un nom (abonnés)", ), dbc.DropdownMenu( id="saved-views-menu", @@ -346,6 +348,7 @@ layout = [ color="secondary", size="sm", children=[], + disabled=True, className="d-inline-block", ), ], @@ -627,11 +630,16 @@ def reset_view(n_clicks, column_state): @callback( - Output("saved-views-bar", "style"), + Output("btn-save-view", "disabled"), + Output("saved-views-menu", "disabled"), Input("tableau_url", "pathname"), ) -def toggle_saved_views_bar(_pathname): - return saved_views_ui.bar_style(current_user_has_subscription()) +def toggle_saved_views_controls(_pathname): + # La barre reste visible pour tous ; « Sauvegarder la vue » et « Mes vues » + # sont grisés et désactivés pour les non-abonnés (le gating serveur de + # save_view reste en place via prepare_view_to_save). + disabled = saved_views_ui.controls_disabled(current_user_has_subscription()) + return disabled, disabled def resolve_vue_from_url(search: str) -> dict | None: diff --git a/src/saved_views/ui.py b/src/saved_views/ui.py index 063d1dc..8a0ec98 100644 --- a/src/saved_views/ui.py +++ b/src/saved_views/ui.py @@ -7,8 +7,10 @@ from unidecode import unidecode from src.utils import DOMAIN_NAME -def bar_style(has_subscription: bool) -> dict: - return {} if has_subscription else {"display": "none"} +def controls_disabled(has_subscription: bool) -> bool: + """Boutons « Sauvegarder la vue » / « Mes vues » : actifs pour les abonnés, + grisés et désactivés sinon. La barre elle-même reste visible pour tous.""" + return not has_subscription def clean_view_name(name: str | None) -> str: diff --git a/tests/saved_views/test_saved_views_bar_visibility.py b/tests/saved_views/test_saved_views_bar_visibility.py new file mode 100644 index 0000000..6358050 --- /dev/null +++ b/tests/saved_views/test_saved_views_bar_visibility.py @@ -0,0 +1,28 @@ +"""Régression : pour un visiteur non abonné, la barre des vues sauvegardées +reste visible, mais « Sauvegarder la vue » et « Mes vues » sont grisés et +désactivés (le gating serveur de save_view reste par ailleurs en place). +""" + +import src.app # noqa: F401 # instancie l'app → register_page() + + +def test_controls_disabled_for_anonymous_visitor(dash_duo): + from src.app import app + + dash_duo.start_server(app) + dash_duo.wait_for_text_to_equal(".logo > h1", "colibre", timeout=6) + dash_duo.wait_for_page(dash_duo.server_url + "/tableau") + + # La barre est visible pour tous. + bar = dash_duo.wait_for_element("#saved-views-bar", timeout=10) + assert bar.value_of_css_property("display") != "none" + + # « Sauvegarder la vue » est désactivé (donc grisé) pour un anonyme. + save_btn = dash_duo.find_element("#btn-save-view") + assert save_btn.get_attribute("disabled") is not None + + # « Mes vues » (le bouton toggle du DropdownMenu) est désactivé aussi. + menu_toggle = dash_duo.find_element("#saved-views-menu .dropdown-toggle") + disabled_attr = menu_toggle.get_attribute("disabled") + has_disabled_class = "disabled" in (menu_toggle.get_attribute("class") or "") + assert disabled_attr is not None or has_disabled_class diff --git a/tests/saved_views/test_ui.py b/tests/saved_views/test_ui.py index 187811b..84da7a2 100644 --- a/tests/saved_views/test_ui.py +++ b/tests/saved_views/test_ui.py @@ -9,9 +9,9 @@ def _view(view_id, name, query, token="abc123"): return _Row(id=view_id, name=name, query=query, token=token) -def test_bar_style_hidden_for_non_subscriber(): - assert ui.bar_style(False) == {"display": "none"} - assert ui.bar_style(True) == {} +def test_controls_disabled_for_non_subscriber(): + assert ui.controls_disabled(False) is True + assert ui.controls_disabled(True) is False def test_clean_view_name_strips_and_empties():