"""Tests for editor integration with the shell and command system.""" from unittest.mock import AsyncMock, 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 mock_writer(): writer = MagicMock() writer.write = MagicMock() writer.drain = AsyncMock() return writer @pytest.fixture def mock_reader(): return MagicMock() @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, "") assert mock_writer.write.called 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 assert mock_writer.write.called 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> "