Restructure config for per-mode style/sound and fix pulse dismiss
Major changes: - Add StyleConfig dataclass with independent color, width, opacity, duration, pulse_speed, sound, and volume per mode (running/completed) - Replace flat flash_*/sound_*/play_on config with running: and completed: YAML sections - Replace CGEventTap (silently fails in forked daemon) with CGEventSourceSecondsSinceLastEventType polling for reliable input-based pulse dismissal when Cursor is already frontmost - Update overlay, sound, and daemon to pass StyleConfig per call - Rewrite tests for new config shape and dismiss mechanism Made-with: Cursor
This commit is contained in:
@@ -1,56 +1,119 @@
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
from cursor_flasher.config import Config, load_config, DEFAULT_CONFIG_PATH
|
||||
from cursor_flasher.config import Config, StyleConfig, load_config
|
||||
|
||||
|
||||
class TestDefaultConfig:
|
||||
def test_has_pulse_settings(self):
|
||||
cfg = Config()
|
||||
assert cfg.pulse_color == "#FF9500"
|
||||
assert cfg.pulse_width == 4
|
||||
assert cfg.pulse_speed == 1.5
|
||||
assert cfg.pulse_opacity_min == 0.3
|
||||
assert cfg.pulse_opacity_max == 1.0
|
||||
def test_running_defaults(self):
|
||||
c = Config()
|
||||
assert c.running.color == "#FF9500"
|
||||
assert c.running.width == 4
|
||||
assert c.running.duration == 1.5
|
||||
assert c.running.opacity == 0.85
|
||||
assert c.running.pulse_speed == 1.5
|
||||
assert c.running.sound == "Glass"
|
||||
assert c.running.volume == 0.5
|
||||
|
||||
def test_has_sound_settings(self):
|
||||
cfg = Config()
|
||||
assert cfg.sound_enabled is True
|
||||
assert cfg.sound_name == "Glass"
|
||||
assert cfg.sound_volume == 0.5
|
||||
def test_completed_defaults(self):
|
||||
c = Config()
|
||||
assert c.completed.color == "#FF9500"
|
||||
assert c.completed.width == 4
|
||||
assert c.completed.sound == ""
|
||||
assert c.completed.volume == 0.0
|
||||
|
||||
def test_has_detection_settings(self):
|
||||
cfg = Config()
|
||||
assert cfg.poll_interval == 0.5
|
||||
assert cfg.cooldown == 3.0
|
||||
def test_has_approval_tools(self):
|
||||
c = Config()
|
||||
assert c.approval_tools == ["Shell", "Write", "Delete"]
|
||||
|
||||
def test_has_timeout_settings(self):
|
||||
cfg = Config()
|
||||
assert cfg.auto_dismiss == 300
|
||||
def test_has_cooldown(self):
|
||||
c = Config()
|
||||
assert c.cooldown == 2.0
|
||||
|
||||
def test_has_flash_mode(self):
|
||||
c = Config()
|
||||
assert c.flash_mode == "screen"
|
||||
|
||||
|
||||
class TestLoadConfig:
|
||||
def test_loads_from_yaml(self, tmp_path):
|
||||
config_file = tmp_path / "config.yaml"
|
||||
config_file.write_text(
|
||||
"pulse:\n"
|
||||
' color: "#00FF00"\n'
|
||||
" width: 8\n"
|
||||
"sound:\n"
|
||||
" enabled: false\n"
|
||||
)
|
||||
cfg = load_config(config_file)
|
||||
assert cfg.pulse_color == "#00FF00"
|
||||
assert cfg.pulse_width == 8
|
||||
assert cfg.sound_enabled is False
|
||||
assert cfg.pulse_speed == 1.5
|
||||
assert cfg.sound_name == "Glass"
|
||||
|
||||
def test_missing_file_returns_defaults(self, tmp_path):
|
||||
cfg = load_config(tmp_path / "nonexistent.yaml")
|
||||
assert cfg.pulse_color == "#FF9500"
|
||||
c = load_config(tmp_path / "nope.yaml")
|
||||
assert c == Config()
|
||||
|
||||
def test_empty_file_returns_defaults(self, tmp_path):
|
||||
config_file = tmp_path / "config.yaml"
|
||||
config_file.write_text("")
|
||||
cfg = load_config(config_file)
|
||||
assert cfg.pulse_color == "#FF9500"
|
||||
p = tmp_path / "config.yaml"
|
||||
p.write_text("")
|
||||
c = load_config(p)
|
||||
assert c == Config()
|
||||
|
||||
def test_loads_running_overrides(self, tmp_path):
|
||||
p = tmp_path / "config.yaml"
|
||||
p.write_text(
|
||||
"running:\n color: '#00FF00'\n duration: 2.0\n sound: Ping\n"
|
||||
)
|
||||
c = load_config(p)
|
||||
assert c.running.color == "#00FF00"
|
||||
assert c.running.duration == 2.0
|
||||
assert c.running.sound == "Ping"
|
||||
assert c.running.width == 4
|
||||
|
||||
def test_loads_completed_overrides(self, tmp_path):
|
||||
p = tmp_path / "config.yaml"
|
||||
p.write_text(
|
||||
"completed:\n color: '#0000FF'\n sound: Hero\n volume: 0.8\n"
|
||||
)
|
||||
c = load_config(p)
|
||||
assert c.completed.color == "#0000FF"
|
||||
assert c.completed.sound == "Hero"
|
||||
assert c.completed.volume == 0.8
|
||||
|
||||
def test_loads_flash_mode(self, tmp_path):
|
||||
p = tmp_path / "config.yaml"
|
||||
p.write_text("flash:\n mode: allscreens\n")
|
||||
c = load_config(p)
|
||||
assert c.flash_mode == "allscreens"
|
||||
|
||||
def test_loads_general_overrides(self, tmp_path):
|
||||
p = tmp_path / "config.yaml"
|
||||
p.write_text("general:\n cooldown: 5.0\n approval_delay: 3.0\n")
|
||||
c = load_config(p)
|
||||
assert c.cooldown == 5.0
|
||||
assert c.approval_delay == 3.0
|
||||
|
||||
def test_loads_approval_tools(self, tmp_path):
|
||||
p = tmp_path / "config.yaml"
|
||||
p.write_text("approval_tools:\n - Shell\n - MCP\n")
|
||||
c = load_config(p)
|
||||
assert c.approval_tools == ["Shell", "MCP"]
|
||||
|
||||
def test_full_config(self, tmp_path):
|
||||
p = tmp_path / "config.yaml"
|
||||
p.write_text(
|
||||
"running:\n"
|
||||
" color: '#FF0000'\n"
|
||||
" width: 6\n"
|
||||
" opacity: 0.9\n"
|
||||
" pulse_speed: 2.0\n"
|
||||
" sound: Glass\n"
|
||||
" volume: 0.8\n"
|
||||
"completed:\n"
|
||||
" color: '#00FF00'\n"
|
||||
" sound: ''\n"
|
||||
"flash:\n"
|
||||
" mode: window\n"
|
||||
"general:\n"
|
||||
" approval_delay: 1.0\n"
|
||||
" cooldown: 3.0\n"
|
||||
"approval_tools:\n"
|
||||
" - Shell\n"
|
||||
)
|
||||
c = load_config(p)
|
||||
assert c.running.color == "#FF0000"
|
||||
assert c.running.width == 6
|
||||
assert c.running.opacity == 0.9
|
||||
assert c.running.pulse_speed == 2.0
|
||||
assert c.running.sound == "Glass"
|
||||
assert c.running.volume == 0.8
|
||||
assert c.completed.color == "#00FF00"
|
||||
assert c.completed.sound == ""
|
||||
assert c.flash_mode == "window"
|
||||
assert c.approval_delay == 1.0
|
||||
assert c.cooldown == 3.0
|
||||
assert c.approval_tools == ["Shell"]
|
||||
|
||||
Reference in New Issue
Block a user