forked from genewildish/Mainline
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.
97 lines
3.3 KiB
Python
97 lines
3.3 KiB
Python
"""
|
|
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)
|