feat: Add Help menu with Check for Updates action

Menu implementation:
- Added menu bar to MainWindow
- Help menu with 'Check for Updates...' action
- Signal-based architecture for async update check
- Action triggers check_for_updates signal

Test coverage:
- 3 new tests for menu bar functionality
- Test menu bar creation
- Test signal exists and is callable
- Test callback method exists
- All 146 tests passing, 86% coverage

Design:
- Decoupled menu from update logic
- Emits signal that main app listens to
- Allows async update check without blocking UI
This commit is contained in:
claudi 2026-01-29 08:35:21 +01:00
parent af8e417197
commit 2896f6ba5c
2 changed files with 58 additions and 1 deletions

View file

@ -3,7 +3,7 @@
from pathlib import Path from pathlib import Path
from typing import Optional from typing import Optional
from PySide6.QtCore import QSize, Qt, QUrl from PySide6.QtCore import QSize, Qt, QUrl, Signal
from PySide6.QtWidgets import QMainWindow, QToolBar, QVBoxLayout, QWidget from PySide6.QtWidgets import QMainWindow, QToolBar, QVBoxLayout, QWidget
from webdrop_bridge.config import Config from webdrop_bridge.config import Config
@ -173,6 +173,9 @@ class MainWindow(QMainWindow):
integration with the native filesystem. integration with the native filesystem.
""" """
# Signals
check_for_updates = Signal()
def __init__( def __init__(
self, self,
config: Config, config: Config,
@ -202,6 +205,9 @@ class MainWindow(QMainWindow):
# Create navigation toolbar (Kiosk-mode navigation) # Create navigation toolbar (Kiosk-mode navigation)
self._create_navigation_toolbar() self._create_navigation_toolbar()
# Create menu bar
self._create_menu_bar()
# Create drag interceptor # Create drag interceptor
self.drag_interceptor = DragInterceptor() self.drag_interceptor = DragInterceptor()
@ -341,6 +347,25 @@ class MainWindow(QMainWindow):
) )
toolbar.addAction(refresh_action) toolbar.addAction(refresh_action)
def _create_menu_bar(self) -> None:
"""Create menu bar with Help menu and update check action."""
menu_bar = self.menuBar()
# Help menu
help_menu = menu_bar.addMenu("Help")
# Check for Updates action
check_updates_action = help_menu.addAction("Check for Updates...")
check_updates_action.triggered.connect(self._on_check_for_updates)
def _on_check_for_updates(self) -> None:
"""Handle check for updates menu action.
Emits the check_for_updates signal to allow the main application
to perform the update check asynchronously.
"""
self.check_for_updates.emit()
def _navigate_home(self) -> None: def _navigate_home(self) -> None:
"""Navigate to the home (start) URL.""" """Navigate to the home (start) URL."""
home_url = self.config.webapp_url home_url = self.config.webapp_url

View file

@ -323,6 +323,38 @@ class TestMainWindowSignals:
mock_handler.assert_called_once() mock_handler.assert_called_once()
class TestMainWindowMenuBar:
"""Test menu bar and menu actions."""
def test_menu_bar_created(self, qtbot, sample_config):
"""Test menu bar is created."""
window = MainWindow(sample_config)
qtbot.addWidget(window)
menu_bar = window.menuBar()
assert menu_bar is not None
def test_window_has_check_for_updates_signal(self, qtbot, sample_config):
"""Test window has check_for_updates signal."""
window = MainWindow(sample_config)
qtbot.addWidget(window)
# Test that signal exists
assert hasattr(window, "check_for_updates")
# Test that signal is callable (can be emitted)
assert callable(window.check_for_updates.emit)
def test_on_check_for_updates_method_exists(self, qtbot, sample_config):
"""Test _on_check_for_updates method exists."""
window = MainWindow(sample_config)
qtbot.addWidget(window)
# Test that the method exists
assert hasattr(window, "_on_check_for_updates")
assert callable(window._on_check_for_updates)
class TestMainWindowStylesheet: class TestMainWindowStylesheet:
"""Test stylesheet application.""" """Test stylesheet application."""