# Unit tests for event_manager.py methods. # # Copyright 2025 Igalia, S.L. # Author: Joanmarie Diggs # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the # Free Software Foundation, Inc., Franklin Street, Fifth Floor, # Boston MA 02110-1301 USA. # pylint: disable=wrong-import-position # pylint: disable=import-outside-toplevel # pylint: disable=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