Files
cthulhu/tests/test_window_title_fallback_regressions.py

264 lines
10 KiB
Python

import sys
import unittest
from pathlib import Path
from unittest import mock
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
from cthulhu import where_am_i_presenter
from cthulhu.plugins.WindowTitleReader.plugin import WindowTitleReader
class WindowTitleFallbackRegressionTests(unittest.TestCase):
def test_activate_registers_reader_api_and_starts_fallback_tracking(self):
plugin = WindowTitleReader()
plugin.app = mock.Mock()
with (
mock.patch.object(plugin, "_register_keybinding"),
mock.patch.object(plugin, "_start_tracking") as startTracking,
):
plugin.activate(plugin)
plugin.app.getDynamicApiManager.return_value.registerAPI.assert_called_once_with(
"WindowTitleReader",
plugin,
overwrite=True,
)
startTracking.assert_called_once_with()
def test_poll_schedules_fallback_after_active_window_changes(self):
plugin = WindowTitleReader()
plugin._pollSourceId = 1
plugin._lastActiveWindowId = 100
activeWindow = mock.Mock(id=200)
with (
mock.patch.object(plugin, "_ensure_x11_display", return_value=True),
mock.patch.object(plugin, "_get_active_window", return_value=activeWindow),
mock.patch.object(plugin, "_get_current_title", return_value="XTerm"),
mock.patch.object(plugin, "_schedule_fallback_title") as scheduleFallback,
):
self.assertTrue(plugin._poll_window_title())
scheduleFallback.assert_called_once_with()
def test_poll_keeps_previous_window_across_transient_missing_active_window(self):
plugin = WindowTitleReader()
plugin._pollSourceId = 1
plugin._lastActiveWindowId = 100
with (
mock.patch.object(plugin, "_ensure_x11_display", return_value=True),
mock.patch.object(plugin, "_get_active_window", return_value=None),
):
self.assertTrue(plugin._poll_window_title())
self.assertEqual(plugin._lastActiveWindowId, 100)
def test_fallback_is_empty_when_atspi_exposes_same_title(self):
plugin = WindowTitleReader()
plugin._pollSourceId = 1
activeWindow = mock.Mock()
with (
mock.patch.object(plugin, "_ensure_x11_display", return_value=True),
mock.patch.object(plugin, "_get_active_window", return_value=activeWindow),
mock.patch.object(plugin, "_get_current_title", return_value="Terminal"),
):
self.assertEqual(plugin.get_fallback_title("Terminal"), "")
def test_fallback_replaces_wine_desktop_title(self):
plugin = WindowTitleReader()
plugin._pollSourceId = 1
activeWindow = mock.Mock()
with (
mock.patch.object(plugin, "_ensure_x11_display", return_value=True),
mock.patch.object(plugin, "_get_active_window", return_value=activeWindow),
mock.patch.object(plugin, "_get_current_title", return_value="Game Window"),
):
self.assertEqual(plugin.get_fallback_title("Wine Desktop"), "Game Window")
def test_fallback_prefers_i3_title_and_window_id(self):
plugin = WindowTitleReader()
plugin.app = mock.Mock()
snapshot = mock.Mock(
backend_name="i3-ipc",
focused_workspace_empty=False,
focused_window_id=0x200000C,
focused_window_title="Accurate i3 title",
)
plugin.app.getCompositorStateAdapter.return_value.get_snapshot.return_value = snapshot
with mock.patch.object(plugin, "_get_active_window") as getX11Window:
self.assertEqual(plugin.get_fallback_title("Incomplete title"), "Accurate i3 title")
getX11Window.assert_not_called()
def test_i3_wine_desktop_title_uses_existing_child_window_resolution(self):
plugin = WindowTitleReader()
plugin.app = mock.Mock()
snapshot = mock.Mock(
backend_name="i3-ipc",
focused_workspace_empty=False,
focused_window_id=0x2400007,
focused_window_title="Wine Desktop",
)
desktopWindow = mock.Mock()
plugin.app.getCompositorStateAdapter.return_value.get_snapshot.return_value = snapshot
plugin._display = mock.Mock()
plugin._display.create_resource_object.return_value = desktopWindow
with (
mock.patch.object(plugin, "_ensure_x11_display", return_value=True),
mock.patch.object(
plugin,
"_get_window_title",
return_value="Wine Desktop",
),
mock.patch.object(
plugin,
"_get_wine_desktop_title",
return_value="Actual Game Window",
) as getWineTitle,
):
self.assertEqual(
plugin.get_fallback_title("Wine Desktop"),
"Actual Game Window",
)
plugin._display.create_resource_object.assert_called_once_with("window", 0x2400007)
getWineTitle.assert_called_once_with(desktopWindow)
def test_wine_title_resolution_ignores_unrelated_x11_focus(self):
plugin = WindowTitleReader()
desktopWindow = mock.Mock(id=10)
unrelatedFocus = mock.Mock(id=20)
with (
mock.patch.object(plugin, "_get_focus_window", return_value=unrelatedFocus),
mock.patch.object(
plugin,
"_window_is_descendant_of",
return_value=False,
) as isDescendant,
mock.patch.object(plugin, "_get_window_title", return_value="Unrelated Terminal"),
mock.patch.object(
plugin,
"_find_child_title",
return_value="Actual Game Window",
) as findChildTitle,
):
self.assertEqual(
plugin._get_wine_desktop_title(desktopWindow),
"Actual Game Window",
)
isDescendant.assert_called_once_with(unrelatedFocus, desktopWindow)
findChildTitle.assert_called_once_with(desktopWindow)
def test_wine_child_title_filter_rejects_infrastructure_windows(self):
plugin = WindowTitleReader()
helperWindow = mock.Mock()
helperWindow.get_wm_class.return_value = (
"cthulhu-wine-access.exe",
"cthulhu-wine-access.exe",
)
gameWindow = mock.Mock()
gameWindow.get_wm_class.return_value = ("game.exe", "game.exe")
self.assertFalse(plugin._is_usable_wine_child_title(gameWindow, "Default IME"))
self.assertFalse(plugin._is_usable_wine_child_title(helperWindow, "NVDA"))
self.assertTrue(plugin._is_usable_wine_child_title(gameWindow, "Actual Game Window"))
def test_confirmed_empty_i3_workspace_does_not_fall_back_to_stale_x11_title(self):
plugin = WindowTitleReader()
plugin.app = mock.Mock()
snapshot = mock.Mock(
backend_name="i3-ipc",
focused_workspace_empty=True,
focused_window_id=None,
focused_window_title="",
)
plugin.app.getCompositorStateAdapter.return_value.get_snapshot.return_value = snapshot
with mock.patch.object(plugin, "_get_active_window") as getX11Window:
self.assertEqual(plugin.get_fallback_title("Browser"), "")
getX11Window.assert_not_called()
def test_unknown_i3_context_falls_back_to_generic_x11_title(self):
plugin = WindowTitleReader()
plugin.app = mock.Mock()
snapshot = mock.Mock(
backend_name="i3-ipc",
focused_workspace_empty=None,
focused_window_id=None,
focused_window_title="",
)
plugin.app.getCompositorStateAdapter.return_value.get_snapshot.return_value = snapshot
plugin._pollSourceId = 1
activeWindow = mock.Mock()
with (
mock.patch.object(plugin, "_ensure_x11_display", return_value=True) as ensureX11,
mock.patch.object(plugin, "_get_active_window", return_value=activeWindow),
mock.patch.object(plugin, "_get_current_title", return_value="Generic X title"),
):
self.assertEqual(plugin.get_fallback_title("Incomplete"), "Generic X title")
ensureX11.assert_called_once_with()
def test_known_i3_xid_with_missing_title_uses_exact_x11_title(self):
plugin = WindowTitleReader()
plugin.app = mock.Mock()
snapshot = mock.Mock(
backend_name="i3-ipc",
focused_workspace_empty=False,
focused_window_id=0x2600003,
focused_window_title="",
)
activeWindow = mock.Mock()
plugin.app.getCompositorStateAdapter.return_value.get_snapshot.return_value = snapshot
plugin._display = mock.Mock()
plugin._display.create_resource_object.return_value = activeWindow
with (
mock.patch.object(plugin, "_ensure_x11_display", return_value=True),
mock.patch.object(
plugin,
"_get_current_title",
return_value="Exact X11 title",
) as getCurrentTitle,
):
self.assertEqual(
plugin.get_fallback_title("Incomplete"),
"Exact X11 title",
)
plugin._display.create_resource_object.assert_called_once_with("window", 0x2600003)
getCurrentTitle.assert_called_once_with(activeWindow)
def test_present_title_uses_fallback_instead_of_atspi_title(self):
presenter = where_am_i_presenter.WhereAmIPresenter()
script = mock.Mock()
script.speechGenerator.generateTitle.return_value = [("Wine Desktop", None)]
with (
mock.patch.object(where_am_i_presenter.cthulhu_state, "locusOfFocus", object()),
mock.patch.object(where_am_i_presenter.AXObject, "is_dead", return_value=False),
mock.patch.object(
presenter,
"_get_fallback_title",
return_value="Game Window",
),
):
self.assertTrue(presenter.present_title(script))
script.presentMessage.assert_called_once_with("Game Window")
if __name__ == "__main__":
unittest.main()