forked from genewildish/Mainline
fix(performance): use simple height estimation instead of PIL rendering
- Replace estimate_block_height (PIL-based) with estimate_simple_height (word wrap) - Update viewport filter tests to match new height-based filtering (~4 items vs 24) - Fix CI task duplication in mise.toml (remove redundant depends) Closes #38 Closes #36
This commit is contained in:
@@ -160,3 +160,236 @@ class TestWebSocketDisplayUnavailable:
|
||||
"""show does nothing when websockets unavailable."""
|
||||
display = WebSocketDisplay()
|
||||
display.show(["line1", "line2"])
|
||||
|
||||
|
||||
class TestWebSocketUIPanelIntegration:
|
||||
"""Tests for WebSocket-UIPanel integration for remote control."""
|
||||
|
||||
def test_set_controller_stores_controller(self):
|
||||
"""set_controller stores the controller reference."""
|
||||
with patch("engine.display.backends.websocket.websockets", MagicMock()):
|
||||
display = WebSocketDisplay()
|
||||
mock_controller = MagicMock()
|
||||
display.set_controller(mock_controller)
|
||||
assert display._controller is mock_controller
|
||||
|
||||
def test_set_command_callback_stores_callback(self):
|
||||
"""set_command_callback stores the callback."""
|
||||
with patch("engine.display.backends.websocket.websockets", MagicMock()):
|
||||
display = WebSocketDisplay()
|
||||
callback = MagicMock()
|
||||
display.set_command_callback(callback)
|
||||
assert display._command_callback is callback
|
||||
|
||||
def test_get_state_snapshot_returns_none_without_controller(self):
|
||||
"""_get_state_snapshot returns None when no controller is set."""
|
||||
with patch("engine.display.backends.websocket.websockets", MagicMock()):
|
||||
display = WebSocketDisplay()
|
||||
assert display._get_state_snapshot() is None
|
||||
|
||||
def test_get_state_snapshot_returns_controller_state(self):
|
||||
"""_get_state_snapshot returns state from controller."""
|
||||
with patch("engine.display.backends.websocket.websockets", MagicMock()):
|
||||
display = WebSocketDisplay()
|
||||
|
||||
# Create mock controller with expected attributes
|
||||
mock_controller = MagicMock()
|
||||
mock_controller.stages = {
|
||||
"test_stage": MagicMock(
|
||||
enabled=True, params={"intensity": 0.5}, selected=False
|
||||
)
|
||||
}
|
||||
mock_controller._current_preset = "demo"
|
||||
mock_controller._presets = ["demo", "test"]
|
||||
mock_controller.selected_stage = "test_stage"
|
||||
|
||||
display.set_controller(mock_controller)
|
||||
state = display._get_state_snapshot()
|
||||
|
||||
assert state is not None
|
||||
assert "stages" in state
|
||||
assert "test_stage" in state["stages"]
|
||||
assert state["stages"]["test_stage"]["enabled"] is True
|
||||
assert state["stages"]["test_stage"]["params"] == {"intensity": 0.5}
|
||||
assert state["preset"] == "demo"
|
||||
assert state["presets"] == ["demo", "test"]
|
||||
assert state["selected_stage"] == "test_stage"
|
||||
|
||||
def test_get_state_snapshot_handles_missing_attributes(self):
|
||||
"""_get_state_snapshot handles controller without all attributes."""
|
||||
with patch("engine.display.backends.websocket.websockets", MagicMock()):
|
||||
display = WebSocketDisplay()
|
||||
|
||||
# Create mock controller without stages attribute using spec
|
||||
# This prevents MagicMock from auto-creating the attribute
|
||||
mock_controller = MagicMock(spec=[]) # Empty spec means no attributes
|
||||
|
||||
display.set_controller(mock_controller)
|
||||
state = display._get_state_snapshot()
|
||||
|
||||
assert state == {}
|
||||
|
||||
def test_broadcast_state_sends_to_clients(self):
|
||||
"""broadcast_state sends state update to all connected clients."""
|
||||
with patch("engine.display.backends.websocket.websockets", MagicMock()):
|
||||
display = WebSocketDisplay()
|
||||
|
||||
# Mock client with send method
|
||||
mock_client = MagicMock()
|
||||
mock_client.send = MagicMock()
|
||||
display._clients.add(mock_client)
|
||||
|
||||
test_state = {"test": "state"}
|
||||
display.broadcast_state(test_state)
|
||||
|
||||
# Verify send was called with JSON containing state
|
||||
mock_client.send.assert_called_once()
|
||||
call_args = mock_client.send.call_args[0][0]
|
||||
assert '"type": "state"' in call_args
|
||||
assert '"test"' in call_args
|
||||
|
||||
def test_broadcast_state_noop_when_no_clients(self):
|
||||
"""broadcast_state does nothing when no clients connected."""
|
||||
with patch("engine.display.backends.websocket.websockets", MagicMock()):
|
||||
display = WebSocketDisplay()
|
||||
display._clients.clear()
|
||||
|
||||
# Should not raise error
|
||||
display.broadcast_state({"test": "state"})
|
||||
|
||||
|
||||
class TestWebSocketHTTPServerPath:
|
||||
"""Tests for WebSocket HTTP server client directory path calculation."""
|
||||
|
||||
def test_client_dir_path_calculation(self):
|
||||
"""Client directory path is correctly calculated from websocket.py location."""
|
||||
import os
|
||||
|
||||
# Use the actual websocket.py file location, not the test file
|
||||
websocket_module = __import__(
|
||||
"engine.display.backends.websocket", fromlist=["WebSocketDisplay"]
|
||||
)
|
||||
websocket_file = websocket_module.__file__
|
||||
parts = websocket_file.split(os.sep)
|
||||
|
||||
if "engine" in parts:
|
||||
engine_idx = parts.index("engine")
|
||||
project_root = os.sep.join(parts[:engine_idx])
|
||||
client_dir = os.path.join(project_root, "client")
|
||||
else:
|
||||
# Fallback calculation (shouldn't happen in normal test runs)
|
||||
client_dir = os.path.join(
|
||||
os.path.dirname(
|
||||
os.path.dirname(os.path.dirname(os.path.dirname(websocket_file)))
|
||||
),
|
||||
"client",
|
||||
)
|
||||
|
||||
# Verify the client directory exists and contains expected files
|
||||
assert os.path.exists(client_dir), f"Client directory not found: {client_dir}"
|
||||
assert "index.html" in os.listdir(client_dir), (
|
||||
"index.html not found in client directory"
|
||||
)
|
||||
assert "editor.html" in os.listdir(client_dir), (
|
||||
"editor.html not found in client directory"
|
||||
)
|
||||
|
||||
# Verify the path is correct (should be .../Mainline/client)
|
||||
assert client_dir.endswith("client"), (
|
||||
f"Client dir should end with 'client': {client_dir}"
|
||||
)
|
||||
assert "Mainline" in client_dir, (
|
||||
f"Client dir should contain 'Mainline': {client_dir}"
|
||||
)
|
||||
|
||||
def test_http_server_directory_serves_client_files(self):
|
||||
"""HTTP server directory correctly serves client files."""
|
||||
import os
|
||||
|
||||
# Use the actual websocket.py file location, not the test file
|
||||
websocket_module = __import__(
|
||||
"engine.display.backends.websocket", fromlist=["WebSocketDisplay"]
|
||||
)
|
||||
websocket_file = websocket_module.__file__
|
||||
parts = websocket_file.split(os.sep)
|
||||
|
||||
if "engine" in parts:
|
||||
engine_idx = parts.index("engine")
|
||||
project_root = os.sep.join(parts[:engine_idx])
|
||||
client_dir = os.path.join(project_root, "client")
|
||||
else:
|
||||
client_dir = os.path.join(
|
||||
os.path.dirname(
|
||||
os.path.dirname(os.path.dirname(os.path.dirname(websocket_file)))
|
||||
),
|
||||
"client",
|
||||
)
|
||||
|
||||
# Verify the handler would be able to serve files from this directory
|
||||
# We can't actually instantiate the handler without a valid request,
|
||||
# but we can verify the directory is accessible
|
||||
assert os.access(client_dir, os.R_OK), (
|
||||
f"Client directory not readable: {client_dir}"
|
||||
)
|
||||
|
||||
# Verify key files exist
|
||||
index_path = os.path.join(client_dir, "index.html")
|
||||
editor_path = os.path.join(client_dir, "editor.html")
|
||||
|
||||
assert os.path.exists(index_path), f"index.html not found at: {index_path}"
|
||||
assert os.path.exists(editor_path), f"editor.html not found at: {editor_path}"
|
||||
|
||||
# Verify files are readable
|
||||
assert os.access(index_path, os.R_OK), "index.html not readable"
|
||||
assert os.access(editor_path, os.R_OK), "editor.html not readable"
|
||||
|
||||
def test_old_buggy_path_does_not_find_client_directory(self):
|
||||
"""The old buggy path (3 dirname calls) should NOT find the client directory.
|
||||
|
||||
This test verifies that the old buggy behavior would have failed.
|
||||
The old code used:
|
||||
client_dir = os.path.join(
|
||||
os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "client"
|
||||
)
|
||||
|
||||
This would resolve to: .../engine/client (which doesn't exist)
|
||||
Instead of: .../Mainline/client (which does exist)
|
||||
"""
|
||||
import os
|
||||
|
||||
# Use the actual websocket.py file location
|
||||
websocket_module = __import__(
|
||||
"engine.display.backends.websocket", fromlist=["WebSocketDisplay"]
|
||||
)
|
||||
websocket_file = websocket_module.__file__
|
||||
|
||||
# OLD BUGGY CODE: 3 dirname calls
|
||||
old_buggy_client_dir = os.path.join(
|
||||
os.path.dirname(os.path.dirname(os.path.dirname(websocket_file))), "client"
|
||||
)
|
||||
|
||||
# This path should NOT exist (it's the buggy path)
|
||||
assert not os.path.exists(old_buggy_client_dir), (
|
||||
f"Old buggy path should not exist: {old_buggy_client_dir}\n"
|
||||
f"If this assertion fails, the bug may have been fixed elsewhere or "
|
||||
f"the test needs updating."
|
||||
)
|
||||
|
||||
# The buggy path should be .../engine/client, not .../Mainline/client
|
||||
assert old_buggy_client_dir.endswith("engine/client"), (
|
||||
f"Old buggy path should end with 'engine/client': {old_buggy_client_dir}"
|
||||
)
|
||||
|
||||
# Verify that going up one more level (4 dirname calls) finds the correct path
|
||||
correct_client_dir = os.path.join(
|
||||
os.path.dirname(
|
||||
os.path.dirname(os.path.dirname(os.path.dirname(websocket_file)))
|
||||
),
|
||||
"client",
|
||||
)
|
||||
assert os.path.exists(correct_client_dir), (
|
||||
f"Correct path should exist: {correct_client_dir}"
|
||||
)
|
||||
assert "index.html" in os.listdir(correct_client_dir), (
|
||||
f"index.html should exist in correct path: {correct_client_dir}"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user