# Unit tests for profile_manager.py methods. # # Copyright 2025-2026 Igalia, S.L. # Author: Joanmarie Diggs # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the # Free Software Foundation, Inc., Franklin Street, Fifth Floor, # Boston MA 02110-1301 USA. # pylint: disable=wrong-import-position # pylint: disable=import-outside-toplevel # pylint: disable=protected-access """Unit tests for profile_manager.py methods.""" from __future__ import annotations from typing import TYPE_CHECKING import gi gi.require_version("Gtk", "3.0") import pytest if TYPE_CHECKING: from unittest.mock import MagicMock from cthulhu_test_context import CthulhuTestContext @pytest.mark.unit class TestProfileManager: """Test ProfileManager methods.""" def _setup_dependencies(self, test_context: CthulhuTestContext) -> dict[str, MagicMock]: """Set up mocks for profile_manager dependencies.""" additional_modules = [ "cthulhu.braille", "cthulhu.cthulhu", "cthulhu.cthulhu_modifier_manager", "cthulhu.speech_manager", "cthulhu.braille_presenter", "cthulhu.presentation_manager", ] essential_modules = test_context.setup_shared_dependencies(additional_modules) from cthulhu import gsettings_registry registry = gsettings_registry.get_registry() registry.set_active_profile("default") test_context.patch( "cthulhu.profile_manager.ProfileManager._get_stored_profiles", return_value=[ ["Default", "default"], ["Spanish", "spanish"], ["Work", "work"], ], ) speech_manager_mock = essential_modules["cthulhu.speech_manager"] speech_manager_mock.get_manager.return_value.refresh_speech.return_value = None braille_mock = essential_modules["cthulhu.braille"] braille_mock.check_braille_setting.return_value = None cthulhu_mock = essential_modules["cthulhu.cthulhu"] cthulhu_mock.load_user_settings.return_value = None messages_mock = essential_modules["cthulhu.messages"] messages_mock.PROFILE_NOT_FOUND = "No profiles found." messages_mock.PROFILE_CHANGED = "Profile set to %s." messages_mock.PROFILE_CURRENT = "Current profile is %s." guilabels_mock = essential_modules["cthulhu.guilabels"] guilabels_mock.PROFILE_DEFAULT = "Default" return essential_modules def test_get_active_profile(self, test_context: CthulhuTestContext) -> None: """Test getting active profile.""" self._setup_dependencies(test_context) from cthulhu.profile_manager import ProfileManager manager = ProfileManager() profile = manager.get_active_profile() assert profile == "default" def test_set_active_profile(self, test_context: CthulhuTestContext) -> None: """Test setting active profile.""" self._setup_dependencies(test_context) from cthulhu import gsettings_registry from cthulhu.profile_manager import ProfileManager manager = ProfileManager() manager.set_active_profile("spanish") assert gsettings_registry.get_registry().get_active_profile() == "spanish" def test_load_profile(self, test_context: CthulhuTestContext) -> None: """Test loading a profile calls set_active_profile and load_user_settings.""" essential_modules = self._setup_dependencies(test_context) from cthulhu import gsettings_registry from cthulhu.profile_manager import ProfileManager manager = ProfileManager() manager.load_profile("spanish") assert gsettings_registry.get_registry().get_active_profile() == "spanish" essential_modules["cthulhu.cthulhu"].load_user_settings.assert_called_once_with( skip_reload_message=True, ) def test_remove_profile(self, test_context: CthulhuTestContext) -> None: """Test removing a profile.""" self._setup_dependencies(test_context) from cthulhu.profile_manager import ProfileManager mock_run = test_context.patch("subprocess.run") manager = ProfileManager() manager.remove_profile("spanish") mock_run.assert_called_once_with( ["dconf", "reset", "-f", "/org/gnome/cthulhu/spanish/"], check=True, ) def test_remove_profile_dconf_failure(self, test_context: CthulhuTestContext) -> None: """Test removing a profile when dconf reset fails.""" import subprocess self._setup_dependencies(test_context) from cthulhu.profile_manager import ProfileManager mock_run = test_context.patch("subprocess.run") mock_run.side_effect = subprocess.CalledProcessError(1, "dconf") manager = ProfileManager() manager.remove_profile("spanish") mock_run.assert_called_once() def test_remove_profile_dconf_not_found(self, test_context: CthulhuTestContext) -> None: """Test removing a profile when dconf is not installed.""" self._setup_dependencies(test_context) from cthulhu.profile_manager import ProfileManager mock_run = test_context.patch("subprocess.run") mock_run.side_effect = FileNotFoundError("dconf not found") manager = ProfileManager() manager.remove_profile("spanish") mock_run.assert_called_once() def test_remove_profile_sanitizes_name(self, test_context: CthulhuTestContext) -> None: """Test removing a profile sanitizes the name for the dconf path.""" self._setup_dependencies(test_context) from cthulhu.profile_manager import ProfileManager mock_run = test_context.patch("subprocess.run") manager = ProfileManager() manager.remove_profile("My Profile") mock_run.assert_called_once_with( ["dconf", "reset", "-f", "/org/gnome/cthulhu/my-profile/"], check=True, ) def test_rename_profile(self, test_context: CthulhuTestContext) -> None: """Test renaming a profile.""" self._setup_dependencies(test_context) from cthulhu import gsettings_registry from cthulhu.profile_manager import ProfileManager registry = gsettings_registry.get_registry() mock_rename = test_context.patch_object(registry, "rename_profile") manager = ProfileManager() manager.rename_profile("spanish", ["Espanol", "espanol"]) mock_rename.assert_called_once_with("spanish", "Espanol", "espanol") def test_commands_registered(self, test_context: CthulhuTestContext) -> None: """Test that profile manager commands are registered with CommandManager.""" self._setup_dependencies(test_context) from cthulhu import command_manager from cthulhu.profile_manager import ProfileManager manager = ProfileManager() manager.set_up_commands() cmd_manager = command_manager.get_manager() assert cmd_manager.get_keyboard_command("cycleSettingsProfileHandler") is not None assert cmd_manager.get_keyboard_command("presentCurrentProfileHandler") is not None def test_cycle_settings_profile_cycles_to_next(self, test_context: CthulhuTestContext) -> None: """Test cycle_settings_profile cycles to next profile.""" essential_modules = self._setup_dependencies(test_context) from unittest.mock import MagicMock from cthulhu import gsettings_registry from cthulhu.profile_manager import ProfileManager manager = ProfileManager() mock_script = MagicMock() pres_manager = essential_modules["cthulhu.presentation_manager"].get_manager() pres_manager.present_message.reset_mock() result = manager.cycle_settings_profile(script=mock_script) assert result is True assert gsettings_registry.get_registry().get_active_profile() == "spanish" pres_manager.present_message.assert_called() def test_cycle_settings_profile_wraps_around(self, test_context: CthulhuTestContext) -> None: """Test cycle_settings_profile wraps to first profile at end.""" self._setup_dependencies(test_context) from unittest.mock import MagicMock from cthulhu import gsettings_registry from cthulhu.profile_manager import ProfileManager gsettings_registry.get_registry().set_active_profile("work") manager = ProfileManager() mock_script = MagicMock() result = manager.cycle_settings_profile(script=mock_script) assert result is True assert gsettings_registry.get_registry().get_active_profile() == "default" def test_cycle_settings_profile_no_profiles(self, test_context: CthulhuTestContext) -> None: """Test cycle_settings_profile handles no profiles.""" essential_modules = self._setup_dependencies(test_context) from unittest.mock import MagicMock from cthulhu.profile_manager import ProfileManager manager = ProfileManager() mock_script = MagicMock() pres_manager = essential_modules["cthulhu.presentation_manager"].get_manager() pres_manager.present_message.reset_mock() result = manager.cycle_settings_profile(script=mock_script) assert result is True pres_manager.present_message.assert_called() def test_present_current_profile(self, test_context: CthulhuTestContext) -> None: """Test present_current_profile presents the current profile name.""" essential_modules = self._setup_dependencies(test_context) from unittest.mock import MagicMock from cthulhu.profile_manager import ProfileManager manager = ProfileManager() mock_script = MagicMock() pres_manager = essential_modules["cthulhu.presentation_manager"].get_manager() pres_manager.present_message.reset_mock() result = manager.present_current_profile(script=mock_script) assert result is True pres_manager.present_message.assert_called() call_args = pres_manager.present_message.call_args assert "Default" in call_args[0][0] @pytest.mark.unit class TestProfilePreferencesGridUI: """Test ProfilePreferencesGrid UI creation.""" def _setup_dependencies(self, test_context: CthulhuTestContext) -> dict[str, MagicMock]: """Set up mocks for ProfilePreferencesGrid dependencies.""" additional_modules = [ "cthulhu.braille", "cthulhu.cthulhu", "cthulhu.speech_manager", "cthulhu.braille_presenter", "cthulhu.presentation_manager", ] essential_modules = test_context.setup_shared_dependencies(additional_modules) from cthulhu import gsettings_registry registry = gsettings_registry.get_registry() registry.set_active_profile("default") test_context.patch( "cthulhu.profile_manager.ProfileManager._get_stored_profiles", return_value=[ ["Default", "default"], ["Spanish", "spanish"], ], ) guilabels_mock = essential_modules["cthulhu.guilabels"] guilabels_mock.GENERAL_PROFILES = "Profiles" guilabels_mock.GENERAL_START_UP_PROFILE = "Start-up profile" guilabels_mock.PROFILE_DEFAULT = "Default" guilabels_mock.PROFILE_CONFLICT_MESSAGE = "Profile %s already exists" guilabels_mock.PROFILE_REMOVE_LABEL = "Remove Profile" guilabels_mock.PROFILE_REMOVE_MESSAGE = "Remove profile %s?" guilabels_mock.MENU_REMOVE_PROFILE = "Remove" guilabels_mock.MENU_RENAME = "Rename" guilabels_mock.PROFILE_SAVE_AS_TITLE = "Save Profile As" guilabels_mock.PROFILE_NAME_LABEL = "Profile name:" guilabels_mock.DIALOG_CANCEL = "Cancel" guilabels_mock.DIALOG_APPLY = "Apply" guilabels_mock.DIALOG_ADD = "Add" guilabels_mock.PROFILES_INFO = "Select a profile to edit or create a new one." guilabels_mock.CURRENT_PROFILE = "Current Profile" guilabels_mock.PROFILE_CREATE_NEW = "_Create New Profile" return essential_modules def test_grid_creates_successfully(self, test_context: CthulhuTestContext) -> None: """Test ProfilePreferencesGrid creates without error.""" from gi.repository import Gtk # pylint: disable=no-name-in-module self._setup_dependencies(test_context) from cthulhu.profile_manager import ProfileManager, ProfilePreferencesGrid manager = ProfileManager() def callback(_profile): return None grid = ProfilePreferencesGrid(manager, callback) assert isinstance(grid, Gtk.Grid) def test_grid_has_auto_grid(self, test_context: CthulhuTestContext) -> None: """Test ProfilePreferencesGrid has auto_grid with controls.""" self._setup_dependencies(test_context) from cthulhu.profile_manager import ProfileManager, ProfilePreferencesGrid manager = ProfileManager() def callback(_profile): return None grid = ProfilePreferencesGrid(manager, callback) assert grid._auto_grid is not None def test_grid_save_settings_returns_dict(self, test_context: CthulhuTestContext) -> None: """Test save_settings returns a dictionary.""" self._setup_dependencies(test_context) from cthulhu.profile_manager import ProfileManager, ProfilePreferencesGrid manager = ProfileManager() def callback(_profile): return None grid = ProfilePreferencesGrid(manager, callback) result = grid.save_settings() assert isinstance(result, dict) def test_grid_has_changes_initially_false(self, test_context: CthulhuTestContext) -> None: """Test has_changes returns False initially.""" self._setup_dependencies(test_context) from cthulhu.profile_manager import ProfileManager, ProfilePreferencesGrid manager = ProfileManager() def callback(_profile): return None grid = ProfilePreferencesGrid(manager, callback) assert grid.has_changes() is False def test_grid_reload_clears_pending_renames(self, test_context: CthulhuTestContext) -> None: """Test reload clears pending renames.""" self._setup_dependencies(test_context) from cthulhu.profile_manager import ProfileManager, ProfilePreferencesGrid manager = ProfileManager() def callback(_profile): return None grid = ProfilePreferencesGrid(manager, callback) grid._pending_renames["old"] = ["New", "new"] assert len(grid._pending_renames) == 1 grid.reload() assert len(grid._pending_renames) == 0 def test_grid_app_specific_disables_startup_setter(self, test_context: CthulhuTestContext) -> None: """Test app-specific grid disables startup profile setter.""" self._setup_dependencies(test_context) from cthulhu.profile_manager import ProfileManager, ProfilePreferencesGrid manager = ProfileManager() def callback(_profile): return None grid = ProfilePreferencesGrid(manager, callback, is_app_specific=True) assert grid._is_app_specific is True def test_validate_profile_name_detects_conflict(self, test_context: CthulhuTestContext) -> None: """Test _validate_profile_name detects existing profile names.""" self._setup_dependencies(test_context) from cthulhu.profile_manager import ProfileManager, ProfilePreferencesGrid manager = ProfileManager() def callback(_profile): return None grid = ProfilePreferencesGrid(manager, callback) is_valid, error_msg = grid._validate_profile_name("Default") assert is_valid is False assert "Default" in error_msg def test_validate_profile_name_allows_unique(self, test_context: CthulhuTestContext) -> None: """Test _validate_profile_name allows unique names.""" self._setup_dependencies(test_context) from cthulhu.profile_manager import ProfileManager, ProfilePreferencesGrid manager = ProfileManager() def callback(_profile): return None grid = ProfilePreferencesGrid(manager, callback) is_valid, error_msg = grid._validate_profile_name("NewProfile") assert is_valid is True assert error_msg == "" def test_get_available_profiles_includes_pending_renames( self, test_context: CthulhuTestContext, ) -> None: """Test _get_available_profiles includes pending renames.""" self._setup_dependencies(test_context) from cthulhu.profile_manager import ProfileManager, ProfilePreferencesGrid manager = ProfileManager() def callback(_profile): return None grid = ProfilePreferencesGrid(manager, callback) grid._pending_renames["spanish"] = ["Espanol", "spanish"] profiles = grid._get_available_profiles() profile_names = [p[0] for p in profiles] assert "Espanol" in profile_names assert "Spanish" not in profile_names