Files
cthulhu/tests/test_steam_selection_regressions.py
2026-04-07 19:03:46 -04:00

357 lines
16 KiB
Python

import sys
import unittest
from pathlib import Path
from unittest import mock
import gi
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.scripts.apps.steamwebhelper import script as steam_script
from cthulhu.scripts.apps.steamwebhelper import script_utilities as steam_script_utilities
class SteamSelectionChangedTests(unittest.TestCase):
def test_selection_changed_tolerates_scalar_any_data(self):
testScript = steam_script.Script.__new__(steam_script.Script)
source = object()
event = mock.Mock(source=source, any_data=0)
chromiumCalls = []
def displayedText(obj):
if obj is source:
return ""
raise TypeError("argument self: Expected Atspi.Accessible, but got int")
def get_name(obj):
if obj is source:
return "Notifications"
raise TypeError("argument self: Expected Atspi.Accessible, but got int")
testScript.utilities = mock.Mock()
testScript.utilities.displayedText.side_effect = displayedText
def chromiumOnSelectionChanged(self, selectionEvent):
chromiumCalls.append((self, selectionEvent))
return True
with (
mock.patch.object(steam_script.AXObject, "get_name", side_effect=get_name),
mock.patch.object(steam_script.AXObject, "get_description", return_value=""),
mock.patch.object(steam_script.AXObject, "get_role_name", return_value="page tab list"),
mock.patch.object(steam_script.AXObject, "get_path", return_value=[1, 2, 3]),
mock.patch.object(
steam_script.Chromium.Script,
"onSelectionChanged",
new=chromiumOnSelectionChanged,
),
):
self.assertTrue(testScript.onSelectionChanged(event))
self.assertEqual(chromiumCalls, [(testScript, event)])
class SteamReturnActivationTests(unittest.TestCase):
def test_return_activates_focused_steam_button(self):
testScript = steam_script.Script.__new__(steam_script.Script)
button = object()
keyboardEvent = mock.Mock(event_string="Return", modifiers=0)
keyboardEvent.is_pressed_key.return_value = True
testScript.utilities = mock.Mock()
testScript.utilities.inDocumentContent.return_value = True
testScript.inFocusMode = mock.Mock(return_value=True)
testScript.presentMessage = mock.Mock()
testScript._presentDelayedMessage = mock.Mock()
testScript._restoreFocusAfterClick = mock.Mock()
def has_action(obj, action_name):
return obj is button and action_name == "press"
with (
mock.patch.object(steam_script.cthulhu_state, "locusOfFocus", button),
mock.patch.object(steam_script.AXUtilities, "is_entry", return_value=False),
mock.patch.object(steam_script.AXUtilities, "is_text", return_value=False),
mock.patch.object(steam_script.AXUtilities, "is_password_text", return_value=False),
mock.patch.object(steam_script.AXUtilities, "is_combo_box", return_value=False),
mock.patch.object(steam_script.AXUtilities, "is_button", return_value=True),
mock.patch.object(steam_script.AXUtilities, "is_push_button", return_value=False),
mock.patch.object(steam_script.AXUtilities, "is_link", return_value=False),
mock.patch.object(steam_script.AXObject, "has_action", side_effect=has_action),
mock.patch.object(steam_script.Script, "_performClickableAction", return_value=True) as performAction,
):
self.assertTrue(testScript.shouldConsumeKeyboardEvent(keyboardEvent, None))
performAction.assert_called_once_with(button)
class SteamVirtualizedListMutationTests(unittest.TestCase):
def test_children_added_skips_generic_web_cache_dump_for_virtualized_list_churn(self):
testScript = steam_script.Script.__new__(steam_script.Script)
source = object()
focus = object()
event = mock.Mock(
type="object:children-changed:add",
source=source,
any_data=object(),
detail1=0,
)
chromiumCalls = []
testScript.utilities = mock.Mock()
testScript.utilities.isSteamVirtualizedList.return_value = True
testScript.utilities.clearSteamVirtualizedListCaches = mock.Mock()
def chromiumOnChildrenAdded(self, addedEvent):
chromiumCalls.append((self, addedEvent))
return False
with (
mock.patch.object(steam_script.cthulhu_state, "locusOfFocus", focus),
mock.patch.object(steam_script.AXObject, "is_dead", return_value=False),
mock.patch.object(steam_script.AXObject, "find_ancestor", return_value=source),
mock.patch.object(steam_script.AXObject, "clear_cache_now") as clearCache,
mock.patch.object(steam_script.Script, "_isSteamNotification", return_value=False),
mock.patch.object(
steam_script.Chromium.Script,
"onChildrenAdded",
new=chromiumOnChildrenAdded,
),
):
self.assertTrue(testScript.onChildrenAdded(event))
clearCache.assert_called_once_with("children-changed event.")
testScript.utilities.clearSteamVirtualizedListCaches.assert_called_once_with()
self.assertEqual(chromiumCalls, [])
def test_children_removed_skips_generic_web_cache_dump_for_virtualized_list_churn(self):
testScript = steam_script.Script.__new__(steam_script.Script)
source = object()
focus = object()
event = mock.Mock(
type="object:children-changed:remove",
source=source,
any_data=object(),
detail1=0,
)
chromiumCalls = []
testScript.utilities = mock.Mock()
testScript.utilities.isSteamVirtualizedList.return_value = True
testScript.utilities.clearSteamVirtualizedListCaches = mock.Mock()
def chromiumOnChildrenRemoved(self, removedEvent):
chromiumCalls.append((self, removedEvent))
return False
with (
mock.patch.object(steam_script.cthulhu_state, "locusOfFocus", focus),
mock.patch.object(steam_script.AXObject, "is_dead", return_value=False),
mock.patch.object(steam_script.AXObject, "find_ancestor", return_value=source),
mock.patch.object(steam_script.AXObject, "clear_cache_now") as clearCache,
mock.patch.object(
steam_script.Chromium.Script,
"onChildrenRemoved",
new=chromiumOnChildrenRemoved,
),
):
self.assertTrue(testScript.onChildrenRemoved(event))
clearCache.assert_called_once_with("children-changed event.")
testScript.utilities.clearSteamVirtualizedListCaches.assert_called_once_with()
self.assertEqual(chromiumCalls, [])
def test_children_removed_defers_to_generic_handler_when_focus_is_dead(self):
testScript = steam_script.Script.__new__(steam_script.Script)
source = object()
focus = object()
event = mock.Mock(
type="object:children-changed:remove",
source=source,
any_data=object(),
detail1=0,
)
chromiumCalls = []
testScript.utilities = mock.Mock()
testScript.utilities.isSteamVirtualizedList.return_value = True
def chromiumOnChildrenRemoved(self, removedEvent):
chromiumCalls.append((self, removedEvent))
return False
with (
mock.patch.object(steam_script.cthulhu_state, "locusOfFocus", focus),
mock.patch.object(steam_script.AXObject, "is_dead", return_value=True),
mock.patch.object(
steam_script.Chromium.Script,
"onChildrenRemoved",
new=chromiumOnChildrenRemoved,
),
):
self.assertFalse(testScript.onChildrenRemoved(event))
self.assertEqual(chromiumCalls, [(testScript, event)])
testScript.utilities.clearSteamVirtualizedListCaches.assert_not_called()
class SteamLabelRecoveryTests(unittest.TestCase):
def test_displayed_label_recovers_friends_list_tab_text_from_parent_context(self):
testScript = mock.Mock(generatorCache={})
utilities = steam_script_utilities.Utilities(testScript)
button = object()
parent = object()
textSibling = object()
utilities.inDocumentContent = mock.Mock(return_value=True)
def get_attribute(obj, name):
if obj is button and name == "class":
return "FriendsListTab Active Panel Focusable gpfocus"
return None
def get_name(obj):
if obj is textSibling:
return "Friends"
return ""
def iter_children(obj, pred=None):
children = [textSibling, button]
if obj is not parent:
children = []
if pred is not None:
children = [child for child in children if pred(child)]
return iter(children)
with (
mock.patch.object(steam_script_utilities.ChromiumUtilities, "displayedLabel", return_value=""),
mock.patch.object(steam_script_utilities.AXObject, "get_parent", side_effect=lambda obj: parent if obj is button else None),
mock.patch.object(steam_script_utilities.AXObject, "get_attribute", side_effect=get_attribute),
mock.patch.object(steam_script_utilities.AXObject, "get_name", side_effect=get_name),
mock.patch.object(steam_script_utilities.AXObject, "supports_text", return_value=False),
mock.patch.object(steam_script_utilities.AXObject, "iter_children", side_effect=iter_children),
mock.patch.object(steam_script_utilities.AXUtilities, "is_button", side_effect=lambda obj: obj is button),
mock.patch.object(steam_script_utilities.AXUtilities, "is_push_button", return_value=False),
):
self.assertEqual(utilities.displayedLabel(button), "Friends")
def test_displayed_label_maps_add_friend_button_class_to_fallback_name(self):
testScript = mock.Mock(generatorCache={})
utilities = steam_script_utilities.Utilities(testScript)
button = object()
utilities.inDocumentContent = mock.Mock(return_value=True)
def get_attribute(obj, name):
if obj is button and name == "class":
return "friendListButton AddFriendButton Panel Focusable"
return None
with (
mock.patch.object(steam_script_utilities.ChromiumUtilities, "displayedLabel", return_value=""),
mock.patch.object(steam_script_utilities.AXObject, "get_attribute", side_effect=get_attribute),
mock.patch.object(steam_script_utilities.AXObject, "get_name", return_value=""),
mock.patch.object(steam_script_utilities.AXUtilities, "is_button", side_effect=lambda obj: obj is button),
mock.patch.object(steam_script_utilities.AXUtilities, "is_push_button", return_value=False),
):
self.assertEqual(utilities.displayedLabel(button), "Add Friend")
class SteamRedundantImageTests(unittest.TestCase):
def test_is_useless_image_treats_unlabeled_click_ancestor_image_with_nearby_text_as_useless(self):
testScript = mock.Mock(generatorCache={})
utilities = steam_script_utilities.Utilities(testScript)
image = object()
parent = object()
entry = object()
utilities.inDocumentContent = mock.Mock(return_value=True)
def get_parent(obj):
if obj in (image, entry):
return parent
return None
def get_name(obj):
if obj is entry:
return "Search for games or profiles..."
return ""
def get_child_count(obj):
if obj is parent:
return 2
return 0
def has_action(obj, actionName):
return obj is image and actionName == "click-ancestor"
def iter_children(obj, pred=None):
children = [image, entry] if obj is parent else []
if pred is not None:
children = [child for child in children if pred(child)]
return iter(children)
with (
mock.patch.object(steam_script_utilities.ChromiumUtilities, "isUselessImage", return_value=False),
mock.patch.object(steam_script_utilities.AXObject, "get_parent", side_effect=get_parent),
mock.patch.object(steam_script_utilities.AXObject, "get_name", side_effect=get_name),
mock.patch.object(steam_script_utilities.AXObject, "get_description", return_value=""),
mock.patch.object(steam_script_utilities.AXObject, "get_child_count", side_effect=get_child_count),
mock.patch.object(steam_script_utilities.AXObject, "has_action", side_effect=has_action),
mock.patch.object(steam_script_utilities.AXObject, "iter_children", side_effect=iter_children),
mock.patch.object(steam_script_utilities.AXObject, "supports_text", return_value=False),
mock.patch.object(
steam_script_utilities.AXObject,
"get_role_description",
side_effect=lambda obj: "Unlabeled image" if obj is image else "",
),
mock.patch.object(
steam_script_utilities.AXUtilities,
"is_image_or_canvas",
side_effect=lambda obj: obj is image,
),
mock.patch.object(steam_script_utilities.AXUtilities, "is_focusable", return_value=False),
mock.patch.object(steam_script_utilities.AXUtilities, "is_button", return_value=False),
mock.patch.object(steam_script_utilities.AXUtilities, "is_push_button", return_value=False),
):
self.assertTrue(utilities.isUselessImage(image))
def test_is_useless_image_defers_to_generic_logic_without_nearby_text(self):
testScript = mock.Mock(generatorCache={})
utilities = steam_script_utilities.Utilities(testScript)
image = object()
utilities.inDocumentContent = mock.Mock(return_value=True)
with (
mock.patch.object(steam_script_utilities.ChromiumUtilities, "isUselessImage", return_value=False),
mock.patch.object(steam_script_utilities.AXObject, "get_parent", return_value=None),
mock.patch.object(steam_script_utilities.AXObject, "get_name", return_value=""),
mock.patch.object(steam_script_utilities.AXObject, "get_description", return_value=""),
mock.patch.object(steam_script_utilities.AXObject, "get_child_count", return_value=0),
mock.patch.object(steam_script_utilities.AXObject, "has_action", return_value=True),
mock.patch.object(steam_script_utilities.AXObject, "supports_text", return_value=False),
mock.patch.object(steam_script_utilities.AXObject, "get_role_description", return_value=""),
mock.patch.object(
steam_script_utilities.AXUtilities,
"is_image_or_canvas",
side_effect=lambda obj: obj is image,
),
mock.patch.object(steam_script_utilities.AXUtilities, "is_focusable", return_value=False),
):
self.assertFalse(utilities.isUselessImage(image))
if __name__ == "__main__":
unittest.main()