"""Unit tests for MainWindow component.""" from unittest.mock import patch import pytest from PySide6.QtWidgets import QToolBar from webdrop_bridge.config import Config from webdrop_bridge.ui.main_window import MainWindow @pytest.fixture def sample_config(tmp_path): """Provide a test configuration.""" # Create required directories allowed_root = tmp_path / "allowed" allowed_root.mkdir(exist_ok=True) # Create webapp HTML webapp_dir = tmp_path / "webapp" webapp_dir.mkdir(exist_ok=True) webapp_file = webapp_dir / "index.html" webapp_file.write_text("Test") config = Config( app_name="Test WebDrop", app_version="1.0.0", log_level="INFO", log_file=None, allowed_roots=[allowed_root], allowed_urls=["localhost", "127.0.0.1"], webapp_url=str(webapp_file), window_width=800, window_height=600, enable_logging=False, ) return config class TestMainWindowInitialization: """Test MainWindow initialization and setup.""" def test_main_window_creation(self, qtbot, sample_config): """Test MainWindow can be instantiated.""" window = MainWindow(sample_config) qtbot.addWidget(window) assert window is not None assert window.config == sample_config def test_main_window_title(self, qtbot, sample_config): """Test window title is set correctly.""" window = MainWindow(sample_config) qtbot.addWidget(window) expected_title = f"{sample_config.app_name} v{sample_config.app_version}" assert window.windowTitle() == expected_title def test_main_window_geometry(self, qtbot, sample_config): """Test window geometry is set correctly.""" window = MainWindow(sample_config) qtbot.addWidget(window) assert window.width() == sample_config.window_width assert window.height() == sample_config.window_height def test_main_window_has_web_view(self, qtbot, sample_config): """Test MainWindow has web_view attribute.""" window = MainWindow(sample_config) qtbot.addWidget(window) assert hasattr(window, "web_view") assert window.web_view is not None def test_main_window_has_drag_interceptor(self, qtbot, sample_config): """Test MainWindow has drag_interceptor attribute.""" window = MainWindow(sample_config) qtbot.addWidget(window) assert hasattr(window, "drag_interceptor") assert window.drag_interceptor is not None class TestMainWindowNavigation: """Test navigation toolbar and functionality.""" def test_navigation_toolbar_created(self, qtbot, sample_config): """Test navigation toolbar is created.""" window = MainWindow(sample_config) qtbot.addWidget(window) toolbars = window.findChildren(QToolBar) assert len(toolbars) > 0 def test_navigation_toolbar_not_movable(self, qtbot, sample_config): """Test navigation toolbar is not movable (locked for Kiosk-mode).""" window = MainWindow(sample_config) qtbot.addWidget(window) toolbar = window.findChild(QToolBar) assert toolbar is not None assert not toolbar.isMovable() def test_navigate_home(self, qtbot, sample_config): """Test home button navigation.""" window = MainWindow(sample_config) qtbot.addWidget(window) with patch.object(window.web_view, "load") as mock_load: window._navigate_home() mock_load.assert_called_once() def test_navigate_home_with_http_url(self, qtbot, tmp_path): """Test home navigation with HTTP URL.""" config = Config( app_name="Test", app_version="1.0.0", log_level="INFO", log_file=None, allowed_roots=[tmp_path], allowed_urls=[], webapp_url="http://localhost:8000", window_width=800, window_height=600, enable_logging=False, ) window = MainWindow(config) qtbot.addWidget(window) with patch.object(window.web_view, "load") as mock_load: window._navigate_home() # Verify load was called with HTTP URL call_args = mock_load.call_args url = call_args[0][0] assert url.scheme() == "http" def test_navigate_home_with_file_url(self, qtbot, sample_config): """Test home navigation with file:// URL.""" window = MainWindow(sample_config) qtbot.addWidget(window) with patch.object(window.web_view, "load") as mock_load: window._navigate_home() call_args = mock_load.call_args url = call_args[0][0] assert url.scheme() == "file" class TestMainWindowWebAppLoading: """Test web application loading.""" def test_load_local_webapp_file(self, qtbot, sample_config): """Test loading local webapp file.""" window = MainWindow(sample_config) qtbot.addWidget(window) # Window should load without errors assert window.web_view is not None def test_load_remote_webapp_url(self, qtbot, tmp_path): """Test loading remote webapp URL.""" config = Config( app_name="Test", app_version="1.0.0", log_level="INFO", log_file=None, allowed_roots=[tmp_path], allowed_urls=["localhost"], webapp_url="http://localhost:3000", window_width=800, window_height=600, enable_logging=False, ) window = MainWindow(config) qtbot.addWidget(window) assert window.web_view is not None def test_load_nonexistent_file_shows_welcome_page(self, qtbot, tmp_path): """Test loading nonexistent file shows welcome page HTML.""" config = Config( app_name="Test", app_version="1.0.0", log_level="INFO", log_file=None, allowed_roots=[tmp_path], allowed_urls=[], webapp_url="/nonexistent/file.html", window_width=800, window_height=600, enable_logging=False, ) with patch.object(config, "webapp_url", "/nonexistent/file.html"): window = MainWindow(config) qtbot.addWidget(window) with patch.object( window.web_view, "setHtml" ) as mock_set_html: window._load_webapp() mock_set_html.assert_called_once() # Verify welcome page is shown instead of error call_args = mock_set_html.call_args[0][0] assert "WebDrop Bridge" in call_args assert "Application Ready" in call_args class TestMainWindowDragIntegration: """Test drag-and-drop integration.""" def test_drag_interceptor_validator_set(self, qtbot, sample_config): """Test drag interceptor validator is configured.""" window = MainWindow(sample_config) qtbot.addWidget(window) assert window.drag_interceptor._validator is not None def test_drag_interceptor_signals_connected(self, qtbot, sample_config): """Test drag interceptor signals are connected.""" window = MainWindow(sample_config) qtbot.addWidget(window) # Signals should be connected assert window.drag_interceptor.drag_started is not None assert window.drag_interceptor.drag_failed is not None def test_initiate_drag_delegates_to_interceptor( self, qtbot, sample_config, tmp_path ): """Test initiate_drag method delegates to interceptor.""" window = MainWindow(sample_config) qtbot.addWidget(window) # Create test file test_file = sample_config.allowed_roots[0] / "test.txt" test_file.write_text("test") with patch.object( window.drag_interceptor, "initiate_drag" ) as mock_drag: mock_drag.return_value = True result = window.initiate_drag([str(test_file)]) mock_drag.assert_called_once_with([str(test_file)]) assert result is True def test_on_drag_started_called(self, qtbot, sample_config): """Test _on_drag_started handler can be called.""" window = MainWindow(sample_config) qtbot.addWidget(window) # Should not raise window._on_drag_started(["/some/path"]) def test_on_drag_failed_called(self, qtbot, sample_config): """Test _on_drag_failed handler can be called.""" window = MainWindow(sample_config) qtbot.addWidget(window) # Should not raise window._on_drag_failed("Test error message") class TestMainWindowURLWhitelist: """Test URL whitelisting integration.""" def test_restricted_web_view_receives_allowed_urls( self, qtbot, sample_config ): """Test RestrictedWebEngineView receives allowed URLs from config.""" window = MainWindow(sample_config) qtbot.addWidget(window) # web_view should have allowed_urls configured assert window.web_view.allowed_urls == sample_config.allowed_urls def test_empty_allowed_urls_list(self, qtbot, tmp_path): """Test with empty allowed URLs (no restriction).""" config = Config( app_name="Test", app_version="1.0.0", log_level="INFO", log_file=None, allowed_roots=[tmp_path], allowed_urls=[], # Empty = no restriction webapp_url="http://localhost", window_width=800, window_height=600, enable_logging=False, ) window = MainWindow(config) qtbot.addWidget(window) assert window.web_view.allowed_urls == [] class TestMainWindowSignals: """Test signal connections.""" def test_drag_started_signal_connection(self, qtbot, sample_config): """Test drag_started signal is connected to handler.""" window = MainWindow(sample_config) qtbot.addWidget(window) with patch.object(window, "_on_drag_started") as mock_handler: window.drag_interceptor.drag_started.emit(["/path/to/file"]) mock_handler.assert_called_once() def test_drag_failed_signal_connection(self, qtbot, sample_config): """Test drag_failed signal is connected to handler.""" window = MainWindow(sample_config) qtbot.addWidget(window) with patch.object(window, "_on_drag_failed") as mock_handler: window.drag_interceptor.drag_failed.emit("Error message") 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_manual_check_for_updates method exists.""" window = MainWindow(sample_config) qtbot.addWidget(window) # Test that the method exists assert hasattr(window, "_on_manual_check_for_updates") assert callable(window._on_manual_check_for_updates) def test_show_about_dialog_method_exists(self, qtbot, sample_config): """Test _show_about_dialog method exists.""" window = MainWindow(sample_config) qtbot.addWidget(window) # Test that the method exists assert hasattr(window, "_show_about_dialog") assert callable(window._show_about_dialog) class TestMainWindowStatusBar: """Test status bar and update status.""" def test_status_bar_created(self, qtbot, sample_config): """Test status bar is created.""" window = MainWindow(sample_config) qtbot.addWidget(window) assert window.statusBar() is not None assert hasattr(window, "status_bar") def test_update_status_label_created(self, qtbot, sample_config): """Test update status label exists.""" window = MainWindow(sample_config) qtbot.addWidget(window) assert hasattr(window, "update_status_label") assert window.update_status_label is not None def test_set_update_status_text_only(self, qtbot, sample_config): """Test setting update status with text only.""" window = MainWindow(sample_config) qtbot.addWidget(window) window.set_update_status("Checking for updates") assert "Checking for updates" in window.update_status_label.text() def test_set_update_status_with_emoji(self, qtbot, sample_config): """Test setting update status with emoji.""" window = MainWindow(sample_config) qtbot.addWidget(window) window.set_update_status("Checking", emoji="🔄") assert "🔄" in window.update_status_label.text() assert "Checking" in window.update_status_label.text() def test_set_update_status_checking(self, qtbot, sample_config): """Test checking for updates status.""" window = MainWindow(sample_config) qtbot.addWidget(window) window.set_update_status("Checking for updates", emoji="🔄") assert "🔄" in window.update_status_label.text() def test_set_update_status_available(self, qtbot, sample_config): """Test update available status.""" window = MainWindow(sample_config) qtbot.addWidget(window) window.set_update_status("Update available v0.0.2", emoji="✅") assert "✅" in window.update_status_label.text() def test_set_update_status_downloading(self, qtbot, sample_config): """Test downloading status.""" window = MainWindow(sample_config) qtbot.addWidget(window) window.set_update_status("Downloading update", emoji="⬇️") assert "⬇️" in window.update_status_label.text() def test_set_update_status_error(self, qtbot, sample_config): """Test error status.""" window = MainWindow(sample_config) qtbot.addWidget(window) window.set_update_status("Update check failed", emoji="⚠️") assert "⚠️" in window.update_status_label.text() class TestMainWindowStylesheet: """Test stylesheet application.""" def test_stylesheet_loading_gracefully_handles_missing_file( self, qtbot, sample_config ): """Test missing stylesheet doesn't crash application.""" window = MainWindow(sample_config) qtbot.addWidget(window) # Should not raise even if stylesheet missing window._apply_stylesheet() def test_stylesheet_loading_with_nonexistent_file( self, qtbot, sample_config ): """Test stylesheet loading with nonexistent file path.""" window = MainWindow(sample_config) qtbot.addWidget(window) with patch("builtins.open", side_effect=OSError("File not found")): # Should handle gracefully window._apply_stylesheet() class TestMainWindowCloseEvent: """Test window close handling.""" def test_close_event_accepted(self, qtbot, sample_config): """Test close event is accepted.""" window = MainWindow(sample_config) qtbot.addWidget(window) from PySide6.QtGui import QCloseEvent event = QCloseEvent() window.closeEvent(event) assert event.isAccepted() class TestMainWindowIntegration: """Integration tests for MainWindow with all components.""" def test_full_initialization_flow(self, qtbot, sample_config): """Test complete initialization flow.""" window = MainWindow(sample_config) qtbot.addWidget(window) # Verify all components initialized assert window.web_view is not None assert window.drag_interceptor is not None assert window.config == sample_config # Verify toolbar exists toolbars = window.findChildren(QToolBar) assert len(toolbars) > 0 def test_window_with_multiple_allowed_roots(self, qtbot, tmp_path): """Test MainWindow with multiple allowed root directories.""" root1 = tmp_path / "root1" root2 = tmp_path / "root2" root1.mkdir() root2.mkdir() webapp_file = tmp_path / "index.html" webapp_file.write_text("") config = Config( app_name="Test", app_version="1.0.0", log_level="INFO", log_file=None, allowed_roots=[root1, root2], allowed_urls=[], webapp_url=str(webapp_file), window_width=800, window_height=600, enable_logging=False, ) window = MainWindow(config) qtbot.addWidget(window) # Verify validator has both roots assert window.drag_interceptor._validator is not None assert len( window.drag_interceptor._validator.allowed_roots ) == 2 def test_window_with_url_whitelist(self, qtbot, tmp_path): """Test MainWindow respects URL whitelist.""" config = Config( app_name="Test", app_version="1.0.0", log_level="INFO", log_file=None, allowed_roots=[tmp_path], allowed_urls=["*.example.com", "localhost"], webapp_url="http://localhost", window_width=800, window_height=600, enable_logging=False, ) window = MainWindow(config) qtbot.addWidget(window) # Verify whitelist is set assert window.web_view.allowed_urls == ["*.example.com", "localhost"]