From 1707dca0201194e44669f46f68c603887d31d00d Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Sun, 12 Apr 2026 15:19:09 -0400 Subject: [PATCH] Fixed a regression in table navigation. --- src/cthulhu/scripts/default.py | 10 +- src/cthulhu/structural_navigation.py | 8 +- ...structural_navigation_table_regressions.py | 47 ++++++++++ ..._toml_backend_legacy_schema_regressions.py | 91 +++++++++++++++++++ 4 files changed, 151 insertions(+), 5 deletions(-) create mode 100644 tests/test_structural_navigation_table_regressions.py create mode 100644 tests/test_toml_backend_legacy_schema_regressions.py diff --git a/src/cthulhu/scripts/default.py b/src/cthulhu/scripts/default.py index 2f89e55..45dffa8 100644 --- a/src/cthulhu/scripts/default.py +++ b/src/cthulhu/scripts/default.py @@ -3370,7 +3370,7 @@ class Script(script.Script): return True def presentMessage(self, fullMessage, briefMessage=None, voice=None, resetStyles=True, - force=False): + force=False, interrupt=True): """Convenience method to speak a message and 'flash' it in braille. Arguments: @@ -3398,7 +3398,13 @@ class Script(script.Script): else: message = fullMessage if message: - self.speakMessage(message, voice=voice, resetStyles=resetStyles, force=force) + self.speakMessage( + message, + voice=voice, + interrupt=interrupt, + resetStyles=resetStyles, + force=force, + ) if (cthulhu.cthulhuApp.settingsManager.getSetting('enableBraille') \ or cthulhu.cthulhuApp.settingsManager.getSetting('enableBrailleMonitor')) \ diff --git a/src/cthulhu/structural_navigation.py b/src/cthulhu/structural_navigation.py index 9764d4c..602d095 100644 --- a/src/cthulhu/structural_navigation.py +++ b/src/cthulhu/structural_navigation.py @@ -2217,13 +2217,15 @@ class StructuralNavigation: if settings.speakCellCoordinates: [row, col] = self.getCellCoordinates(cell) - self._script.presentMessage(messages.TABLE_CELL_COORDINATES \ - % {"row" : row + 1, "column" : col + 1}) + self._script.presentMessage( + messages.TABLE_CELL_COORDINATES % {"row": row + 1, "column": col + 1}, + interrupt=False, + ) rowspan, colspan = self._script.utilities.rowAndColumnSpan(cell) spanString = messages.cellSpan(rowspan, colspan) if spanString and settings.speakCellSpan: - self._script.presentMessage(spanString) + self._script.presentMessage(spanString, interrupt=False) ######################## # # diff --git a/tests/test_structural_navigation_table_regressions.py b/tests/test_structural_navigation_table_regressions.py new file mode 100644 index 0000000..7a3120f --- /dev/null +++ b/tests/test_structural_navigation_table_regressions.py @@ -0,0 +1,47 @@ +import os +import sys +import unittest +from pathlib import Path +from unittest import mock + +import gi + +os.environ.setdefault("GSETTINGS_BACKEND", "memory") + +gi.require_version("Atspi", "2.0") + +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) + +from cthulhu import messages +from cthulhu import structural_navigation + + +class StructuralNavigationTableRegressionTests(unittest.TestCase): + def test_table_cell_coordinates_do_not_interrupt_cell_contents(self): + navigator = structural_navigation.StructuralNavigation.__new__( + structural_navigation.StructuralNavigation + ) + navigator._script = mock.Mock() + navigator._script.utilities.rowAndColumnSpan.return_value = (1, 1) + navigator._getCaretPosition = mock.Mock(return_value=("cell", 0)) + navigator._setCaretPosition = mock.Mock(return_value=("cell", 0)) + navigator._isBlankCell = mock.Mock(return_value=False) + navigator._presentObject = mock.Mock() + navigator.getCellCoordinates = mock.Mock(return_value=(1, 2)) + + with ( + mock.patch.object(structural_navigation.settings, "speakCellHeaders", False), + mock.patch.object(structural_navigation.settings, "speakCellCoordinates", True), + mock.patch.object(structural_navigation.settings, "speakCellSpan", False), + ): + navigator._tableCellPresentation("cell", None) + + navigator._presentObject.assert_called_once_with("cell", 0) + navigator._script.presentMessage.assert_called_once_with( + messages.TABLE_CELL_COORDINATES % {"row": 2, "column": 3}, + interrupt=False, + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_toml_backend_legacy_schema_regressions.py b/tests/test_toml_backend_legacy_schema_regressions.py new file mode 100644 index 0000000..0dbb870 --- /dev/null +++ b/tests/test_toml_backend_legacy_schema_regressions.py @@ -0,0 +1,91 @@ +import tempfile +import unittest +from pathlib import Path + +from cthulhu import settings +from cthulhu.backends.toml_backend import Backend + + +LEGACY_SETTINGS = """format-version = 2 + +[profiles.default.metadata] +display-name = "Default" +internal-name = "default" + +[profiles.default.keybindings] +keyboard-layout = "desktop" +desktop-modifier-keys = ["Insert", "KP_Insert"] + +[profiles.default.ai-assistant] +enabled = false +provider = "ollama" +api-key-file = "" +ollama-model = "llama3.2-vision" +ollama-endpoint = "http://localhost:11434" +confirmation-required = true +action-timeout = 30 +screenshot-quality = "medium" +max-context-length = 4000 + +[profiles.default.ocr] +language-code = "eng" +scale-factor = 3 +grayscale-image = false +invert-image = false +black-white-image = false +black-white-threshold = 200 +color-calculation = false +color-calculation-max = 3 +copy-to-clipboard = false + +[profiles.default.plugins] +active-plugins = ["PluginManager", "OCR"] +plugin-sources = [] +""" + + +class LegacyTomlSchemaMigrationTests(unittest.TestCase): + def test_backend_migrates_legacy_nested_profile_schema_on_read(self): + with tempfile.TemporaryDirectory() as tempDir: + Path(tempDir, "user-settings.toml").write_text( + LEGACY_SETTINGS, + encoding="utf-8", + ) + + backend = Backend(tempDir) + + self.assertEqual(backend.availableProfiles(), [["Default", "default"]]) + + general = backend.getGeneral("default") + + self.assertEqual(general["profile"], ["Default", "default"]) + self.assertEqual( + general["keyboardLayout"], + settings.GENERAL_KEYBOARD_LAYOUT_DESKTOP, + ) + self.assertEqual(general["cthulhuModifierKeys"], settings.DESKTOP_MODIFIER_KEYS) + self.assertEqual(general["activePlugins"], ["PluginManager", "OCR"]) + self.assertEqual(general["aiProvider"], settings.AI_PROVIDER_OLLAMA) + self.assertFalse(general["aiAssistantEnabled"]) + self.assertEqual(general["ocrLanguageCode"], "eng") + self.assertEqual(backend.getKeybindings("default"), {}) + + def test_saving_after_legacy_read_rewrites_current_schema(self): + with tempfile.TemporaryDirectory() as tempDir: + settingsPath = Path(tempDir, "user-settings.toml") + settingsPath.write_text(LEGACY_SETTINGS, encoding="utf-8") + + backend = Backend(tempDir) + general = backend.getGeneral("default") + backend.saveProfileSettings("default", dict(general), {}, {}) + + savedSettings = settingsPath.read_text(encoding="utf-8") + + self.assertIn('profile = ["Default", "default"]', savedSettings) + self.assertIn('activePlugins = ["PluginManager", "OCR"]', savedSettings) + self.assertNotIn("format-version = 2", savedSettings) + self.assertNotIn("[profiles.default.metadata]", savedSettings) + + +if __name__ == "__main__": + unittest.main()