Merge branch 'testing' into wine-access

This commit is contained in:
Storm Dragon
2026-07-26 17:10:39 -04:00
5 changed files with 210 additions and 5 deletions
+14
View File
@@ -3441,6 +3441,20 @@ class Script(script.Script):
return requested return requested
def should_interrupt_for_mode_announcement(
self,
requested: bool,
already_handled: bool = False,
) -> bool:
"""Returns whether a mode announcement should interrupt prior output."""
if requested and already_handled:
msg = "DEFAULT: Mode announcement interruption already handled by input dispatch"
debug.printMessage(debug.LEVEL_INFO, msg, True)
return False
return requested
def presentKeyboardEvent(self, event): def presentKeyboardEvent(self, event):
"""Convenience method to present the KeyboardEvent event. Returns True """Convenience method to present the KeyboardEvent event. Returns True
if we fully present the event; False otherwise.""" if we fully present the event; False otherwise."""
+10 -2
View File
@@ -1523,7 +1523,11 @@ class Script(default.Script):
self.utilities.setCaretContext(AXObject.get_parent(parent), -1) self.utilities.setCaretContext(AXObject.get_parent(parent), -1)
if not self._loadingDocumentContent: if not self._loadingDocumentContent:
if inputEvent is not None: if inputEvent is not None:
self.presentMessage(messages.MODE_BROWSE, interrupt=True) interrupt = self.should_interrupt_for_mode_announcement(
True,
already_handled=True,
)
self.presentMessage(messages.MODE_BROWSE, interrupt=interrupt)
else: else:
self.presentMessage(messages.MODE_BROWSE) self.presentMessage(messages.MODE_BROWSE)
if not self._shouldSuppressBrowseModeSound(obj, inputEvent): if not self._shouldSuppressBrowseModeSound(obj, inputEvent):
@@ -1536,7 +1540,11 @@ class Script(default.Script):
self.utilities.grabFocus(obj) self.utilities.grabFocus(obj)
if inputEvent is not None: if inputEvent is not None:
self.presentMessage(messages.MODE_FOCUS, interrupt=True) interrupt = self.should_interrupt_for_mode_announcement(
True,
already_handled=True,
)
self.presentMessage(messages.MODE_FOCUS, interrupt=interrupt)
else: else:
self.presentMessage(messages.MODE_FOCUS) self.presentMessage(messages.MODE_FOCUS)
sound_theme_manager.getManager().playFocusModeSound() sound_theme_manager.getManager().playFocusModeSound()
+1 -1
View File
@@ -493,7 +493,7 @@ structNavTriggersFocusMode = False
caretNavTriggersFocusMode = False caretNavTriggersFocusMode = False
nativeNavTriggersFocusMode = True nativeNavTriggersFocusMode = True
layoutMode = True layoutMode = False
rewindAndFastForwardInSayAll = False rewindAndFastForwardInSayAll = False
structNavInSayAll = False structNavInSayAll = False
+22 -2
View File
@@ -269,13 +269,33 @@ class WebPresentationModeSpeechRegressionTests(unittest.TestCase):
self.assertFalse(testScript._browseModeIsSticky) self.assertFalse(testScript._browseModeIsSticky)
testScript.refreshKeyGrabs.assert_called_once_with() testScript.refreshKeyGrabs.assert_called_once_with()
def test_manual_presentation_mode_toggle_still_interrupts(self): def test_manual_focus_mode_announcement_does_not_interrupt_after_keyboard_dispatch(self):
testScript = self._make_script(inFocusMode=False) testScript = self._make_script(inFocusMode=False)
with mock.patch.object(web_script.sound_theme_manager, "getManager", return_value=mock.Mock()): with mock.patch.object(web_script.sound_theme_manager, "getManager", return_value=mock.Mock()):
web_script.Script.togglePresentationMode(testScript, object(), "document") web_script.Script.togglePresentationMode(testScript, object(), "document")
testScript.presentMessage.assert_called_once_with(messages.MODE_FOCUS, interrupt=True) testScript.presentMessage.assert_called_once_with(messages.MODE_FOCUS, interrupt=False)
def test_manual_browse_mode_announcement_does_not_interrupt_after_keyboard_dispatch(self):
testScript = self._make_script(inFocusMode=True)
with (
mock.patch.object(web_script.AXObject, "get_parent", return_value=None),
mock.patch.object(web_script.AXUtilities, "is_list_box", return_value=False),
mock.patch.object(web_script.AXUtilities, "is_menu", return_value=False),
mock.patch.object(web_script.sound_theme_manager, "getManager", return_value=mock.Mock()),
):
web_script.Script.togglePresentationMode(testScript, object(), "document")
testScript.presentMessage.assert_called_once_with(messages.MODE_BROWSE, interrupt=False)
def test_non_keyboard_mode_announcement_keeps_interrupt_request(self):
testScript = self._make_script(inFocusMode=False)
result = web_script.Script.should_interrupt_for_mode_announcement(testScript, True)
self.assertTrue(result)
class WebObjectPresentationInterruptionRegressionTests(unittest.TestCase): class WebObjectPresentationInterruptionRegressionTests(unittest.TestCase):
+163
View File
@@ -0,0 +1,163 @@
import sys
import tempfile
import unittest
from pathlib import Path
from unittest import mock
import gi
gi.require_version("Atspi", "2.0")
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
from cthulhu import settings
from cthulhu import settings_manager
from cthulhu.scripts.web import script_utilities as web_script_utilities
class WebLayoutModeSettingsRegressionTests(unittest.TestCase):
def test_layout_mode_defaults_to_object_mode(self):
self.assertFalse(settings.layoutMode)
def test_explicit_persisted_layout_mode_true_is_preserved(self):
with tempfile.TemporaryDirectory() as tempDir:
with mock.patch.object(settings, "layoutMode", False):
manager = settings_manager.SettingsManager(mock.Mock())
manager.activate(prefsDir=tempDir)
settingsPath = Path(tempDir, "user-settings.toml")
settingsText = settingsPath.read_text(encoding="utf-8")
settingsPath.write_text(
settingsText.replace("layoutMode = false", "layoutMode = true"),
encoding="utf-8",
)
reloadedManager = settings_manager.SettingsManager(mock.Mock())
reloadedManager.activate(prefsDir=tempDir)
self.assertTrue(reloadedManager.getSetting("layoutMode"))
def test_application_specific_layout_mode_true_is_preserved(self):
with tempfile.TemporaryDirectory() as tempDir:
with mock.patch.object(settings, "layoutMode", False):
manager = settings_manager.SettingsManager(mock.Mock())
manager.activate(prefsDir=tempDir)
manager._backend.saveAppSettings(
"Browser",
manager.profile,
{"layoutMode": True},
{},
{},
)
script = mock.Mock()
script.app = object()
script.getKeyBindings.return_value = mock.Mock()
with mock.patch.object(
settings_manager.AXObject,
"get_name",
return_value="Browser",
):
manager.loadAppSettings(script)
self.assertTrue(manager.getSetting("layoutMode"))
class WebLayoutModeLineRegressionTests(unittest.TestCase):
def _make_utilities(self):
obj = object()
contents = [[obj, 0, 4, "Text"]]
utilities = web_script_utilities.Utilities.__new__(
web_script_utilities.Utilities
)
utilities._script = mock.Mock()
utilities._script.inFocusMode.return_value = False
utilities._currentLineContents = []
utilities._debugContentsInfo = mock.Mock()
utilities.findObjectInContents = mock.Mock(return_value=-1)
utilities.treatAsEndOfLine = mock.Mock(return_value=False)
utilities.getExtents = mock.Mock(return_value=[0, 0, 10, 10])
utilities.isInlineListDescendant = mock.Mock(return_value=False)
utilities._getContentsForObj = mock.Mock(return_value=contents)
utilities.isLink = mock.Mock(return_value=False)
utilities.isMath = mock.Mock(return_value=False)
utilities.isMathTopLevel = mock.Mock(return_value=False)
utilities.getDocumentForObject = mock.Mock(return_value=object())
utilities.findPreviousCaretInOrder = mock.Mock(return_value=(None, -1))
utilities.findNextCaretInOrder = mock.Mock(return_value=(None, -1))
utilities.isContentEditableWithEmbeddedObjects = mock.Mock(return_value=False)
return utilities, obj, contents
def _get_line_for_setting(self, layoutMode):
utilities, obj, contents = self._make_utilities()
settingsManager = mock.Mock()
settingsManager.getSetting.return_value = layoutMode
with (
mock.patch.object(
web_script_utilities.cthulhu.cthulhuApp,
"settingsManager",
settingsManager,
),
mock.patch.object(
web_script_utilities.AXObject,
"is_dead",
return_value=False,
),
mock.patch.object(
web_script_utilities.AXObject,
"find_ancestor",
return_value=None,
),
mock.patch.object(
web_script_utilities.AXUtilities,
"is_tool_bar",
return_value=False,
),
mock.patch.object(
web_script_utilities.AXUtilities,
"is_menu_bar",
return_value=False,
),
mock.patch.object(
web_script_utilities.AXUtilities,
"is_table_row",
return_value=False,
),
):
result = web_script_utilities.Utilities._getLineContentsAtOffset(
utilities,
obj,
0,
layoutMode=None,
useCache=False,
)
return utilities, obj, contents, result
def test_unset_profile_setting_uses_object_mode_for_web_line(self):
utilities, obj, contents, result = self._get_line_for_setting(False)
self.assertEqual(result, contents)
utilities.getDocumentForObject.assert_not_called()
utilities._debugContentsInfo.assert_called_once_with(
obj,
0,
contents,
"Line (not layout mode)",
)
def test_explicit_layout_mode_true_uses_visual_web_line(self):
utilities, obj, contents, result = self._get_line_for_setting(True)
self.assertEqual(result, contents)
utilities.getDocumentForObject.assert_called_once_with(obj)
utilities._debugContentsInfo.assert_called_once_with(
obj,
0,
contents,
"Line (layout mode)",
)
if __name__ == "__main__":
unittest.main()