forked from genewildish/Mainline
refactor: phase 2 - modularization of scroll engine
Split monolithic scroll.py into focused modules: - viewport.py: terminal size (tw/th), ANSI positioning helpers - frame.py: FrameTimer class, scroll step calculation - layers.py: message overlay, ticker zone, firehose rendering - scroll.py: simplified orchestrator, imports from new modules Add stream controller and event types for future event-driven architecture: - controller.py: StreamController for source initialization and stream lifecycle - events.py: EventType enum and event dataclasses (HeadlineEvent, FrameTickEvent, etc.) Added tests for new modules: - test_viewport.py: 8 tests for viewport utilities - test_frame.py: 10 tests for frame timing - test_layers.py: 13 tests for layer compositing - test_events.py: 11 tests for event types - test_controller.py: 6 tests for stream controller This enables: - Testable chunks with clear responsibilities - Reusable viewport utilities across modules - Better separation of concerns in render pipeline - Foundation for future event-driven architecture Also includes Phase 1 documentation updates in code comments.
This commit is contained in:
96
tests/test_layers.py
Normal file
96
tests/test_layers.py
Normal file
@@ -0,0 +1,96 @@
|
||||
"""
|
||||
Tests for engine.layers module.
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
from engine import layers
|
||||
|
||||
|
||||
class TestRenderMessageOverlay:
|
||||
"""Tests for render_message_overlay function."""
|
||||
|
||||
def test_no_message_returns_empty(self):
|
||||
"""Returns empty list when msg is None."""
|
||||
result, cache = layers.render_message_overlay(None, 80, 24, (None, None))
|
||||
assert result == []
|
||||
assert cache[0] is None
|
||||
|
||||
def test_message_returns_overlay_lines(self):
|
||||
"""Returns non-empty list when message is present."""
|
||||
msg = ("Test Title", "Test Body", time.monotonic())
|
||||
result, cache = layers.render_message_overlay(msg, 80, 24, (None, None))
|
||||
assert len(result) > 0
|
||||
assert cache[0] is not None
|
||||
|
||||
def test_cache_key_changes_with_text(self):
|
||||
"""Cache key changes when message text changes."""
|
||||
msg1 = ("Title1", "Body1", time.monotonic())
|
||||
msg2 = ("Title2", "Body2", time.monotonic())
|
||||
|
||||
_, cache1 = layers.render_message_overlay(msg1, 80, 24, (None, None))
|
||||
_, cache2 = layers.render_message_overlay(msg2, 80, 24, cache1)
|
||||
|
||||
assert cache1[0] != cache2[0]
|
||||
|
||||
def test_cache_reuse_avoids_recomputation(self):
|
||||
"""Cache is returned when same message is passed (interface test)."""
|
||||
msg = ("Same Title", "Same Body", time.monotonic())
|
||||
|
||||
result1, cache1 = layers.render_message_overlay(msg, 80, 24, (None, None))
|
||||
result2, cache2 = layers.render_message_overlay(msg, 80, 24, cache1)
|
||||
|
||||
assert len(result1) > 0
|
||||
assert len(result2) > 0
|
||||
assert cache1[0] == cache2[0]
|
||||
|
||||
|
||||
class TestRenderFirehose:
|
||||
"""Tests for render_firehose function."""
|
||||
|
||||
def test_no_firehose_returns_empty(self):
|
||||
"""Returns empty list when firehose height is 0."""
|
||||
items = [("Headline", "Source", "12:00")]
|
||||
result = layers.render_firehose(items, 80, 0, 24)
|
||||
assert result == []
|
||||
|
||||
def test_firehose_returns_lines(self):
|
||||
"""Returns lines when firehose height > 0."""
|
||||
items = [("Headline", "Source", "12:00")]
|
||||
result = layers.render_firehose(items, 80, 4, 24)
|
||||
assert len(result) == 4
|
||||
|
||||
def test_firehose_includes_ansi_escapes(self):
|
||||
"""Returns lines containing ANSI escape sequences."""
|
||||
items = [("Headline", "Source", "12:00")]
|
||||
result = layers.render_firehose(items, 80, 1, 24)
|
||||
assert "\033[" in result[0]
|
||||
|
||||
|
||||
class TestApplyGlitch:
|
||||
"""Tests for apply_glitch function."""
|
||||
|
||||
def test_empty_buffer_unchanged(self):
|
||||
"""Empty buffer is returned unchanged."""
|
||||
result = layers.apply_glitch([], 0, 0.0, 80)
|
||||
assert result == []
|
||||
|
||||
def test_buffer_length_preserved(self):
|
||||
"""Buffer length is preserved after glitch application."""
|
||||
buf = [f"\033[{i + 1};1Htest\033[K" for i in range(10)]
|
||||
result = layers.apply_glitch(buf, 0, 0.5, 80)
|
||||
assert len(result) == len(buf)
|
||||
|
||||
|
||||
class TestRenderTickerZone:
|
||||
"""Tests for render_ticker_zone function - focusing on interface."""
|
||||
|
||||
def test_returns_list(self):
|
||||
"""Returns a list of strings."""
|
||||
result, cache = layers.render_ticker_zone([], 0, 10, 80, {}, 0.0)
|
||||
assert isinstance(result, list)
|
||||
|
||||
def test_returns_dict_for_cache(self):
|
||||
"""Returns a dict for the noise cache."""
|
||||
result, cache = layers.render_ticker_zone([], 0, 10, 80, {}, 0.0)
|
||||
assert isinstance(cache, dict)
|
||||
Reference in New Issue
Block a user