First upload, 18 controller version
This commit is contained in:
181
tests/test_controller.py
Normal file
181
tests/test_controller.py
Normal file
@@ -0,0 +1,181 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
from app.core.controller import InfinityMirrorController
|
||||
from app.core.pattern_engine import PatternEngine
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SAMPLE_MAPPING = ROOT / "sample_data" / "infinity_mirror_mapping_clean.xml"
|
||||
|
||||
|
||||
class ControllerTests(unittest.TestCase):
|
||||
def test_scene_state_returns_snapshot_instead_of_live_mutable_state(self) -> None:
|
||||
controller = InfinityMirrorController(ROOT)
|
||||
try:
|
||||
controller.set_foh_mode(True)
|
||||
controller.set_pattern("sparkle")
|
||||
|
||||
observed_events: list[str] = []
|
||||
controller.state_changed.connect(lambda: observed_events.append("state"))
|
||||
|
||||
snapshot = controller.scene_state("next")
|
||||
snapshot.pattern_id = "solid"
|
||||
snapshot.params.primary_color = "#FFFFFF"
|
||||
|
||||
self.assertEqual(controller.scene_state("live").pattern_id, "solid")
|
||||
self.assertEqual(controller.scene_state("next").pattern_id, "sparkle")
|
||||
self.assertEqual(controller.scene_state("next").params.primary_color, "#4D7CFF")
|
||||
self.assertEqual(observed_events, [])
|
||||
finally:
|
||||
controller.timer.stop()
|
||||
|
||||
def test_params_property_returns_snapshot_copy(self) -> None:
|
||||
controller = InfinityMirrorController(ROOT)
|
||||
try:
|
||||
observed_events: list[str] = []
|
||||
controller.state_changed.connect(lambda: observed_events.append("state"))
|
||||
|
||||
params = controller.params
|
||||
params.strobe_duty_cycle = 0.12
|
||||
params.flip_horizontal = True
|
||||
|
||||
self.assertEqual(controller.params.strobe_duty_cycle, 0.5)
|
||||
self.assertFalse(controller.params.flip_horizontal)
|
||||
self.assertEqual(observed_events, [])
|
||||
finally:
|
||||
controller.timer.stop()
|
||||
|
||||
def test_numeric_parameter_values_are_coerced_and_sanitized(self) -> None:
|
||||
controller = InfinityMirrorController(ROOT)
|
||||
try:
|
||||
controller.set_parameter("strobe_duty_cycle", "0.99")
|
||||
self.assertEqual(controller.params.strobe_duty_cycle, 0.98)
|
||||
finally:
|
||||
controller.timer.stop()
|
||||
|
||||
def test_invalid_numeric_parameter_value_is_ignored(self) -> None:
|
||||
controller = InfinityMirrorController(ROOT)
|
||||
try:
|
||||
initial = controller.params.strobe_duty_cycle
|
||||
controller.set_parameter("strobe_duty_cycle", "invalid")
|
||||
self.assertEqual(controller.params.strobe_duty_cycle, initial)
|
||||
finally:
|
||||
controller.timer.stop()
|
||||
|
||||
def test_parameter_updates_preserve_existing_scan_flags(self) -> None:
|
||||
controller = InfinityMirrorController(ROOT)
|
||||
try:
|
||||
controller.set_parameter("flip_horizontal", True)
|
||||
controller.set_parameter("flip_vertical", True)
|
||||
controller.set_parameter("band_thickness", 1.6)
|
||||
controller.set_parameter("strobe_duty_cycle", 0.4)
|
||||
|
||||
params = controller.params
|
||||
self.assertTrue(params.flip_horizontal)
|
||||
self.assertTrue(params.flip_vertical)
|
||||
self.assertEqual(params.band_thickness, 1.6)
|
||||
self.assertEqual(params.strobe_duty_cycle, 0.4)
|
||||
finally:
|
||||
controller.timer.stop()
|
||||
|
||||
def test_single_preview_mode_keeps_next_scene_synced_with_live(self) -> None:
|
||||
controller = InfinityMirrorController(ROOT)
|
||||
try:
|
||||
controller.set_pattern("sparkle")
|
||||
controller.set_tempo_bpm(132.0)
|
||||
self.assertEqual(controller.scene_state("live").pattern_id, "sparkle")
|
||||
self.assertEqual(controller.scene_state("next").pattern_id, "sparkle")
|
||||
self.assertEqual(controller.tempo_bpm, 132.0)
|
||||
finally:
|
||||
controller.timer.stop()
|
||||
|
||||
def test_foh_mode_edits_next_scene_without_touching_live(self) -> None:
|
||||
controller = InfinityMirrorController(ROOT)
|
||||
try:
|
||||
controller.set_foh_mode(True)
|
||||
controller.set_pattern("sparkle")
|
||||
controller.set_tempo_bpm(140.0)
|
||||
self.assertEqual(controller.scene_state("live").pattern_id, "solid")
|
||||
self.assertEqual(controller.scene_state("next").pattern_id, "sparkle")
|
||||
self.assertEqual(controller.pattern_id, "sparkle")
|
||||
self.assertAlmostEqual(controller.tempo_bpm, 140.0)
|
||||
finally:
|
||||
controller.timer.stop()
|
||||
|
||||
def test_go_promotes_next_scene_to_live(self) -> None:
|
||||
controller = InfinityMirrorController(ROOT)
|
||||
try:
|
||||
controller.set_foh_mode(True)
|
||||
controller.set_pattern("sparkle")
|
||||
controller.set_tempo_bpm(126.0)
|
||||
controller.go_scene()
|
||||
self.assertEqual(controller.scene_state("live").pattern_id, "sparkle")
|
||||
self.assertAlmostEqual(controller.tempo_bpm, 126.0)
|
||||
self.assertEqual(controller.scene_state("next").pattern_id, "sparkle")
|
||||
finally:
|
||||
controller.timer.stop()
|
||||
|
||||
def test_fade_go_commits_queued_scene_after_transition(self) -> None:
|
||||
controller = InfinityMirrorController(ROOT)
|
||||
try:
|
||||
controller.load_mapping(ROOT / "sample_data" / "infinity_mirror_mapping_clean.xml")
|
||||
controller.set_foh_mode(True)
|
||||
controller.set_pattern("sparkle")
|
||||
controller.set_tempo_bpm(144.0)
|
||||
controller.render_once(timestamp=10.0)
|
||||
|
||||
controller.fade_go(0.5, timestamp=10.0)
|
||||
self.assertTrue(controller.transition_active)
|
||||
self.assertEqual(controller.scene_state("live").pattern_id, "solid")
|
||||
self.assertEqual(controller.scene_state("next").pattern_id, "sparkle")
|
||||
self.assertIsNotNone(controller.preview_frame_for("next"))
|
||||
|
||||
controller.render_once(timestamp=10.6)
|
||||
self.assertFalse(controller.transition_active)
|
||||
self.assertEqual(controller.scene_state("live").pattern_id, "sparkle")
|
||||
self.assertEqual(controller.current_frame.pattern_id, "sparkle")
|
||||
finally:
|
||||
controller.timer.stop()
|
||||
|
||||
def test_legacy_speed_parameter_maps_to_global_tempo(self) -> None:
|
||||
controller = InfinityMirrorController(ROOT)
|
||||
try:
|
||||
controller.set_parameter("speed", 2.0)
|
||||
self.assertEqual(controller.tempo_bpm, 120.0)
|
||||
finally:
|
||||
controller.timer.stop()
|
||||
|
||||
def test_tempo_change_preserves_current_visual_phase(self) -> None:
|
||||
controller = InfinityMirrorController(ROOT)
|
||||
try:
|
||||
controller.load_mapping(SAMPLE_MAPPING)
|
||||
controller.set_pattern("center_pulse")
|
||||
controller.set_parameter("color_mode", "mono")
|
||||
controller.set_parameter("primary_color", "#FFFFFF")
|
||||
controller.set_parameter("secondary_color", "#000000")
|
||||
|
||||
def active_tiles(timestamp: float) -> set[str]:
|
||||
frame = controller._render_scene_frame(PatternEngine(), controller.scene_state("live"), timestamp)
|
||||
return {
|
||||
tile_id
|
||||
for tile_id, tile in frame.tiles.items()
|
||||
if max(tile.fill_color.to_8bit_tuple()) >= 240
|
||||
}
|
||||
|
||||
before = active_tiles(10.0)
|
||||
|
||||
controller.set_tempo_bpm(240.0, timestamp=10.0)
|
||||
after = active_tiles(10.0)
|
||||
self.assertEqual(before, after)
|
||||
|
||||
advanced = active_tiles(10.26)
|
||||
self.assertNotEqual(after, advanced)
|
||||
finally:
|
||||
controller.timer.stop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user