Files
cthulhu/tests/test_script_manager.py

975 lines
38 KiB
Python

# Unit tests for script_manager.py methods.
#
# Copyright 2025 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=too-many-public-methods
# pylint: disable=too-many-statements
# pylint: disable=protected-access
# pylint: disable=too-many-arguments
# pylint: disable=too-many-positional-arguments
# pylint: disable=too-many-locals
# pylint: disable=too-many-instance-attributes
# pylint: disable=too-many-lines
"""Unit tests for script_manager.py methods."""
from __future__ import annotations
import sys
from typing import TYPE_CHECKING
from unittest.mock import Mock
import pytest
if TYPE_CHECKING:
from unittest.mock import MagicMock
from .cthulhu_test_context import CthulhuTestContext
@pytest.mark.unit
class TestScriptManager:
"""Test ScriptManager class methods."""
def _setup_dependencies(self, test_context: CthulhuTestContext) -> dict[str, MagicMock]:
"""Returns dependencies for script_manager module testing."""
modules_to_clean = ["cthulhu.script_manager"]
for module_name in modules_to_clean:
if module_name in sys.modules:
del sys.modules[module_name]
additional_modules = [
"gi",
"gi.repository",
"gi.repository.Atspi",
"cthulhu.ax_utilities",
"cthulhu.command_manager",
"cthulhu.scripts",
"cthulhu.scripts.apps",
"cthulhu.scripts.default",
"cthulhu.scripts.sleepmode",
"cthulhu.scripts.toolkits",
"cthulhu.sleep_mode_manager",
"cthulhu.speech_manager",
]
essential_modules = test_context.setup_shared_dependencies(additional_modules)
if "cthulhu.script_manager" in essential_modules:
del essential_modules["cthulhu.script_manager"]
if "cthulhu.script_manager" in sys.modules:
del sys.modules["cthulhu.script_manager"]
gi_repository_mock = essential_modules["gi.repository"]
atspi_mock = essential_modules["gi.repository.Atspi"]
class UnionSupportingMock(Mock):
"""Mock that supports union operations for Atspi.Accessible."""
def __or__(self, other):
return UnionSupportingMock()
atspi_mock.Accessible = UnionSupportingMock()
gi_repository_mock.Atspi = atspi_mock
ax_object_mock = essential_modules["cthulhu.ax_object"]
ax_object_class_mock = test_context.Mock()
ax_object_class_mock.get_name = test_context.Mock(return_value="test-app")
ax_object_class_mock.get_attribute = test_context.Mock(return_value="GTK")
ax_object_mock.AXObject = ax_object_class_mock
ax_utilities_mock = essential_modules["cthulhu.ax_utilities"]
ax_utilities_class_mock = test_context.Mock()
ax_utilities_class_mock.is_terminal = test_context.Mock(return_value=False)
ax_utilities_class_mock.get_application_toolkit_name = test_context.Mock(return_value="gtk")
ax_utilities_class_mock.is_application_in_desktop = test_context.Mock(return_value=True)
ax_utilities_class_mock.is_frame = test_context.Mock(return_value=False)
ax_utilities_class_mock.is_status_bar = test_context.Mock(return_value=False)
ax_utilities_mock.AXUtilities = ax_utilities_class_mock
braille_mock = essential_modules["cthulhu.braille"]
braille_mock.check_braille_setting = test_context.Mock()
braille_mock.setup_key_ranges = test_context.Mock()
debug_mock = essential_modules["cthulhu.debug"]
debug_mock.print_message = test_context.Mock()
debug_mock.LEVEL_INFO = 800
speech_verbosity_mock = essential_modules["cthulhu.speech_manager"]
speech_manager_instance = test_context.Mock()
speech_manager_instance.check_speech_setting = test_context.Mock()
speech_verbosity_mock.get_manager = test_context.Mock(return_value=speech_manager_instance)
command_manager_mock = essential_modules["cthulhu.command_manager"]
command_manager_instance = test_context.Mock()
command_manager_instance.check_keyboard_settings = test_context.Mock()
command_manager_mock.get_manager = test_context.Mock(return_value=command_manager_instance)
scripts_mock = essential_modules["cthulhu.scripts"]
apps_mock = essential_modules["cthulhu.scripts.apps"]
toolkits_mock = essential_modules["cthulhu.scripts.toolkits"]
default_mock = essential_modules["cthulhu.scripts.default"]
sleepmode_mock = essential_modules["cthulhu.scripts.sleepmode"]
apps_mock.__all__ = ["evolution", "gnome-shell"]
toolkits_mock.__all__ = ["gtk", "Gecko", "Qt"]
class MockScript:
"""Mock script class for testing."""
def __init__(self, app=None):
self.app = app
self.register_event_listeners = test_context.Mock()
self.deregister_event_listeners = test_context.Mock()
self.activate = test_context.Mock()
self.deactivate = test_context.Mock()
def __or__(self, other):
return MockScript
default_script_constructor = test_context.Mock(return_value=MockScript())
default_script_constructor.__or__ = lambda self, other: MockScript()
sleepmode_script_constructor = test_context.Mock(return_value=MockScript())
sleepmode_script_constructor.__or__ = lambda self, other: MockScript()
default_mock.Script = default_script_constructor
sleepmode_mock.Script = sleepmode_script_constructor
scripts_mock.apps = apps_mock
scripts_mock.default = default_mock
scripts_mock.sleepmode = sleepmode_mock
scripts_mock.toolkits = toolkits_mock
essential_modules["speech_manager_instance"] = speech_manager_instance
default_script = test_context.Mock()
default_script.app = test_context.Mock()
default_script.register_event_listeners = test_context.Mock()
default_script.deregister_event_listeners = test_context.Mock()
default_script.activate = test_context.Mock()
default_script.deactivate = test_context.Mock()
sleepmode_script = test_context.Mock()
sleepmode_script.app = test_context.Mock()
sleepmode_script.register_event_listeners = test_context.Mock()
sleepmode_script.deregister_event_listeners = test_context.Mock()
sleepmode_script.activate = test_context.Mock()
sleepmode_script.deactivate = test_context.Mock()
sleep_mode_manager_instance = test_context.Mock()
sleep_mode_manager_instance.is_active_for_app = test_context.Mock(return_value=False)
# Set up the module-level get_manager mock
sleep_mode_manager_mock = essential_modules["cthulhu.sleep_mode_manager"]
sleep_mode_manager_mock.get_manager = test_context.Mock(
return_value=sleep_mode_manager_instance,
)
essential_modules["default_script"] = default_script
essential_modules["sleepmode_script"] = sleepmode_script
essential_modules["sleep_mode_manager"] = sleep_mode_manager_instance
default_module = test_context.Mock()
default_module.Script = test_context.Mock(return_value=test_context.Mock())
sleepmode_module = test_context.Mock()
sleepmode_module.Script = test_context.Mock(return_value=test_context.Mock())
deps_speech_manager = test_context.Mock()
deps_speech_manager_instance = test_context.Mock()
deps_speech_manager_instance.check_speech_setting = test_context.Mock()
deps_speech_manager.get_manager = test_context.Mock(
return_value=deps_speech_manager_instance,
)
braille_module = test_context.Mock()
braille_module.check_braille_setting = test_context.Mock()
braille_module.setup_key_ranges = test_context.Mock()
essential_modules["default_module"] = default_module
essential_modules["sleepmode_module"] = sleepmode_module
essential_modules["deps_speech_manager"] = deps_speech_manager
essential_modules["braille_module"] = braille_module
return essential_modules
def test_init(self, test_context: CthulhuTestContext) -> None:
"""Test ScriptManager.__init__."""
self._setup_dependencies(test_context)
from cthulhu.script_manager import ScriptManager
manager = ScriptManager()
assert not manager.app_scripts
assert not manager.toolkit_scripts
assert not manager.custom_scripts
assert not manager._sleep_mode_scripts
assert manager._default_script is None
assert manager._active_script is None
assert manager._active is False
@pytest.mark.parametrize(
"case",
[
{
"id": "inactive_to_active",
"initially_active": False,
"expects_script_creation": True,
},
{"id": "already_active", "initially_active": True, "expects_script_creation": False},
],
ids=lambda case: case["id"],
)
def test_activate(self, test_context: CthulhuTestContext, case: dict) -> None:
"""Test ScriptManager.activate with different initial states."""
essential_modules = self._setup_dependencies(test_context)
default_script = essential_modules["default_script"]
if case["expects_script_creation"]:
mock_default_script = test_context.Mock()
test_context.patch("cthulhu.script_manager.default.Script", new=mock_default_script)
mock_default_script.return_value = default_script
from cthulhu.script_manager import ScriptManager
manager = ScriptManager()
manager._active = case["initially_active"]
manager.activate()
assert manager._active is True
if case["expects_script_creation"]:
assert manager._default_script is not None
assert manager._active_script is not None
default_script.register_event_listeners.assert_called_once()
@pytest.mark.parametrize(
"case",
[
{"id": "active_to_inactive", "initially_active": True, "expects_cleanup": True},
{"id": "already_inactive", "initially_active": False, "expects_cleanup": False},
],
ids=lambda case: case["id"],
)
def test_deactivate(self, test_context: CthulhuTestContext, case: dict) -> None:
"""Test ScriptManager.deactivate with different initial states."""
self._setup_dependencies(test_context)
from cthulhu.script_manager import ScriptManager
manager = ScriptManager()
manager._active = case["initially_active"]
if case["expects_cleanup"]:
mock_script = test_context.Mock()
mock_script.deregister_event_listeners = test_context.Mock()
manager._default_script = mock_script
manager.app_scripts = {"test": "script"}
manager.toolkit_scripts = {"test": "script"}
manager.custom_scripts = {"test": "script"}
manager.deactivate()
assert manager._active is False
if case["expects_cleanup"]:
assert manager._default_script is None
assert manager._active_script is None
assert not manager.app_scripts
assert not manager.toolkit_scripts
assert not manager.custom_scripts
mock_script.deregister_event_listeners.assert_called_once()
@pytest.mark.parametrize(
"case",
[
{
"id": "app_in_apps_list",
"app_name": "evolution",
"expected_result": "evolution",
"use_null_app": False,
},
{
"id": "toolkit_in_toolkits_list",
"app_name": "gtk",
"expected_result": "gtk",
"use_null_app": False,
},
{
"id": "mapped_app_name",
"app_name": "mate-notification-daemon",
"expected_result": "notification-daemon",
"use_null_app": False,
},
{
"id": "steam_alias",
"app_name": "Steam Web Helper",
"expected_result": "steamwebhelper",
"use_null_app": False,
},
{
"id": "python_extension",
"app_name": "test-app.py",
"expected_result": "test-app",
"use_null_app": False,
},
{
"id": "bin_extension",
"app_name": "test-app.bin",
"expected_result": "test-app",
"use_null_app": False,
},
{
"id": "reverse_domain",
"app_name": "org.gnome.TestApp",
"expected_result": "TestApp",
"use_null_app": False,
},
{
"id": "reverse_domain_com",
"app_name": "com.example.TestApp",
"expected_result": "TestApp",
"use_null_app": False,
},
{
"id": "unknown_app",
"app_name": "unknown-app",
"expected_result": "unknown-app",
"use_null_app": False,
},
{"id": "null_app", "app_name": None, "expected_result": None, "use_null_app": True},
{"id": "nameless_app", "app_name": "", "expected_result": None, "use_null_app": False},
],
ids=lambda case: case["id"],
)
def test_get_module_name_scenarios(self, test_context, case: dict) -> None:
"""Test ScriptManager.get_module_name with various scenarios."""
self._setup_dependencies(test_context)
from cthulhu.script_manager import ScriptManager
manager = ScriptManager()
if case["use_null_app"]:
result = manager.get_module_name(None)
else:
mock_app = test_context.Mock()
mock_ax_object = test_context.Mock()
test_context.patch("cthulhu.script_manager.AXObject", new=mock_ax_object)
mock_ax_object.get_name.return_value = case["app_name"]
result = manager.get_module_name(mock_app)
assert result == case["expected_result"]
@pytest.mark.parametrize(
"case",
[
{"id": "gtk_toolkit", "toolkit_attribute": "GTK", "expected_result": "gtk"},
{"id": "gail_mapped_to_gtk", "toolkit_attribute": "GAIL", "expected_result": "gtk"},
{
"id": "webkitgtk_mapped_to_local_dir",
"toolkit_attribute": "WebKitGTK",
"expected_result": "WebKitGtk",
},
{"id": "qt_toolkit", "toolkit_attribute": "Qt", "expected_result": "Qt"},
{"id": "empty_toolkit", "toolkit_attribute": "", "expected_result": ""},
{"id": "none_toolkit", "toolkit_attribute": None, "expected_result": None},
],
ids=lambda case: case["id"],
)
def test_toolkit_for_object(self, test_context, case: dict) -> None:
"""Test ScriptManager._toolkit_for_object."""
self._setup_dependencies(test_context)
from cthulhu.script_manager import ScriptManager
manager = ScriptManager()
mock_obj = test_context.Mock()
mock_ax_object = test_context.Mock()
test_context.patch("cthulhu.script_manager.AXObject", new=mock_ax_object)
mock_ax_object.get_attribute.return_value = case["toolkit_attribute"]
result = manager._toolkit_for_object(mock_obj)
assert result == case["expected_result"]
mock_ax_object.get_attribute.assert_called_once_with(mock_obj, "toolkit")
@pytest.mark.parametrize(
"case",
[
{"id": "terminal_role", "is_terminal": True, "expected_result": "terminal"},
{"id": "non_terminal_role", "is_terminal": False, "expected_result": ""},
],
ids=lambda case: case["id"],
)
def test_script_for_role(self, test_context, case: dict) -> None:
"""Test ScriptManager._script_for_role."""
self._setup_dependencies(test_context)
from cthulhu.script_manager import ScriptManager
manager = ScriptManager()
mock_obj = test_context.Mock()
mock_ax_utilities = test_context.Mock()
test_context.patch("cthulhu.script_manager.AXUtilities", new=mock_ax_utilities)
mock_ax_utilities.is_terminal.return_value = case["is_terminal"]
result = manager._script_for_role(mock_obj)
assert result == case["expected_result"]
mock_ax_utilities.is_terminal.assert_called_once_with(mock_obj)
@pytest.mark.parametrize(
"case",
[
{
"id": "null_app_empty_name",
"app": None,
"name": "",
"has_module": False,
"has_get_script": False,
"has_script_class": False,
"should_succeed": False,
},
{
"id": "empty_name",
"app": "app",
"name": "",
"has_module": False,
"has_get_script": False,
"has_script_class": False,
"should_succeed": False,
},
{
"id": "no_module",
"app": "app",
"name": "test",
"has_module": False,
"has_get_script": False,
"has_script_class": False,
"should_succeed": False,
},
{
"id": "has_get_script",
"app": "app",
"name": "test",
"has_module": True,
"has_get_script": True,
"has_script_class": False,
"should_succeed": True,
},
{
"id": "has_script_class",
"app": "app",
"name": "test",
"has_module": True,
"has_get_script": False,
"has_script_class": True,
"should_succeed": True,
},
{
"id": "no_script_creation",
"app": "app",
"name": "test",
"has_module": True,
"has_get_script": False,
"has_script_class": False,
"should_succeed": False,
},
],
ids=lambda case: case["id"],
)
def test_new_named_script(self, test_context, case: dict) -> None:
"""Test ScriptManager._new_named_script."""
self._setup_dependencies(test_context)
from cthulhu.script_manager import ScriptManager
manager = ScriptManager()
mock_app = test_context.Mock() if case["app"] else None
mock_script = test_context.Mock()
mock_import = test_context.Mock()
test_context.patch("importlib.import_module", side_effect=mock_import)
if case["has_module"]:
mock_module = type("MockModule", (), {})()
if case["has_get_script"]:
mock_module.get_script = test_context.Mock(return_value=mock_script)
elif case["has_script_class"]:
mock_module.Script = test_context.Mock(return_value=mock_script)
mock_import.return_value = mock_module
else:
mock_import.side_effect = ImportError("Module not found")
result = manager._new_named_script(mock_app, case["name"])
if case["should_succeed"]:
assert result == mock_script
else:
assert result is None
@pytest.mark.parametrize(
"case",
[
{
"id": "os_error_returns_none",
"exception_type": "OSError",
},
{
"id": "script_creation_error",
"exception_type": "AttributeError",
},
],
ids=lambda case: case["id"],
)
def test_new_named_script_error_handling(
self,
test_context: CthulhuTestContext,
case: dict,
) -> None:
"""Test ScriptManager._new_named_script handles various errors."""
self._setup_dependencies(test_context)
from cthulhu.script_manager import ScriptManager
manager = ScriptManager()
mock_app = test_context.Mock()
mock_import = test_context.Mock()
test_context.patch("importlib.import_module", side_effect=mock_import)
if case["exception_type"] == "OSError":
mock_import.side_effect = OSError("Permission denied")
result = manager._new_named_script(mock_app, "test")
assert result is None
else:
mock_module = type("MockModule", (), {})()
mock_module.Script = test_context.Mock(
side_effect=AttributeError("Script class not found"),
)
mock_import.return_value = mock_module
result = manager._new_named_script(mock_app, "test")
assert result is None
@pytest.mark.parametrize(
"case",
[
{"id": "no_module_name", "module_name": None, "expected_result_type": "default_script"},
{
"id": "with_module_name",
"module_name": "test_script",
"expected_result_type": "named_script",
},
],
ids=lambda case: case["id"],
)
def test_create_script(self, test_context: CthulhuTestContext, case: dict) -> None:
"""Test ScriptManager._create_script with different module name scenarios."""
essential_modules = self._setup_dependencies(test_context)
default_script = essential_modules["default_script"]
from cthulhu.script_manager import ScriptManager
manager = ScriptManager()
mock_app = test_context.Mock()
mock_obj = test_context.Mock()
mock_script = test_context.Mock()
test_context.patch_object(manager, "get_module_name", return_value=case["module_name"])
test_context.patch_object(manager, "_toolkit_for_object", return_value=None)
test_context.patch_object(manager, "get_default_script", return_value=default_script)
test_context.patch(
"cthulhu.script_manager.AXUtilities.get_application_toolkit_name",
return_value=None,
)
if case["expected_result_type"] == "default_script":
mock_new_named_script = test_context.Mock(return_value=None)
else:
mock_new_named_script = test_context.Mock(return_value=mock_script)
test_context.patch_object(manager, "_new_named_script", side_effect=mock_new_named_script)
result = manager._create_script(mock_app, mock_obj)
if case["expected_result_type"] == "default_script":
assert result == default_script
else:
assert result == mock_script
mock_new_named_script.assert_called_once_with(mock_app, case["module_name"])
def test_get_default_script(self, test_context: CthulhuTestContext) -> None:
"""Test ScriptManager.get_default_script."""
essential_modules = self._setup_dependencies(test_context)
default_script = essential_modules["default_script"]
from cthulhu.script_manager import ScriptManager
mock_default_script = test_context.Mock()
test_context.patch("cthulhu.script_manager.default.Script", new=mock_default_script)
mock_default_script.return_value = default_script
manager = ScriptManager()
mock_app = test_context.Mock()
result = manager.get_default_script(mock_app)
assert result is not None
mock_default_script.assert_called_with(mock_app)
manager._default_script = default_script
result = manager.get_default_script(None)
assert result == default_script
manager._default_script = None
result = manager.get_default_script(None)
assert result is not None
assert manager._default_script is not None
def test_get_or_create_sleep_mode_script(self, test_context: CthulhuTestContext) -> None:
"""Test ScriptManager.get_or_create_sleep_mode_script."""
essential_modules = self._setup_dependencies(test_context)
sleepmode_script = essential_modules["sleepmode_script"]
from cthulhu.script_manager import ScriptManager
mock_sleepmode_script = test_context.Mock()
test_context.patch("cthulhu.script_manager.sleepmode.Script", new=mock_sleepmode_script)
mock_sleepmode_script.return_value = sleepmode_script
manager = ScriptManager()
mock_app = test_context.Mock()
result = manager.get_or_create_sleep_mode_script(mock_app)
assert result is not None
assert result == sleepmode_script
assert manager._sleep_mode_scripts[mock_app] == result
result2 = manager.get_or_create_sleep_mode_script(mock_app)
assert result2 == result
assert len(manager._sleep_mode_scripts) == 1
@pytest.mark.parametrize(
"case",
[
{
"id": "null_app_and_obj",
"app": None,
"obj": None,
"sleep_mode_active": False,
"has_custom_script": False,
"has_toolkit_script": False,
"expected_script_type": "default",
},
{
"id": "sleep_mode_active",
"app": "app",
"obj": "obj",
"sleep_mode_active": True,
"has_custom_script": False,
"has_toolkit_script": False,
"expected_script_type": "sleep",
},
{
"id": "custom_script",
"app": "app",
"obj": "obj",
"sleep_mode_active": False,
"has_custom_script": True,
"has_toolkit_script": False,
"expected_script_type": "custom",
},
{
"id": "toolkit_script",
"app": "app",
"obj": "obj",
"sleep_mode_active": False,
"has_custom_script": False,
"has_toolkit_script": True,
"expected_script_type": "toolkit",
},
{
"id": "app_script",
"app": "app",
"obj": "obj",
"sleep_mode_active": False,
"has_custom_script": False,
"has_toolkit_script": False,
"expected_script_type": "app",
},
],
ids=lambda case: case["id"],
)
def test_get_script(self, test_context, case: dict) -> None:
"""Test ScriptManager.get_script."""
essential_modules = self._setup_dependencies(test_context)
sleepmode_script = essential_modules["sleepmode_script"]
sleep_mode_manager = essential_modules["sleep_mode_manager"]
from cthulhu.script_manager import ScriptManager
manager = ScriptManager()
mock_app = test_context.Mock() if case["app"] else None
mock_obj = test_context.Mock() if case["obj"] else None
mock_sleep_script = sleepmode_script
mock_custom_script = test_context.Mock()
class ToolkitScript:
"""Mock toolkit script class."""
class AppScript:
"""Mock app script class."""
mock_toolkit_script = test_context.Mock(spec=ToolkitScript)
mock_app_script = test_context.Mock(spec=AppScript)
sleep_mode_manager.is_active_for_app = test_context.Mock(
return_value=case["sleep_mode_active"],
)
test_context.patch_object(
manager,
"get_or_create_sleep_mode_script",
return_value=mock_sleep_script,
)
test_context.patch_object(
manager,
"_script_for_role",
return_value="terminal" if case["has_custom_script"] else "",
)
test_context.patch_object(
manager,
"_toolkit_for_object",
return_value="gtk" if case["has_toolkit_script"] else None,
)
def create_script_side_effect(_app, obj):
if obj and case["has_toolkit_script"]:
return mock_toolkit_script
return mock_app_script
test_context.patch_object(manager, "_create_script", side_effect=create_script_side_effect)
if case["has_custom_script"]:
test_context.patch_object(manager, "_new_named_script", return_value=mock_custom_script)
result = manager.get_script(mock_app, mock_obj)
if case["expected_script_type"] == "default":
assert result is not None
assert hasattr(result, "register_event_listeners")
elif case["expected_script_type"] == "sleep":
assert result == mock_sleep_script
elif case["expected_script_type"] == "custom":
assert result == mock_custom_script
elif case["expected_script_type"] == "toolkit":
assert result == mock_toolkit_script
elif case["expected_script_type"] == "app":
assert result == mock_app_script
def test_get_script_exception_handling(self, test_context: CthulhuTestContext) -> None:
"""Test ScriptManager.get_script handles exceptions."""
self._setup_dependencies(test_context)
from cthulhu.script_manager import ScriptManager
manager = ScriptManager()
mock_app = test_context.Mock()
mock_obj = test_context.Mock()
test_context.patch_object(manager, "_script_for_role", return_value="")
test_context.patch_object(manager, "_toolkit_for_object", return_value=None)
def create_script_side_effect(app, obj):
raise KeyError("Script creation failed")
test_context.patch_object(manager, "_create_script", side_effect=create_script_side_effect)
result = manager.get_script(mock_app, mock_obj)
assert result is not None
assert hasattr(result, "register_event_listeners")
@pytest.mark.parametrize(
"case",
[
{"id": "no_active_script", "has_active_script": False, "test_type": "script"},
{"id": "has_active_script", "has_active_script": True, "test_type": "script"},
{"id": "no_active_script_app", "has_active_script": False, "test_type": "app"},
{"id": "has_active_script_app", "has_active_script": True, "test_type": "app"},
],
ids=lambda case: case["id"],
)
def test_get_active_script_and_app(self, test_context: CthulhuTestContext, case: dict) -> None:
"""Test ScriptManager.get_active_script and get_active_script_app."""
essential_modules = self._setup_dependencies(test_context)
default_script = essential_modules["default_script"]
from cthulhu.script_manager import ScriptManager
manager = ScriptManager()
mock_app = test_context.Mock()
if case["has_active_script"]:
default_script.app = mock_app
manager._active_script = default_script
if case["test_type"] == "script":
result = manager.get_active_script()
expected = default_script if case["has_active_script"] else None
else:
result = manager.get_active_script_app()
expected = mock_app if case["has_active_script"] else None
assert result == expected
def test_set_active_script(self, test_context: CthulhuTestContext) -> None:
"""Test ScriptManager.set_active_script."""
essential_modules = self._setup_dependencies(test_context)
default_script = essential_modules["default_script"]
from cthulhu.script_manager import ScriptManager
mock_get_speech_manager = test_context.Mock()
test_context.patch(
"cthulhu.script_manager.speech_manager.get_manager",
new=mock_get_speech_manager,
)
mock_speech_manager_instance = test_context.Mock()
mock_get_speech_manager.return_value = mock_speech_manager_instance
manager = ScriptManager()
old_script = test_context.Mock()
old_script.app = test_context.Mock()
old_script.deactivate = test_context.Mock()
new_script = default_script
new_script.app = test_context.Mock()
new_script.activate = test_context.Mock()
manager._active_script = old_script
manager.set_active_script(new_script, "test reason")
old_script.deactivate.assert_called_once()
new_script.activate.assert_called_once()
assert manager._active_script == new_script
mock_speech_manager_instance.check_speech_setting.assert_called_once()
@pytest.mark.parametrize(
"case",
[
{
"id": "same_script_no_change",
"new_script_type": "same",
"expects_deactivate": False,
"expects_activate": False,
},
{
"id": "set_to_none",
"new_script_type": "none",
"expects_deactivate": True,
"expects_activate": False,
},
],
ids=lambda case: case["id"],
)
def test_set_active_script_special_cases(
self,
test_context: CthulhuTestContext,
case: dict,
) -> None:
"""Test ScriptManager.set_active_script special cases."""
essential_modules = self._setup_dependencies(test_context)
default_script = essential_modules["default_script"]
from cthulhu.script_manager import ScriptManager
manager = ScriptManager()
old_script = test_context.Mock()
old_script.deactivate = test_context.Mock()
manager._active_script = old_script
if case["new_script_type"] == "same":
manager._active_script = default_script
manager.set_active_script(default_script, "same script")
if case["expects_deactivate"]:
default_script.deactivate.assert_called_once()
else:
default_script.deactivate.assert_not_called()
default_script.activate.assert_not_called()
else:
manager.set_active_script(None)
if case["expects_deactivate"]:
old_script.deactivate.assert_called_once()
assert manager._active_script is None
@pytest.mark.parametrize(
"case",
[
{"id": "mixed_apps_normal", "has_app_in_desktop": True, "has_key_error": False},
{
"id": "no_desktop_apps_with_key_error",
"has_app_in_desktop": False,
"has_key_error": True,
},
],
ids=lambda case: case["id"],
)
def test_reclaim_scripts(self, test_context: CthulhuTestContext, case: dict) -> None:
"""Test ScriptManager.reclaim_scripts with various scenarios."""
self._setup_dependencies(test_context)
from cthulhu.script_manager import ScriptManager
desktop_patch = "cthulhu.script_manager.AXUtilities.is_application_in_desktop"
mock_is_in_desktop = test_context.Mock()
test_context.patch(desktop_patch, new=mock_is_in_desktop)
manager = ScriptManager()
app_not_in_desktop = test_context.Mock()
if case["has_app_in_desktop"]:
app_in_desktop = test_context.Mock()
manager.app_scripts = {
app_in_desktop: test_context.Mock(),
app_not_in_desktop: test_context.Mock(),
}
manager.toolkit_scripts = {app_not_in_desktop: test_context.Mock()}
manager.custom_scripts = {app_not_in_desktop: test_context.Mock()}
manager._sleep_mode_scripts = {app_not_in_desktop: test_context.Mock()}
def mock_is_in_desktop_func(app):
return app == app_in_desktop
mock_is_in_desktop.side_effect = mock_is_in_desktop_func
manager.reclaim_scripts()
assert app_in_desktop in manager.app_scripts
assert app_not_in_desktop not in manager.app_scripts
assert app_not_in_desktop not in manager.toolkit_scripts
assert app_not_in_desktop not in manager.custom_scripts
assert app_not_in_desktop not in manager._sleep_mode_scripts
else:
manager.app_scripts = {app_not_in_desktop: test_context.Mock()}
if case["has_key_error"]:
manager.toolkit_scripts = {}
manager.custom_scripts = {}
manager._sleep_mode_scripts = {}
else:
manager.toolkit_scripts = {app_not_in_desktop: test_context.Mock()}
manager.custom_scripts = {app_not_in_desktop: test_context.Mock()}
manager._sleep_mode_scripts = {app_not_in_desktop: test_context.Mock()}
mock_is_in_desktop.return_value = False
manager.reclaim_scripts()
assert app_not_in_desktop not in manager.app_scripts
if not case["has_key_error"]:
assert app_not_in_desktop not in manager.toolkit_scripts
assert app_not_in_desktop not in manager.custom_scripts
assert app_not_in_desktop not in manager._sleep_mode_scripts
def test_get_manager(self, test_context: CthulhuTestContext) -> None:
"""Test script_manager.get_manager."""
self._setup_dependencies(test_context)
from cthulhu import script_manager
manager1 = script_manager.get_manager()
manager2 = script_manager.get_manager()
assert manager1 is manager2
assert isinstance(manager1, script_manager.ScriptManager)