Add the document selection adapter, integrate it through script utilities and major callers, and package the clipboard fallback work that was needed during manual testing on Wayland. Also include a handoff note for the still-open browser link-selection issue so other developers can continue from the current branch state without reconstructing the debug trail.
232 lines
10 KiB
Python
232 lines
10 KiB
Python
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("Gdk", "3.0")
|
|
gi.require_version("Gtk", "3.0")
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
|
|
|
soundGeneratorModule = sys.modules.get("cthulhu.sound_generator")
|
|
if soundGeneratorModule is not None and not hasattr(soundGeneratorModule, "SoundGenerator"):
|
|
class _StubSoundGenerator:
|
|
pass
|
|
|
|
soundGeneratorModule.SoundGenerator = _StubSoundGenerator
|
|
|
|
from cthulhu import messages
|
|
from cthulhu.scripts.web import script as web_script
|
|
from cthulhu.scripts.web import script_utilities as web_script_utilities
|
|
|
|
|
|
class WebKeyGrabRegressionTests(unittest.TestCase):
|
|
def _make_partial_script(self):
|
|
testScript = web_script.Script.__new__(web_script.Script)
|
|
testScript._lastCommandWasCaretNav = True
|
|
testScript._lastCommandWasStructNav = True
|
|
testScript._lastCommandWasMouseButton = True
|
|
testScript._lastMouseButtonContext = ("old", 7)
|
|
testScript._madeFindAnnouncement = True
|
|
testScript._inFocusMode = True
|
|
testScript._navSuspended = False
|
|
testScript.removeKeyGrabs = mock.Mock()
|
|
testScript.refreshKeyGrabs = mock.Mock()
|
|
testScript._setNavigationSuspended = mock.Mock()
|
|
testScript.utilities = mock.Mock()
|
|
testScript.utilities.isZombie.return_value = False
|
|
testScript.utilities.isDocument.return_value = False
|
|
testScript.utilities.getTopLevelDocumentForObject.return_value = None
|
|
return testScript
|
|
|
|
def test_window_deactivate_does_not_drop_key_grabs(self):
|
|
testScript = self._make_partial_script()
|
|
|
|
result = web_script.Script.onWindowDeactivated(testScript, mock.Mock())
|
|
|
|
self.assertFalse(result)
|
|
self.assertFalse(testScript._lastCommandWasCaretNav)
|
|
self.assertFalse(testScript._lastCommandWasStructNav)
|
|
self.assertFalse(testScript._lastCommandWasMouseButton)
|
|
self.assertEqual(testScript._lastMouseButtonContext, (None, -1))
|
|
testScript.removeKeyGrabs.assert_not_called()
|
|
|
|
def test_non_document_focus_change_without_prior_document_does_not_refresh_grabs(self):
|
|
testScript = self._make_partial_script()
|
|
oldFocus = object()
|
|
newFocus = object()
|
|
|
|
with mock.patch("cthulhu.scripts.web.script.AXObject.is_dead", return_value=False):
|
|
result = web_script.Script.locus_of_focus_changed(testScript, None, oldFocus, newFocus)
|
|
|
|
self.assertFalse(result)
|
|
self.assertFalse(testScript._madeFindAnnouncement)
|
|
self.assertFalse(testScript._inFocusMode)
|
|
testScript._setNavigationSuspended.assert_called_once_with(
|
|
True,
|
|
"focus left document content",
|
|
)
|
|
testScript.refreshKeyGrabs.assert_not_called()
|
|
|
|
def test_non_document_focus_change_refreshes_grabs_after_leaving_document(self):
|
|
testScript = self._make_partial_script()
|
|
oldFocus = object()
|
|
newFocus = object()
|
|
oldDocument = object()
|
|
|
|
testScript.utilities.getTopLevelDocumentForObject.side_effect = (
|
|
lambda obj: oldDocument if obj is oldFocus else None
|
|
)
|
|
|
|
with mock.patch("cthulhu.scripts.web.script.AXObject.is_dead", return_value=False):
|
|
result = web_script.Script.locus_of_focus_changed(testScript, None, oldFocus, newFocus)
|
|
|
|
self.assertFalse(result)
|
|
testScript.refreshKeyGrabs.assert_called_once_with()
|
|
|
|
def test_non_document_focus_change_refreshes_grabs_when_old_focus_missing(self):
|
|
testScript = self._make_partial_script()
|
|
newFocus = object()
|
|
|
|
with mock.patch("cthulhu.scripts.web.script.AXObject.is_dead", return_value=False):
|
|
result = web_script.Script.locus_of_focus_changed(testScript, None, None, newFocus)
|
|
|
|
self.assertFalse(result)
|
|
testScript.refreshKeyGrabs.assert_called_once_with()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|
|
|
|
|
|
class WebSelectionRegressionTests(unittest.TestCase):
|
|
def _make_script(self):
|
|
testScript = web_script.Script.__new__(web_script.Script)
|
|
testScript.pointOfReference = {}
|
|
testScript.utilities = mock.Mock()
|
|
testScript.utilities.sanityCheckActiveWindow = mock.Mock()
|
|
testScript.utilities.isZombie.return_value = False
|
|
testScript.utilities.eventIsBrowserUINoise.return_value = False
|
|
testScript.utilities.eventIsBrowserUIAutocompleteNoise.return_value = False
|
|
testScript.utilities.lastInputEventWasTab.return_value = False
|
|
testScript.utilities.inFindContainer.return_value = False
|
|
testScript.utilities.eventIsFromLocusOfFocusDocument.return_value = True
|
|
testScript.utilities.textEventIsDueToInsertion.return_value = False
|
|
testScript.utilities.textEventIsDueToDeletion.return_value = False
|
|
testScript.utilities.isItemForEditableComboBox.return_value = False
|
|
testScript.utilities.eventIsAutocompleteNoise.return_value = False
|
|
testScript.utilities.eventIsSpinnerNoise.return_value = False
|
|
testScript.utilities.caretMovedOutsideActiveGrid.return_value = False
|
|
testScript.utilities.treatEventAsSpinnerValueChange.return_value = False
|
|
testScript.utilities.queryNonEmptyText.return_value = "Darkest Dungeon"
|
|
testScript.utilities.lastInputEventWasCaretNavWithSelection.return_value = True
|
|
testScript.utilities.getCharacterContentsAtOffset.return_value = [[None, 0, 1, "D"]]
|
|
testScript.utilities.pathComparison.return_value = -1
|
|
testScript.updateBraille = mock.Mock()
|
|
testScript.speakContents = mock.Mock()
|
|
testScript.speakMessage = mock.Mock()
|
|
testScript._lastCommandWasCaretNav = True
|
|
testScript._lastCommandWasStructNav = False
|
|
testScript._lastCommandWasMouseButton = False
|
|
testScript._lastMouseButtonContext = (None, -1)
|
|
testScript._inFocusMode = False
|
|
testScript._focusModeIsSticky = False
|
|
testScript._browseModeIsSticky = False
|
|
return testScript
|
|
|
|
def test_broken_web_caret_selection_synthesizes_selection_from_caret_context(self):
|
|
testScript = self._make_script()
|
|
document = object()
|
|
section = object()
|
|
event = mock.Mock(source=section, detail1=-1)
|
|
manager = mock.Mock()
|
|
manager.last_event_was_forward_caret_selection.return_value = True
|
|
manager.last_event_was_backward_caret_selection.return_value = False
|
|
|
|
testScript.utilities.getTopLevelDocumentForObject.return_value = document
|
|
testScript.utilities.getCaretContext.return_value = (section, 0)
|
|
testScript.utilities.nextContext.return_value = (section, 1)
|
|
testScript.utilities.getCharacterContentsAtOffset.return_value = [[section, 0, 1, "D"]]
|
|
|
|
with (
|
|
mock.patch.object(web_script.cthulhu_state, "locusOfFocus", section),
|
|
mock.patch.object(web_script.input_event_manager, "get_manager", return_value=manager),
|
|
mock.patch.object(web_script.cthulhu, "setLocusOfFocus") as setLocusOfFocus,
|
|
):
|
|
result = web_script.Script.onCaretMoved(testScript, event)
|
|
|
|
self.assertTrue(result)
|
|
testScript.utilities.setCaretContext.assert_called_once_with(section, 1, document)
|
|
setLocusOfFocus.assert_called_once_with(event, section, False, True)
|
|
testScript.speakContents.assert_called_once_with([[section, 0, 1, "D"]])
|
|
testScript.speakMessage.assert_called_once_with(messages.TEXT_SELECTED, interrupt=False)
|
|
self.assertEqual(
|
|
testScript.pointOfReference["syntheticWebSelection"]["string"],
|
|
"D",
|
|
)
|
|
|
|
def test_broken_web_text_selection_event_synthesizes_selection_from_event_source(self):
|
|
testScript = self._make_script()
|
|
document = object()
|
|
anchor = object()
|
|
link = object()
|
|
event = mock.Mock(source=link)
|
|
|
|
testScript.utilities.getTopLevelDocumentForObject.return_value = document
|
|
testScript.utilities.getCaretContext.return_value = (anchor, 0)
|
|
testScript.utilities.nextContext.side_effect = [(anchor, 0)]
|
|
testScript.utilities.getCharacterContentsAtOffset.side_effect = (
|
|
lambda obj, _offset: [[link, 0, 1, "S"]] if obj is link else []
|
|
)
|
|
testScript.utilities.textEventIsForNonNavigableTextObject.return_value = False
|
|
testScript.utilities.isContentEditableWithEmbeddedObjects.return_value = False
|
|
testScript._compareCaretContexts = mock.Mock(return_value=1)
|
|
|
|
with (
|
|
mock.patch.object(web_script.cthulhu_state, "locusOfFocus", anchor),
|
|
mock.patch.object(web_script.AXText, "get_caret_offset", return_value=0),
|
|
mock.patch.object(web_script.AXText, "get_selected_ranges", return_value=[]),
|
|
mock.patch.object(web_script.AXText, "get_substring", return_value="S"),
|
|
mock.patch.object(web_script.cthulhu, "setLocusOfFocus") as setLocusOfFocus,
|
|
):
|
|
result = web_script.Script.onTextSelectionChanged(testScript, event)
|
|
|
|
self.assertTrue(result)
|
|
testScript.utilities.setCaretContext.assert_called_once_with(link, 0, document)
|
|
setLocusOfFocus.assert_called_once_with(event, link, False, True)
|
|
testScript.speakContents.assert_called_once_with([[link, 0, 1, "S"]])
|
|
testScript.speakMessage.assert_called_once_with(messages.TEXT_SELECTED, interrupt=False)
|
|
self.assertEqual(
|
|
testScript.pointOfReference["syntheticWebSelection"]["string"],
|
|
"S",
|
|
)
|
|
|
|
def test_web_utilities_all_selected_text_falls_back_to_synthetic_selection(self):
|
|
testScript = mock.Mock(pointOfReference={})
|
|
utilities = web_script_utilities.Utilities.__new__(web_script_utilities.Utilities)
|
|
utilities._script = testScript
|
|
document = object()
|
|
obj = object()
|
|
testScript.pointOfReference["syntheticWebSelection"] = {
|
|
"document": document,
|
|
"string": "Dark",
|
|
}
|
|
|
|
with (
|
|
mock.patch.object(
|
|
web_script_utilities.script_utilities.Utilities,
|
|
"allSelectedText",
|
|
return_value=["", 0, 0],
|
|
),
|
|
mock.patch.object(utilities, "inDocumentContent", return_value=True),
|
|
mock.patch.object(utilities, "getTopLevelDocumentForObject", return_value=document),
|
|
):
|
|
result = web_script_utilities.Utilities.allSelectedText(utilities, obj)
|
|
|
|
self.assertEqual(result, ["Dark", 0, 4])
|