Detector now walks each AXWindow subtree independently and returns both aggregate signals (for state machine) and a list of AXWindow element refs for windows with active approval signals. Overlay reads position/size directly from AXWindow elements via AXValueGetValue, eliminating the CGWindowList dependency (which returned empty names for Electron windows anyway). Daemon passes only the active AXWindow refs to the overlay, so only the specific window(s) waiting for user input get flashed. Made-with: Cursor
82 lines
2.9 KiB
Python
82 lines
2.9 KiB
Python
import pytest
|
|
from unittest.mock import patch, MagicMock
|
|
from cursor_flasher.detector import (
|
|
CursorDetector,
|
|
PollResult,
|
|
parse_ui_signals,
|
|
UISignals,
|
|
)
|
|
|
|
|
|
class TestParseUISignals:
|
|
def test_no_elements_means_no_signals(self):
|
|
signals = parse_ui_signals([])
|
|
assert signals.agent_working is False
|
|
assert signals.approval_needed is False
|
|
|
|
def test_stop_text_means_agent_working(self):
|
|
elements = [{"role": "AXStaticText", "value": "Stop"}]
|
|
signals = parse_ui_signals(elements)
|
|
assert signals.agent_working is True
|
|
|
|
def test_cancel_generating_means_agent_working(self):
|
|
elements = [{"role": "AXStaticText", "value": "Cancel generating"}]
|
|
signals = parse_ui_signals(elements)
|
|
assert signals.agent_working is True
|
|
|
|
def test_accept_text_means_approval_needed(self):
|
|
elements = [{"role": "AXStaticText", "value": "Accept"}]
|
|
signals = parse_ui_signals(elements)
|
|
assert signals.approval_needed is True
|
|
|
|
def test_reject_text_means_approval_needed(self):
|
|
elements = [{"role": "AXStaticText", "value": "Reject"}]
|
|
signals = parse_ui_signals(elements)
|
|
assert signals.approval_needed is True
|
|
|
|
def test_run_this_time_means_approval_needed(self):
|
|
elements = [{"role": "AXStaticText", "value": "Run this time only (⏎)"}]
|
|
signals = parse_ui_signals(elements)
|
|
assert signals.approval_needed is True
|
|
|
|
def test_both_signals(self):
|
|
elements = [
|
|
{"role": "AXStaticText", "value": "Stop"},
|
|
{"role": "AXStaticText", "value": "Accept"},
|
|
]
|
|
signals = parse_ui_signals(elements)
|
|
assert signals.agent_working is True
|
|
assert signals.approval_needed is True
|
|
|
|
def test_irrelevant_text_ignored(self):
|
|
elements = [{"role": "AXStaticText", "value": "Settings"}]
|
|
signals = parse_ui_signals(elements)
|
|
assert signals.agent_working is False
|
|
assert signals.approval_needed is False
|
|
|
|
def test_button_role_also_detected(self):
|
|
elements = [{"role": "AXButton", "title": "Accept"}]
|
|
signals = parse_ui_signals(elements)
|
|
assert signals.approval_needed is True
|
|
|
|
def test_partial_match_on_run_command(self):
|
|
elements = [{"role": "AXStaticText", "value": "Run command"}]
|
|
signals = parse_ui_signals(elements)
|
|
assert signals.approval_needed is True
|
|
|
|
|
|
class TestCursorDetector:
|
|
def test_returns_none_when_cursor_not_running(self):
|
|
detector = CursorDetector()
|
|
with patch.object(detector, "_find_cursor_pid", return_value=None):
|
|
result = detector.poll()
|
|
assert result is None
|
|
|
|
|
|
class TestPollResult:
|
|
def test_default_has_empty_active_windows(self):
|
|
result = PollResult(signals=UISignals())
|
|
assert result.active_windows == []
|
|
assert result.signals.agent_working is False
|
|
assert result.signals.approval_needed is False
|