feat: add compositor state adapter scaffold
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
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 cthulhu_state
|
||||
from cthulhu import compositor_state_adapter
|
||||
from cthulhu import compositor_state_types
|
||||
|
||||
|
||||
class FakeWorkspaceBackend:
|
||||
def __init__(self, available: bool, name: str) -> None:
|
||||
self.available = available
|
||||
self.name = name
|
||||
self.activate_calls = []
|
||||
self.deactivate_calls = []
|
||||
|
||||
def is_available(self, session_type: str | None = None) -> bool:
|
||||
return self.available
|
||||
|
||||
def activate(self, adapter=None) -> None:
|
||||
self.activate_calls.append(adapter)
|
||||
|
||||
def deactivate(self, adapter=None) -> None:
|
||||
self.deactivate_calls.append(adapter)
|
||||
|
||||
|
||||
class CompositorStateAdapterRegressionTests(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
cthulhu_state.compositorSnapshot = None
|
||||
cthulhu_state.pauseAtspiChurn = False
|
||||
cthulhu_state.prioritizedDesktopContextToken = None
|
||||
|
||||
def test_activate_selects_first_available_backend(self) -> None:
|
||||
unavailableBackend = FakeWorkspaceBackend(False, "unavailable")
|
||||
selectedBackend = FakeWorkspaceBackend(True, "selected")
|
||||
adapter = compositor_state_adapter.CompositorStateAdapter(
|
||||
workspace_backends=[unavailableBackend, selectedBackend]
|
||||
)
|
||||
|
||||
adapter.activate()
|
||||
|
||||
self.assertEqual(unavailableBackend.activate_calls, [])
|
||||
self.assertEqual(selectedBackend.activate_calls, [adapter])
|
||||
self.assertIs(adapter._workspaceBackend, selectedBackend)
|
||||
|
||||
def test_sync_accessible_context_emits_focus_context_changed_when_active_window_token_changes(self) -> None:
|
||||
adapter = compositor_state_adapter.CompositorStateAdapter()
|
||||
events = []
|
||||
adapter.add_listener(events.append)
|
||||
firstWindow = object()
|
||||
secondWindow = object()
|
||||
|
||||
def get_process_id(obj):
|
||||
return 111 if obj is firstWindow else 222
|
||||
|
||||
def get_name(obj):
|
||||
return "Terminal"
|
||||
|
||||
with (
|
||||
mock.patch.object(compositor_state_adapter.AXObject, "get_process_id", side_effect=get_process_id),
|
||||
mock.patch.object(compositor_state_adapter.AXObject, "get_name", side_effect=get_name),
|
||||
):
|
||||
cthulhu_state.activeWindow = firstWindow
|
||||
cthulhu_state.locusOfFocus = firstWindow
|
||||
adapter.sync_accessible_context("startup")
|
||||
|
||||
events.clear()
|
||||
cthulhu_state.activeWindow = secondWindow
|
||||
cthulhu_state.locusOfFocus = secondWindow
|
||||
adapter.sync_accessible_context("workspace transition")
|
||||
|
||||
self.assertIn(
|
||||
compositor_state_types.DESKTOP_FOCUS_CONTEXT_CHANGED,
|
||||
[event.type for event in events],
|
||||
)
|
||||
self.assertIn(
|
||||
compositor_state_types.PRIORITIZE_FOCUS,
|
||||
[event.type for event in events],
|
||||
)
|
||||
|
||||
def test_sync_accessible_context_builds_stable_active_window_tokens(self) -> None:
|
||||
adapter = compositor_state_adapter.CompositorStateAdapter()
|
||||
window = object()
|
||||
|
||||
with (
|
||||
mock.patch.object(compositor_state_adapter.AXObject, "get_process_id", return_value=4242),
|
||||
mock.patch.object(compositor_state_adapter.AXObject, "get_name", return_value="Terminal"),
|
||||
):
|
||||
cthulhu_state.activeWindow = window
|
||||
cthulhu_state.locusOfFocus = window
|
||||
snapshot = adapter.sync_accessible_context("startup")
|
||||
|
||||
self.assertEqual(snapshot.active_window_token, "4242:Terminal")
|
||||
self.assertEqual(cthulhu_state.compositorSnapshot.active_window_token, "4242:Terminal")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user