Remove redundant bare-truthy and .called checks where more specific content or entity validation already exists on subsequent lines.
286 lines
8.5 KiB
Python
286 lines
8.5 KiB
Python
"""Tests for editor integration with the shell and command system."""
|
|
|
|
from unittest.mock import MagicMock
|
|
|
|
import pytest
|
|
|
|
from mudlib import commands
|
|
from mudlib.commands.edit import cmd_edit
|
|
from mudlib.editor import Editor
|
|
from mudlib.player import Player
|
|
|
|
|
|
@pytest.fixture
|
|
def player(mock_reader, mock_writer):
|
|
return Player(name="TestPlayer", x=5, y=5, reader=mock_reader, writer=mock_writer)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_edit_command_enters_editor_mode(player):
|
|
"""Test that edit command sets up editor mode."""
|
|
assert player.mode == "normal"
|
|
assert player.editor is None
|
|
|
|
await cmd_edit(player, "")
|
|
|
|
assert player.mode == "editor"
|
|
assert player.editor is not None
|
|
assert isinstance(player.editor, Editor)
|
|
assert player.mode_stack == ["normal", "editor"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_edit_command_sends_welcome_message(player, mock_writer):
|
|
"""Test that edit command sends welcome message."""
|
|
await cmd_edit(player, "")
|
|
|
|
output = "".join([call[0][0] for call in mock_writer.write.call_args_list])
|
|
assert "editor" in output.lower()
|
|
assert ":h" in output
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_edit_command_sets_save_callback(player):
|
|
"""Test that edit command sets up a save callback."""
|
|
await cmd_edit(player, "")
|
|
|
|
assert player.editor is not None
|
|
assert player.editor.save_callback is not None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_editor_handle_input_preserves_whitespace(player):
|
|
"""Test that editor receives input with whitespace preserved."""
|
|
await cmd_edit(player, "")
|
|
|
|
# Simulate code with indentation
|
|
response = await player.editor.handle_input(" def foo():")
|
|
|
|
assert player.editor.buffer == [" def foo():"]
|
|
assert response.done is False
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_editor_done_clears_editor_and_pops_mode(player):
|
|
"""Test that when editor returns done=True, mode is popped and editor cleared."""
|
|
await cmd_edit(player, "")
|
|
|
|
assert player.mode == "editor"
|
|
assert player.editor is not None
|
|
|
|
# Quit the editor
|
|
response = await player.editor.handle_input(":q")
|
|
assert response.done is True
|
|
|
|
# Simulate what the shell loop should do
|
|
player.editor = None
|
|
player.mode_stack.pop()
|
|
|
|
assert player.mode == "normal"
|
|
assert player.editor is None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_editor_save_callback_sends_message(player, mock_writer):
|
|
"""Test that save callback sends confirmation to player."""
|
|
await cmd_edit(player, "")
|
|
mock_writer.reset_mock()
|
|
|
|
await player.editor.handle_input("test line")
|
|
response = await player.editor.handle_input(":w")
|
|
|
|
assert response.saved is True
|
|
# Save callback should have sent a message
|
|
output = "".join([call[0][0] for call in mock_writer.write.call_args_list])
|
|
assert "saved" in output.lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_edit_command_only_works_in_normal_mode(player):
|
|
"""Test that edit command has mode='normal' restriction."""
|
|
# Push a different mode onto the stack
|
|
player.mode_stack.append("combat")
|
|
|
|
# Try to invoke edit command through dispatch
|
|
await commands.dispatch(player, "edit")
|
|
|
|
# Should be blocked by mode check
|
|
assert player.mode == "combat"
|
|
assert player.editor is None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_mode_stack_push_and_pop(player):
|
|
"""Test mode stack mechanics for editor mode."""
|
|
assert player.mode_stack == ["normal"]
|
|
assert player.mode == "normal"
|
|
|
|
# Enter editor mode
|
|
player.mode_stack.append("editor")
|
|
assert player.mode == "editor"
|
|
assert player.mode_stack == ["normal", "editor"]
|
|
|
|
# Exit editor mode
|
|
player.mode_stack.pop()
|
|
assert player.mode == "normal"
|
|
assert player.mode_stack == ["normal"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_empty_input_allowed_in_editor(player):
|
|
"""Test that empty lines are valid in editor mode (for blank lines in code)."""
|
|
await cmd_edit(player, "")
|
|
|
|
# Empty line should be appended to buffer
|
|
response = await player.editor.handle_input("")
|
|
assert response.done is False
|
|
assert player.editor.buffer == [""]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_editor_prompt_uses_cursor(player):
|
|
"""Test that editor prompt should show line number from cursor."""
|
|
await cmd_edit(player, "")
|
|
|
|
# Add some lines
|
|
await player.editor.handle_input("line 1")
|
|
await player.editor.handle_input("line 2")
|
|
|
|
# After appending, cursor should be at the last line (1 in 0-indexed)
|
|
# This test verifies the cursor field exists and can be used for prompts
|
|
assert player.editor.cursor == 1
|
|
# Shell loop prompt: f" {player.editor.cursor + 1}> " = " 2> "
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_edit_no_args_opens_blank_editor(player):
|
|
"""Test that edit with no args opens a blank editor."""
|
|
await cmd_edit(player, "")
|
|
|
|
assert player.editor is not None
|
|
assert player.editor.buffer == []
|
|
assert player.mode == "editor"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_edit_combat_move_opens_toml(player, tmp_path):
|
|
"""Test that edit roundhouse opens the TOML file for editing."""
|
|
from mudlib.combat import commands as combat_commands
|
|
|
|
# Create a test TOML file
|
|
toml_content = """name = "roundhouse"
|
|
aliases = ["rh"]
|
|
move_type = "attack"
|
|
stamina_cost = 8.0
|
|
hit_time_ms = 2000
|
|
"""
|
|
toml_file = tmp_path / "roundhouse.toml"
|
|
toml_file.write_text(toml_content)
|
|
|
|
# Set up combat moves
|
|
combat_commands.combat_moves = {"roundhouse": MagicMock(command="roundhouse")}
|
|
combat_commands.combat_content_dir = tmp_path
|
|
|
|
await cmd_edit(player, "roundhouse")
|
|
|
|
assert player.editor is not None
|
|
assert player.mode == "editor"
|
|
assert toml_content in "\n".join(player.editor.buffer)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_edit_combat_move_saves_to_disk(player, tmp_path, mock_writer):
|
|
"""Test that saving in editor writes back to the TOML file."""
|
|
from mudlib.combat import commands as combat_commands
|
|
|
|
# Create a test TOML file
|
|
original_content = """name = "roundhouse"
|
|
aliases = ["rh"]
|
|
move_type = "attack"
|
|
"""
|
|
toml_file = tmp_path / "roundhouse.toml"
|
|
toml_file.write_text(original_content)
|
|
|
|
# Set up combat moves
|
|
combat_commands.combat_moves = {"roundhouse": MagicMock(command="roundhouse")}
|
|
combat_commands.combat_content_dir = tmp_path
|
|
|
|
await cmd_edit(player, "roundhouse")
|
|
mock_writer.reset_mock()
|
|
|
|
# Modify the buffer
|
|
player.editor.buffer = [
|
|
'name = "roundhouse"',
|
|
'aliases = ["rh"]',
|
|
'move_type = "attack"',
|
|
"stamina_cost = 9.0",
|
|
]
|
|
|
|
# Save
|
|
await player.editor.handle_input(":w")
|
|
|
|
# Check that file was written
|
|
saved_content = toml_file.read_text()
|
|
assert "stamina_cost = 9.0" in saved_content
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_edit_variant_base_opens_toml(player, tmp_path):
|
|
"""Test that edit punch opens punch.toml (variant base name)."""
|
|
from mudlib.combat import commands as combat_commands
|
|
|
|
# Create punch.toml with variants
|
|
toml_content = """name = "punch"
|
|
move_type = "attack"
|
|
|
|
[variants.left]
|
|
aliases = ["pl"]
|
|
"""
|
|
toml_file = tmp_path / "punch.toml"
|
|
toml_file.write_text(toml_content)
|
|
|
|
# Set up combat moves with variant
|
|
combat_commands.combat_moves = {
|
|
"punch left": MagicMock(command="punch", variant="left")
|
|
}
|
|
combat_commands.combat_content_dir = tmp_path
|
|
|
|
await cmd_edit(player, "punch")
|
|
|
|
assert player.editor is not None
|
|
assert player.mode == "editor"
|
|
assert "[variants.left]" in "\n".join(player.editor.buffer)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_edit_unknown_content_shows_error(player, mock_writer, tmp_path):
|
|
"""Test that edit nonexistent shows an error."""
|
|
from mudlib.combat import commands as combat_commands
|
|
|
|
combat_commands.combat_moves = {}
|
|
combat_commands.combat_content_dir = tmp_path
|
|
|
|
await cmd_edit(player, "nonexistent")
|
|
|
|
assert player.editor is None
|
|
assert player.mode == "normal"
|
|
output = "".join([call[0][0] for call in mock_writer.write.call_args_list])
|
|
assert "unknown" in output.lower()
|
|
assert "nonexistent" in output.lower()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_edit_combat_move_uses_toml_content_type(player, tmp_path):
|
|
"""Test that editor for combat moves uses toml content type."""
|
|
from mudlib.combat import commands as combat_commands
|
|
|
|
toml_file = tmp_path / "roundhouse.toml"
|
|
toml_file.write_text("name = 'roundhouse'\n")
|
|
|
|
combat_commands.combat_moves = {"roundhouse": MagicMock(command="roundhouse")}
|
|
combat_commands.combat_content_dir = tmp_path
|
|
|
|
await cmd_edit(player, "roundhouse")
|
|
|
|
assert player.editor is not None
|
|
assert player.editor.content_type == "toml"
|