forked from genewildish/Mainline
feat: Add gallery presets, MultiDisplay support, and viewport tests
- Add ~20 gallery presets covering sources, effects, cameras, displays - Add MultiDisplay support with --display multi:terminal,pygame syntax - Fix ViewportFilterStage to recompute layout on viewport_width change - Add benchmark.py module for hook-based performance testing - Add viewport resize tests to test_viewport_filter_performance.py
This commit is contained in:
@@ -30,11 +30,11 @@ class TestMain:
|
||||
patch("engine.app.run_pipeline_mode") as mock_run,
|
||||
):
|
||||
mock_config.PIPELINE_DIAGRAM = False
|
||||
mock_config.PRESET = "border-test"
|
||||
mock_config.PRESET = "gallery-sources"
|
||||
mock_config.PIPELINE_MODE = False
|
||||
sys.argv = ["mainline.py"]
|
||||
main()
|
||||
mock_run.assert_called_once_with("border-test")
|
||||
mock_run.assert_called_once_with("gallery-sources")
|
||||
|
||||
def test_main_exits_on_unknown_preset(self):
|
||||
"""main() exits with error for unknown preset."""
|
||||
@@ -120,11 +120,11 @@ class TestRunPipelineMode:
|
||||
mock_create.return_value = mock_display
|
||||
|
||||
try:
|
||||
run_pipeline_mode("border-test")
|
||||
run_pipeline_mode("gallery-display-terminal")
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
pass
|
||||
|
||||
# Verify display was created with 'terminal' (preset display for border-test)
|
||||
# Verify display was created with 'terminal' (preset display)
|
||||
mock_create.assert_called_once_with("terminal")
|
||||
|
||||
def test_run_pipeline_mode_respects_display_cli_flag(self):
|
||||
|
||||
@@ -158,3 +158,96 @@ class TestViewportFilterIntegration:
|
||||
# Verify we kept the first N items in order
|
||||
for i, item in enumerate(filtered):
|
||||
assert item.content == f"Headline {i}"
|
||||
|
||||
|
||||
class TestViewportResize:
|
||||
"""Test ViewportFilterStage handles viewport resize correctly."""
|
||||
|
||||
def test_layout_recomputes_on_width_change(self):
|
||||
"""Test that layout is recomputed when viewport_width changes."""
|
||||
stage = ViewportFilterStage()
|
||||
# Use long headlines that will wrap differently at different widths
|
||||
items = [
|
||||
SourceItem(
|
||||
f"This is a very long headline number {i} that will definitely wrap at narrow widths",
|
||||
"test",
|
||||
str(i),
|
||||
)
|
||||
for i in range(50)
|
||||
]
|
||||
|
||||
# Initial render at 80 cols
|
||||
ctx = PipelineContext()
|
||||
ctx.params = MockParams(viewport_width=80, viewport_height=24)
|
||||
ctx.set("camera_y", 0)
|
||||
|
||||
stage.process(items, ctx)
|
||||
cached_layout_80 = stage._layout.copy()
|
||||
|
||||
# Resize to 40 cols - layout should recompute
|
||||
ctx.params.viewport_width = 40
|
||||
stage.process(items, ctx)
|
||||
cached_layout_40 = stage._layout.copy()
|
||||
|
||||
# With narrower viewport, items wrap to more lines
|
||||
# So the cumulative heights should be different
|
||||
assert cached_layout_40 != cached_layout_80, (
|
||||
"Layout should recompute when viewport_width changes"
|
||||
)
|
||||
|
||||
def test_layout_recomputes_on_height_change(self):
|
||||
"""Test that visible items change when viewport_height changes."""
|
||||
stage = ViewportFilterStage()
|
||||
items = [SourceItem(f"Headline {i}", "test", str(i)) for i in range(100)]
|
||||
|
||||
ctx = PipelineContext()
|
||||
ctx.set("camera_y", 0)
|
||||
|
||||
# Small viewport - fewer items visible
|
||||
ctx.params = MockParams(viewport_width=80, viewport_height=12)
|
||||
result_small = stage.process(items, ctx)
|
||||
|
||||
# Larger viewport - more items visible
|
||||
ctx.params.viewport_height = 48
|
||||
result_large = stage.process(items, ctx)
|
||||
|
||||
# With larger viewport, more items should be visible
|
||||
assert len(result_large) >= len(result_small)
|
||||
|
||||
def test_camera_y_propagates_to_filter(self):
|
||||
"""Test that camera_y is read from context."""
|
||||
stage = ViewportFilterStage()
|
||||
items = [SourceItem(f"Headline {i}", "test", str(i)) for i in range(100)]
|
||||
|
||||
ctx = PipelineContext()
|
||||
ctx.params = MockParams(viewport_width=80, viewport_height=24)
|
||||
|
||||
# Camera at y=0
|
||||
ctx.set("camera_y", 0)
|
||||
result_at_0 = stage.process(items, ctx)
|
||||
|
||||
# Camera at y=100
|
||||
ctx.set("camera_y", 100)
|
||||
result_at_100 = stage.process(items, ctx)
|
||||
|
||||
# With different camera positions, different items should be visible
|
||||
# (unless items are very short)
|
||||
first_item_at_0 = result_at_0[0].content if result_at_0 else None
|
||||
first_item_at_100 = result_at_100[0].content if result_at_100 else None
|
||||
|
||||
# The items at different positions should be different
|
||||
assert first_item_at_0 != first_item_at_100 or first_item_at_0 is None
|
||||
|
||||
def test_resize_handles_edge_case_small_width(self):
|
||||
"""Test that very narrow viewport doesn't crash."""
|
||||
stage = ViewportFilterStage()
|
||||
items = [SourceItem("Short", "test", "1")]
|
||||
|
||||
ctx = PipelineContext()
|
||||
ctx.params = MockParams(viewport_width=10, viewport_height=5)
|
||||
ctx.set("camera_y", 0)
|
||||
|
||||
# Should not crash with very narrow viewport
|
||||
result = stage.process(items, ctx)
|
||||
assert result is not None
|
||||
assert len(result) > 0
|
||||
|
||||
Reference in New Issue
Block a user