Files
cthulhu/tests/test_presentation_manager.py

581 lines
25 KiB
Python

# Unit tests for presentation_manager.py methods.
#
# Copyright 2026 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=protected-access
# pylint: disable=too-many-locals
# pylint: disable=too-many-public-methods
# pylint: disable=too-many-statements
"""Unit tests for presentation_manager.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
@pytest.mark.unit
class TestPresentationManager:
"""Test PresentationManager class methods."""
def _setup_dependencies(self, test_context: CthulhuTestContext) -> dict[str, MagicMock]:
"""Returns dependencies for presentation_manager module testing."""
additional_modules = [
"cthulhu.braille_presenter",
"cthulhu.live_region_presenter",
"cthulhu.sound_presenter",
"cthulhu.speech_manager",
"cthulhu.speech_presenter",
"cthulhu.typing_echo_presenter",
]
essential_modules = test_context.setup_shared_dependencies(additional_modules)
debug_mock = essential_modules["cthulhu.debug"]
debug_mock.print_message = test_context.Mock()
debug_mock.print_tokens = test_context.Mock()
debug_mock.print_exception = test_context.Mock()
debug_mock.debugFile = None
debug_mock.LEVEL_INFO = 800
debug_mock.LEVEL_WARNING = 600
braille_mock = essential_modules["cthulhu.braille"]
braille_mock.killFlash = test_context.Mock()
braille_mock.displayMessage = test_context.Mock()
braille_mock.clear = test_context.Mock()
mock_line = test_context.Mock()
mock_line.regions = []
mock_line.add_regions = test_context.Mock(side_effect=mock_line.regions.append)
braille_mock.Line = test_context.Mock(return_value=mock_line)
braille_mock.add_line = test_context.Mock()
braille_mock.setFocus = test_context.Mock()
braille_mock.refresh = test_context.Mock()
braille_presenter_mock = essential_modules["cthulhu.braille_presenter"]
braille_presenter_instance = test_context.Mock()
braille_presenter_instance.use_braille = test_context.Mock(return_value=True)
braille_presenter_instance.get_flash_messages_are_enabled = test_context.Mock(
return_value=True,
)
braille_presenter_instance.get_flash_messages_are_detailed = test_context.Mock(
return_value=True,
)
braille_presenter_instance.get_flashtime_from_settings = test_context.Mock(
return_value=5000,
)
braille_presenter_instance.kill_flash = test_context.Mock()
braille_presenter_instance.present_message = test_context.Mock()
braille_presenter_instance.present_regions = test_context.Mock()
braille_presenter_mock.get_presenter = test_context.Mock(
return_value=braille_presenter_instance,
)
live_region_presenter_mock = essential_modules["cthulhu.live_region_presenter"]
live_region_instance = test_context.Mock()
live_region_instance.flush_messages = test_context.Mock()
live_region_presenter_mock.get_presenter = test_context.Mock(
return_value=live_region_instance,
)
sound_presenter_mock = essential_modules["cthulhu.sound_presenter"]
sound_presenter_instance = test_context.Mock()
sound_presenter_instance.play = test_context.Mock()
sound_presenter_mock.get_presenter = test_context.Mock(
return_value=sound_presenter_instance,
)
speech_manager_mock = essential_modules["cthulhu.speech_manager"]
speech_manager_instance = test_context.Mock()
speech_manager_instance.interrupt_speech = test_context.Mock()
speech_manager_instance.start_speech = test_context.Mock()
speech_manager_instance.shutdown_speech = test_context.Mock()
speech_manager_instance.refresh_speech = test_context.Mock()
speech_manager_instance.get_speech_is_enabled_and_not_muted = test_context.Mock(
return_value=True,
)
speech_manager_instance.get_speech_is_muted = test_context.Mock(return_value=False)
speech_manager_mock.get_manager = test_context.Mock(return_value=speech_manager_instance)
speech_presenter_mock = essential_modules["cthulhu.speech_presenter"]
speech_presenter_instance = test_context.Mock()
speech_presenter_instance.present_key_event = test_context.Mock()
speech_presenter_instance.speak_message = test_context.Mock()
speech_presenter_instance.speak_contents = test_context.Mock()
speech_presenter_instance.speak_character = test_context.Mock()
speech_presenter_instance.spell_item = test_context.Mock()
speech_presenter_instance.spell_phonetically = test_context.Mock()
speech_presenter_instance.get_messages_are_detailed = test_context.Mock(return_value=True)
speech_presenter_mock.get_presenter = test_context.Mock(
return_value=speech_presenter_instance,
)
typing_echo_presenter_mock = essential_modules["cthulhu.typing_echo_presenter"]
typing_echo_instance = test_context.Mock()
typing_echo_instance.echo_keyboard_event = test_context.Mock()
typing_echo_presenter_mock.get_presenter = test_context.Mock(
return_value=typing_echo_instance,
)
script_manager_mock = essential_modules["cthulhu.script_manager"]
mock_script = test_context.Mock()
speech_gen = test_context.Mock()
speech_gen.voice = test_context.Mock(return_value=[{"family": "default"}])
speech_gen.generate_contents = test_context.Mock(return_value=["generated speech"])
mock_script.get_speech_generator = test_context.Mock(return_value=speech_gen)
braille_gen = test_context.Mock()
braille_gen.generate_contents = test_context.Mock(
return_value=([test_context.Mock()], test_context.Mock()),
)
mock_script.get_braille_generator = test_context.Mock(return_value=braille_gen)
script_manager_instance = test_context.Mock()
script_manager_instance.get_active_script = test_context.Mock(return_value=mock_script)
script_manager_mock.get_manager = test_context.Mock(return_value=script_manager_instance)
return essential_modules
def test_get_manager_returns_singleton(self, test_context: CthulhuTestContext) -> None:
"""Test get_manager returns the same instance."""
self._setup_dependencies(test_context)
from cthulhu.presentation_manager import get_manager
manager1 = get_manager()
manager2 = get_manager()
assert manager1 is manager2
assert manager1.__class__.__name__ == "PresentationManager"
def test_interrupt_presentation(self, test_context: CthulhuTestContext) -> None:
"""Test interrupt_presentation interrupts speech and braille."""
essential_modules = self._setup_dependencies(test_context)
from cthulhu.presentation_manager import get_manager
manager = get_manager()
manager.interrupt_presentation()
speech_manager = essential_modules["cthulhu.speech_manager"].get_manager()
speech_manager.interrupt_speech.assert_called_once()
braille_presenter = essential_modules["cthulhu.braille_presenter"].get_presenter()
braille_presenter.kill_flash.assert_called_once()
live_region = essential_modules["cthulhu.live_region_presenter"].get_presenter()
live_region.flush_messages.assert_called_once()
def test_interrupt_presentation_without_kill_flash(self, test_context: CthulhuTestContext) -> None:
"""Test interrupt_presentation with kill_flash=False."""
essential_modules = self._setup_dependencies(test_context)
from cthulhu.presentation_manager import get_manager
manager = get_manager()
manager.interrupt_presentation(kill_flash=False)
speech_manager = essential_modules["cthulhu.speech_manager"].get_manager()
speech_manager.interrupt_speech.assert_called_once()
essential_modules["cthulhu.braille"].killFlash.assert_not_called()
def test_present_keyboard_event(self, test_context: CthulhuTestContext) -> None:
"""Test present_keyboard_event delegates to typing_echo_presenter."""
essential_modules = self._setup_dependencies(test_context)
from cthulhu.presentation_manager import get_manager
manager = get_manager()
mock_event = test_context.Mock()
manager.present_keyboard_event(mock_event)
typing_echo = essential_modules["cthulhu.typing_echo_presenter"].get_presenter()
typing_echo.echo_keyboard_event.assert_called_once_with(mock_event)
def test_present_key_event_delegates(self, test_context: CthulhuTestContext) -> None:
"""Test present_key_event delegates to speech_presenter."""
essential_modules = self._setup_dependencies(test_context)
from cthulhu.presentation_manager import get_manager
manager = get_manager()
mock_event = test_context.Mock()
mock_event.get_key_name.return_value = "BackSpace"
manager.present_key_event(mock_event)
speech_pres = essential_modules["cthulhu.speech_presenter"].get_presenter()
speech_pres.present_key_event.assert_called_once_with(mock_event)
def test_present_message_delegates_speech(self, test_context: CthulhuTestContext) -> None:
"""Test present_message delegates speech to speech_presenter."""
essential_modules = self._setup_dependencies(test_context)
from cthulhu.presentation_manager import get_manager
manager = get_manager()
manager.present_message("This is a full message")
speech_pres = essential_modules["cthulhu.speech_presenter"].get_presenter()
speech_pres.speak_message.assert_called_once_with(
"This is a full message",
)
braille_presenter = essential_modules["cthulhu.braille_presenter"].get_presenter()
braille_presenter.present_message.assert_called()
def test_present_message_with_brief(self, test_context: CthulhuTestContext) -> None:
"""Test present_message uses full when messages_are_detailed is True."""
essential_modules = self._setup_dependencies(test_context)
from cthulhu.presentation_manager import get_manager
manager = get_manager()
manager.present_message("Full message", brief="Brief")
speech_pres = essential_modules["cthulhu.speech_presenter"].get_presenter()
speech_pres.speak_message.assert_called_once_with(
"Full message",
)
def test_present_message_empty_string(self, test_context: CthulhuTestContext) -> None:
"""Test present_message with empty string returns early."""
essential_modules = self._setup_dependencies(test_context)
from cthulhu.presentation_manager import get_manager
manager = get_manager()
manager.present_message("")
speech_pres = essential_modules["cthulhu.speech_presenter"].get_presenter()
speech_pres.speak_message.assert_not_called()
essential_modules["cthulhu.braille"].displayMessage.assert_not_called()
def test_present_message_braille_disabled(self, test_context: CthulhuTestContext) -> None:
"""Test present_message when braille is disabled."""
essential_modules = self._setup_dependencies(test_context)
braille_presenter = essential_modules["cthulhu.braille_presenter"].get_presenter()
braille_presenter.use_braille.return_value = False
from cthulhu.presentation_manager import get_manager
manager = get_manager()
manager.present_message("Test message")
speech_pres = essential_modules["cthulhu.speech_presenter"].get_presenter()
speech_pres.speak_message.assert_called()
essential_modules["cthulhu.braille"].displayMessage.assert_not_called()
def test_present_message_uses_brief(self, test_context: CthulhuTestContext) -> None:
"""Test present_message uses brief when messages_are_detailed is False."""
essential_modules = self._setup_dependencies(test_context)
speech_pres = essential_modules["cthulhu.speech_presenter"].get_presenter()
speech_pres.get_messages_are_detailed.return_value = False
from cthulhu.presentation_manager import get_manager
manager = get_manager()
manager.present_message("Full message", brief="Brief")
speech_pres.speak_message.assert_called_once_with(
"Brief",
)
def test_present_message_speech_disabled(self, test_context: CthulhuTestContext) -> None:
"""Test present_message skips speech when speech is disabled."""
essential_modules = self._setup_dependencies(test_context)
speech_mgr = essential_modules["cthulhu.speech_manager"].get_manager()
speech_mgr.get_speech_is_enabled_and_not_muted.return_value = False
from cthulhu.presentation_manager import get_manager
manager = get_manager()
manager.present_message("Test message")
speech_pres = essential_modules["cthulhu.speech_presenter"].get_presenter()
speech_pres.speak_message.assert_not_called()
def test_speak_message_muted(self, test_context: CthulhuTestContext) -> None:
"""Test speak_message skips speech when muted."""
essential_modules = self._setup_dependencies(test_context)
speech_mgr = essential_modules["cthulhu.speech_manager"].get_manager()
speech_mgr.get_speech_is_muted.return_value = True
from cthulhu.presentation_manager import get_manager
manager = get_manager()
manager.speak_message("Hello world")
speech_pres = essential_modules["cthulhu.speech_presenter"].get_presenter()
speech_pres.speak_message.assert_not_called()
def test_speak_accessible_text_muted(self, test_context: CthulhuTestContext) -> None:
"""Test speak_accessible_text skips speech when muted."""
essential_modules = self._setup_dependencies(test_context)
speech_mgr = essential_modules["cthulhu.speech_manager"].get_manager()
speech_mgr.get_speech_is_muted.return_value = True
from cthulhu.presentation_manager import get_manager
manager = get_manager()
mock_obj = test_context.Mock()
manager.speak_accessible_text(mock_obj, "Hello")
speech_pres = essential_modules["cthulhu.speech_presenter"].get_presenter()
speech_pres.speak_accessible_text.assert_not_called()
def test_play_sound(self, test_context: CthulhuTestContext) -> None:
"""Test play_sound delegates to sound_presenter."""
essential_modules = self._setup_dependencies(test_context)
from cthulhu.presentation_manager import PresentationManager
mock_sound = test_context.Mock()
PresentationManager.play_sound(mock_sound)
sound_presenter = essential_modules["cthulhu.sound_presenter"].get_presenter()
sound_presenter.play.assert_called_once_with(mock_sound, True)
def test_play_sound_no_interrupt(self, test_context: CthulhuTestContext) -> None:
"""Test play_sound with interrupt=False."""
essential_modules = self._setup_dependencies(test_context)
from cthulhu.presentation_manager import PresentationManager
mock_sound = test_context.Mock()
PresentationManager.play_sound(mock_sound, interrupt=False)
sound_presenter = essential_modules["cthulhu.sound_presenter"].get_presenter()
sound_presenter.play.assert_called_once_with(mock_sound, False)
def test_present_braille_message(self, test_context: CthulhuTestContext) -> None:
"""Test present_braille_message shows braille message."""
essential_modules = self._setup_dependencies(test_context)
from cthulhu.presentation_manager import PresentationManager
PresentationManager.present_braille_message("Test braille", restore_previous=False)
braille_presenter = essential_modules["cthulhu.braille_presenter"].get_presenter()
braille_presenter.present_message.assert_called_once_with(
"Test braille",
restore_previous=False,
)
def test_present_braille_message_braille_disabled(self, test_context: CthulhuTestContext) -> None:
"""Test present_braille_message delegates even when braille is disabled."""
essential_modules = self._setup_dependencies(test_context)
braille_presenter_instance = essential_modules["cthulhu.braille_presenter"].get_presenter()
braille_presenter_instance.use_braille.return_value = False
from cthulhu.presentation_manager import PresentationManager
PresentationManager.present_braille_message("Test braille")
braille_presenter_instance.present_message.assert_called_once()
def test_spell_item_delegates(self, test_context: CthulhuTestContext) -> None:
"""Test spell_item delegates to speech_presenter."""
essential_modules = self._setup_dependencies(test_context)
from cthulhu.presentation_manager import get_manager
manager = get_manager()
manager.spell_item("abc")
speech_pres = essential_modules["cthulhu.speech_presenter"].get_presenter()
speech_pres.spell_item.assert_called_once_with("abc")
def test_spell_phonetically_delegates(self, test_context: CthulhuTestContext) -> None:
"""Test spell_phonetically delegates to speech_presenter."""
essential_modules = self._setup_dependencies(test_context)
from cthulhu.presentation_manager import get_manager
manager = get_manager()
manager.spell_phonetically("ab")
speech_pres = essential_modules["cthulhu.speech_presenter"].get_presenter()
speech_pres.spell_phonetically.assert_called_once_with("ab")
def test_speak_character_delegates(self, test_context: CthulhuTestContext) -> None:
"""Test speak_character delegates to speech_presenter."""
essential_modules = self._setup_dependencies(test_context)
from cthulhu.presentation_manager import get_manager
manager = get_manager()
manager.speak_character("a")
speech_pres = essential_modules["cthulhu.speech_presenter"].get_presenter()
speech_pres.speak_character.assert_called_once_with(
"a",
voice_from="a",
cap_style=None,
obj=None,
)
def test_speak_message_delegates(self, test_context: CthulhuTestContext) -> None:
"""Test speak_message delegates to speech_presenter."""
essential_modules = self._setup_dependencies(test_context)
from cthulhu.presentation_manager import get_manager
manager = get_manager()
manager.speak_message("Hello world")
speech_pres = essential_modules["cthulhu.speech_presenter"].get_presenter()
speech_pres.speak_message.assert_called_once_with(
"Hello world",
)
def test_speak_contents_delegates(self, test_context: CthulhuTestContext) -> None:
"""Test speak_contents delegates to speech_presenter."""
essential_modules = self._setup_dependencies(test_context)
from cthulhu.presentation_manager import get_manager
manager = get_manager()
mock_contents = [(test_context.Mock(), 0, 10, "test text")]
manager.speak_contents(mock_contents)
speech_pres = essential_modules["cthulhu.speech_presenter"].get_presenter()
speech_pres.speak_contents.assert_called_once_with(mock_contents)
def test_speak_contents_with_kwargs_delegates(self, test_context: CthulhuTestContext) -> None:
"""Test speak_contents passes keyword arguments to speech_presenter."""
essential_modules = self._setup_dependencies(test_context)
from cthulhu.presentation_manager import get_manager
manager = get_manager()
mock_contents = [(test_context.Mock(), 0, 10, "test text")]
mock_prior = test_context.Mock()
manager.speak_contents(mock_contents, priorObj=mock_prior)
speech_pres = essential_modules["cthulhu.speech_presenter"].get_presenter()
speech_pres.speak_contents.assert_called_once_with(mock_contents, priorObj=mock_prior)
def test_display_contents(self, test_context: CthulhuTestContext) -> None:
"""Test display_contents delegates to braille presenter."""
essential_modules = self._setup_dependencies(test_context)
script_manager = essential_modules["cthulhu.script_manager"].get_manager()
script = script_manager.get_active_script()
from cthulhu.presentation_manager import get_manager
manager = get_manager()
mock_contents = [(test_context.Mock(), 0, 10, "test text")]
manager.display_contents(mock_contents)
braille_presenter_instance = essential_modules["cthulhu.braille_presenter"].get_presenter()
braille_presenter_instance.display_generated_contents.assert_called_once_with(
script,
mock_contents,
)
def test_display_contents_braille_disabled(self, test_context: CthulhuTestContext) -> None:
"""Test display_contents still delegates to braille presenter (which checks internally)."""
essential_modules = self._setup_dependencies(test_context)
from cthulhu.presentation_manager import get_manager
manager = get_manager()
mock_contents = [(test_context.Mock(), 0, 10, "test text")]
manager.display_contents(mock_contents)
braille_presenter_instance = essential_modules["cthulhu.braille_presenter"].get_presenter()
braille_presenter_instance.display_generated_contents.assert_called_once()
def test_display_contents_no_active_script(self, test_context: CthulhuTestContext) -> None:
"""Test display_contents returns early when no active script."""
essential_modules = self._setup_dependencies(test_context)
script_manager = essential_modules["cthulhu.script_manager"].get_manager()
script_manager.get_active_script.return_value = None
from cthulhu.presentation_manager import get_manager
manager = get_manager()
mock_contents = [(test_context.Mock(), 0, 10, "test text")]
manager.display_contents(mock_contents)
braille_presenter_instance = essential_modules["cthulhu.braille_presenter"].get_presenter()
braille_presenter_instance.display_generated_contents.assert_not_called()
def test_display_contents_empty_regions(self, test_context: CthulhuTestContext) -> None:
"""Test display_contents delegates even with empty contents."""
essential_modules = self._setup_dependencies(test_context)
from cthulhu.presentation_manager import get_manager
manager = get_manager()
mock_contents: list = []
manager.display_contents(mock_contents)
braille_presenter_instance = essential_modules["cthulhu.braille_presenter"].get_presenter()
braille_presenter_instance.display_generated_contents.assert_called_once()
@pytest.mark.parametrize(
"flash_enabled,use_braille,expected_flash_called",
[
pytest.param(True, True, True, id="flash_and_braille_enabled"),
pytest.param(False, True, False, id="flash_disabled"),
pytest.param(True, False, False, id="braille_disabled"),
pytest.param(False, False, False, id="both_disabled"),
],
)
def test_present_message_braille_conditions(
self,
test_context: CthulhuTestContext,
flash_enabled: bool,
use_braille: bool,
expected_flash_called: bool,
) -> None:
"""Test present_message braille behavior under various conditions."""
essential_modules = self._setup_dependencies(test_context)
braille_presenter_instance = essential_modules["cthulhu.braille_presenter"].get_presenter()
braille_presenter_instance.use_braille.return_value = use_braille
braille_presenter_instance.get_flash_messages_are_enabled.return_value = flash_enabled
from cthulhu.presentation_manager import get_manager
manager = get_manager()
manager.present_message("Test message")
if expected_flash_called:
braille_presenter_instance.present_message.assert_called()
else:
braille_presenter_instance.present_message.assert_not_called()