- Add scroll_offset to REPLState (max 50 lines) - Modify _render_repl() to use manual scroll position - Add scroll_output(delta) method for scroll control - Add PageUp/PageDown keyboard support (scroll 10 lines) - Add mouse wheel support via SGR mouse tracking - Update HUD to show scroll percentage (like vim) and position - Reset scroll when new output arrives - Add tests for scroll functionality
259 lines
8.9 KiB
Python
259 lines
8.9 KiB
Python
"""Tests for the REPL effect plugin."""
|
|
|
|
import pytest
|
|
from pathlib import Path
|
|
|
|
from engine.effects.plugins import discover_plugins
|
|
from engine.effects.registry import get_registry
|
|
from engine.effects.plugins.repl import ReplEffect, REPLState
|
|
|
|
|
|
class TestReplEffectRegistration:
|
|
"""Tests for REPL effect registration."""
|
|
|
|
def test_repl_registered(self):
|
|
"""REPL effect is registered in the registry."""
|
|
discover_plugins()
|
|
registry = get_registry()
|
|
repl = registry.get("repl")
|
|
assert repl is not None
|
|
assert repl.name == "repl"
|
|
|
|
|
|
class TestReplEffectCreation:
|
|
"""Tests for creating REPL effect instances."""
|
|
|
|
def test_create_repl_effect(self):
|
|
"""Can create REPL effect instance."""
|
|
repl = ReplEffect()
|
|
assert repl.name == "repl"
|
|
assert repl.config.enabled is True
|
|
assert repl.config.intensity == 1.0
|
|
|
|
def test_repl_state(self):
|
|
"""REPL state is initialized correctly."""
|
|
repl = ReplEffect()
|
|
assert repl.state.command_history == []
|
|
assert repl.state.current_command == ""
|
|
assert repl.state.history_index == -1
|
|
assert repl.state.output_buffer == []
|
|
|
|
|
|
class TestReplEffectCommands:
|
|
"""Tests for REPL command processing."""
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def setup(self):
|
|
"""Setup before each test."""
|
|
self.repl = ReplEffect()
|
|
|
|
def test_process_command_help(self):
|
|
"""Help command adds help text to output."""
|
|
self.repl.process_command("help")
|
|
assert "> help" in self.repl.state.output_buffer
|
|
assert any(
|
|
"Available commands:" in line for line in self.repl.state.output_buffer
|
|
)
|
|
|
|
def test_process_command_status(self):
|
|
"""Status command adds status info to output."""
|
|
self.repl.process_command("status")
|
|
assert "> status" in self.repl.state.output_buffer
|
|
assert any("Output lines:" in line for line in self.repl.state.output_buffer)
|
|
|
|
def test_process_command_clear(self):
|
|
"""Clear command clears output buffer."""
|
|
self.repl.process_command("help")
|
|
initial_count = len(self.repl.state.output_buffer)
|
|
assert initial_count > 0
|
|
|
|
self.repl.process_command("clear")
|
|
assert len(self.repl.state.output_buffer) == 0
|
|
|
|
def test_process_command_unknown(self):
|
|
"""Unknown command adds error message."""
|
|
self.repl.process_command("unknown_command_xyz")
|
|
assert "> unknown_command_xyz" in self.repl.state.output_buffer
|
|
assert any("Unknown command" in line for line in self.repl.state.output_buffer)
|
|
|
|
def test_command_history(self):
|
|
"""Commands are added to history."""
|
|
self.repl.process_command("help")
|
|
self.repl.process_command("status")
|
|
assert len(self.repl.state.command_history) == 2
|
|
assert self.repl.state.command_history[0] == "help"
|
|
assert self.repl.state.command_history[1] == "status"
|
|
|
|
def test_current_command_cleared(self):
|
|
"""Current command is cleared after processing."""
|
|
self.repl.state.current_command = "test"
|
|
self.repl.process_command("help")
|
|
assert self.repl.state.current_command == ""
|
|
|
|
|
|
class TestReplNavigation:
|
|
"""Tests for REPL navigation (history, editing)."""
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def setup(self):
|
|
"""Setup before each test."""
|
|
self.repl = ReplEffect()
|
|
self.repl.state.command_history = ["help", "status", "effects"]
|
|
|
|
def test_navigate_history_up(self):
|
|
"""Navigate up through command history."""
|
|
self.repl.navigate_history(-1) # Up
|
|
assert self.repl.state.history_index == 0
|
|
assert self.repl.state.current_command == "help"
|
|
|
|
def test_navigate_history_down(self):
|
|
"""Navigate down through command history."""
|
|
self.repl.state.history_index = 0
|
|
self.repl.navigate_history(1) # Down
|
|
assert self.repl.state.history_index == 1
|
|
assert self.repl.state.current_command == "status"
|
|
|
|
def test_append_to_command(self):
|
|
"""Append character to current command."""
|
|
self.repl.append_to_command("h")
|
|
self.repl.append_to_command("e")
|
|
self.repl.append_to_command("l")
|
|
self.repl.append_to_command("p")
|
|
assert self.repl.state.current_command == "help"
|
|
|
|
def test_backspace(self):
|
|
"""Remove last character from command."""
|
|
self.repl.state.current_command = "hel"
|
|
self.repl.backspace()
|
|
assert self.repl.state.current_command == "he"
|
|
|
|
def test_clear_command(self):
|
|
"""Clear current command."""
|
|
self.repl.state.current_command = "test"
|
|
self.repl.clear_command()
|
|
assert self.repl.state.current_command == ""
|
|
|
|
|
|
class TestReplProcess:
|
|
"""Tests for REPL effect processing."""
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def setup(self):
|
|
"""Setup before each test."""
|
|
discover_plugins()
|
|
self.repl = ReplEffect()
|
|
|
|
def test_process_renders_output(self):
|
|
"""Process renders REPL interface."""
|
|
buf = ["line1", "line2", "line3"]
|
|
from engine.effects.types import EffectContext
|
|
|
|
ctx = EffectContext(
|
|
terminal_width=80, terminal_height=24, scroll_cam=0, ticker_height=0
|
|
)
|
|
result = self.repl.process(buf, ctx)
|
|
|
|
assert len(result) == 24 # Should match terminal height
|
|
assert any("MAINLINE REPL" in line for line in result)
|
|
assert any("COMMANDS:" in line for line in result)
|
|
assert any("OUTPUT:" in line for line in result)
|
|
|
|
def test_process_with_commands(self):
|
|
"""Process shows command output in REPL."""
|
|
# Test the output buffer directly instead of rendered output
|
|
# This is more robust as it's not affected by display size limits
|
|
self.repl.process_command("help")
|
|
|
|
# Check that the command was recorded in output buffer
|
|
assert "> help" in self.repl.state.output_buffer
|
|
|
|
# Check that help text appears in the output buffer
|
|
# (testing buffer directly is more reliable than testing rendered output)
|
|
assert any(
|
|
"Available commands:" in line for line in self.repl.state.output_buffer
|
|
)
|
|
|
|
|
|
class TestReplConfig:
|
|
"""Tests for REPL configuration."""
|
|
|
|
def test_config_params(self):
|
|
"""REPL config has expected parameters."""
|
|
repl = ReplEffect()
|
|
assert "display_height" in repl.config.params
|
|
assert "show_hud" in repl.config.params
|
|
assert repl.config.params["display_height"] == 8
|
|
assert repl.config.params["show_hud"] is True
|
|
|
|
def test_configure(self):
|
|
"""Can configure REPL effect."""
|
|
repl = ReplEffect()
|
|
from engine.effects.types import EffectConfig
|
|
|
|
config = EffectConfig(
|
|
enabled=False,
|
|
intensity=0.5,
|
|
params={"display_height": 10, "show_hud": False},
|
|
)
|
|
repl.configure(config)
|
|
assert repl.config.enabled is False
|
|
assert repl.config.intensity == 0.5
|
|
assert repl.config.params["display_height"] == 10
|
|
|
|
|
|
class TestReplScrolling:
|
|
"""Tests for REPL scrolling functionality."""
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def setup(self):
|
|
"""Setup before each test."""
|
|
self.repl = ReplEffect()
|
|
|
|
def test_scroll_offset_initial(self):
|
|
"""Scroll offset starts at 0."""
|
|
assert self.repl.state.scroll_offset == 0
|
|
|
|
def test_scroll_output_positive(self):
|
|
"""Scrolling with positive delta moves back through buffer."""
|
|
# Add some output
|
|
self.repl.state.output_buffer = [f"line{i}" for i in range(20)]
|
|
|
|
# Scroll up 5 lines
|
|
self.repl.scroll_output(5)
|
|
assert self.repl.state.scroll_offset == 5
|
|
|
|
def test_scroll_output_negative(self):
|
|
"""Scrolling with negative delta moves forward through buffer."""
|
|
# Add some output and scroll up first
|
|
self.repl.state.output_buffer = [f"line{i}" for i in range(20)]
|
|
self.repl.state.scroll_offset = 10
|
|
|
|
# Scroll down 3 lines
|
|
self.repl.scroll_output(-3)
|
|
assert self.repl.state.scroll_offset == 7
|
|
|
|
def test_scroll_output_bounds(self):
|
|
"""Scroll offset stays within valid bounds."""
|
|
# Add some output
|
|
self.repl.state.output_buffer = [f"line{i}" for i in range(10)]
|
|
|
|
# Try to scroll past top
|
|
self.repl.scroll_output(100)
|
|
assert self.repl.state.scroll_offset == 9 # max: len(output) - 1
|
|
|
|
# Try to scroll past bottom
|
|
self.repl.state.scroll_offset = 5
|
|
self.repl.scroll_output(-100)
|
|
assert self.repl.state.scroll_offset == 0
|
|
|
|
def test_scroll_resets_on_new_output(self):
|
|
"""Scroll offset resets when new command output arrives."""
|
|
self.repl.state.output_buffer = [f"line{i}" for i in range(20)]
|
|
self.repl.state.scroll_offset = 10
|
|
|
|
# Process a new command
|
|
self.repl.process_command("test command")
|
|
|
|
# Scroll offset should be reset to 0
|
|
assert self.repl.state.scroll_offset == 0
|