Fix stuff, apply ruff rules.

This commit is contained in:
2026-04-11 20:55:30 -04:00
parent b38d10575e
commit 09c03ad06a
26 changed files with 1466 additions and 30 deletions
+36
View File
@@ -0,0 +1,36 @@
"""Regression tests for BrlAPI command exports."""
from __future__ import annotations
import pytest
from cthulhu import braille
@pytest.mark.unit
def test_braille_exports_brlapi_command_aliases():
expected_names = [
"BRLAPI_KEY_CMD_HWINLT",
"BRLAPI_KEY_CMD_FWINLT",
"BRLAPI_KEY_CMD_FWINLTSKIP",
"BRLAPI_KEY_CMD_HWINRT",
"BRLAPI_KEY_CMD_FWINRT",
"BRLAPI_KEY_CMD_FWINRTSKIP",
"BRLAPI_KEY_CMD_LNUP",
"BRLAPI_KEY_CMD_LNDN",
"BRLAPI_KEY_CMD_FREEZE",
"BRLAPI_KEY_CMD_TOP_LEFT",
"BRLAPI_KEY_CMD_BOT_LEFT",
"BRLAPI_KEY_CMD_HOME",
"BRLAPI_KEY_CMD_SIXDOTS",
"BRLAPI_KEY_CMD_ROUTE",
"BRLAPI_KEY_CMD_CUTBEGIN",
"BRLAPI_KEY_CMD_CUTLINE",
]
for name in expected_names:
assert hasattr(braille, name), name
if braille._brlAPIAvailable:
assert braille.BRLAPI_KEY_CMD_HWINLT == braille.brlapi.KEY_CMD_HWINLT
assert braille.BRLAPI_KEY_CMD_ROUTE == braille.brlapi.KEY_CMD_ROUTE
+128
View File
@@ -0,0 +1,128 @@
"""Regression tests for braille API compatibility."""
from __future__ import annotations
import pytest
from cthulhu import braille
@pytest.mark.unit
def test_braille_exports_snake_case_compatibility_apis():
expected_names = [
"check_braille_setting",
"disable_braille",
"display_line",
"display_message",
"get_caret_context",
"get_default_contraction_table",
"get_region_at_cell",
"is_flash_active",
"kill_flash",
"pan_left",
"pan_right",
"process_routing_key",
"return_to_region_with_focus",
"set_brlapi_priority",
"set_contraction_table",
"set_enable_braille",
"set_enable_computer_braille_at_cursor",
"set_enable_contracted_braille",
"set_enable_eol",
"set_enable_word_wrap",
"set_link_indicator",
"set_monitor_callback",
"set_selector_indicator",
"set_text_attributes_indicator",
"setup_key_ranges",
"toggle_contracted_braille",
"try_reposition_cursor",
]
for name in expected_names:
assert hasattr(braille, name), name
@pytest.mark.unit
def test_line_exports_snake_case_helpers():
line = braille.Line()
first = braille.Region("first")
second = braille.Region("second")
line.add_region(first)
line.add_regions([second])
assert line.regions == [first, second]
assert line.get_line_info() == line.getLineInfo()
@pytest.mark.unit
def test_setup_key_ranges_delegates_to_legacy_api(monkeypatch):
calls = []
def setup_key_ranges(keys):
calls.append(keys)
monkeypatch.setattr(braille, "setupKeyRanges", setup_key_ranges)
braille.setup_key_ranges({1, 2})
assert calls == [{1, 2}]
@pytest.mark.unit
def test_snake_case_runtime_setters_update_legacy_settings(monkeypatch):
monkeypatch.setattr(braille.settings, "enableBraille", True)
monkeypatch.setattr(braille.settings, "enableContractedBraille", False)
monkeypatch.setattr(braille.settings, "brailleContractionTable", "")
monkeypatch.setattr(braille.settings, "disableBrailleEOL", False)
monkeypatch.setattr(braille.settings, "enableBrailleWordWrap", False)
monkeypatch.setattr(braille.settings, "brailleSelectorIndicator", 0)
monkeypatch.setattr(braille.settings, "brailleLinkIndicator", 0)
monkeypatch.setattr(braille.settings, "textAttributesBrailleIndicator", 0)
braille.set_enable_braille(False)
braille.set_enable_contracted_braille(True)
braille.set_contraction_table("/tmp/table.ctb")
braille.set_enable_eol(False)
braille.set_enable_word_wrap(True)
braille.set_selector_indicator(64)
braille.set_link_indicator(128)
braille.set_text_attributes_indicator(192)
assert braille.settings.enableBraille is False
assert braille.settings.enableContractedBraille is True
assert braille.settings.brailleContractionTable == "/tmp/table.ctb"
assert braille.settings.disableBrailleEOL is True
assert braille.settings.enableBrailleWordWrap is True
assert braille.settings.brailleSelectorIndicator == 64
assert braille.settings.brailleLinkIndicator == 128
assert braille.settings.textAttributesBrailleIndicator == 192
@pytest.mark.unit
def test_display_message_accepts_snake_case_keyword(monkeypatch):
calls = []
def display_message(message, cursor=-1, flashTime=0):
calls.append((message, cursor, flashTime))
monkeypatch.setattr(braille, "displayMessage", display_message)
braille.display_message("hello", flash_time=250)
assert calls == [("hello", -1, 250)]
@pytest.mark.unit
def test_kill_flash_accepts_snake_case_keyword(monkeypatch):
calls = []
def kill_flash(restoreSaved=True):
calls.append(restoreSaved)
monkeypatch.setattr(braille, "killFlash", kill_flash)
braille.kill_flash(restore_saved=False)
assert calls == [False]
+38
View File
@@ -0,0 +1,38 @@
"""Regression tests for command-name exports."""
from __future__ import annotations
import pytest
from cthulhu import cmdnames
@pytest.mark.unit
@pytest.mark.parametrize(
"name",
[
"BYPASS_MODE_TOGGLE",
"CHAT_NEXT_MESSAGE",
"CLIPBOARD_PRESENT_CONTENTS",
"DEBUG_CLEAR_ATSPI_CACHE_FOR_APPLICATION",
"DEBUG_CYCLE_LEVEL",
"LIVE_REGIONS_ARE_ANNOUNCED",
"LIVE_REGIONS_NEXT",
"LIVE_REGIONS_PREVIOUS",
"PRESENT_BATTERY_STATUS",
"PRESENT_CELL_FORMULA",
"PRESENT_CPU_AND_MEMORY_USAGE",
"PRESENT_CURRENT_PROFILE",
"STRUCTURAL_NAVIGATION_MODE_CYCLE",
"TABLE_CELL_BEGINNING_OF_ROW",
"TABLE_CELL_BOTTOM_OF_COLUMN",
"TABLE_CELL_END_OF_ROW",
"TABLE_CELL_TOP_OF_COLUMN",
"TABLE_NAVIGATION_TOGGLE",
"TOGGLE_BRAILLE_MONITOR",
"TOGGLE_KEYBOARD_LAYOUT",
"TOGGLE_SPEECH_MONITOR",
],
)
def test_referenced_command_name_is_exported(name: str) -> None:
assert getattr(cmdnames, name)
+33
View File
@@ -0,0 +1,33 @@
"""Regression tests for focus manager API compatibility."""
from __future__ import annotations
import pytest
from cthulhu import focus_manager
@pytest.mark.unit
def test_is_in_preferences_window_tracks_cthulhu_active_window(monkeypatch):
manager = focus_manager.FocusManager.__new__(focus_manager.FocusManager)
window = object()
app = object()
manager._window = window
monkeypatch.setattr(focus_manager.AXObject, "get_application", lambda obj: app)
monkeypatch.setattr(focus_manager.AXObject, "get_name", lambda obj: "Cthulhu")
assert manager.is_in_preferences_window() is True
@pytest.mark.unit
def test_is_in_preferences_window_is_false_for_non_cthulhu_window(monkeypatch):
manager = focus_manager.FocusManager.__new__(focus_manager.FocusManager)
window = object()
app = object()
manager._window = window
monkeypatch.setattr(focus_manager.AXObject, "get_application", lambda obj: app)
monkeypatch.setattr(focus_manager.AXObject, "get_name", lambda obj: "Terminal")
assert manager.is_in_preferences_window() is False
+23
View File
@@ -348,6 +348,29 @@ class TestInputEventManager:
if case["expects_debug_call"]:
essential_modules["cthulhu.debug"].print_tokens.assert_called()
def test_add_grabs_for_keybinding_allows_default_cthulhu_modifiers(
self,
test_context: CthulhuTestContext,
) -> None:
"""Plugin bindings can be grabbed before a script supplies modifier keys."""
input_event_manager, _essential_modules = self._setup_input_event_manager(test_context)
mock_device = test_context.Mock()
mock_device.add_key_grab.return_value = 123
input_event_manager._device = mock_device
mock_kd = test_context.Mock()
mock_binding = test_context.Mock()
mock_binding.has_grabs.return_value = False
mock_binding.key_definitions.return_value = [mock_kd]
result = input_event_manager.add_grabs_for_keybinding(mock_binding)
assert result == [123]
mock_binding.key_definitions.assert_called_once_with(None)
mock_device.add_key_grab.assert_called_once_with(mock_kd, None)
@pytest.mark.parametrize(
"case",
[
+88
View File
@@ -0,0 +1,88 @@
"""Regression tests for keybinding API compatibility."""
from __future__ import annotations
import pytest
from cthulhu import keybindings
class DummyHandler:
description = "dummy handler"
@pytest.mark.unit
def test_keybinding_accepts_lightweight_command_manager_form():
binding = keybindings.KeyBinding("KP_Divide", keybindings.CTHULHU_MODIFIER_MASK)
assert binding.keysymstring == "KP_Divide"
assert binding.modifier_mask == keybindings.defaultModifierMask
assert binding.modifiers == keybindings.CTHULHU_MODIFIER_MASK
assert binding.handler is None
assert binding.click_count == 1
@pytest.mark.unit
def test_keybinding_accepts_lightweight_positional_click_count():
binding = keybindings.KeyBinding("a", keybindings.CTHULHU_MODIFIER_MASK, 2)
assert binding.modifier_mask == keybindings.defaultModifierMask
assert binding.modifiers == keybindings.CTHULHU_MODIFIER_MASK
assert binding.click_count == 2
@pytest.mark.unit
def test_keybinding_preserves_legacy_handler_form():
handler = DummyHandler()
binding = keybindings.KeyBinding(
"b",
keybindings.defaultModifierMask,
keybindings.CTHULHU_SHIFT_MODIFIER_MASK,
handler,
3,
)
assert binding.modifier_mask == keybindings.defaultModifierMask
assert binding.modifiers == keybindings.CTHULHU_SHIFT_MODIFIER_MASK
assert binding.handler is handler
assert binding.click_count == 3
@pytest.mark.unit
def test_keybinding_exposes_command_manager_helpers():
binding = keybindings.KeyBinding("c", keybindings.CTHULHU_MODIFIER_MASK)
assert keybindings.get_modifier_names is keybindings.getModifierNames
assert keybindings.CTHULHU_ALT_SHIFT_MODIFIER_MASK == (
keybindings.CTHULHU_MODIFIER_MASK
| keybindings.ALT_MODIFIER_MASK
| keybindings.SHIFT_MODIFIER_MASK
)
binding.set_grab_ids([42])
assert binding.get_grab_ids() == [42]
@pytest.mark.unit
def test_keybinding_add_grabs_accepts_explicit_cthulhu_modifiers(monkeypatch):
binding = keybindings.KeyBinding("d", keybindings.CTHULHU_MODIFIER_MASK)
captured = {}
class FakeInputEventManager:
def add_grabs_for_keybinding(self, grab_binding, cthulhu_modifiers):
captured["binding"] = grab_binding
captured["cthulhu_modifiers"] = cthulhu_modifiers
return [7]
from cthulhu import input_event_manager
monkeypatch.setattr(
input_event_manager,
"get_manager",
lambda: FakeInputEventManager(),
)
binding.add_grabs(["Insert"])
assert captured == {"binding": binding, "cthulhu_modifiers": ["Insert"]}
assert binding.get_grab_ids() == [7]
+6
View File
@@ -17,6 +17,12 @@ from cthulhu import learn_mode_presenter
class LearnModePresenterRegressionTests(unittest.TestCase):
def test_get_presenter_alias_matches_legacy_get_presenter(self):
self.assertIs(learn_mode_presenter.get_presenter, learn_mode_presenter.getPresenter)
def test_exposes_default_extension_setup_hook(self):
self.assertTrue(hasattr(learn_mode_presenter.LearnModePresenter, "set_up_commands"))
def test_escape_keyval_exits_learn_mode_when_event_string_is_control_character(self):
presenter = learn_mode_presenter.LearnModePresenter(mock.Mock())
presenter._is_active = True
@@ -59,6 +59,12 @@ class InputEventManagerPointerMonitorTests(unittest.TestCase):
def setUp(self):
self.manager = input_event_manager.InputEventManager()
def test_get_reviewer_alias_matches_legacy_get_reviewer(self):
self.assertIs(mouse_review.get_reviewer, mouse_review.getReviewer)
def test_exposes_default_extension_setup_hook(self):
self.assertTrue(hasattr(mouse_review.MouseReviewer, "set_up_commands"))
def test_activate_device_creates_the_device_only_once(self):
device = FakeDevice()
deviceFactory = FakeDeviceFactory(device)
@@ -0,0 +1,15 @@
"""Compatibility tests for the notification presenter."""
from __future__ import annotations
import pytest
from cthulhu import notification_presenter
@pytest.mark.unit
def test_get_presenter_alias_matches_legacy_get_presenter() -> None:
"""The Mako monitor still uses the legacy camelCase singleton accessor."""
assert notification_presenter.getPresenter is notification_presenter.get_presenter
assert notification_presenter.getPresenter() is notification_presenter.get_presenter()
+61
View File
@@ -0,0 +1,61 @@
"""Regression tests for script startup ordering."""
from __future__ import annotations
import pytest
from cthulhu.scripts import default
script_module = default.script
@pytest.mark.unit
def test_script_initializes_formatting_before_generators(monkeypatch):
"""Generator construction needs script.formatting during startup."""
monkeypatch.setattr(script_module.AXObject, "get_name", staticmethod(lambda _app: "test-app"))
init_order = []
formatting = {"sentinel": True}
class FormattingAwareScript(script_module.Script):
def get_formatting(self):
init_order.append("formatting")
return formatting
def get_listeners(self):
return {}
def get_utilities(self):
return object()
def _create_braille_generator(self):
init_order.append("braille")
assert self.formatting is formatting
return "braille"
def _create_sound_generator(self):
init_order.append("sound")
assert self.formatting is formatting
return "sound"
def _create_speech_generator(self):
init_order.append("speech")
assert self.formatting is formatting
return "speech"
def get_label_inference(self):
return None
def get_chat(self):
return None
def set_up_commands(self):
pass
test_script = FormattingAwareScript(object())
assert init_order == ["formatting", "braille", "sound", "speech"]
assert test_script.formatting is formatting
assert test_script.get_braille_generator() == "braille"
assert test_script.get_sound_generator() == "sound"
assert test_script.get_speech_generator() == "speech"
+55
View File
@@ -0,0 +1,55 @@
"""Compatibility tests for the sleep mode manager."""
from __future__ import annotations
import importlib
import sys
from typing import TYPE_CHECKING
import pytest
if TYPE_CHECKING:
from .cthulhu_test_context import CthulhuTestContext
@pytest.mark.unit
def test_get_manager_alias_matches_legacy_get_manager(
test_context: CthulhuTestContext,
) -> None:
"""The default script expects the snake_case singleton accessor."""
test_context.setup_shared_dependencies()
sys.modules.pop("cthulhu.sleep_mode_manager", None)
sleep_mode_manager = importlib.import_module("cthulhu.sleep_mode_manager")
assert sleep_mode_manager.get_manager is sleep_mode_manager.getManager
assert sleep_mode_manager.get_manager() is sleep_mode_manager.getManager()
@pytest.mark.unit
def test_sleep_mode_manager_exposes_default_extension_setup_hook(
test_context: CthulhuTestContext,
) -> None:
"""The default script calls set_up_commands() on all extensions."""
test_context.setup_shared_dependencies()
sys.modules.pop("cthulhu.sleep_mode_manager", None)
sleep_mode_manager = importlib.import_module("cthulhu.sleep_mode_manager")
assert hasattr(sleep_mode_manager.get_manager(), "set_up_commands")
@pytest.mark.unit
def test_sleep_mode_manager_exposes_snake_case_app_state_accessor(
test_context: CthulhuTestContext,
) -> None:
"""The script manager checks sleep mode using the snake_case method name."""
test_context.setup_shared_dependencies()
sys.modules.pop("cthulhu.sleep_mode_manager", None)
sleep_mode_manager = importlib.import_module("cthulhu.sleep_mode_manager")
manager = sleep_mode_manager.get_manager()
app = object()
assert manager.is_active_for_app(app) == manager.isActiveForApp(app)
+53
View File
@@ -0,0 +1,53 @@
"""Regression tests for speech monitor callback registration."""
from __future__ import annotations
import pytest
from cthulhu import speech
@pytest.mark.unit
def test_set_monitor_callbacks_accepts_snake_case_keywords() -> None:
captured: list[str] = []
speech.set_monitor_callbacks(
write_text=captured.append,
write_key=lambda _key: None,
begin_group=lambda: None,
end_group=lambda: None,
)
try:
speech._write_to_monitor("hello")
finally:
speech.set_monitor_callbacks()
assert captured == ["hello"]
@pytest.mark.unit
def test_set_monitor_callbacks_preserves_legacy_write_text_argument() -> None:
captured: list[str] = []
speech.set_monitor_callbacks(writeText=captured.append)
try:
speech._write_to_monitor("legacy")
finally:
speech.set_monitor_callbacks()
assert captured == ["legacy"]
@pytest.mark.unit
def test_speech_server_snake_case_accessors_delegate_to_legacy_api(monkeypatch) -> None:
monkeypatch.setattr(speech, "_refreshEchoSpeechServer", lambda: None)
original_server = speech.getSpeechServer()
server = object()
try:
speech.set_server(server)
assert speech.get_speech_server() is server
assert speech.getSpeechServer() is server
finally:
speech.setSpeechServer(original_server)
+121
View File
@@ -0,0 +1,121 @@
"""Regression tests for speech server API compatibility."""
from __future__ import annotations
import pytest
from cthulhu import speechserver
from cthulhu.acss import ACSS
class DummySpeechServer(speechserver.SpeechServer):
factory_calls = []
active_servers_shutdown = False
@staticmethod
def getFactoryName():
return "Dummy"
@staticmethod
def getSpeechServers():
return ["server-a"]
@staticmethod
def getSpeechServer(info=None):
DummySpeechServer.factory_calls.append(info)
return DummySpeechServer()
@staticmethod
def shutdownActiveServers():
DummySpeechServer.active_servers_shutdown = True
def __init__(self):
self.calls = []
self._current_voice_properties = {}
def getInfo(self):
return ["Dummy", "dummy"]
def getVoiceFamilies(self):
return ["voice-a"]
def getVoiceFamiliesForLanguage(self, language, dialect="", maximum=None):
return [(language, dialect, maximum)]
def getOutputModule(self):
return "module-a"
def setOutputModule(self, module):
self.calls.append(("setOutputModule", module))
def updateCapitalizationStyle(self):
self.calls.append(("updateCapitalizationStyle",))
def updatePunctuationLevel(self):
self.calls.append(("updatePunctuationLevel",))
def increaseSpeechRate(self, step=5):
self.calls.append(("increaseSpeechRate", step))
def decreaseSpeechVolume(self, step=0.5):
self.calls.append(("decreaseSpeechVolume", step))
@pytest.mark.unit
def test_snake_case_factory_methods_delegate_to_legacy_methods():
DummySpeechServer.factory_calls.clear()
DummySpeechServer.active_servers_shutdown = False
server = DummySpeechServer.get_speech_server(("Dummy", "dummy"))
assert isinstance(server, DummySpeechServer)
assert DummySpeechServer.factory_calls == [("Dummy", "dummy")]
assert DummySpeechServer.get_speech_servers() == ["server-a"]
assert DummySpeechServer.get_factory_name() == "Dummy"
DummySpeechServer.shutdown_active_servers()
assert DummySpeechServer.active_servers_shutdown is True
@pytest.mark.unit
def test_snake_case_instance_methods_delegate_to_legacy_methods():
server = DummySpeechServer()
assert server.get_info() == ["Dummy", "dummy"]
assert server.get_voice_families() == ["voice-a"]
assert server.get_voice_families_for_language("en", variant="us") == [("en", "us", None)]
assert server.get_output_module() == "module-a"
server.set_output_module("module-b")
server.update_capitalization_style("none")
server.update_punctuation_level(speechserver.PunctuationStyle.SOME)
server.increase_speech_rate(9)
server.decrease_speech_volume(1.5)
assert server.calls == [
("setOutputModule", "module-b"),
("updateCapitalizationStyle",),
("updatePunctuationLevel",),
("increaseSpeechRate", 9),
("decreaseSpeechVolume", 1.5),
]
@pytest.mark.unit
def test_voice_property_helpers_store_and_clear_default_voice():
server = DummySpeechServer()
default_voice = {ACSS.FAMILY: {speechserver.VoiceFamily.NAME: "Voice A"}}
next_family = {speechserver.VoiceFamily.NAME: "Voice B"}
server.set_default_voice(default_voice)
assert server.get_voice_family() == default_voice[ACSS.FAMILY]
server.set_voice_family(next_family)
assert server.get_voice_family() == next_family
server.clear_cached_voice_properties()
assert server.get_voice_family() is None