2026-03-10 02:37:59 -04:00
|
|
|
import pytest
|
|
|
|
|
from unittest.mock import patch, MagicMock
|
|
|
|
|
from cursor_flasher.detector import (
|
|
|
|
|
CursorDetector,
|
2026-03-10 02:54:15 -04:00
|
|
|
PollResult,
|
2026-03-10 02:37:59 -04:00
|
|
|
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):
|
2026-03-10 02:54:15 -04:00
|
|
|
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
|