feat: add accessibility-based agent state detector
Adapted from plan to match real a11y tree structure: Electron web content exposes in-app buttons as AXStaticText values, not AXButton titles. Detects via exact matches and regex patterns. Uses correct Cursor bundle ID for process lookup. Made-with: Cursor
This commit is contained in:
72
tests/test_detector.py
Normal file
72
tests/test_detector.py
Normal file
@@ -0,0 +1,72 @@
|
||||
import pytest
|
||||
from unittest.mock import patch, MagicMock
|
||||
from cursor_flasher.detector import (
|
||||
CursorDetector,
|
||||
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):
|
||||
signals = detector.poll()
|
||||
assert signals is None
|
||||
Reference in New Issue
Block a user