diff --git a/resources/translations/de.json b/resources/translations/de.json index 1597ddd..e3162e4 100644 --- a/resources/translations/de.json +++ b/resources/translations/de.json @@ -58,6 +58,10 @@ "dialog.language_changed.msg": "Die Spracheinstellung wurde aktualisiert. Starten Sie jetzt neu, um die ausgew\u00e4hlte Sprache \u00fcberall anzuwenden.", "dialog.language_changed.restart_now": "Jetzt neu starten", "dialog.language_changed.restart_later": "Sp\u00e4ter neu starten", + "dialog.branding_changed.title": "Branding ge\u00e4ndert", + "dialog.branding_changed.msg": "Das aktive Branding wurde geändert. Starten Sie jetzt neu, damit die aktualisierte visuelle Identität überall angewendet wird.", + "dialog.branding_changed.restart_now": "Jetzt neu starten", + "dialog.branding_changed.restart_later": "Sp\u00e4ter neu starten", "dialog.restart_failed.title": "Neustart fehlgeschlagen", "dialog.restart_failed.msg": "Die Anwendung konnte nicht automatisch neu gestartet werden:\n\n{error}\n\nBitte starten Sie manuell neu.", "dialog.update_timeout.title": "Zeitüberschreitung bei der Update-Pr\u00fcfung", diff --git a/resources/translations/en.json b/resources/translations/en.json index 1638741..d92c546 100644 --- a/resources/translations/en.json +++ b/resources/translations/en.json @@ -58,6 +58,10 @@ "dialog.language_changed.msg": "The language setting was updated. Restart now to apply the selected language everywhere.", "dialog.language_changed.restart_now": "Restart Now", "dialog.language_changed.restart_later": "Restart Later", + "dialog.branding_changed.title": "Branding Changed", + "dialog.branding_changed.msg": "The active branding was changed. Restart now so the updated visual identity is applied everywhere.", + "dialog.branding_changed.restart_now": "Restart Now", + "dialog.branding_changed.restart_later": "Restart Later", "dialog.restart_failed.title": "Restart Failed", "dialog.restart_failed.msg": "Could not automatically restart the application:\n\n{error}\n\nPlease restart manually.", "dialog.update_timeout.title": "Update Check Timeout", diff --git a/src/webdrop_bridge/ui/main_window.py b/src/webdrop_bridge/ui/main_window.py index 74ecb97..a32a09e 100644 --- a/src/webdrop_bridge/ui/main_window.py +++ b/src/webdrop_bridge/ui/main_window.py @@ -1958,6 +1958,7 @@ class MainWindow(QMainWindow): # Store current URL before opening dialog old_webapp_url = self.config.webapp_url old_language = self.config.language + old_branding_id = self.config.active_branding_id # Show dialog dialog = SettingsDialog(self.config, self) @@ -1966,6 +1967,9 @@ class MainWindow(QMainWindow): # Check if webapp URL changed new_webapp_url = self.config.webapp_url language_changed = old_language != self.config.language + branding_changed = old_branding_id != self.config.active_branding_id + restart_prompt_shown = False + if old_webapp_url != new_webapp_url: logger.info(f"Web application URL changed: {old_webapp_url} → {new_webapp_url}") @@ -1975,6 +1979,7 @@ class MainWindow(QMainWindow): if domain_changed: logger.warning("Domain has changed - recommending restart") self._handle_domain_change_restart() + restart_prompt_shown = True else: logger.info("Path changed but domain is same - reloading...") # Clear cache and navigate to home asynchronously @@ -1982,7 +1987,16 @@ class MainWindow(QMainWindow): self.web_view.clear_cache_and_cookies() QTimer.singleShot(100, self._navigate_home) - if language_changed: + if not restart_prompt_shown and branding_changed: + logger.info( + "Branding changed: %s → %s", + old_branding_id, + self.config.active_branding_id, + ) + self._handle_branding_change_restart() + restart_prompt_shown = True + + if not restart_prompt_shown and language_changed: logger.info(f"Language changed: {old_language} → {self.config.language}") self._handle_language_change_restart() @@ -2046,21 +2060,42 @@ class MainWindow(QMainWindow): self.web_view.clear_cache_and_cookies() self._navigate_home() + def _handle_branding_change_restart(self) -> None: + """Handle branding change by prompting for an optional restart.""" + self._show_restart_prompt( + title_key="dialog.branding_changed.title", + message_key="dialog.branding_changed.msg", + restart_now_key="dialog.branding_changed.restart_now", + restart_later_key="dialog.branding_changed.restart_later", + ) + def _handle_language_change_restart(self) -> None: """Handle language change by prompting for an optional restart.""" + self._show_restart_prompt( + title_key="dialog.language_changed.title", + message_key="dialog.language_changed.msg", + restart_now_key="dialog.language_changed.restart_now", + restart_later_key="dialog.language_changed.restart_later", + ) + + def _show_restart_prompt( + self, + *, + title_key: str, + message_key: str, + restart_now_key: str, + restart_later_key: str, + ) -> None: + """Show a restart prompt for settings that require a full restart.""" from PySide6.QtWidgets import QMessageBox msg = QMessageBox(self) - msg.setWindowTitle(tr("dialog.language_changed.title")) + msg.setWindowTitle(tr(title_key)) msg.setIcon(QMessageBox.Icon.Information) - msg.setText(tr("dialog.language_changed.msg")) + msg.setText(tr(message_key)) - restart_now_btn = msg.addButton( - tr("dialog.language_changed.restart_now"), QMessageBox.ButtonRole.AcceptRole - ) - msg.addButton( - tr("dialog.language_changed.restart_later"), QMessageBox.ButtonRole.RejectRole - ) + restart_now_btn = msg.addButton(tr(restart_now_key), QMessageBox.ButtonRole.AcceptRole) + msg.addButton(tr(restart_later_key), QMessageBox.ButtonRole.RejectRole) msg.exec() diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py index 09e4d6d..065caee 100644 --- a/tests/unit/test_config.py +++ b/tests/unit/test_config.py @@ -9,11 +9,14 @@ from webdrop_bridge.config import Config, ConfigurationError @pytest.fixture(autouse=True) -def clear_env(): +def clear_env(tmp_path): """Clear environment variables before each test to avoid persistence.""" # Save current env saved_env = os.environ.copy() + # Isolate runtime branding state from the developer machine + os.environ["WEBDROP_BRANDING_DIR"] = str(tmp_path / "branding") + # Clear relevant variables for key in list(os.environ.keys()): if key.startswith( diff --git a/tests/unit/test_main_window.py b/tests/unit/test_main_window.py index be89ab6..1fa7d66 100644 --- a/tests/unit/test_main_window.py +++ b/tests/unit/test_main_window.py @@ -82,6 +82,25 @@ class TestMainWindowInitialization: assert window.drag_interceptor is not None +class TestSettingsRestartBehavior: + """Test restart prompts for settings changes that require a restart.""" + + def test_branding_change_prompts_restart(self, qtbot, sample_config): + """Changing the active branding should trigger the restart flow.""" + window = MainWindow(sample_config) + qtbot.addWidget(window) + + with patch.object(window, "_handle_branding_change_restart") as mock_restart: + with patch("webdrop_bridge.ui.settings_dialog.SettingsDialog") as mock_dialog_cls: + mock_dialog = mock_dialog_cls.return_value + mock_dialog.exec.side_effect = lambda: setattr( + window.config, "active_branding_id", "agravity" + ) + window._show_settings_dialog() + + mock_restart.assert_called_once() + + class TestMainWindowDragIntegration: """Test drag-and-drop integration.""" @@ -207,15 +226,15 @@ class TestMainWindowOpenWith: test_file.write_text("test") call_count = [0] # Use list to make it mutable in nested function - + class _AppChooseResult: returncode = 0 stdout = "TextEdit" # Simulated chosen app name - + class _OpenResult: returncode = 0 stdout = "" - + def mock_run(*args, **kwargs): """Mock subprocess.run with two different behaviors per call.""" call_count[0] += 1 @@ -227,8 +246,7 @@ class TestMainWindowOpenWith: return _OpenResult() else: raise AssertionError(f"Unexpected call #{call_count[0]} to subprocess.run") - - + with patch("webdrop_bridge.ui.main_window.sys.platform", "darwin"): with patch("webdrop_bridge.ui.main_window.subprocess.run", side_effect=mock_run): assert window._open_with_app_chooser(str(test_file)) is True