502 lines
18 KiB
Python
502 lines
18 KiB
Python
# Unit tests for profile_manager.py methods.
|
|
#
|
|
# Copyright 2025-2026 Igalia, S.L.
|
|
# Author: Joanmarie Diggs <jdiggs@igalia.com>
|
|
#
|
|
# 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
|