forked from genewildish/Mainline
BUG FIXES:
1. Border parameter not being passed to display.show()
- Display backends support border parameter but app.py wasn't passing it
- Now app.py passes params.border to display.show(border=params.border)
- Enables border-test preset to actually render borders
2. WebSocket and Multi displays didn't support border parameter
- Updated WebSocket Protocol to include border parameter
- Updated MultiDisplay.show() to accept and forward border parameter
- Updated test to expect border parameter in mock calls
3. app.py didn't properly handle special sources (empty, pipeline-inspect)
- Border-test preset with source='empty' was still fetching headlines
- Pipeline-inspect source was never using the introspection data source
- Now app.py detects special sources and uses appropriate data source stages:
* 'empty' source → EmptyDataSource stage
* 'pipeline-inspect' → PipelineIntrospectionSource stage
* Other sources → traditional items-based approach
- Uses SourceItemsToBufferStage for special sources instead of RenderStage
- Sets pipeline on introspection source after build to avoid circular dependency
TESTING:
- All 463 tests pass
- Linting passes
- Manual test: `uv run mainline.py --preset border-test` now correctly shows empty source
- border-test preset now properly initializes without fetching unnecessary content
The issue was that the enhanced app.py code from the original diff didn't make it into
the refactor commit. This fix restores that functionality.
211 lines
6.9 KiB
Python
211 lines
6.9 KiB
Python
"""
|
|
Tests for engine.display module.
|
|
"""
|
|
|
|
from unittest.mock import MagicMock
|
|
|
|
from engine.display import DisplayRegistry, NullDisplay, TerminalDisplay
|
|
from engine.display.backends.multi import MultiDisplay
|
|
|
|
|
|
class TestDisplayProtocol:
|
|
"""Test that display backends satisfy the Display protocol."""
|
|
|
|
def test_terminal_display_is_display(self):
|
|
"""TerminalDisplay satisfies Display protocol."""
|
|
display = TerminalDisplay()
|
|
assert hasattr(display, "init")
|
|
assert hasattr(display, "show")
|
|
assert hasattr(display, "clear")
|
|
assert hasattr(display, "cleanup")
|
|
|
|
def test_null_display_is_display(self):
|
|
"""NullDisplay satisfies Display protocol."""
|
|
display = NullDisplay()
|
|
assert hasattr(display, "init")
|
|
assert hasattr(display, "show")
|
|
assert hasattr(display, "clear")
|
|
assert hasattr(display, "cleanup")
|
|
|
|
|
|
class TestDisplayRegistry:
|
|
"""Tests for DisplayRegistry class."""
|
|
|
|
def setup_method(self):
|
|
"""Reset registry before each test."""
|
|
DisplayRegistry._backends = {}
|
|
DisplayRegistry._initialized = False
|
|
|
|
def test_register_adds_backend(self):
|
|
"""register adds a backend to the registry."""
|
|
DisplayRegistry.register("test", TerminalDisplay)
|
|
assert DisplayRegistry.get("test") == TerminalDisplay
|
|
|
|
def test_register_case_insensitive(self):
|
|
"""register is case insensitive."""
|
|
DisplayRegistry.register("TEST", TerminalDisplay)
|
|
assert DisplayRegistry.get("test") == TerminalDisplay
|
|
|
|
def test_get_returns_none_for_unknown(self):
|
|
"""get returns None for unknown backend."""
|
|
assert DisplayRegistry.get("unknown") is None
|
|
|
|
def test_list_backends_returns_all(self):
|
|
"""list_backends returns all registered backends."""
|
|
DisplayRegistry.register("a", TerminalDisplay)
|
|
DisplayRegistry.register("b", NullDisplay)
|
|
backends = DisplayRegistry.list_backends()
|
|
assert "a" in backends
|
|
assert "b" in backends
|
|
|
|
def test_create_returns_instance(self):
|
|
"""create returns a display instance."""
|
|
DisplayRegistry.register("test", NullDisplay)
|
|
display = DisplayRegistry.create("test")
|
|
assert isinstance(display, NullDisplay)
|
|
|
|
def test_create_returns_none_for_unknown(self):
|
|
"""create returns None for unknown backend."""
|
|
display = DisplayRegistry.create("unknown")
|
|
assert display is None
|
|
|
|
def test_initialize_registers_defaults(self):
|
|
"""initialize registers default backends."""
|
|
DisplayRegistry.initialize()
|
|
assert DisplayRegistry.get("terminal") == TerminalDisplay
|
|
assert DisplayRegistry.get("null") == NullDisplay
|
|
from engine.display.backends.sixel import SixelDisplay
|
|
from engine.display.backends.websocket import WebSocketDisplay
|
|
|
|
assert DisplayRegistry.get("websocket") == WebSocketDisplay
|
|
assert DisplayRegistry.get("sixel") == SixelDisplay
|
|
|
|
def test_initialize_idempotent(self):
|
|
"""initialize can be called multiple times safely."""
|
|
DisplayRegistry.initialize()
|
|
DisplayRegistry._backends["custom"] = TerminalDisplay
|
|
DisplayRegistry.initialize()
|
|
assert "custom" in DisplayRegistry.list_backends()
|
|
|
|
|
|
class TestTerminalDisplay:
|
|
"""Tests for TerminalDisplay class."""
|
|
|
|
def test_init_sets_dimensions(self):
|
|
"""init stores terminal dimensions."""
|
|
display = TerminalDisplay()
|
|
display.init(80, 24)
|
|
assert display.width == 80
|
|
assert display.height == 24
|
|
|
|
def test_show_returns_none(self):
|
|
"""show returns None after writing to stdout."""
|
|
display = TerminalDisplay()
|
|
display.width = 80
|
|
display.height = 24
|
|
display.show(["line1", "line2"])
|
|
|
|
def test_clear_does_not_error(self):
|
|
"""clear works without error."""
|
|
display = TerminalDisplay()
|
|
display.clear()
|
|
|
|
def test_cleanup_does_not_error(self):
|
|
"""cleanup works without error."""
|
|
display = TerminalDisplay()
|
|
display.cleanup()
|
|
|
|
|
|
class TestNullDisplay:
|
|
"""Tests for NullDisplay class."""
|
|
|
|
def test_init_stores_dimensions(self):
|
|
"""init stores dimensions."""
|
|
display = NullDisplay()
|
|
display.init(100, 50)
|
|
assert display.width == 100
|
|
assert display.height == 50
|
|
|
|
def test_show_does_nothing(self):
|
|
"""show discards buffer without error."""
|
|
display = NullDisplay()
|
|
display.show(["line1", "line2", "line3"])
|
|
|
|
def test_clear_does_nothing(self):
|
|
"""clear does nothing."""
|
|
display = NullDisplay()
|
|
display.clear()
|
|
|
|
def test_cleanup_does_nothing(self):
|
|
"""cleanup does nothing."""
|
|
display = NullDisplay()
|
|
display.cleanup()
|
|
|
|
|
|
class TestMultiDisplay:
|
|
"""Tests for MultiDisplay class."""
|
|
|
|
def test_init_stores_dimensions(self):
|
|
"""init stores dimensions and forwards to displays."""
|
|
mock_display1 = MagicMock()
|
|
mock_display2 = MagicMock()
|
|
multi = MultiDisplay([mock_display1, mock_display2])
|
|
|
|
multi.init(120, 40)
|
|
|
|
assert multi.width == 120
|
|
assert multi.height == 40
|
|
mock_display1.init.assert_called_once_with(120, 40, reuse=False)
|
|
mock_display2.init.assert_called_once_with(120, 40, reuse=False)
|
|
|
|
def test_show_forwards_to_all_displays(self):
|
|
"""show forwards buffer to all displays."""
|
|
mock_display1 = MagicMock()
|
|
mock_display2 = MagicMock()
|
|
multi = MultiDisplay([mock_display1, mock_display2])
|
|
|
|
buffer = ["line1", "line2"]
|
|
multi.show(buffer, border=False)
|
|
|
|
mock_display1.show.assert_called_once_with(buffer, border=False)
|
|
mock_display2.show.assert_called_once_with(buffer, border=False)
|
|
|
|
def test_clear_forwards_to_all_displays(self):
|
|
"""clear forwards to all displays."""
|
|
mock_display1 = MagicMock()
|
|
mock_display2 = MagicMock()
|
|
multi = MultiDisplay([mock_display1, mock_display2])
|
|
|
|
multi.clear()
|
|
|
|
mock_display1.clear.assert_called_once()
|
|
mock_display2.clear.assert_called_once()
|
|
|
|
def test_cleanup_forwards_to_all_displays(self):
|
|
"""cleanup forwards to all displays."""
|
|
mock_display1 = MagicMock()
|
|
mock_display2 = MagicMock()
|
|
multi = MultiDisplay([mock_display1, mock_display2])
|
|
|
|
multi.cleanup()
|
|
|
|
mock_display1.cleanup.assert_called_once()
|
|
mock_display2.cleanup.assert_called_once()
|
|
|
|
def test_empty_displays_list(self):
|
|
"""handles empty displays list gracefully."""
|
|
multi = MultiDisplay([])
|
|
multi.init(80, 24)
|
|
multi.show(["test"])
|
|
multi.clear()
|
|
multi.cleanup()
|
|
|
|
def test_init_with_reuse(self):
|
|
"""init passes reuse flag to child displays."""
|
|
mock_display = MagicMock()
|
|
multi = MultiDisplay([mock_display])
|
|
|
|
multi.init(80, 24, reuse=True)
|
|
|
|
mock_display.init.assert_called_once_with(80, 24, reuse=True)
|