forked from genewildish/Mainline
feat(app): add direct CLI mode, validation framework, fixtures, and UI panel integration
- Add run_pipeline_mode_direct() for constructing pipelines from CLI flags - Add engine/pipeline/validation.py with validate_pipeline_config() and MVP rules - Add fixtures system: engine/fixtures/headlines.json for cached test data - Enhance fetch.py to use fixtures cache path - Support fixture source in run_pipeline_mode() - Add --pipeline-* CLI flags: source, effects, camera, display, UI, border - Integrate UIPanel: raw mode, preset picker, event callbacks, param adjustment - Add UI_PRESET support in app and hot-rebuild pipeline on preset change - Add test UIPanel rendering and interaction tests This provides a flexible pipeline construction interface with validation and interactive control. Fixes #29, #30, #31
This commit is contained in:
184
tests/test_ui_panel.py
Normal file
184
tests/test_ui_panel.py
Normal file
@@ -0,0 +1,184 @@
|
||||
"""
|
||||
Tests for UIPanel.
|
||||
"""
|
||||
|
||||
from engine.pipeline.ui import StageControl, UIConfig, UIPanel
|
||||
|
||||
|
||||
class MockStage:
|
||||
"""Mock stage for testing."""
|
||||
|
||||
def __init__(self, name, category="effect"):
|
||||
self.name = name
|
||||
self.category = category
|
||||
self._enabled = True
|
||||
|
||||
def is_enabled(self):
|
||||
return self._enabled
|
||||
|
||||
|
||||
class TestUIPanel:
|
||||
"""Tests for UIPanel."""
|
||||
|
||||
def test_init(self):
|
||||
"""UIPanel initializes with default config."""
|
||||
panel = UIPanel()
|
||||
assert panel.config.panel_width == 24
|
||||
assert panel.config.stage_list_height == 12
|
||||
assert panel.scroll_offset == 0
|
||||
assert panel.selected_stage is None
|
||||
|
||||
def test_register_stage(self):
|
||||
"""register_stage adds a stage control."""
|
||||
panel = UIPanel()
|
||||
stage = MockStage("noise")
|
||||
panel.register_stage(stage, enabled=True)
|
||||
assert "noise" in panel.stages
|
||||
ctrl = panel.stages["noise"]
|
||||
assert ctrl.name == "noise"
|
||||
assert ctrl.enabled is True
|
||||
assert ctrl.selected is False
|
||||
|
||||
def test_select_stage(self):
|
||||
"""select_stage sets selection."""
|
||||
panel = UIPanel()
|
||||
stage1 = MockStage("noise")
|
||||
stage2 = MockStage("fade")
|
||||
panel.register_stage(stage1)
|
||||
panel.register_stage(stage2)
|
||||
panel.select_stage("fade")
|
||||
assert panel.selected_stage == "fade"
|
||||
assert panel.stages["fade"].selected is True
|
||||
assert panel.stages["noise"].selected is False
|
||||
|
||||
def test_toggle_stage(self):
|
||||
"""toggle_stage flips enabled state."""
|
||||
panel = UIPanel()
|
||||
stage = MockStage("glitch")
|
||||
panel.register_stage(stage, enabled=True)
|
||||
result = panel.toggle_stage("glitch")
|
||||
assert result is False
|
||||
assert panel.stages["glitch"].enabled is False
|
||||
result = panel.toggle_stage("glitch")
|
||||
assert result is True
|
||||
|
||||
def test_get_enabled_stages(self):
|
||||
"""get_enabled_stages returns only enabled stage names."""
|
||||
panel = UIPanel()
|
||||
panel.register_stage(MockStage("noise"), enabled=True)
|
||||
panel.register_stage(MockStage("fade"), enabled=False)
|
||||
panel.register_stage(MockStage("glitch"), enabled=True)
|
||||
enabled = panel.get_enabled_stages()
|
||||
assert set(enabled) == {"noise", "glitch"}
|
||||
|
||||
def test_scroll_stages(self):
|
||||
"""scroll_stages moves the view."""
|
||||
panel = UIPanel(UIConfig(stage_list_height=3))
|
||||
for i in range(10):
|
||||
panel.register_stage(MockStage(f"stage{i}"))
|
||||
assert panel.scroll_offset == 0
|
||||
panel.scroll_stages(1)
|
||||
assert panel.scroll_offset == 1
|
||||
panel.scroll_stages(-1)
|
||||
assert panel.scroll_offset == 0
|
||||
# Clamp at max
|
||||
panel.scroll_stages(100)
|
||||
assert panel.scroll_offset == 7 # 10 - 3 = 7
|
||||
|
||||
def test_render_produces_lines(self):
|
||||
"""render produces list of strings of correct width."""
|
||||
panel = UIPanel(UIConfig(panel_width=20))
|
||||
panel.register_stage(MockStage("noise"), enabled=True)
|
||||
panel.register_stage(MockStage("fade"), enabled=False)
|
||||
panel.select_stage("noise")
|
||||
lines = panel.render(80, 24)
|
||||
# All lines should be exactly panel_width chars (20)
|
||||
for line in lines:
|
||||
assert len(line) == 20
|
||||
# Should have header, stage rows, separator, params area, footer
|
||||
assert len(lines) >= 5
|
||||
|
||||
def test_process_key_event_space_toggles_stage(self):
|
||||
"""process_key_event with space toggles UI panel visibility."""
|
||||
panel = UIPanel()
|
||||
stage = MockStage("glitch")
|
||||
panel.register_stage(stage, enabled=True)
|
||||
panel.select_stage("glitch")
|
||||
# Space should now toggle UI panel visibility, not stage
|
||||
assert panel._show_panel is True
|
||||
handled = panel.process_key_event(" ")
|
||||
assert handled is True
|
||||
assert panel._show_panel is False
|
||||
# Pressing space again should show panel
|
||||
handled = panel.process_key_event(" ")
|
||||
assert panel._show_panel is True
|
||||
|
||||
def test_process_key_event_space_does_not_toggle_in_picker(self):
|
||||
"""Space should not toggle UI panel when preset picker is active."""
|
||||
panel = UIPanel()
|
||||
panel._show_panel = True
|
||||
panel._show_preset_picker = True
|
||||
handled = panel.process_key_event(" ")
|
||||
assert handled is False # Not handled when picker active
|
||||
assert panel._show_panel is True # Unchanged
|
||||
|
||||
def test_process_key_event_s_selects_next(self):
|
||||
"""process_key_event with s cycles selection."""
|
||||
panel = UIPanel()
|
||||
panel.register_stage(MockStage("noise"))
|
||||
panel.register_stage(MockStage("fade"))
|
||||
panel.register_stage(MockStage("glitch"))
|
||||
panel.select_stage("noise")
|
||||
handled = panel.process_key_event("s")
|
||||
assert handled is True
|
||||
assert panel.selected_stage == "fade"
|
||||
|
||||
def test_process_key_event_hjkl_navigation(self):
|
||||
"""process_key_event with HJKL keys."""
|
||||
panel = UIPanel()
|
||||
stage = MockStage("noise")
|
||||
panel.register_stage(stage)
|
||||
panel.select_stage("noise")
|
||||
|
||||
# J or Down should scroll or adjust param
|
||||
assert panel.scroll_stages(1) is None # Just test it doesn't error
|
||||
# H or Left should adjust param (when param selected)
|
||||
panel.selected_stage = "noise"
|
||||
panel._focused_param = "intensity"
|
||||
panel.stages["noise"].params["intensity"] = 0.5
|
||||
|
||||
# Left/H should decrease
|
||||
handled = panel.process_key_event("h")
|
||||
assert handled is True
|
||||
# L or Right should increase
|
||||
handled = panel.process_key_event("l")
|
||||
assert handled is True
|
||||
|
||||
# K should scroll up
|
||||
panel.selected_stage = None
|
||||
handled = panel.process_key_event("k")
|
||||
assert handled is True
|
||||
|
||||
def test_set_event_callback(self):
|
||||
"""set_event_callback registers callback."""
|
||||
panel = UIPanel()
|
||||
called = []
|
||||
|
||||
def callback(stage_name, enabled):
|
||||
called.append((stage_name, enabled))
|
||||
|
||||
panel.set_event_callback("stage_toggled", callback)
|
||||
panel.toggle_stage("test") # No stage, won't trigger
|
||||
# Simulate toggle through event
|
||||
panel._emit_event("stage_toggled", stage_name="noise", enabled=False)
|
||||
assert called == [("noise", False)]
|
||||
|
||||
def test_register_stage_returns_control(self):
|
||||
"""register_stage should return the StageControl instance."""
|
||||
panel = UIPanel()
|
||||
stage = MockStage("noise_effect")
|
||||
control = panel.register_stage(stage, enabled=True)
|
||||
assert control is not None
|
||||
assert isinstance(control, StageControl)
|
||||
assert control.name == "noise_effect"
|
||||
assert control.enabled is True
|
||||
Reference in New Issue
Block a user