import sys import types import unittest from pathlib import Path from unittest import mock from types import SimpleNamespace sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) sound_stub = types.ModuleType("cthulhu.sound") sound_stub.getPlayer = mock.Mock(return_value=None) sys.modules.setdefault("cthulhu.sound", sound_stub) speech_stub = types.ModuleType("cthulhu.speech") speech_stub.speak = mock.Mock() sys.modules.setdefault("cthulhu.speech", speech_stub) from cthulhu import settings from cthulhu.sound_theme_manager import SoundThemeManager from cthulhu.speech_generator import SpeechGenerator from cthulhu.script_utilities import Utilities class LinkSoundThemeRegressionTests(unittest.TestCase): def test_default_theme_install_manifest_includes_link_sounds(self): mesonPath = Path(__file__).resolve().parents[1] / "sounds" / "meson.build" mesonText = mesonPath.read_text(encoding="utf-8") self.assertIn("'default/link.wav'", mesonText) self.assertIn("'default/link_visited.wav'", mesonText) def _create_manager(self): app = mock.Mock() settingsManager = mock.Mock() settingsManager.getSetting.return_value = "default" app.getSettingsManager.return_value = settingsManager return SoundThemeManager(app) def test_visited_link_prefers_visited_asset(self): manager = self._create_manager() with mock.patch.object( manager, "getSoundPath", side_effect=["/tmp/link_visited.wav", "/tmp/link.wav"], ): icon = manager.getLinkSoundIcon(visited=True) self.assertTrue(icon.path.endswith("link_visited.wav")) def test_visited_link_falls_back_to_plain_link_asset(self): manager = self._create_manager() with mock.patch.object( manager, "getSoundPath", side_effect=[None, "/tmp/link.wav"], ): icon = manager.getLinkSoundIcon(visited=True) self.assertTrue(icon.path.endswith("link.wav")) class SpeechGeneratorLinkSoundRegressionTests(unittest.TestCase): def test_role_sound_icon_uses_visited_link_asset_for_visited_links(self): generator = object.__new__(SpeechGenerator) generator._script = mock.Mock() generator._script.utilities = mock.Mock() icon = SimpleNamespace(path="/tmp/link_visited.wav") with ( mock.patch("cthulhu.speech_generator.AXUtilities.is_link", return_value=True), mock.patch("cthulhu.speech_generator.AXUtilities.is_visited", return_value=True), mock.patch("cthulhu.speech_generator.sound_theme_manager.getManager") as get_manager, ): get_manager.return_value.getLinkSoundIcon.return_value = icon result = generator._getRoleSoundIcon(mock.Mock(), "link-role") self.assertEqual(result, icon) get_manager.return_value.getLinkSoundIcon.assert_called_once_with(visited=True) class LinkIndicatorPresentationRegressionTests(unittest.TestCase): def test_sound_only_inline_link_marker_uses_icon_instead_of_word(self): utility = object.__new__(Utilities) utility.adjustForLinks = mock.Mock(return_value="Docs link") icon = SimpleNamespace(path="/tmp/link.wav") link = mock.Mock() def get_setting(name): values = { "roleSoundPresentation": settings.ROLE_SOUND_PRESENTATION_SOUND_ONLY, "enableSound": True, } return values.get(name) fakeApp = mock.Mock() fakeApp.settingsManager.getSetting.side_effect = get_setting with ( mock.patch("cthulhu.script_utilities.AXObject.supports_hypertext", return_value=True), mock.patch("cthulhu.script_utilities.AXHypertext.get_all_links", return_value=[link]), mock.patch("cthulhu.script_utilities.AXHypertext.get_link_start_offset", return_value=0), mock.patch("cthulhu.script_utilities.AXHypertext.get_link_end_offset", return_value=4), mock.patch("cthulhu.script_utilities.AXUtilities.is_visited", return_value=False), mock.patch("cthulhu.sound_theme_manager.getManager") as get_manager, mock.patch("cthulhu.script_utilities.cthulhu.cthulhuApp", fakeApp), ): get_manager.return_value.getLinkSoundIcon.return_value = icon spoken, icons = utility.getLinkIndicatorPresentation(mock.Mock(), "Docs", 0) self.assertEqual(spoken, "Docs") self.assertEqual(icons, [icon]) def test_sound_and_speech_inline_link_marker_keeps_word_and_adds_icon(self): utility = object.__new__(Utilities) utility.adjustForLinks = mock.Mock(return_value="Docs link") icon = SimpleNamespace(path="/tmp/link_visited.wav") link = mock.Mock() def get_setting(name): values = { "roleSoundPresentation": settings.ROLE_SOUND_PRESENTATION_SOUND_AND_SPEECH, "enableSound": True, } return values.get(name) fakeApp = mock.Mock() fakeApp.settingsManager.getSetting.side_effect = get_setting with ( mock.patch("cthulhu.script_utilities.AXObject.supports_hypertext", return_value=True), mock.patch("cthulhu.script_utilities.AXHypertext.get_all_links", return_value=[link]), mock.patch("cthulhu.script_utilities.AXHypertext.get_link_start_offset", return_value=0), mock.patch("cthulhu.script_utilities.AXHypertext.get_link_end_offset", return_value=4), mock.patch("cthulhu.script_utilities.AXUtilities.is_visited", return_value=True), mock.patch("cthulhu.sound_theme_manager.getManager") as get_manager, mock.patch("cthulhu.script_utilities.cthulhu.cthulhuApp", fakeApp), ): get_manager.return_value.getLinkSoundIcon.return_value = icon spoken, icons = utility.getLinkIndicatorPresentation(mock.Mock(), "Docs", 0) self.assertEqual(spoken, "Docs link") self.assertEqual(icons, [icon]) if __name__ == "__main__": unittest.main()