mud/tests/test_editor_integration.py
Jared Miller 50a1d356a8
Strengthen loose assertions in test suite
Remove redundant bare-truthy and .called checks where more specific
content or entity validation already exists on subsequent lines.
2026-02-16 16:10:38 -05:00

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"