From 67bec4a40ce470c8f0a0e5b4e9b1d2f78df5d711 Mon Sep 17 00:00:00 2001 From: Justin Bollinger Date: Wed, 22 Apr 2026 20:22:43 -0400 Subject: [PATCH] test(notify): cover submenu label refresh; document inline-import rationale MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added `test_labels_refresh_between_iterations` that sequences a toggle then captures the submenu items twice, asserting the label flips between renders. Guards against a regression where `items` is hoisted out of the while-loop. Also documented why the inline `from hate_crack.menu import interactive_menu` is not actually redundant with the module-scope import at main.py:77 — it re-reads the attribute on every call, which is what lets tests patch `hate_crack.menu.interactive_menu`. Co-Authored-By: Claude Opus 4.7 (1M context) --- hate_crack/main.py | 10 +++++++++- tests/test_notifications_submenu.py | 27 +++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/hate_crack/main.py b/hate_crack/main.py index a975686..08800d5 100755 --- a/hate_crack/main.py +++ b/hate_crack/main.py @@ -3902,7 +3902,15 @@ def rule_tools_submenu(): def notifications_submenu(): - """Submenu for all Pushover notification controls (main-menu option 82).""" + """Submenu for all Pushover notification controls (main-menu option 82). + + The inline ``interactive_menu`` import is not redundant with the + module-scope import at the top of this file: re-importing inside the + function re-reads ``hate_crack.menu.interactive_menu`` on every call, + which lets tests patch the real menu function via + ``monkeypatch.setattr(hate_crack.menu, "interactive_menu", ...)``. + Removing it breaks test isolation. + """ from hate_crack.menu import interactive_menu while True: diff --git a/tests/test_notifications_submenu.py b/tests/test_notifications_submenu.py index 6189104..e317239 100644 --- a/tests/test_notifications_submenu.py +++ b/tests/test_notifications_submenu.py @@ -107,3 +107,30 @@ class TestNotificationsSubmenu: assert labels["3"] == "Send Test Pushover Notification" assert labels["99"] == "Back to Main Menu" assert "Notifications" in captured_items["title"] + + def test_labels_refresh_between_iterations(self, monkeypatch): + # Guards against a regression where items are built once outside + # the while-loop: labels would go stale after a toggle. + settings = NotifySettings(enabled=False, per_crack_enabled=False) + monkeypatch.setattr(_main_mod._notify, "get_settings", lambda: settings) + + def _flip_enabled(): + settings.enabled = not settings.enabled + + monkeypatch.setattr(_main_mod, "toggle_notifications", _flip_enabled) + monkeypatch.setattr(_main_mod, "toggle_per_crack_notifications", lambda: None) + monkeypatch.setattr(_main_mod, "test_pushover_notification", lambda: None) + + captured = [] + choices = iter(["1", "99"]) + + def _fake_menu(items, title=""): + captured.append(dict(items)) + return next(choices) + + monkeypatch.setattr(_menu_mod, "interactive_menu", _fake_menu) + _main_mod.notifications_submenu() + + assert len(captured) == 2 + assert "[OFF]" in captured[0]["1"] + assert "[ON]" in captured[1]["1"]