# Unit tests for live_region_presenter.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 live_region_presenter.py methods.""" from __future__ import annotations from typing import TYPE_CHECKING import pytest if TYPE_CHECKING: from unittest.mock import MagicMock from cthulhu_test_context import CthulhuTestContext class Fake: """Stub used as a stand-in for objects that need an identity.""" @pytest.mark.unit class TestLivePoliteness: """Test LivePoliteness enum.""" def _setup_dependencies(self, test_context: CthulhuTestContext) -> dict[str, MagicMock]: """Set up dependencies for live_region_presenter module testing.""" additional_modules = [ "gi", "gi.repository", "gi.repository.Atspi", "gi.repository.GLib", "cthulhu.braille_presenter", "cthulhu.presentation_manager", ] essential_modules = test_context.setup_shared_dependencies(additional_modules) gi_mock = essential_modules["gi"] gi_mock.require_version = test_context.Mock() gi_repository_mock = essential_modules["gi.repository"] atspi_mock = essential_modules["gi.repository.Atspi"] atspi_mock.Role = Fake atspi_mock.Accessible = Fake atspi_mock.MatchRule = Fake atspi_mock.Relation = Fake gi_repository_mock.Atspi = atspi_mock glib_mock = essential_modules["gi.repository.GLib"] glib_mock.timeout_add = test_context.Mock(return_value=1) return essential_modules def test_politeness_values(self, test_context: CthulhuTestContext) -> None: """Test LivePoliteness enum values and properties.""" self._setup_dependencies(test_context) from cthulhu.live_region_presenter import LivePoliteness assert LivePoliteness.ASSERTIVE.priority == 0 assert LivePoliteness.ASSERTIVE.name_str == "assertive" assert LivePoliteness.POLITE.priority == 1 assert LivePoliteness.POLITE.name_str == "polite" assert LivePoliteness.OFF.priority == 2 assert LivePoliteness.OFF.name_str == "off" @pytest.mark.parametrize( "input_str,expected", [ pytest.param("assertive", "ASSERTIVE", id="assertive"), pytest.param("polite", "POLITE", id="polite"), pytest.param("off", "OFF", id="off"), pytest.param("invalid", "OFF", id="invalid_defaults_to_off"), pytest.param(None, "OFF", id="none_defaults_to_off"), ], ) def test_from_string(self, test_context: CthulhuTestContext, input_str, expected) -> None: """Test LivePoliteness.from_string conversion.""" self._setup_dependencies(test_context) from cthulhu.live_region_presenter import LivePoliteness result = LivePoliteness.OFF.from_string(input_str) assert result.name == expected @pytest.mark.parametrize( "politeness,expected_str", [ pytest.param("ASSERTIVE", "assertive", id="assertive"), pytest.param("POLITE", "polite", id="polite"), pytest.param("OFF", "off", id="off"), ], ) def test_to_string(self, test_context: CthulhuTestContext, politeness, expected_str) -> None: """Test LivePoliteness.to_string conversion.""" self._setup_dependencies(test_context) from cthulhu.live_region_presenter import LivePoliteness politeness_enum = getattr(LivePoliteness, politeness) assert politeness_enum.to_string() == expected_str @pytest.mark.unit class TestLiveRegionMessage: """Test LiveRegionMessage class.""" def _setup_dependencies(self, test_context: CthulhuTestContext): """Set up dependencies for live_region_presenter module testing.""" additional_modules = [ "gi", "gi.repository", "gi.repository.Atspi", "gi.repository.GLib", "cthulhu.braille_presenter", "cthulhu.presentation_manager", ] essential_modules = test_context.setup_shared_dependencies(additional_modules) gi_mock = essential_modules["gi"] gi_mock.require_version = test_context.Mock() gi_repository_mock = essential_modules["gi.repository"] atspi_mock = essential_modules["gi.repository.Atspi"] atspi_mock.Role = Fake atspi_mock.Accessible = Fake atspi_mock.MatchRule = Fake atspi_mock.Relation = Fake gi_repository_mock.Atspi = atspi_mock glib_mock = essential_modules["gi.repository.GLib"] glib_mock.timeout_add = test_context.Mock(return_value=1) return essential_modules def test_message_creation(self, test_context: CthulhuTestContext) -> None: """Test LiveRegionMessage creation.""" self._setup_dependencies(test_context) from cthulhu.live_region_presenter import LivePoliteness, LiveRegionMessage mock_obj = test_context.Mock() test_context.patch("time.time", return_value=1234567890.0) message = LiveRegionMessage( text="Test message", politeness=LivePoliteness.POLITE, obj=mock_obj, ) assert message.text == "Test message" assert message.politeness == LivePoliteness.POLITE assert message.obj == mock_obj assert message.timestamp == 1234567890.0 def test_message_with_explicit_timestamp(self, test_context: CthulhuTestContext) -> None: """Test LiveRegionMessage with explicit timestamp.""" self._setup_dependencies(test_context) from cthulhu.live_region_presenter import LivePoliteness, LiveRegionMessage mock_obj = test_context.Mock() message = LiveRegionMessage( text="Test", politeness=LivePoliteness.ASSERTIVE, obj=mock_obj, timestamp=999.0, ) assert message.timestamp == 999.0 @pytest.mark.parametrize( "politeness1,politeness2,timestamp1,timestamp2,expected_less_than", [ pytest.param("ASSERTIVE", "POLITE", 100.0, 100.0, True, id="assertive_before_polite"), pytest.param("POLITE", "ASSERTIVE", 100.0, 100.0, False, id="polite_after_assertive"), pytest.param("POLITE", "POLITE", 100.0, 200.0, True, id="older_before_newer"), pytest.param("POLITE", "POLITE", 200.0, 100.0, False, id="newer_after_older"), ], ) def test_message_comparison( self, test_context: CthulhuTestContext, politeness1, politeness2, timestamp1, timestamp2, expected_less_than, ) -> None: """Test LiveRegionMessage comparison for priority queue ordering.""" self._setup_dependencies(test_context) from cthulhu.live_region_presenter import LivePoliteness, LiveRegionMessage mock_obj = test_context.Mock() msg1 = LiveRegionMessage( text="Message 1", politeness=getattr(LivePoliteness, politeness1), obj=mock_obj, timestamp=timestamp1, ) msg2 = LiveRegionMessage( text="Message 2", politeness=getattr(LivePoliteness, politeness2), obj=mock_obj, timestamp=timestamp2, ) assert (msg1 < msg2) == expected_less_than @pytest.mark.parametrize( "text1,text2,time_diff,expected_duplicate", [ pytest.param("Same", "Same", 0.1, True, id="duplicate_within_window"), pytest.param("Same", "Same", 0.25, True, id="duplicate_at_boundary"), pytest.param("Same", "Same", 0.26, False, id="not_duplicate_outside_window"), pytest.param("Different", "Text", 0.1, False, id="different_text"), ], ) def test_is_duplicate_of( self, test_context: CthulhuTestContext, text1, text2, time_diff, expected_duplicate, ) -> None: """Test LiveRegionMessage.is_duplicate_of.""" self._setup_dependencies(test_context) from cthulhu.live_region_presenter import LivePoliteness, LiveRegionMessage mock_obj = test_context.Mock() base_time = 1000.0 msg1 = LiveRegionMessage( text=text1, politeness=LivePoliteness.POLITE, obj=mock_obj, timestamp=base_time, ) msg2 = LiveRegionMessage( text=text2, politeness=LivePoliteness.POLITE, obj=mock_obj, timestamp=base_time + time_diff, ) assert msg2.is_duplicate_of(msg1) == expected_duplicate def test_is_duplicate_of_none(self, test_context: CthulhuTestContext) -> None: """Test LiveRegionMessage.is_duplicate_of with None.""" self._setup_dependencies(test_context) from cthulhu.live_region_presenter import LivePoliteness, LiveRegionMessage mock_obj = test_context.Mock() message = LiveRegionMessage(text="Test", politeness=LivePoliteness.POLITE, obj=mock_obj) assert message.is_duplicate_of(None) is False @pytest.mark.unit class TestLiveRegionMessageQueue: """Test LiveRegionMessageQueue class.""" def _setup_dependencies(self, test_context: CthulhuTestContext): """Set up dependencies for live_region_presenter module testing.""" additional_modules = [ "gi", "gi.repository", "gi.repository.Atspi", "gi.repository.GLib", "cthulhu.braille_presenter", "cthulhu.presentation_manager", ] essential_modules = test_context.setup_shared_dependencies(additional_modules) gi_mock = essential_modules["gi"] gi_mock.require_version = test_context.Mock() gi_repository_mock = essential_modules["gi.repository"] atspi_mock = essential_modules["gi.repository.Atspi"] atspi_mock.Role = Fake atspi_mock.Accessible = Fake atspi_mock.MatchRule = Fake atspi_mock.Relation = Fake gi_repository_mock.Atspi = atspi_mock glib_mock = essential_modules["gi.repository.GLib"] glib_mock.timeout_add = test_context.Mock(return_value=1) return essential_modules def test_queue_creation(self, test_context: CthulhuTestContext) -> None: """Test LiveRegionMessageQueue creation.""" self._setup_dependencies(test_context) from cthulhu.live_region_presenter import LiveRegionMessageQueue queue = LiveRegionMessageQueue(max_size=5) assert len(queue) == 0 def test_enqueue_dequeue(self, test_context: CthulhuTestContext) -> None: """Test basic enqueue and dequeue operations.""" self._setup_dependencies(test_context) from cthulhu.live_region_presenter import ( LivePoliteness, LiveRegionMessage, LiveRegionMessageQueue, ) queue = LiveRegionMessageQueue(max_size=5) mock_obj = test_context.Mock() msg = LiveRegionMessage("Test", LivePoliteness.POLITE, mock_obj, timestamp=100.0) queue.enqueue(msg) assert len(queue) == 1 dequeued = queue.dequeue() assert dequeued == msg assert len(queue) == 0 def test_priority_ordering(self, test_context: CthulhuTestContext) -> None: """Test messages are dequeued in priority order.""" self._setup_dependencies(test_context) from cthulhu.live_region_presenter import ( LivePoliteness, LiveRegionMessage, LiveRegionMessageQueue, ) queue = LiveRegionMessageQueue(max_size=5) mock_obj = test_context.Mock() polite_msg = LiveRegionMessage("Polite", LivePoliteness.POLITE, mock_obj, timestamp=100.0) assertive_msg = LiveRegionMessage( "Assertive", LivePoliteness.ASSERTIVE, mock_obj, timestamp=200.0, ) queue.enqueue(polite_msg) queue.enqueue(assertive_msg) # Assertive should come out first first = queue.dequeue() assert first is not None assert first.text == "Assertive" second = queue.dequeue() assert second is not None assert second.text == "Polite" def test_max_size_enforcement(self, test_context: CthulhuTestContext) -> None: """Test queue enforces max size limit.""" self._setup_dependencies(test_context) from cthulhu.live_region_presenter import ( LivePoliteness, LiveRegionMessage, LiveRegionMessageQueue, ) queue = LiveRegionMessageQueue(max_size=3) mock_obj = test_context.Mock() for i in range(5): msg = LiveRegionMessage( f"Message {i}", LivePoliteness.POLITE, mock_obj, timestamp=float(i), ) queue.enqueue(msg) assert len(queue) == 3 def test_clear(self, test_context: CthulhuTestContext) -> None: """Test queue clear operation.""" self._setup_dependencies(test_context) from cthulhu.live_region_presenter import ( LivePoliteness, LiveRegionMessage, LiveRegionMessageQueue, ) queue = LiveRegionMessageQueue(max_size=5) mock_obj = test_context.Mock() for i in range(3): msg = LiveRegionMessage(f"Message {i}", LivePoliteness.POLITE, mock_obj) queue.enqueue(msg) assert len(queue) == 3 queue.clear() assert len(queue) == 0 def test_dequeue_empty_returns_none(self, test_context: CthulhuTestContext) -> None: """Test dequeue on empty queue returns None.""" self._setup_dependencies(test_context) from cthulhu.live_region_presenter import LiveRegionMessageQueue queue = LiveRegionMessageQueue(max_size=5) assert queue.dequeue() is None def test_purge_by_keep_alive(self, test_context: CthulhuTestContext) -> None: """Test purge_by_keep_alive removes old messages.""" self._setup_dependencies(test_context) from cthulhu.live_region_presenter import ( LivePoliteness, LiveRegionMessage, LiveRegionMessageQueue, ) queue = LiveRegionMessageQueue(max_size=10) mock_obj = test_context.Mock() current_time = 1000.0 test_context.patch("time.time", return_value=current_time) # Add old message (older than MSG_KEEPALIVE_TIME) old_msg = LiveRegionMessage( "Old", LivePoliteness.POLITE, mock_obj, timestamp=current_time - 50, ) queue.enqueue(old_msg) # Add recent message new_msg = LiveRegionMessage( "New", LivePoliteness.POLITE, mock_obj, timestamp=current_time - 10, ) queue.enqueue(new_msg) assert len(queue) == 2 queue.purge_by_keep_alive() assert len(queue) == 1 remaining = queue.dequeue() assert remaining is not None assert remaining.text == "New" def test_purge_by_priority(self, test_context: CthulhuTestContext) -> None: """Test purge_by_priority removes lower priority messages.""" self._setup_dependencies(test_context) from cthulhu.live_region_presenter import ( LivePoliteness, LiveRegionMessage, LiveRegionMessageQueue, ) queue = LiveRegionMessageQueue(max_size=10) mock_obj = test_context.Mock() assertive_msg = LiveRegionMessage( "Assertive", LivePoliteness.ASSERTIVE, mock_obj, timestamp=100.0, ) polite_msg = LiveRegionMessage("Polite", LivePoliteness.POLITE, mock_obj, timestamp=200.0) off_msg = LiveRegionMessage("Off", LivePoliteness.OFF, mock_obj, timestamp=300.0) queue.enqueue(assertive_msg) queue.enqueue(polite_msg) queue.enqueue(off_msg) assert len(queue) == 3 # Purge messages with priority >= POLITE (removes POLITE and OFF, keeps ASSERTIVE) queue.purge_by_priority(LivePoliteness.POLITE) assert len(queue) == 1 remaining = queue.dequeue() assert remaining is not None assert remaining.text == "Assertive" @pytest.mark.unit class TestLiveRegionPresenter: """Test LiveRegionPresenter class methods.""" def _setup_dependencies(self, test_context: CthulhuTestContext): """Set up dependencies for LiveRegionPresenter testing.""" additional_modules = [ "gi", "gi.repository", "gi.repository.Atspi", "gi.repository.GLib", "cthulhu.cmdnames", "cthulhu.dbus_service", "cthulhu.debug", "cthulhu.focus_manager", "cthulhu.input_event", "cthulhu.keybindings", "cthulhu.messages", "cthulhu.script_manager", "cthulhu.AXObject", "cthulhu.AXUtilities", "cthulhu.braille_presenter", "cthulhu.presentation_manager", ] essential_modules = test_context.setup_shared_dependencies(additional_modules) gi_mock = essential_modules["gi"] gi_mock.require_version = test_context.Mock() gi_repository_mock = essential_modules["gi.repository"] atspi_mock = essential_modules["gi.repository.Atspi"] atspi_mock.Role = Fake atspi_mock.Accessible = Fake atspi_mock.MatchRule = Fake atspi_mock.Relation = Fake gi_repository_mock.Atspi = atspi_mock glib_mock = essential_modules["gi.repository.GLib"] glib_mock.timeout_add = test_context.Mock(return_value=1) cmdnames_mock = essential_modules["cthulhu.cmdnames"] cmdnames_mock.LIVE_REGIONS_MONITOR = "toggleLiveRegionMonitoring" cmdnames_mock.LIVE_REGIONS_PREVIOUS = "presentPreviousLiveRegionMessage" cmdnames_mock.LIVE_REGIONS_ADVANCE_POLITENESS = "advanceLivePoliteness" cmdnames_mock.LIVE_REGIONS_ARE_ANNOUNCED = "toggleLiveRegionPresentation" cmdnames_mock.LIVE_REGIONS_NEXT = "presentNextLiveRegionMessage" dbus_service_mock = essential_modules["cthulhu.dbus_service"] controller_mock = test_context.Mock() controller_mock.register_decorated_module = test_context.Mock() dbus_service_mock.get_remote_controller = test_context.Mock(return_value=controller_mock) debug_mock = essential_modules["cthulhu.debug"] debug_mock.print_message = test_context.Mock() debug_mock.print_tokens = test_context.Mock() debug_mock.LEVEL_INFO = 800 input_event_mock = essential_modules["cthulhu.input_event"] input_event_handler_class = test_context.Mock() input_event_handler_instance = test_context.Mock() input_event_handler_class.return_value = input_event_handler_instance input_event_mock.InputEventHandler = input_event_handler_class keybindings_mock = essential_modules["cthulhu.keybindings"] def create_keybindings_instance(): instance = test_context.Mock() instance.is_empty = test_context.Mock(return_value=True) instance.remove_key_grabs = test_context.Mock() instance.add = test_context.Mock() instance.remove = test_context.Mock() instance.key_bindings = [] return instance keybindings_mock.KeyBindings = test_context.Mock(side_effect=create_keybindings_instance) keybinding_instance = test_context.Mock() keybindings_mock.KeyBinding = test_context.Mock(return_value=keybinding_instance) keybindings_mock.DEFAULT_MODIFIER_MASK = 1 keybindings_mock.NO_MODIFIER_MASK = 0 keybindings_mock.CTHULHU_MODIFIER_MASK = 8 messages_mock = essential_modules["cthulhu.messages"] messages_mock.LIVE_REGIONS_SUPPORT_DISABLED = "Live regions support disabled" messages_mock.LIVE_REGIONS_LEVEL_POLITE = "Live regions set to polite" messages_mock.LIVE_REGIONS_LEVEL_ASSERTIVE = "Live regions set to assertive" messages_mock.LIVE_REGIONS_LEVEL_OFF = "Live regions set to off" messages_mock.LIVE_REGIONS_NO_MESSAGES = "No live region messages" messages_mock.LIVE_REGIONS_LIST_TOP = "Top of live region list" messages_mock.LIVE_REGIONS_LIST_BOTTOM = "Bottom of live region list" messages_mock.LIVE_REGIONS_ENABLED = "Live regions monitoring enabled" messages_mock.LIVE_REGIONS_DISABLED = "Live regions monitoring disabled" messages_mock.LIVE_REGIONS_ALL_OFF = "Live regions all off" messages_mock.LIVE_REGIONS_ALL_RESTORED = "Live regions restored" focus_manager_mock = essential_modules["cthulhu.focus_manager"] focus_instance = test_context.Mock() focus_instance.get_locus_of_focus = test_context.Mock(return_value=None) focus_manager_mock.get_manager = test_context.Mock(return_value=focus_instance) script_manager_mock = essential_modules["cthulhu.script_manager"] script_instance = test_context.Mock() script_instance.present_message = test_context.Mock() script_instance.utilities = test_context.Mock() script_manager_mock.get_manager = test_context.Mock() script_manager_mock.get_manager.return_value.get_active_script = test_context.Mock( return_value=script_instance, ) axobject_mock = essential_modules["cthulhu.AXObject"] axobject_mock.get_attributes_dict = test_context.Mock(return_value={}) axobject_mock.get_name = test_context.Mock(return_value="") axobject_mock.find_ancestor = test_context.Mock(return_value=None) axobject_mock.find_ancestor_inclusive = test_context.Mock(return_value=None) axutilities_mock = essential_modules["cthulhu.AXUtilities"] axutilities_mock.is_live_region = test_context.Mock(return_value=True) axutilities_mock.is_aria_alert = test_context.Mock(return_value=False) axutilities_mock.get_focused_object = test_context.Mock(return_value=None) axutilities_mock.find_all_live_regions = test_context.Mock(return_value=[]) from cthulhu import gsettings_registry registry = gsettings_registry.get_registry() registry.clear_runtime_values() return essential_modules def test_init(self, test_context: CthulhuTestContext) -> None: """Test LiveRegionPresenter.__init__.""" self._setup_dependencies(test_context) from cthulhu.live_region_presenter import LiveRegionPresenter presenter = LiveRegionPresenter() assert presenter.msg_queue is not None assert len(presenter.msg_queue) == 0 assert len(presenter.msg_cache) == 0 assert presenter._monitoring is True assert presenter._current_index == 9 # QUEUE_SIZE def test_commands_registered(self, test_context: CthulhuTestContext) -> None: """Test LiveRegionPresenter commands are registered in CommandManager.""" self._setup_dependencies(test_context) from cthulhu import command_manager from cthulhu.live_region_presenter import LiveRegionPresenter presenter = LiveRegionPresenter() presenter.set_up_commands() cmd_manager = command_manager.get_manager() expected_commands = [ "toggle_live_region_support", "present_previous_live_region_message", "advance_live_politeness", "toggle_live_region_presentation", "present_next_live_region_message", ] for cmd_name in expected_commands: assert cmd_manager.get_keyboard_command(cmd_name) is not None def test_reset(self, test_context: CthulhuTestContext) -> None: """Test LiveRegionPresenter.reset.""" self._setup_dependencies(test_context) from cthulhu.live_region_presenter import LivePoliteness, LiveRegionPresenter presenter = LiveRegionPresenter() presenter._politeness_overrides = {123: LivePoliteness.POLITE} presenter.reset() assert not presenter._politeness_overrides def test_get_is_enabled(self, test_context: CthulhuTestContext) -> None: """Test LiveRegionPresenter.get_is_enabled.""" self._setup_dependencies(test_context) from cthulhu.live_region_presenter import LiveRegionPresenter presenter = LiveRegionPresenter() assert presenter.get_is_enabled() is True def test_set_is_enabled(self, test_context: CthulhuTestContext) -> None: """Test LiveRegionPresenter.set_is_enabled.""" self._setup_dependencies(test_context) from cthulhu.live_region_presenter import LiveRegionPresenter presenter = LiveRegionPresenter() presenter.set_is_enabled(False) result = presenter.set_is_enabled(True) assert result is True assert presenter.get_is_enabled() is True def test_toggle_monitoring_enable(self, test_context: CthulhuTestContext) -> None: """Test LiveRegionPresenter.toggle_monitoring enables monitoring.""" essential_modules = self._setup_dependencies(test_context) from cthulhu.live_region_presenter import LiveRegionPresenter presenter = LiveRegionPresenter() presenter.set_is_enabled(False) mock_script = test_context.Mock() mock_script.present_message = test_context.Mock() mock_event = test_context.Mock() result = presenter.toggle_monitoring(mock_script, mock_event) assert result is True pres_manager = essential_modules["cthulhu.presentation_manager"].get_manager() pres_manager.present_message.assert_called_with("Live regions monitoring enabled") def test_toggle_monitoring_disable(self, test_context: CthulhuTestContext) -> None: """Test LiveRegionPresenter.toggle_monitoring disables monitoring.""" essential_modules = self._setup_dependencies(test_context) from cthulhu.live_region_presenter import LiveRegionPresenter presenter = LiveRegionPresenter() mock_script = test_context.Mock() mock_script.present_message = test_context.Mock() mock_event = test_context.Mock() result = presenter.toggle_monitoring(mock_script, mock_event) assert result is True pres_manager = essential_modules["cthulhu.presentation_manager"].get_manager() pres_manager.present_message.assert_called_with("Live regions monitoring disabled") def test_flush_messages(self, test_context: CthulhuTestContext) -> None: """Test LiveRegionPresenter.flush_messages.""" self._setup_dependencies(test_context) from cthulhu.live_region_presenter import ( LivePoliteness, LiveRegionMessage, LiveRegionPresenter, ) presenter = LiveRegionPresenter() mock_obj = test_context.Mock() msg = LiveRegionMessage("Test", LivePoliteness.POLITE, mock_obj) presenter.msg_queue.enqueue(msg) assert len(presenter.msg_queue) > 0 presenter.flush_messages() assert len(presenter.msg_queue) == 0 def test_present_previous_live_region_message_no_messages( self, test_context: CthulhuTestContext, ) -> None: """Test present_previous_live_region_message with no messages.""" essential_modules = self._setup_dependencies(test_context) from cthulhu.live_region_presenter import LiveRegionPresenter presenter = LiveRegionPresenter() mock_script = test_context.Mock() mock_script.present_message = test_context.Mock() result = presenter.present_previous_live_region_message(mock_script, None) assert result is True pres_manager = essential_modules["cthulhu.presentation_manager"].get_manager() pres_manager.present_message.assert_called_with("No live region messages") def test_present_previous_live_region_message_disabled( self, test_context: CthulhuTestContext, ) -> None: """Test present_previous_live_region_message when disabled.""" essential_modules = self._setup_dependencies(test_context) from cthulhu.live_region_presenter import LiveRegionPresenter presenter = LiveRegionPresenter() presenter.set_is_enabled(False) mock_script = test_context.Mock() mock_script.present_message = test_context.Mock() result = presenter.present_previous_live_region_message(mock_script, None) assert result is False pres_manager = essential_modules["cthulhu.presentation_manager"].get_manager() pres_manager.present_message.assert_called_with("Live regions support disabled") def test_present_next_live_region_message_no_messages( self, test_context: CthulhuTestContext, ) -> None: """Test present_next_live_region_message with no messages.""" essential_modules = self._setup_dependencies(test_context) from cthulhu.live_region_presenter import LiveRegionPresenter presenter = LiveRegionPresenter() mock_script = test_context.Mock() mock_script.present_message = test_context.Mock() result = presenter.present_next_live_region_message(mock_script, None) assert result is True pres_manager = essential_modules["cthulhu.presentation_manager"].get_manager() pres_manager.present_message.assert_called_with("No live region messages") def test_go_last_live_region_no_message(self, test_context: CthulhuTestContext) -> None: """Test go_last_live_region with no last message.""" self._setup_dependencies(test_context) from cthulhu.live_region_presenter import LiveRegionPresenter presenter = LiveRegionPresenter() mock_script = test_context.Mock() result = presenter.go_last_live_region(mock_script, None) assert result is False def test_is_presentable_live_region_event_wrong_type( self, test_context: CthulhuTestContext, ) -> None: """Test is_presentable_live_region_event with wrong event type.""" self._setup_dependencies(test_context) from cthulhu.live_region_presenter import LiveRegionPresenter presenter = LiveRegionPresenter() mock_script = test_context.Mock() mock_event = test_context.Mock() mock_event.type = "object:state-changed:focused" mock_event.source = test_context.Mock() result = presenter.is_presentable_live_region_event(mock_script, mock_event) assert result is False def test_is_presentable_live_region_event_disabled(self, test_context: CthulhuTestContext) -> None: """Test is_presentable_live_region_event when presenter is disabled.""" self._setup_dependencies(test_context) from cthulhu.live_region_presenter import LiveRegionPresenter presenter = LiveRegionPresenter() presenter.set_is_enabled(False) mock_script = test_context.Mock() mock_event = test_context.Mock() mock_event.type = "object:text-changed:insert" mock_event.source = test_context.Mock() result = presenter.is_presentable_live_region_event(mock_script, mock_event) assert result is False def test_is_presentable_live_region_event_not_live_region( self, test_context: CthulhuTestContext, ) -> None: """Test is_presentable_live_region_event when source is not a live region.""" self._setup_dependencies(test_context) from cthulhu import ax_utilities from cthulhu.live_region_presenter import LiveRegionPresenter presenter = LiveRegionPresenter() test_context.patch_object(ax_utilities.AXUtilities, "is_live_region", return_value=False) mock_script = test_context.Mock() mock_event = test_context.Mock() mock_event.type = "object:text-changed:insert" mock_event.source = test_context.Mock() result = presenter.is_presentable_live_region_event(mock_script, mock_event) assert result is False @pytest.mark.unit class TestLiveRegionPresenterModule: """Test module-level functions.""" def _setup_dependencies(self, test_context: CthulhuTestContext): """Set up dependencies for live_region_presenter module testing.""" additional_modules = [ "gi", "gi.repository", "gi.repository.Atspi", "gi.repository.GLib", "cthulhu.cmdnames", "cthulhu.dbus_service", "cthulhu.debug", "cthulhu.focus_manager", "cthulhu.input_event", "cthulhu.keybindings", "cthulhu.messages", "cthulhu.script_manager", "cthulhu.AXObject", "cthulhu.AXUtilities", "cthulhu.braille_presenter", "cthulhu.presentation_manager", ] essential_modules = test_context.setup_shared_dependencies(additional_modules) gi_mock = essential_modules["gi"] gi_mock.require_version = test_context.Mock() gi_repository_mock = essential_modules["gi.repository"] atspi_mock = essential_modules["gi.repository.Atspi"] atspi_mock.Role = Fake atspi_mock.Accessible = Fake atspi_mock.MatchRule = Fake atspi_mock.Relation = Fake gi_repository_mock.Atspi = atspi_mock glib_mock = essential_modules["gi.repository.GLib"] glib_mock.timeout_add = test_context.Mock(return_value=1) dbus_service_mock = essential_modules["cthulhu.dbus_service"] controller_mock = test_context.Mock() controller_mock.register_decorated_module = test_context.Mock() dbus_service_mock.get_remote_controller = test_context.Mock(return_value=controller_mock) debug_mock = essential_modules["cthulhu.debug"] debug_mock.print_message = test_context.Mock() debug_mock.print_tokens = test_context.Mock() debug_mock.LEVEL_INFO = 800 essential_modules["cthulhu.cmdnames"].LIVE_REGIONS_MONITOR = "test" essential_modules["cthulhu.cmdnames"].LIVE_REGIONS_PREVIOUS = "test" essential_modules["cthulhu.cmdnames"].LIVE_REGIONS_ADVANCE_POLITENESS = "test" essential_modules["cthulhu.cmdnames"].LIVE_REGIONS_ARE_ANNOUNCED = "test" essential_modules["cthulhu.cmdnames"].LIVE_REGIONS_NEXT = "test" input_event_mock = essential_modules["cthulhu.input_event"] input_event_handler_class = test_context.Mock() input_event_handler_instance = test_context.Mock() input_event_handler_class.return_value = input_event_handler_instance input_event_mock.InputEventHandler = input_event_handler_class keybindings_mock = essential_modules["cthulhu.keybindings"] def create_keybindings_instance(): instance = test_context.Mock() instance.is_empty = test_context.Mock(return_value=True) instance.remove_key_grabs = test_context.Mock() instance.add = test_context.Mock() instance.remove = test_context.Mock() instance.key_bindings = [] return instance keybindings_mock.KeyBindings = test_context.Mock(side_effect=create_keybindings_instance) keybindings_mock.KeyBinding = test_context.Mock() keybindings_mock.DEFAULT_MODIFIER_MASK = 1 keybindings_mock.NO_MODIFIER_MASK = 0 keybindings_mock.CTHULHU_MODIFIER_MASK = 8 essential_modules["cthulhu.messages"].LIVE_REGIONS_SUPPORT_DISABLED = "test" focus_manager_mock = essential_modules["cthulhu.focus_manager"] focus_instance = test_context.Mock() focus_instance.get_locus_of_focus = test_context.Mock(return_value=None) focus_manager_mock.get_manager = test_context.Mock(return_value=focus_instance) script_manager_mock = essential_modules["cthulhu.script_manager"] script_instance = test_context.Mock() script_manager_mock.get_manager = test_context.Mock() script_manager_mock.get_manager.return_value.get_active_script = test_context.Mock( return_value=script_instance, ) axobject_mock = essential_modules["cthulhu.AXObject"] axobject_mock.get_attributes_dict = test_context.Mock(return_value={}) axobject_mock.get_name = test_context.Mock(return_value="") axobject_mock.find_ancestor = test_context.Mock(return_value=None) axobject_mock.find_ancestor_inclusive = test_context.Mock(return_value=None) axutilities_mock = essential_modules["cthulhu.AXUtilities"] axutilities_mock.is_live_region = test_context.Mock(return_value=True) axutilities_mock.is_aria_alert = test_context.Mock(return_value=False) axutilities_mock.get_focused_object = test_context.Mock(return_value=None) axutilities_mock.find_all_live_regions = test_context.Mock(return_value=[]) from cthulhu import gsettings_registry registry = gsettings_registry.get_registry() registry.clear_runtime_values() return essential_modules def test_get_presenter(self, test_context: CthulhuTestContext) -> None: """Test get_presenter function returns singleton.""" _ = self._setup_dependencies(test_context) from cthulhu.live_region_presenter import get_presenter presenter1 = get_presenter() assert presenter1 is not None assert presenter1.msg_queue is not None presenter2 = get_presenter() assert presenter1 is presenter2