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