2002 lines
85 KiB
Python
2002 lines
85 KiB
Python
# Unit tests for event_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-lines
|
|
|
|
"""Unit tests for event_manager.py methods."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import itertools
|
|
import queue
|
|
from typing import TYPE_CHECKING
|
|
from unittest.mock import call
|
|
|
|
import gi
|
|
import pytest
|
|
|
|
gi.require_version("Atspi", "2.0")
|
|
from gi.repository import Atspi
|
|
|
|
if TYPE_CHECKING:
|
|
from unittest.mock import MagicMock
|
|
|
|
from .cthulhu_test_context import CthulhuTestContext
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestEventManager:
|
|
"""Test EventManager class methods."""
|
|
|
|
def _setup_ignore_event_ax_utilities_mocks(self, test_context, mock_time=5000.0, is_text=True):
|
|
"""Set up common AXUtilities mocks for event ignore testing scenarios."""
|
|
|
|
test_context.patch("time.time", return_value=mock_time)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_text", return_value=is_text)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_window", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_frame", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_notification", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_alert", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_selected", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_focused", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_section", return_value=False)
|
|
|
|
def _setup_dependencies(self, test_context: CthulhuTestContext) -> dict[str, MagicMock]:
|
|
"""Returns dependencies for event_manager module testing."""
|
|
|
|
additional_modules = [
|
|
"cthulhu.input_event_manager",
|
|
"cthulhu.cthulhu_modifier_manager",
|
|
"cthulhu.ax_utilities_debugging",
|
|
"cthulhu.ax_utilities",
|
|
"cthulhu.braille_presenter",
|
|
]
|
|
essential_modules = test_context.setup_shared_dependencies(additional_modules)
|
|
|
|
debug_mock = essential_modules["cthulhu.debug"]
|
|
debug_mock.LEVEL_INFO = 800
|
|
debug_mock.LEVEL_WARNING = 2
|
|
debug_mock.LEVEL_SEVERE = 3
|
|
debug_mock.debugLevel = 0
|
|
|
|
braille_presenter_mock = essential_modules["cthulhu.braille_presenter"]
|
|
braille_presenter_instance = test_context.Mock()
|
|
braille_presenter_instance.disable_braille = test_context.Mock()
|
|
braille_presenter_mock.get_presenter = test_context.Mock(
|
|
return_value=braille_presenter_instance,
|
|
)
|
|
|
|
focus_manager_mock = essential_modules["cthulhu.focus_manager"]
|
|
focus_mgr_instance = test_context.Mock()
|
|
focus_mgr_instance.get_locus_of_focus = test_context.Mock()
|
|
focus_mgr_instance.get_active_window = test_context.Mock()
|
|
focus_mgr_instance.focus_and_window_are_unknown = test_context.Mock(return_value=False)
|
|
focus_mgr_instance.clear_state = test_context.Mock()
|
|
focus_manager_mock.get_manager = test_context.Mock(return_value=focus_mgr_instance)
|
|
|
|
input_event_mock = essential_modules["cthulhu.input_event"]
|
|
|
|
class MockKeyboardEvent:
|
|
"""Mock KeyboardEvent for testing."""
|
|
|
|
class MockBrailleEvent:
|
|
"""Mock BrailleEvent for testing."""
|
|
|
|
class MockMouseButtonEvent:
|
|
"""Mock MouseButtonEvent for testing."""
|
|
|
|
input_event_mock.KeyboardEvent = MockKeyboardEvent
|
|
input_event_mock.BrailleEvent = MockBrailleEvent
|
|
input_event_mock.MouseButtonEvent = MockMouseButtonEvent
|
|
|
|
input_event_manager_mock = essential_modules["cthulhu.input_event_manager"]
|
|
input_mgr_instance = test_context.Mock()
|
|
input_mgr_instance.start_key_watcher = test_context.Mock()
|
|
input_mgr_instance.stop_key_watcher = test_context.Mock()
|
|
input_event_manager_mock.get_manager = test_context.Mock(return_value=input_mgr_instance)
|
|
|
|
script_manager_mock = essential_modules["cthulhu.script_manager"]
|
|
script_mgr_instance = test_context.Mock()
|
|
script_instance = test_context.Mock()
|
|
script_instance.app = test_context.Mock()
|
|
script_instance.event_cache = {}
|
|
script_instance.listeners = {}
|
|
script_instance.is_activatable_event = test_context.Mock(return_value=True)
|
|
script_instance.force_script_activation = test_context.Mock(return_value=False)
|
|
script_instance.present_if_inactive = False
|
|
script_mgr_instance.get_active_script = test_context.Mock(return_value=script_instance)
|
|
script_mgr_instance.get_script = test_context.Mock(return_value=script_instance)
|
|
script_mgr_instance.set_active_script = test_context.Mock()
|
|
script_mgr_instance.get_default_script = test_context.Mock(return_value=script_instance)
|
|
script_mgr_instance.reclaim_scripts = test_context.Mock()
|
|
script_manager_mock.get_manager = test_context.Mock(return_value=script_mgr_instance)
|
|
|
|
ax_utils_debugging_mock = essential_modules["cthulhu.ax_utilities_debugging"]
|
|
ax_utils_debugging_mock.object_event_details_as_string = test_context.Mock(
|
|
return_value="mock details",
|
|
)
|
|
|
|
ax_object_mock = essential_modules["cthulhu.ax_object"]
|
|
ax_object_mock.get_name = test_context.Mock()
|
|
ax_object_mock.get_parent = test_context.Mock()
|
|
ax_object_mock.get_role = test_context.Mock()
|
|
ax_object_mock.get_attribute = test_context.Mock()
|
|
ax_object_mock.is_dead = test_context.Mock(return_value=False)
|
|
ax_object_mock.has_state = test_context.Mock(return_value=False)
|
|
|
|
ax_utilities_mock = essential_modules["cthulhu.ax_utilities"]
|
|
ax_utilities_mock.is_frame = test_context.Mock(return_value=False)
|
|
ax_utilities_mock.is_dialog_or_alert = test_context.Mock(return_value=False)
|
|
ax_utilities_mock.is_window = test_context.Mock(return_value=False)
|
|
ax_utilities_mock.is_text = test_context.Mock(return_value=False)
|
|
ax_utilities_mock.is_notification = test_context.Mock(return_value=False)
|
|
ax_utilities_mock.is_alert = test_context.Mock(return_value=False)
|
|
ax_utilities_mock.is_selected = test_context.Mock(return_value=False)
|
|
ax_utilities_mock.is_focused = test_context.Mock(return_value=False)
|
|
ax_utilities_mock.manages_descendants = test_context.Mock(return_value=False)
|
|
ax_utilities_mock.is_section = test_context.Mock(return_value=False)
|
|
ax_utilities_mock.get_application = test_context.Mock()
|
|
ax_utilities_mock.get_desktop = test_context.Mock()
|
|
ax_utilities_mock.is_invalid_role = test_context.Mock(return_value=False)
|
|
ax_utilities_mock.is_menu_related = test_context.Mock(return_value=False)
|
|
ax_utilities_mock.is_image = test_context.Mock(return_value=False)
|
|
ax_utilities_mock.is_showing = test_context.Mock(return_value=True)
|
|
ax_utilities_mock.is_selectable = test_context.Mock(return_value=False)
|
|
ax_utilities_mock.is_menu = test_context.Mock(return_value=False)
|
|
ax_utilities_mock.is_focusable = test_context.Mock(return_value=False)
|
|
ax_utilities_mock.is_panel = test_context.Mock(return_value=False)
|
|
ax_utilities_mock.is_modal = test_context.Mock(return_value=False)
|
|
ax_utilities_mock.is_progress_bar = test_context.Mock(return_value=False)
|
|
ax_utilities_mock.is_defunct = test_context.Mock(return_value=False)
|
|
ax_utilities_mock.is_application_in_desktop = test_context.Mock(return_value=True)
|
|
ax_utilities_mock.is_iconified = test_context.Mock(return_value=False)
|
|
|
|
glib_mock = test_context.Mock()
|
|
glib_mock.idle_add = test_context.Mock(return_value=123)
|
|
glib_mock.timeout_add = test_context.Mock()
|
|
test_context.patch("gi.repository.GLib", new=glib_mock)
|
|
|
|
essential_modules["focus_manager_instance"] = focus_mgr_instance
|
|
essential_modules["input_event_manager_instance"] = input_mgr_instance
|
|
essential_modules["script_manager_instance"] = script_mgr_instance
|
|
essential_modules["script_instance"] = script_instance
|
|
essential_modules["glib"] = glib_mock
|
|
|
|
return essential_modules
|
|
|
|
def test_init(self, test_context: CthulhuTestContext) -> None:
|
|
"""Test EventManager.__init__."""
|
|
|
|
self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
assert not manager._script_listener_counts
|
|
assert manager._active is False
|
|
assert manager._paused is False
|
|
assert isinstance(manager._counter, itertools.count)
|
|
assert isinstance(manager._event_queue, queue.PriorityQueue)
|
|
assert manager._gidle_id == 0
|
|
assert not manager._event_history
|
|
|
|
@pytest.mark.parametrize(
|
|
"case",
|
|
[
|
|
{
|
|
"id": "activate_manager",
|
|
"operation": "activate",
|
|
"initial_active": False,
|
|
"initial_counts": {},
|
|
"expected_active": True,
|
|
"watcher_method": "start_key_watcher",
|
|
},
|
|
{
|
|
"id": "deactivate_manager",
|
|
"operation": "deactivate",
|
|
"initial_active": True,
|
|
"initial_counts": {"test": 1},
|
|
"expected_active": False,
|
|
"watcher_method": "stop_key_watcher",
|
|
},
|
|
],
|
|
ids=lambda case: case["id"],
|
|
)
|
|
def test_activation_operations(
|
|
self,
|
|
test_context: CthulhuTestContext,
|
|
case: dict,
|
|
) -> None:
|
|
"""Test EventManager activate/deactivate operations."""
|
|
self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
mock_get_input_mgr = test_context.Mock()
|
|
test_context.patch(
|
|
"cthulhu.event_manager.input_event_manager.get_manager",
|
|
new=mock_get_input_mgr,
|
|
)
|
|
mock_input_mgr = test_context.Mock()
|
|
mock_get_input_mgr.return_value = mock_input_mgr
|
|
manager = EventManager()
|
|
manager._active = case["initial_active"]
|
|
manager._script_listener_counts = case["initial_counts"].copy()
|
|
|
|
getattr(manager, case["operation"])()
|
|
assert manager._active is case["expected_active"]
|
|
|
|
if case["operation"] == "deactivate":
|
|
assert not manager._script_listener_counts
|
|
|
|
getattr(mock_input_mgr, case["watcher_method"]).assert_called_once()
|
|
|
|
getattr(mock_input_mgr, case["watcher_method"]).reset_mock()
|
|
getattr(manager, case["operation"])()
|
|
getattr(mock_input_mgr, case["watcher_method"]).assert_not_called()
|
|
|
|
@pytest.mark.parametrize(
|
|
"case",
|
|
[
|
|
{"id": "pause_only", "pause": True, "clear_queue": False, "reason": ""},
|
|
{
|
|
"id": "unpause_with_reason",
|
|
"pause": False,
|
|
"clear_queue": False,
|
|
"reason": "test reason",
|
|
},
|
|
{
|
|
"id": "pause_and_clear",
|
|
"pause": True,
|
|
"clear_queue": True,
|
|
"reason": "clear and pause",
|
|
},
|
|
],
|
|
ids=lambda case: case["id"],
|
|
)
|
|
def test_pause_queuing(self, test_context, case: dict) -> None:
|
|
"""Test EventManager.pause_queuing."""
|
|
|
|
self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
original_queue = manager._event_queue
|
|
manager.pause_queuing(case["pause"], case["clear_queue"], case["reason"])
|
|
assert manager._paused == case["pause"]
|
|
if case["clear_queue"]:
|
|
assert manager._event_queue is not original_queue
|
|
else:
|
|
assert manager._event_queue is original_queue
|
|
|
|
@pytest.mark.parametrize(
|
|
"case",
|
|
[
|
|
{"id": "window_event", "event_type": "window:activate", "expected_priority": 2},
|
|
{
|
|
"id": "focus_changed",
|
|
"event_type": "object:state-changed:focused",
|
|
"expected_priority": 3,
|
|
},
|
|
{
|
|
"id": "active_descendant",
|
|
"event_type": "object:active-descendant-changed",
|
|
"expected_priority": 3,
|
|
},
|
|
{
|
|
"id": "announcement_normal",
|
|
"event_type": "object:announcement",
|
|
"expected_priority": 4,
|
|
},
|
|
{
|
|
"id": "invalid_entry",
|
|
"event_type": "object:state-changed:invalid-entry",
|
|
"expected_priority": 5,
|
|
},
|
|
{
|
|
"id": "children_changed",
|
|
"event_type": "object:children-changed:add",
|
|
"expected_priority": 6,
|
|
},
|
|
{
|
|
"id": "other_event",
|
|
"event_type": "object:text-changed:insert",
|
|
"expected_priority": 4,
|
|
},
|
|
],
|
|
ids=lambda case: case["id"],
|
|
)
|
|
def test_get_priority(self, test_context, case: dict) -> None:
|
|
"""Test EventManager._get_priority."""
|
|
|
|
essential_modules: dict[str, MagicMock] = self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
mock_event = test_context.Mock(spec=Atspi.Event)
|
|
mock_event.type = case["event_type"]
|
|
mock_event.source = test_context.Mock()
|
|
mock_event.detail1 = 0
|
|
ax_utilities = essential_modules["cthulhu.ax_utilities"]
|
|
if case["event_type"] == "object:state-changed:active":
|
|
ax_utilities.is_frame.return_value = True
|
|
elif case["event_type"] == "object:announcement":
|
|
mock_event.detail1 = Atspi.Live.POLITE if case["expected_priority"] == 3 else 0
|
|
priority = manager._get_priority(mock_event)
|
|
assert priority == case["expected_priority"]
|
|
|
|
def test_get_priority_announcement_levels(self, test_context: CthulhuTestContext) -> None:
|
|
"""Test EventManager._get_priority for announcement event levels."""
|
|
|
|
self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
mock_event = test_context.Mock(spec=Atspi.Event)
|
|
mock_event.type = "object:announcement"
|
|
mock_event.source = test_context.Mock()
|
|
|
|
mock_event.detail1 = Atspi.Live.ASSERTIVE
|
|
priority = manager._get_priority(mock_event)
|
|
assert priority == 2 # PRIORITY_IMPORTANT
|
|
|
|
mock_event.detail1 = Atspi.Live.POLITE
|
|
priority = manager._get_priority(mock_event)
|
|
assert priority == 3 # PRIORITY_HIGH
|
|
|
|
mock_event.detail1 = 999
|
|
priority = manager._get_priority(mock_event)
|
|
assert priority == 4 # PRIORITY_NORMAL
|
|
|
|
@pytest.mark.parametrize(
|
|
"case",
|
|
[
|
|
{
|
|
"id": "duplicate_events",
|
|
"new_type": "object:text-changed:insert",
|
|
"new_detail1": 5,
|
|
"new_detail2": 10,
|
|
"new_data": "test",
|
|
"existing_type": "object:text-changed:insert",
|
|
"existing_detail1": 5,
|
|
"existing_detail2": 10,
|
|
"existing_data": "test",
|
|
"priority": 4,
|
|
"should_obsolete": True,
|
|
},
|
|
{
|
|
"id": "window_events",
|
|
"new_type": "window:activate",
|
|
"new_detail1": 0,
|
|
"new_detail2": 0,
|
|
"new_data": None,
|
|
"existing_type": "window:deactivate",
|
|
"existing_detail1": 0,
|
|
"existing_detail2": 0,
|
|
"existing_data": None,
|
|
"priority": 2,
|
|
"should_obsolete": True,
|
|
},
|
|
{
|
|
"id": "no_obsolescence",
|
|
"new_type": "object:text-changed:insert",
|
|
"new_detail1": 0,
|
|
"new_detail2": 0,
|
|
"new_data": None,
|
|
"existing_type": None,
|
|
"existing_detail1": None,
|
|
"existing_detail2": None,
|
|
"existing_data": None,
|
|
"priority": None,
|
|
"should_obsolete": False,
|
|
},
|
|
],
|
|
ids=lambda case: case["id"],
|
|
)
|
|
def test_is_obsoleted_by_scenarios(
|
|
self,
|
|
test_context: CthulhuTestContext,
|
|
case: dict,
|
|
) -> None:
|
|
"""Test EventManager._is_obsoleted_by for various obsolescence scenarios."""
|
|
self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
mock_event = test_context.Mock(spec=Atspi.Event)
|
|
mock_event.type = case["new_type"]
|
|
mock_event.source = test_context.Mock()
|
|
mock_event.detail1 = case["new_detail1"]
|
|
mock_event.detail2 = case["new_detail2"]
|
|
mock_event.any_data = case["new_data"]
|
|
|
|
if case["existing_type"] is not None and case["priority"] is not None:
|
|
existing_event = test_context.Mock(spec=Atspi.Event)
|
|
existing_event.type = case["existing_type"]
|
|
existing_event.source = mock_event.source
|
|
existing_event.detail1 = case["existing_detail1"]
|
|
existing_event.detail2 = case["existing_detail2"]
|
|
existing_event.any_data = case["existing_data"]
|
|
manager._event_queue.put((case["priority"], 1, existing_event))
|
|
|
|
result = manager._is_obsoleted_by(mock_event)
|
|
if case["should_obsolete"]:
|
|
assert result is not None
|
|
else:
|
|
assert result is None
|
|
|
|
@pytest.mark.parametrize(
|
|
"case",
|
|
[
|
|
{"id": "inactive_manager", "active": False, "paused": False, "expected": True},
|
|
{"id": "paused_manager", "active": True, "paused": True, "expected": True},
|
|
],
|
|
ids=lambda case: case["id"],
|
|
)
|
|
def test_ignore_inactive_or_paused_manager(
|
|
self,
|
|
test_context: CthulhuTestContext,
|
|
case: dict,
|
|
) -> None:
|
|
"""Test EventManager._ignore when manager is inactive or paused."""
|
|
self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
mock_event = test_context.Mock(spec=Atspi.Event)
|
|
mock_event.type = "object:text-changed:insert"
|
|
|
|
manager._active = case["active"]
|
|
manager._paused = case["paused"]
|
|
assert manager._ignore(mock_event) is case["expected"]
|
|
|
|
@pytest.mark.parametrize(
|
|
"case",
|
|
[
|
|
{
|
|
"id": "window_activate_event",
|
|
"event_type": "window:activate",
|
|
"event_config": {},
|
|
"expected_result": False,
|
|
},
|
|
{
|
|
"id": "mouse_button_event",
|
|
"event_type": "mouse:button:1p",
|
|
"event_config": {},
|
|
"expected_result": False,
|
|
},
|
|
{
|
|
"id": "focused_window_event",
|
|
"event_type": "object:state-changed:focused",
|
|
"event_config": {"is_window": True},
|
|
"expected_result": True,
|
|
},
|
|
],
|
|
ids=lambda case: case["id"],
|
|
)
|
|
def test_ignore_window_and_mouse_events(
|
|
self,
|
|
test_context: CthulhuTestContext,
|
|
case: dict,
|
|
) -> None:
|
|
"""Test EventManager._ignore for window, mouse, and focused window events."""
|
|
essential_modules: dict[str, MagicMock] = self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
manager._active = True
|
|
manager._paused = False
|
|
mock_event = test_context.Mock(spec=Atspi.Event)
|
|
mock_event.type = case["event_type"]
|
|
mock_event.source = test_context.Mock()
|
|
|
|
if "is_window" in case["event_config"]:
|
|
ax_utilities = essential_modules["cthulhu.ax_utilities"]
|
|
ax_utilities.is_window.return_value = case["event_config"]["is_window"]
|
|
|
|
assert manager._ignore(mock_event) is case["expected_result"]
|
|
|
|
def test_ignore_frame_events(self, test_context: CthulhuTestContext) -> None:
|
|
"""Test EventManager._ignore for frame events."""
|
|
|
|
essential_modules: dict[str, MagicMock] = self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
manager._active = True
|
|
manager._paused = False
|
|
mock_event = test_context.Mock(spec=Atspi.Event)
|
|
mock_event.type = "object:test-event"
|
|
mock_event.source = test_context.Mock()
|
|
mock_event.any_data = test_context.Mock()
|
|
mock_event.detail1 = 0
|
|
mock_event.detail2 = 0
|
|
ax_utilities = essential_modules["cthulhu.ax_utilities"]
|
|
focus_mgr = essential_modules["focus_manager_instance"]
|
|
|
|
focus_mgr.get_locus_of_focus.return_value = None
|
|
ax_utilities.is_window.return_value = False
|
|
test_context.patch("time.time", return_value=1000.0)
|
|
manager._event_history = {}
|
|
|
|
mock_app = test_context.Mock()
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_window", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_frame", return_value=True)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.get_application", return_value=mock_app)
|
|
test_context.patch("cthulhu.event_manager.AXObject.get_name", return_value="mutter-x11-frames")
|
|
assert manager._ignore(mock_event) is True
|
|
|
|
regular_app = test_context.Mock()
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_window", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_frame", return_value=True)
|
|
test_context.patch(
|
|
"cthulhu.event_manager.AXUtilities.get_application",
|
|
return_value=regular_app,
|
|
)
|
|
test_context.patch("cthulhu.event_manager.AXObject.get_name", return_value="regular-app")
|
|
assert manager._ignore(mock_event) is False
|
|
|
|
def test_ignore_text_events(self, test_context: CthulhuTestContext) -> None:
|
|
"""Test EventManager._ignore for text-related events."""
|
|
|
|
essential_modules: dict[str, MagicMock] = self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
manager._active = True
|
|
manager._paused = False
|
|
mock_event = test_context.Mock(spec=Atspi.Event)
|
|
mock_event.source = test_context.Mock()
|
|
mock_event.detail2 = 5001 # Large text insertion
|
|
mock_event.any_data = test_context.Mock()
|
|
mock_event.detail1 = 0
|
|
ax_utilities = essential_modules["cthulhu.ax_utilities"]
|
|
focus_mgr = essential_modules["focus_manager_instance"]
|
|
focus_mgr.get_locus_of_focus.return_value = None
|
|
ax_utilities.is_window.return_value = False
|
|
ax_utilities.is_frame.return_value = False
|
|
ax_utilities.is_text.return_value = True # This triggers text logic
|
|
ax_utilities.is_notification.return_value = False
|
|
ax_utilities.is_alert.return_value = False
|
|
ax_utilities.is_selected.return_value = False
|
|
ax_utilities.is_focused.return_value = False
|
|
ax_utilities.is_section.return_value = False
|
|
|
|
self._setup_ignore_event_ax_utilities_mocks(test_context, mock_time=5000.0, is_text=True)
|
|
manager._event_history = {}
|
|
|
|
mock_event.type = "object:text-changed:insert"
|
|
result = manager._ignore(mock_event)
|
|
assert result is True, f"Expected True but got {result}"
|
|
|
|
mock_event.detail2 = 100
|
|
assert manager._ignore(mock_event) is False
|
|
|
|
mock_event.type = "object:text-changed:delete"
|
|
assert manager._ignore(mock_event) is False
|
|
|
|
@pytest.mark.parametrize(
|
|
"case",
|
|
[
|
|
{
|
|
"id": "notification_event",
|
|
"utility_method": "is_notification",
|
|
"utility_value": True,
|
|
"expected_result": False,
|
|
},
|
|
{
|
|
"id": "alert_event",
|
|
"utility_method": "is_alert",
|
|
"utility_value": True,
|
|
"expected_result": False,
|
|
},
|
|
],
|
|
ids=lambda case: case["id"],
|
|
)
|
|
def test_ignore_notification_and_alert_events(
|
|
self,
|
|
test_context: CthulhuTestContext,
|
|
case: dict,
|
|
) -> None:
|
|
"""Test EventManager._ignore for notification and alert events."""
|
|
essential_modules: dict[str, MagicMock] = self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
manager._active = True
|
|
manager._paused = False
|
|
mock_event = test_context.Mock(spec=Atspi.Event)
|
|
mock_event.type = "object:test-event"
|
|
mock_event.source = test_context.Mock()
|
|
ax_utilities = essential_modules["cthulhu.ax_utilities"]
|
|
|
|
ax_utilities.is_notification.return_value = False
|
|
ax_utilities.is_alert.return_value = False
|
|
getattr(ax_utilities, case["utility_method"]).return_value = case["utility_value"]
|
|
|
|
assert manager._ignore(mock_event) is case["expected_result"]
|
|
|
|
def test_ignore_focus_and_selection_events(self, test_context: CthulhuTestContext) -> None:
|
|
"""Test EventManager._ignore for focus and selection events."""
|
|
|
|
essential_modules: dict[str, MagicMock] = self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
manager._active = True
|
|
manager._paused = False
|
|
mock_event = test_context.Mock(spec=Atspi.Event)
|
|
mock_event.type = "object:test-event"
|
|
mock_event.source = test_context.Mock()
|
|
mock_event.any_data = test_context.Mock()
|
|
ax_utilities = essential_modules["cthulhu.ax_utilities"]
|
|
focus_mgr = essential_modules["focus_manager_instance"]
|
|
|
|
focus_mgr.get_locus_of_focus.return_value = mock_event.source
|
|
assert manager._ignore(mock_event) is False
|
|
|
|
focus_mgr.get_locus_of_focus.return_value = mock_event.any_data
|
|
assert manager._ignore(mock_event) is False
|
|
|
|
focus_mgr.get_locus_of_focus.return_value = test_context.Mock()
|
|
ax_utilities.is_selected.return_value = True
|
|
assert manager._ignore(mock_event) is False
|
|
|
|
def test_ignore_focused_source_events(self, test_context: CthulhuTestContext) -> None:
|
|
"""Test EventManager._ignore for events from focused sources."""
|
|
|
|
essential_modules: dict[str, MagicMock] = self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
manager._active = True
|
|
manager._paused = False
|
|
mock_event = test_context.Mock(spec=Atspi.Event)
|
|
mock_event.source = test_context.Mock()
|
|
mock_event.detail1 = 1
|
|
mock_event.detail2 = 0
|
|
mock_event.any_data = test_context.Mock()
|
|
focus_mgr = essential_modules["focus_manager_instance"]
|
|
|
|
focus_mgr.get_locus_of_focus.return_value = None
|
|
test_context.patch("time.time", return_value=3000.0)
|
|
manager._event_history = {}
|
|
|
|
mock_event.type = "object:test-event"
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_window", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_frame", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_text", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_notification", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_alert", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_selected", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_focused", return_value=True)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.manages_descendants", return_value=False)
|
|
assert manager._ignore(mock_event) is False
|
|
|
|
# - should not be ignored
|
|
mock_event.type = "object:state-changed:focused"
|
|
mock_event.detail1 = 1
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_window", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_frame", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_text", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_notification", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_alert", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_selected", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_focused", return_value=True)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.manages_descendants", return_value=True)
|
|
assert manager._ignore(mock_event) is False
|
|
|
|
def test_ignore_live_region_events(self, test_context: CthulhuTestContext) -> None:
|
|
"""Test EventManager._ignore for live region events."""
|
|
|
|
essential_modules: dict[str, MagicMock] = self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
manager._active = True
|
|
manager._paused = False
|
|
mock_event = test_context.Mock(spec=Atspi.Event)
|
|
mock_event.type = "object:text-changed:insert"
|
|
mock_event.source = test_context.Mock()
|
|
mock_event.detail2 = 100
|
|
ax_utilities = essential_modules["cthulhu.ax_utilities"]
|
|
ax_object = essential_modules["cthulhu.ax_object"]
|
|
focus_mgr = essential_modules["focus_manager_instance"]
|
|
|
|
focus_mgr.get_locus_of_focus.return_value = test_context.Mock()
|
|
ax_utilities.is_selected.return_value = False
|
|
ax_utilities.is_focused.return_value = False
|
|
ax_utilities.is_section.return_value = True
|
|
|
|
ax_object.get_attribute.return_value = "polite"
|
|
assert manager._ignore(mock_event) is False
|
|
|
|
ax_object.get_attribute.return_value = "off"
|
|
mock_app = test_context.Mock()
|
|
ax_utilities.get_application.return_value = mock_app
|
|
manager._event_history = {}
|
|
assert manager._ignore(mock_event) is False
|
|
|
|
def test_ignore_spam_filtering(self, test_context: CthulhuTestContext) -> None:
|
|
"""Test EventManager._ignore spam filtering mechanism."""
|
|
|
|
essential_modules: dict[str, MagicMock] = self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
manager._active = True
|
|
manager._paused = False
|
|
mock_event = test_context.Mock(spec=Atspi.Event)
|
|
mock_event.type = "object:test-event"
|
|
mock_event.source = test_context.Mock()
|
|
mock_event.detail1 = 0
|
|
mock_event.detail2 = 0
|
|
mock_event.any_data = test_context.Mock()
|
|
mock_app = test_context.Mock()
|
|
original_hash = hash
|
|
|
|
def mock_hash(obj):
|
|
if obj is mock_app:
|
|
return 12345
|
|
return original_hash(obj)
|
|
|
|
test_context.patch("builtins.hash", new=mock_hash)
|
|
focus_mgr = essential_modules["focus_manager_instance"]
|
|
focus_mgr.get_locus_of_focus.return_value = None
|
|
|
|
manager._event_history = {}
|
|
test_context.patch("time.time", return_value=100.0)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_window", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_frame", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_text", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_notification", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_alert", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_selected", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_focused", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_section", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.manages_descendants", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.get_application", return_value=mock_app)
|
|
test_context.patch("cthulhu.event_manager.AXObject.get_name", return_value="regular-app")
|
|
test_context.patch("cthulhu.event_manager.AXObject.get_attribute", return_value=None)
|
|
test_context.patch("cthulhu.event_manager.focus_manager.get_manager", return_value=focus_mgr)
|
|
assert manager._ignore(mock_event) is False
|
|
|
|
test_context.patch("time.time", return_value=100.05)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_window", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_frame", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_text", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_notification", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_alert", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_selected", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_focused", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_section", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.manages_descendants", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.get_application", return_value=mock_app)
|
|
test_context.patch("cthulhu.event_manager.AXObject.get_name", return_value="regular-app")
|
|
test_context.patch("cthulhu.event_manager.AXObject.get_attribute", return_value=None)
|
|
test_context.patch("cthulhu.event_manager.focus_manager.get_manager", return_value=focus_mgr)
|
|
assert manager._ignore(mock_event) is True
|
|
|
|
test_context.patch("time.time", return_value=100.2)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_window", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_frame", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_text", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_notification", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_alert", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_selected", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_focused", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_section", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.manages_descendants", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.get_application", return_value=mock_app)
|
|
test_context.patch("cthulhu.event_manager.AXObject.get_name", return_value="regular-app")
|
|
test_context.patch("cthulhu.event_manager.AXObject.get_attribute", return_value=None)
|
|
test_context.patch("cthulhu.event_manager.focus_manager.get_manager", return_value=focus_mgr)
|
|
assert manager._ignore(mock_event) is False
|
|
|
|
def test_ignore_mutter_events(self, test_context: CthulhuTestContext) -> None:
|
|
"""Test EventManager._ignore for mutter-x11-frames events."""
|
|
|
|
essential_modules: dict[str, MagicMock] = self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
manager._active = True
|
|
manager._paused = False
|
|
mock_event = test_context.Mock(spec=Atspi.Event)
|
|
mock_event.type = "object:test-event"
|
|
mock_event.source = test_context.Mock()
|
|
mock_event.detail1 = 0
|
|
mock_event.detail2 = 0
|
|
mock_event.any_data = test_context.Mock()
|
|
mock_app = test_context.Mock()
|
|
original_hash = hash
|
|
|
|
def mock_hash(obj):
|
|
if obj is mock_app:
|
|
return 12345
|
|
return original_hash(obj)
|
|
|
|
test_context.patch("builtins.hash", new=mock_hash)
|
|
focus_mgr = essential_modules["focus_manager_instance"]
|
|
focus_mgr.get_locus_of_focus.return_value = None
|
|
|
|
test_context.patch("time.time", return_value=100.0)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_window", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_frame", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_text", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_notification", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_alert", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_selected", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_focused", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_section", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.manages_descendants", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.get_application", return_value=mock_app)
|
|
test_context.patch("cthulhu.event_manager.AXObject.get_name", return_value="mutter-x11-frames")
|
|
test_context.patch("cthulhu.event_manager.AXObject.get_attribute", return_value=None)
|
|
test_context.patch("cthulhu.event_manager.focus_manager.get_manager", return_value=focus_mgr)
|
|
manager._event_history = {}
|
|
assert manager._ignore(mock_event) is True
|
|
|
|
@pytest.mark.parametrize(
|
|
"case",
|
|
[
|
|
{
|
|
"id": "window_event",
|
|
"event_type": "window:activate",
|
|
"is_frame": False,
|
|
"is_dialog_or_alert": False,
|
|
"detail1": 0,
|
|
"expected_priority": 2,
|
|
},
|
|
{
|
|
"id": "active_frame",
|
|
"event_type": "object:state-changed:active",
|
|
"is_frame": True,
|
|
"is_dialog_or_alert": False,
|
|
"detail1": 0,
|
|
"expected_priority": 2,
|
|
},
|
|
{
|
|
"id": "active_dialog",
|
|
"event_type": "object:state-changed:active",
|
|
"is_frame": False,
|
|
"is_dialog_or_alert": True,
|
|
"detail1": 0,
|
|
"expected_priority": 2,
|
|
},
|
|
{
|
|
"id": "focused_event",
|
|
"event_type": "object:state-changed:focused",
|
|
"is_frame": False,
|
|
"is_dialog_or_alert": False,
|
|
"detail1": 0,
|
|
"expected_priority": 3,
|
|
},
|
|
{
|
|
"id": "active_descendant",
|
|
"event_type": "object:active-descendant-changed",
|
|
"is_frame": False,
|
|
"is_dialog_or_alert": False,
|
|
"detail1": 0,
|
|
"expected_priority": 3,
|
|
},
|
|
{
|
|
"id": "assertive_announcement",
|
|
"event_type": "object:announcement",
|
|
"is_frame": False,
|
|
"is_dialog_or_alert": False,
|
|
"detail1": 2,
|
|
"expected_priority": 2,
|
|
},
|
|
{
|
|
"id": "polite_announcement",
|
|
"event_type": "object:announcement",
|
|
"is_frame": False,
|
|
"is_dialog_or_alert": False,
|
|
"detail1": 1,
|
|
"expected_priority": 3,
|
|
},
|
|
{
|
|
"id": "other_announcement",
|
|
"event_type": "object:announcement",
|
|
"is_frame": False,
|
|
"is_dialog_or_alert": False,
|
|
"detail1": 3,
|
|
"expected_priority": 4,
|
|
},
|
|
{
|
|
"id": "invalid_entry",
|
|
"event_type": "object:state-changed:invalid-entry",
|
|
"is_frame": False,
|
|
"is_dialog_or_alert": False,
|
|
"detail1": 0,
|
|
"expected_priority": 5,
|
|
},
|
|
{
|
|
"id": "children_changed",
|
|
"event_type": "object:children-changed:add",
|
|
"is_frame": False,
|
|
"is_dialog_or_alert": False,
|
|
"detail1": 0,
|
|
"expected_priority": 6,
|
|
},
|
|
{
|
|
"id": "default_normal",
|
|
"event_type": "object:text-changed:insert",
|
|
"is_frame": False,
|
|
"is_dialog_or_alert": False,
|
|
"detail1": 0,
|
|
"expected_priority": 4,
|
|
},
|
|
],
|
|
ids=lambda case: case["id"],
|
|
)
|
|
def test_get_priority_various_event_types(
|
|
self,
|
|
test_context: CthulhuTestContext,
|
|
case: dict,
|
|
) -> None:
|
|
"""Test EventManager._get_priority with various event types and conditions."""
|
|
|
|
essential_modules: dict[str, MagicMock] = self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
mock_event = test_context.Mock(spec=Atspi.Event)
|
|
mock_event.type = case["event_type"]
|
|
mock_event.detail1 = case["detail1"]
|
|
mock_event.source = test_context.Mock()
|
|
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_frame", return_value=case["is_frame"])
|
|
test_context.patch(
|
|
"cthulhu.event_manager.AXUtilities.is_dialog_or_alert",
|
|
return_value=case["is_dialog_or_alert"],
|
|
)
|
|
|
|
result = manager._get_priority(mock_event)
|
|
assert result == case["expected_priority"]
|
|
essential_modules["cthulhu.debug"].print_tokens.assert_called()
|
|
|
|
@pytest.mark.parametrize(
|
|
"case",
|
|
[
|
|
{
|
|
"id": "notification_not_ignored",
|
|
"source_role": "notification",
|
|
"event_type": "object:text-changed:insert",
|
|
"source_is_focused": False,
|
|
"manages_descendants": False,
|
|
"detail1": 0,
|
|
"expected_ignore": False,
|
|
},
|
|
{
|
|
"id": "alert_not_ignored",
|
|
"source_role": "alert",
|
|
"event_type": "object:text-changed:insert",
|
|
"source_is_focused": False,
|
|
"manages_descendants": False,
|
|
"detail1": 0,
|
|
"expected_ignore": False,
|
|
},
|
|
{
|
|
"id": "large_text_insertion_ignored",
|
|
"source_role": "text",
|
|
"event_type": "object:text-changed:insert",
|
|
"source_is_focused": False,
|
|
"manages_descendants": False,
|
|
"detail1": 5001,
|
|
"expected_ignore": True,
|
|
},
|
|
{
|
|
"id": "small_text_insertion_not_ignored",
|
|
"source_role": "text",
|
|
"event_type": "object:text-changed:insert",
|
|
"source_is_focused": False,
|
|
"manages_descendants": False,
|
|
"detail1": 4999,
|
|
"expected_ignore": False,
|
|
},
|
|
{
|
|
"id": "focused_source_not_ignored",
|
|
"source_role": "push_button",
|
|
"event_type": "object:state-changed:focused",
|
|
"source_is_focused": True,
|
|
"manages_descendants": False,
|
|
"detail1": 0,
|
|
"expected_ignore": False,
|
|
},
|
|
{
|
|
"id": "focused_managing_descendants_with_detail1",
|
|
"source_role": "push_button",
|
|
"event_type": "object:state-changed:focused",
|
|
"source_is_focused": True,
|
|
"manages_descendants": True,
|
|
"detail1": 1,
|
|
"expected_ignore": False,
|
|
},
|
|
{
|
|
"id": "window_focused_event_ignored",
|
|
"source_role": "window",
|
|
"event_type": "object:state-changed:focused",
|
|
"source_is_focused": False,
|
|
"manages_descendants": False,
|
|
"detail1": 0,
|
|
"expected_ignore": True,
|
|
},
|
|
],
|
|
ids=lambda case: case["id"],
|
|
)
|
|
def test_ignore_event_conditions(
|
|
self,
|
|
test_context: CthulhuTestContext,
|
|
case: dict,
|
|
) -> None:
|
|
"""Test EventManager._ignore with various event conditions."""
|
|
|
|
essential_modules: dict[str, MagicMock] = self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
manager._active = True
|
|
manager._paused = False
|
|
manager._event_history = {}
|
|
|
|
mock_event = test_context.Mock(spec=Atspi.Event)
|
|
mock_event.type = case["event_type"]
|
|
mock_event.detail1 = case["detail1"]
|
|
mock_event.detail2 = (
|
|
0 if case["event_type"] != "object:text-changed:insert" else case["detail1"]
|
|
)
|
|
mock_event.source = test_context.Mock()
|
|
mock_event.any_data = test_context.Mock()
|
|
|
|
mock_app = test_context.Mock()
|
|
focus_mgr = essential_modules["focus_manager_instance"]
|
|
focus_mgr.get_locus_of_focus.return_value = None
|
|
|
|
test_context.patch("time.time", return_value=100.0)
|
|
test_context.patch(
|
|
"cthulhu.event_manager.AXUtilities.is_window",
|
|
side_effect=lambda obj: (
|
|
case["source_role"] == "window" if obj == mock_event.source else False
|
|
),
|
|
)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_frame", return_value=False)
|
|
test_context.patch(
|
|
"cthulhu.event_manager.AXUtilities.is_text",
|
|
side_effect=lambda obj: (
|
|
case["source_role"] == "text" if obj == mock_event.source else False
|
|
),
|
|
)
|
|
test_context.patch(
|
|
"cthulhu.event_manager.AXUtilities.is_notification",
|
|
side_effect=lambda obj: case["source_role"] == "notification"
|
|
if obj == mock_event.source
|
|
else False,
|
|
)
|
|
test_context.patch(
|
|
"cthulhu.event_manager.AXUtilities.is_alert",
|
|
side_effect=lambda obj: (
|
|
case["source_role"] == "alert" if obj == mock_event.source else False
|
|
),
|
|
)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_selected", return_value=False)
|
|
test_context.patch(
|
|
"cthulhu.event_manager.AXUtilities.is_focused",
|
|
side_effect=lambda obj: (
|
|
case["source_is_focused"] if obj == mock_event.source else False
|
|
),
|
|
)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_section", return_value=False)
|
|
test_context.patch(
|
|
"cthulhu.event_manager.AXUtilities.manages_descendants",
|
|
side_effect=lambda obj: (
|
|
case["manages_descendants"] if obj == mock_event.source else False
|
|
),
|
|
)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.get_application", return_value=mock_app)
|
|
test_context.patch("cthulhu.event_manager.AXObject.get_name", return_value="regular-app")
|
|
|
|
result = manager._ignore(mock_event)
|
|
assert result is case["expected_ignore"]
|
|
|
|
def test_ignore_live_region_text_insertion(self, test_context: CthulhuTestContext) -> None:
|
|
"""Test EventManager._ignore for live region text insertions."""
|
|
|
|
essential_modules: dict[str, MagicMock] = self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
manager._active = True
|
|
manager._paused = False
|
|
manager._event_history = {}
|
|
|
|
mock_event = test_context.Mock(spec=Atspi.Event)
|
|
mock_event.type = "object:text-changed:insert"
|
|
mock_event.detail1 = 0
|
|
mock_event.detail2 = 100
|
|
mock_event.source = test_context.Mock()
|
|
mock_event.any_data = test_context.Mock()
|
|
|
|
mock_app = test_context.Mock()
|
|
focus_mgr = essential_modules["focus_manager_instance"]
|
|
focus_mgr.get_locus_of_focus.return_value = None
|
|
|
|
test_context.patch("time.time", return_value=100.0)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_window", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_frame", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_text", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_notification", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_alert", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_selected", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_focused", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_section", return_value=True)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.manages_descendants", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.get_application", return_value=mock_app)
|
|
test_context.patch("cthulhu.event_manager.AXObject.get_name", return_value="regular-app")
|
|
test_context.patch(
|
|
"cthulhu.event_manager.AXObject.get_attribute",
|
|
side_effect=lambda obj, attr: "polite" if attr == "live" else None,
|
|
)
|
|
|
|
result = manager._ignore(mock_event)
|
|
assert result is False
|
|
|
|
def test_ignore_event_history_filtering(self, test_context: CthulhuTestContext) -> None:
|
|
"""Test EventManager._ignore with event history spam filtering."""
|
|
|
|
essential_modules: dict[str, MagicMock] = self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
manager._active = True
|
|
manager._paused = False
|
|
manager._event_history = {}
|
|
|
|
mock_event = test_context.Mock(spec=Atspi.Event)
|
|
mock_event.type = "object:test-event"
|
|
mock_event.detail1 = 0
|
|
mock_event.detail2 = 0
|
|
mock_event.source = test_context.Mock()
|
|
mock_event.any_data = test_context.Mock()
|
|
|
|
mock_app = test_context.Mock()
|
|
focus_mgr = essential_modules["focus_manager_instance"]
|
|
focus_mgr.get_locus_of_focus.return_value = None
|
|
|
|
original_hash = hash
|
|
|
|
def mock_hash(obj):
|
|
if obj is mock_app:
|
|
return 12345
|
|
return original_hash(obj)
|
|
|
|
test_context.patch("builtins.hash", new=mock_hash)
|
|
test_context.patch("time.time", return_value=100.0)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_window", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_frame", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_text", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_notification", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_alert", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_selected", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_focused", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_section", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.manages_descendants", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.get_application", return_value=mock_app)
|
|
test_context.patch("cthulhu.event_manager.AXObject.get_name", return_value="regular-app")
|
|
test_context.patch("cthulhu.event_manager.AXObject.get_attribute", return_value=None)
|
|
|
|
# First call should not be ignored
|
|
result1 = manager._ignore(mock_event)
|
|
assert result1 is False
|
|
assert manager._event_history["object:test-event"] == (12345, 100.0)
|
|
|
|
# Second call within 0.1s should be ignored
|
|
test_context.patch("time.time", return_value=100.05)
|
|
result2 = manager._ignore(mock_event)
|
|
assert result2 is True
|
|
|
|
def test_is_obsoleted_by_identical_events(self, test_context: CthulhuTestContext) -> None:
|
|
"""Test EventManager._is_obsoleted_by with identical events in queue."""
|
|
|
|
self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
mock_event = test_context.Mock(spec=Atspi.Event)
|
|
mock_event.type = "object:test-event"
|
|
mock_event.source = test_context.Mock()
|
|
mock_event.detail1 = 1
|
|
mock_event.detail2 = 2
|
|
mock_event.any_data = "test_data"
|
|
|
|
identical_event = test_context.Mock(spec=Atspi.Event)
|
|
identical_event.type = "object:test-event"
|
|
identical_event.source = mock_event.source
|
|
identical_event.detail1 = 1
|
|
identical_event.detail2 = 2
|
|
identical_event.any_data = "test_data"
|
|
|
|
queue_data = [(4, 1, identical_event)]
|
|
with manager._event_queue.mutex:
|
|
manager._event_queue.queue = queue_data
|
|
|
|
result = manager._is_obsoleted_by(mock_event)
|
|
assert result == identical_event
|
|
|
|
@pytest.mark.parametrize(
|
|
"case",
|
|
[
|
|
{
|
|
"id": "active_descendant_obsoletes",
|
|
"existing_type": "object:active-descendant-changed",
|
|
"existing_source_same": True,
|
|
"new_type": "object:active-descendant-changed",
|
|
"should_obsolete": True,
|
|
},
|
|
{
|
|
"id": "state_changed_obsoletes",
|
|
"existing_type": "object:state-changed:focused",
|
|
"existing_source_same": True,
|
|
"new_type": "object:state-changed:focused",
|
|
"should_obsolete": True,
|
|
},
|
|
{
|
|
"id": "caret_moved_obsoletes",
|
|
"existing_type": "object:text-caret-moved",
|
|
"existing_source_same": True,
|
|
"new_type": "object:text-caret-moved",
|
|
"should_obsolete": True,
|
|
},
|
|
{
|
|
"id": "window_activate_obsoletes",
|
|
"existing_type": "window:activate",
|
|
"existing_source_same": True,
|
|
"new_type": "window:activate",
|
|
"should_obsolete": True,
|
|
},
|
|
{
|
|
"id": "non_skippable_does_not_obsolete",
|
|
"existing_type": "object:other-event",
|
|
"existing_source_same": True,
|
|
"new_type": "object:other-event",
|
|
"should_obsolete": False,
|
|
},
|
|
],
|
|
ids=lambda case: case["id"],
|
|
)
|
|
def test_is_obsoleted_by_same_type_and_object(
|
|
self,
|
|
test_context: CthulhuTestContext,
|
|
case: dict,
|
|
) -> None:
|
|
"""Test EventManager._is_obsoleted_by with same type and object conditions."""
|
|
|
|
self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
mock_source = test_context.Mock()
|
|
mock_event = test_context.Mock(spec=Atspi.Event)
|
|
mock_event.type = case["new_type"]
|
|
mock_event.source = mock_source
|
|
mock_event.detail1 = 0
|
|
mock_event.detail2 = 0
|
|
mock_event.any_data = None
|
|
|
|
existing_event = test_context.Mock(spec=Atspi.Event)
|
|
existing_event.type = case["existing_type"]
|
|
existing_event.source = mock_source if case["existing_source_same"] else test_context.Mock()
|
|
existing_event.detail1 = (
|
|
0 if case["should_obsolete"] else 1
|
|
) # Make it different for non-obsoleting case
|
|
existing_event.detail2 = 0
|
|
existing_event.any_data = None
|
|
|
|
queue_data = [(4, 1, existing_event)]
|
|
with manager._event_queue.mutex:
|
|
manager._event_queue.queue = queue_data
|
|
|
|
result = manager._is_obsoleted_by(mock_event)
|
|
if case["should_obsolete"]:
|
|
assert result == existing_event
|
|
else:
|
|
assert result is None
|
|
|
|
def test_is_obsoleted_by_sibling_events(self, test_context: CthulhuTestContext) -> None:
|
|
"""Test EventManager._is_obsoleted_by with sibling event conditions."""
|
|
|
|
self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
parent_mock = test_context.Mock()
|
|
mock_source = test_context.Mock()
|
|
sibling_source = test_context.Mock()
|
|
|
|
mock_event = test_context.Mock(spec=Atspi.Event)
|
|
mock_event.type = "object:state-changed:focused"
|
|
mock_event.source = mock_source
|
|
mock_event.detail1 = 1
|
|
mock_event.detail2 = 2
|
|
mock_event.any_data = "test_data"
|
|
|
|
sibling_event = test_context.Mock(spec=Atspi.Event)
|
|
sibling_event.type = "object:state-changed:focused"
|
|
sibling_event.source = sibling_source
|
|
sibling_event.detail1 = 1
|
|
sibling_event.detail2 = 2
|
|
sibling_event.any_data = "test_data"
|
|
|
|
test_context.patch(
|
|
"cthulhu.event_manager.AXObject.get_parent",
|
|
side_effect=lambda obj: parent_mock
|
|
if obj in [mock_source, sibling_source]
|
|
else test_context.Mock(),
|
|
)
|
|
|
|
queue_data = [(4, 1, sibling_event)]
|
|
with manager._event_queue.mutex:
|
|
manager._event_queue.queue = queue_data
|
|
|
|
result = manager._is_obsoleted_by(mock_event)
|
|
assert result == sibling_event
|
|
|
|
def test_is_obsoleted_by_window_events(self, test_context: CthulhuTestContext) -> None:
|
|
"""Test EventManager._is_obsoleted_by with window event conditions."""
|
|
|
|
self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
mock_source = test_context.Mock()
|
|
|
|
mock_event = test_context.Mock(spec=Atspi.Event)
|
|
mock_event.type = "window:activate"
|
|
mock_event.source = mock_source
|
|
mock_event.detail1 = 0
|
|
mock_event.detail2 = 0
|
|
mock_event.any_data = None
|
|
|
|
existing_event = test_context.Mock(spec=Atspi.Event)
|
|
existing_event.type = "window:activate"
|
|
existing_event.source = mock_source
|
|
existing_event.detail1 = 0
|
|
existing_event.detail2 = 0
|
|
existing_event.any_data = None
|
|
|
|
queue_data = [(4, 1, existing_event)]
|
|
with manager._event_queue.mutex:
|
|
manager._event_queue.queue = queue_data
|
|
|
|
result = manager._is_obsoleted_by(mock_event)
|
|
assert result == existing_event
|
|
|
|
def test_focus_conditions_in_ignore(self, test_context: CthulhuTestContext) -> None:
|
|
"""Test EventManager._ignore focus-related conditions."""
|
|
|
|
essential_modules: dict[str, MagicMock] = self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
manager._active = True
|
|
manager._paused = False
|
|
manager._event_history = {}
|
|
|
|
mock_event = test_context.Mock(spec=Atspi.Event)
|
|
mock_event.type = "object:test-event"
|
|
mock_event.detail1 = 0
|
|
mock_event.detail2 = 0
|
|
mock_event.source = test_context.Mock()
|
|
mock_event.any_data = test_context.Mock()
|
|
|
|
focus_mgr = essential_modules["focus_manager_instance"]
|
|
|
|
focus_mgr.get_locus_of_focus.return_value = mock_event.source
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_window", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_frame", return_value=False)
|
|
|
|
result = manager._ignore(mock_event)
|
|
assert result is False
|
|
|
|
focus_mgr.get_locus_of_focus.return_value = mock_event.any_data
|
|
result = manager._ignore(mock_event)
|
|
assert result is False
|
|
|
|
def test_ignore_selected_source_not_ignored(self, test_context: CthulhuTestContext) -> None:
|
|
"""Test EventManager._ignore does not ignore events from selected sources."""
|
|
|
|
essential_modules: dict[str, MagicMock] = self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
manager._active = True
|
|
manager._paused = False
|
|
manager._event_history = {}
|
|
|
|
mock_event = test_context.Mock(spec=Atspi.Event)
|
|
mock_event.type = "object:test-event"
|
|
mock_event.detail1 = 0
|
|
mock_event.detail2 = 0
|
|
mock_event.source = test_context.Mock()
|
|
mock_event.any_data = test_context.Mock()
|
|
|
|
mock_app = test_context.Mock()
|
|
focus_mgr = essential_modules["focus_manager_instance"]
|
|
focus_mgr.get_locus_of_focus.return_value = None
|
|
|
|
test_context.patch("time.time", return_value=100.0)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_window", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_frame", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_text", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_notification", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_alert", return_value=False)
|
|
test_context.patch(
|
|
"cthulhu.event_manager.AXUtilities.is_selected",
|
|
return_value=True, # Selected source should not be ignored
|
|
)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_focused", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_section", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.manages_descendants", return_value=False)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.get_application", return_value=mock_app)
|
|
test_context.patch("cthulhu.event_manager.AXObject.get_name", return_value="regular-app")
|
|
test_context.patch("cthulhu.event_manager.AXObject.get_attribute", return_value=None)
|
|
|
|
result = manager._ignore(mock_event)
|
|
assert result is False
|
|
|
|
def test_queue_println(self, test_context: CthulhuTestContext) -> None:
|
|
"""Test EventManager._queue_println."""
|
|
|
|
essential_modules: dict[str, MagicMock] = self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
mock_event = test_context.Mock(spec=Atspi.Event)
|
|
mock_event.type = "test:event"
|
|
debug_mock = essential_modules["cthulhu.debug"]
|
|
debug_mock.debugLevel = 10
|
|
manager._queue_println(mock_event)
|
|
manager._queue_println(mock_event, is_enqueue=False)
|
|
|
|
def test_enqueue_object_event_ignored(self, test_context: CthulhuTestContext) -> None:
|
|
"""Test EventManager._enqueue_object_event for ignored events."""
|
|
|
|
self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
manager._active = False
|
|
mock_event = test_context.Mock(spec=Atspi.Event)
|
|
mock_event.type = "object:text-changed:insert"
|
|
manager._enqueue_object_event(mock_event)
|
|
assert manager._event_queue.empty()
|
|
|
|
def test_on_no_focus(self, test_context: CthulhuTestContext) -> None:
|
|
"""Test EventManager._on_no_focus."""
|
|
|
|
self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
mock_get_focus_mgr = test_context.Mock()
|
|
test_context.patch("cthulhu.event_manager.focus_manager.get_manager", new=mock_get_focus_mgr)
|
|
mock_get_script_mgr = test_context.Mock()
|
|
test_context.patch("cthulhu.event_manager.script_manager.get_manager", new=mock_get_script_mgr)
|
|
mock_braille_presenter = test_context.Mock()
|
|
mock_presenter_instance = test_context.Mock()
|
|
mock_braille_presenter.get_presenter.return_value = mock_presenter_instance
|
|
test_context.patch("cthulhu.event_manager.braille_presenter", new=mock_braille_presenter)
|
|
mock_focus_mgr = test_context.Mock()
|
|
mock_script_mgr = test_context.Mock()
|
|
mock_get_focus_mgr.return_value = mock_focus_mgr
|
|
mock_get_script_mgr.return_value = mock_script_mgr
|
|
|
|
mock_focus_mgr.focus_and_window_are_unknown.return_value = True
|
|
result = manager._on_no_focus()
|
|
assert result is False
|
|
|
|
mock_focus_mgr.focus_and_window_are_unknown.return_value = False
|
|
mock_script_mgr.get_active_script.return_value = None
|
|
result = manager._on_no_focus()
|
|
assert result is False
|
|
mock_script_mgr.set_active_script.assert_called_once()
|
|
mock_presenter_instance.disable_braille.assert_called_once()
|
|
|
|
def test_dequeue_object_event_empty_queue(self, test_context: CthulhuTestContext) -> None:
|
|
"""Test EventManager._dequeue_object_event with empty queue."""
|
|
|
|
self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
result = manager._dequeue_object_event()
|
|
assert result is False
|
|
assert manager._gidle_id == 0
|
|
|
|
@pytest.mark.parametrize(
|
|
"case",
|
|
[
|
|
{
|
|
"id": "first_registration",
|
|
"operation": "register",
|
|
"initial_count": 0,
|
|
"expected_count": 1,
|
|
"should_call_listener": True,
|
|
},
|
|
{
|
|
"id": "duplicate_registration",
|
|
"operation": "register",
|
|
"initial_count": 1,
|
|
"expected_count": 2,
|
|
"should_call_listener": False,
|
|
},
|
|
{
|
|
"id": "partial_deregistration",
|
|
"operation": "deregister",
|
|
"initial_count": 2,
|
|
"expected_count": 1,
|
|
"should_call_listener": False,
|
|
},
|
|
{
|
|
"id": "final_deregistration",
|
|
"operation": "deregister",
|
|
"initial_count": 1,
|
|
"expected_count": 0,
|
|
"should_call_listener": True,
|
|
},
|
|
],
|
|
ids=lambda case: case["id"],
|
|
)
|
|
def test_listener_registration(
|
|
self,
|
|
test_context: CthulhuTestContext,
|
|
case: dict,
|
|
) -> None:
|
|
"""Test EventManager register/deregister listener operations."""
|
|
self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
manager._listener = test_context.Mock()
|
|
event_type = "object:text-changed:insert"
|
|
|
|
if case["initial_count"] > 0:
|
|
manager._script_listener_counts[event_type] = case["initial_count"]
|
|
|
|
if case["operation"] == "register":
|
|
manager.register_listener(event_type)
|
|
if case["should_call_listener"]:
|
|
manager._listener.register.assert_called_once_with(event_type)
|
|
else:
|
|
manager._listener.register.assert_not_called()
|
|
else: # deregister
|
|
manager.deregister_listener(event_type)
|
|
if case["should_call_listener"]:
|
|
manager._listener.deregister.assert_called_once_with(event_type)
|
|
else:
|
|
manager._listener.deregister.assert_not_called()
|
|
|
|
if case["expected_count"] == 0:
|
|
assert event_type not in manager._script_listener_counts
|
|
else:
|
|
assert manager._script_listener_counts[event_type] == case["expected_count"]
|
|
|
|
@pytest.mark.parametrize(
|
|
"case",
|
|
[
|
|
{"id": "register_script_listeners", "operation": "register"},
|
|
{"id": "deregister_script_listeners", "operation": "deregister"},
|
|
],
|
|
ids=lambda case: case["id"],
|
|
)
|
|
def test_script_listeners_operations(self, test_context: CthulhuTestContext, case: dict) -> None:
|
|
"""Test EventManager register/deregister script listeners operations."""
|
|
self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
mock_listener_method = test_context.Mock()
|
|
method_name = f"{case['operation']}_listener"
|
|
test_context.patch_object(manager, method_name, new=mock_listener_method)
|
|
|
|
mock_script = test_context.Mock()
|
|
mock_script.listeners = {
|
|
"object:text-changed:insert": test_context.Mock(),
|
|
"object:state-changed:focused": test_context.Mock(),
|
|
}
|
|
|
|
getattr(manager, f"{case['operation']}_script_listeners")(mock_script)
|
|
|
|
expected_calls = [
|
|
call("object:text-changed:insert"),
|
|
call("object:state-changed:focused"),
|
|
]
|
|
mock_listener_method.assert_has_calls(expected_calls, any_order=True)
|
|
|
|
def test_get_script_for_event_focus(self, test_context: CthulhuTestContext) -> None:
|
|
"""Test EventManager._get_script_for_event for focused events."""
|
|
|
|
self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
mock_event = test_context.Mock(spec=Atspi.Event)
|
|
mock_event.source = test_context.Mock()
|
|
mock_event.type = "object:state-changed:focused"
|
|
active_script = test_context.Mock()
|
|
mock_get_focus_mgr = test_context.Mock()
|
|
test_context.patch("cthulhu.event_manager.focus_manager.get_manager", new=mock_get_focus_mgr)
|
|
mock_focus_mgr = test_context.Mock()
|
|
mock_get_focus_mgr.return_value = mock_focus_mgr
|
|
mock_focus_mgr.get_locus_of_focus.return_value = mock_event.source
|
|
result = manager._get_script_for_event(mock_event, active_script)
|
|
assert result == active_script
|
|
|
|
def test_get_script_for_event_mouse(self, test_context: CthulhuTestContext) -> None:
|
|
"""Test EventManager._get_script_for_event for mouse events."""
|
|
|
|
self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
mock_event = test_context.Mock(spec=Atspi.Event)
|
|
mock_event.type = "mouse:button:1p"
|
|
mock_event.source = test_context.Mock()
|
|
expected_script = test_context.Mock()
|
|
mock_mouse_event = test_context.Mock()
|
|
mock_mouse_event.app = test_context.Mock()
|
|
mock_mouse_event.window = test_context.Mock()
|
|
mock_get_script_mgr = test_context.Mock()
|
|
test_context.patch("cthulhu.event_manager.script_manager.get_manager", new=mock_get_script_mgr)
|
|
test_context.patch(
|
|
"cthulhu.event_manager.input_event.MouseButtonEvent",
|
|
return_value=mock_mouse_event,
|
|
)
|
|
mock_script_mgr = test_context.Mock()
|
|
mock_get_script_mgr.return_value = mock_script_mgr
|
|
mock_script_mgr.get_script.return_value = expected_script
|
|
result = manager._get_script_for_event(mock_event)
|
|
assert result == expected_script
|
|
|
|
def test_get_script_for_event_defunct_app(self, test_context: CthulhuTestContext) -> None:
|
|
"""Test EventManager._get_script_for_event with defunct application."""
|
|
|
|
essential_modules: dict[str, MagicMock] = self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
mock_event = test_context.Mock(spec=Atspi.Event)
|
|
mock_event.type = "object:text-changed:insert"
|
|
mock_event.source = test_context.Mock()
|
|
mock_app = test_context.Mock()
|
|
ax_utilities = essential_modules["cthulhu.ax_utilities"]
|
|
ax_utilities.get_application.return_value = mock_app
|
|
ax_utilities.is_defunct.return_value = True
|
|
result = manager._get_script_for_event(mock_event)
|
|
assert result is None
|
|
|
|
@pytest.mark.parametrize(
|
|
"case",
|
|
[
|
|
{
|
|
"id": "focus_claimed",
|
|
"event_type": "object:state-changed:focused",
|
|
"detail1": 1,
|
|
"expected": True,
|
|
"reason_contains": "claimed focus",
|
|
},
|
|
{
|
|
"id": "menu_selection",
|
|
"event_type": "object:state-changed:selected",
|
|
"detail1": 1,
|
|
"expected": True,
|
|
"reason_contains": "Selection change",
|
|
},
|
|
{
|
|
"id": "modal_panel",
|
|
"event_type": "object:state-changed:showing",
|
|
"detail1": 0,
|
|
"expected": True,
|
|
"reason_contains": "Modal panel",
|
|
},
|
|
{
|
|
"id": "no_activation",
|
|
"event_type": "object:text-changed:insert",
|
|
"detail1": 0,
|
|
"expected": False,
|
|
"reason_contains": "No reason",
|
|
},
|
|
],
|
|
ids=lambda case: case["id"],
|
|
)
|
|
def test_is_activatable_event(self, test_context: CthulhuTestContext, case: dict) -> None:
|
|
"""Test EventManager._is_activatable_event."""
|
|
|
|
essential_modules: dict[str, MagicMock] = self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
mock_event = test_context.Mock(spec=Atspi.Event)
|
|
mock_event.type = case["event_type"]
|
|
mock_event.detail1 = case["detail1"]
|
|
mock_event.source = test_context.Mock()
|
|
mock_script = test_context.Mock()
|
|
mock_script.is_activatable_event.return_value = True
|
|
mock_script.force_script_activation.return_value = False
|
|
ax_utilities = essential_modules["cthulhu.ax_utilities"]
|
|
if case["event_type"] == "object:state-changed:selected":
|
|
ax_utilities.is_menu.return_value = True
|
|
ax_utilities.is_focusable.return_value = True
|
|
elif case["event_type"] == "object:state-changed:showing":
|
|
ax_utilities.is_panel.return_value = True
|
|
ax_utilities.is_modal.return_value = True
|
|
else:
|
|
ax_utilities.is_frame.return_value = case["event_type"] == "object:state-changed:active"
|
|
focus_mgr = essential_modules["focus_manager_instance"]
|
|
if case["event_type"] == "window:activate":
|
|
focus_mgr.get_active_window.return_value = mock_event.source
|
|
else:
|
|
focus_mgr.get_active_window.return_value = test_context.Mock()
|
|
result, reason = manager._is_activatable_event(mock_event, mock_script)
|
|
assert result == case["expected"]
|
|
assert case["reason_contains"].lower() in reason.lower()
|
|
|
|
def test_is_activatable_event_no_source(self, test_context: CthulhuTestContext) -> None:
|
|
"""Test EventManager._is_activatable_event with no event source."""
|
|
|
|
self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
mock_event = test_context.Mock(spec=Atspi.Event)
|
|
mock_event.source = None
|
|
result, reason = manager._is_activatable_event(mock_event)
|
|
assert result is False
|
|
assert "event.source" in reason
|
|
|
|
def test_is_activatable_event_script_forces(self, test_context: CthulhuTestContext) -> None:
|
|
"""Test EventManager._is_activatable_event when script forces activation."""
|
|
|
|
self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
mock_event = test_context.Mock(spec=Atspi.Event)
|
|
mock_event.type = "object:text-changed:insert"
|
|
mock_event.source = test_context.Mock()
|
|
mock_script = test_context.Mock()
|
|
mock_script.is_activatable_event.return_value = True
|
|
mock_script.force_script_activation.return_value = True
|
|
result, reason = manager._is_activatable_event(mock_event, mock_script)
|
|
assert result is True
|
|
assert "insists" in reason
|
|
|
|
@pytest.mark.parametrize(
|
|
"case",
|
|
[
|
|
{
|
|
"id": "active_script",
|
|
"event_script_is_active": True,
|
|
"present_if_inactive": False,
|
|
"event_type": "test:event",
|
|
"is_progress_bar": False,
|
|
"expected": True,
|
|
},
|
|
{
|
|
"id": "inactive_but_presents",
|
|
"event_script_is_active": False,
|
|
"present_if_inactive": True,
|
|
"event_type": "test:event",
|
|
"is_progress_bar": False,
|
|
"expected": True,
|
|
},
|
|
{
|
|
"id": "progress_bar_value_change",
|
|
"event_script_is_active": False,
|
|
"present_if_inactive": False,
|
|
"event_type": "object:property-change:accessible-value",
|
|
"is_progress_bar": True,
|
|
"expected": True,
|
|
},
|
|
{
|
|
"id": "progress_bar_non_value_event",
|
|
"event_script_is_active": False,
|
|
"present_if_inactive": False,
|
|
"event_type": "object:state-changed:focused",
|
|
"is_progress_bar": True,
|
|
"expected": False,
|
|
},
|
|
{
|
|
"id": "no_reason_to_process",
|
|
"event_script_is_active": False,
|
|
"present_if_inactive": False,
|
|
"event_type": "test:event",
|
|
"is_progress_bar": False,
|
|
"expected": False,
|
|
},
|
|
],
|
|
ids=lambda case: case["id"],
|
|
)
|
|
def test_should_process_event(
|
|
self,
|
|
test_context: CthulhuTestContext,
|
|
case: dict,
|
|
) -> None:
|
|
"""Test EventManager._should_process_event."""
|
|
|
|
self._setup_dependencies(test_context)
|
|
from cthulhu.ax_utilities import AXUtilities
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
mock_event = test_context.Mock(spec=Atspi.Event)
|
|
mock_event.type = case["event_type"]
|
|
mock_event.source = test_context.Mock()
|
|
event_script = test_context.Mock()
|
|
event_script.present_if_inactive = case["present_if_inactive"]
|
|
test_context.patch_object(
|
|
AXUtilities,
|
|
"is_progress_bar",
|
|
side_effect=lambda obj: case["is_progress_bar"],
|
|
)
|
|
if case["event_script_is_active"]:
|
|
active_script = event_script
|
|
else:
|
|
active_script = test_context.Mock()
|
|
result = manager._should_process_event(mock_event, event_script, active_script)
|
|
assert result == case["expected"]
|
|
|
|
def test_process_object_event_obsoleted(self, test_context: CthulhuTestContext) -> None:
|
|
"""Test EventManager._process_object_event with obsoleted event."""
|
|
|
|
self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
mock_event = test_context.Mock(spec=Atspi.Event)
|
|
mock_is_obsoleted = test_context.Mock(return_value=test_context.Mock())
|
|
test_context.patch_object(manager, "_is_obsoleted_by", new=mock_is_obsoleted)
|
|
manager._process_object_event(mock_event)
|
|
|
|
def test_process_object_event_dead_source(self, test_context: CthulhuTestContext) -> None:
|
|
"""Test EventManager._process_object_event with dead event source."""
|
|
|
|
self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
mock_event = test_context.Mock(spec=Atspi.Event)
|
|
mock_event.type = "window:deactivate"
|
|
mock_event.source = test_context.Mock()
|
|
mock_is_obsoleted = test_context.Mock(return_value=None)
|
|
test_context.patch_object(manager, "_is_obsoleted_by", new=mock_is_obsoleted)
|
|
test_context.patch("cthulhu.event_manager.AXObject.is_dead", return_value=True)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.is_defunct", return_value=False)
|
|
mock_get_focus_mgr = test_context.Mock()
|
|
test_context.patch("cthulhu.event_manager.focus_manager.get_manager", new=mock_get_focus_mgr)
|
|
mock_get_script_mgr = test_context.Mock()
|
|
test_context.patch("cthulhu.event_manager.script_manager.get_manager", new=mock_get_script_mgr)
|
|
mock_focus_mgr = test_context.Mock()
|
|
mock_script_mgr = test_context.Mock()
|
|
mock_get_focus_mgr.return_value = mock_focus_mgr
|
|
mock_get_script_mgr.return_value = mock_script_mgr
|
|
mock_focus_mgr.get_active_window.return_value = mock_event.source
|
|
manager._process_object_event(mock_event)
|
|
|
|
mock_focus_mgr.clear_state.assert_called_once()
|
|
mock_script_mgr.set_active_script.assert_called_once_with(
|
|
None,
|
|
"Active window is dead or defunct",
|
|
)
|
|
|
|
def test_process_object_event_no_listener(self, test_context: CthulhuTestContext) -> None:
|
|
"""Test EventManager._process_object_event with no matching listener."""
|
|
|
|
essential_modules: dict[str, MagicMock] = self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
mock_event = test_context.Mock(spec=Atspi.Event)
|
|
mock_event.type = "object:unknown-event"
|
|
mock_event.source = test_context.Mock()
|
|
mock_is_obsoleted = test_context.Mock(return_value=None)
|
|
test_context.patch_object(manager, "_is_obsoleted_by", new=mock_is_obsoleted)
|
|
ax_object = essential_modules["cthulhu.ax_object"]
|
|
ax_utilities = essential_modules["cthulhu.ax_utilities"]
|
|
ax_object.is_dead.return_value = False
|
|
ax_utilities.is_defunct.return_value = False
|
|
ax_utilities.is_iconified.return_value = False
|
|
mock_script = test_context.Mock()
|
|
mock_script.listeners = {}
|
|
script_mgr = essential_modules["script_manager_instance"]
|
|
script_mgr.get_active_script.return_value = mock_script
|
|
mock_get_script = test_context.Mock(return_value=mock_script)
|
|
mock_is_activatable = test_context.Mock(return_value=(False, "test"))
|
|
mock_should_process = test_context.Mock(return_value=True)
|
|
test_context.patch_object(manager, "_get_script_for_event", new=mock_get_script)
|
|
test_context.patch_object(manager, "_is_activatable_event", new=mock_is_activatable)
|
|
test_context.patch_object(manager, "_should_process_event", new=mock_should_process)
|
|
manager._process_object_event(mock_event)
|
|
|
|
def test_get_manager(self, test_context: CthulhuTestContext) -> None:
|
|
"""Test event_manager.get_manager."""
|
|
|
|
self._setup_dependencies(test_context)
|
|
from cthulhu import event_manager
|
|
|
|
manager1 = event_manager.get_manager()
|
|
manager2 = event_manager.get_manager()
|
|
|
|
assert manager1 is manager2
|
|
assert isinstance(manager1, event_manager.EventManager)
|
|
|
|
def test_enqueue_with_comprehensive_gidle_management(
|
|
self,
|
|
test_context: CthulhuTestContext,
|
|
) -> None:
|
|
"""Test EventManager._enqueue_object_event with comprehensive GLib idle management."""
|
|
|
|
self._setup_dependencies(test_context)
|
|
from cthulhu.event_manager import EventManager
|
|
|
|
manager = EventManager()
|
|
manager._active = True
|
|
manager._paused = False
|
|
manager._gidle_id = 0 # No existing idle handler
|
|
mock_event = test_context.Mock(spec=Atspi.Event)
|
|
mock_event.type = "object:text-changed:insert"
|
|
mock_event.source = test_context.Mock()
|
|
mock_app = test_context.Mock()
|
|
mock_script = test_context.Mock()
|
|
mock_script.event_cache = {}
|
|
|
|
mock_ignore = test_context.Mock(return_value=False)
|
|
test_context.patch_object(manager, "_ignore", new=mock_ignore)
|
|
test_context.patch("cthulhu.event_manager.AXUtilities.get_application", return_value=mock_app)
|
|
mock_get_script_mgr = test_context.Mock()
|
|
test_context.patch("cthulhu.event_manager.script_manager.get_manager", new=mock_get_script_mgr)
|
|
mock_idle_add = test_context.Mock(return_value=456)
|
|
test_context.patch("cthulhu.event_manager.GLib.idle_add", new=mock_idle_add)
|
|
test_context.patch("time.time", return_value=200.0)
|
|
mock_script_mgr = test_context.Mock()
|
|
mock_get_script_mgr.return_value = mock_script_mgr
|
|
mock_script_mgr.get_script.return_value = mock_script
|
|
manager._enqueue_object_event(mock_event)
|
|
|
|
assert not manager._event_queue.empty()
|
|
assert mock_event.type in mock_script.event_cache
|
|
mock_idle_add.assert_called_once_with(manager._dequeue_object_event)
|
|
assert manager._gidle_id == 456
|
|
|
|
mock_idle_add.reset_mock()
|
|
manager._gidle_id = 456
|
|
test_context.patch("time.time", return_value=200.1)
|
|
manager._enqueue_object_event(mock_event)
|
|
|
|
mock_idle_add.assert_not_called()
|
|
assert manager._gidle_id == 456
|