"""Tests for paint mode - terrain editing admin tool.""" import pytest from mudlib.player import Player from mudlib.zone import Zone @pytest.fixture def player(): """Create a test player with a mock writer.""" mock_writer = MockWriter() player = Player( name="TestPlayer", x=5, y=5, writer=mock_writer, ) # Create a simple zone for testing zone = Zone( "test_zone", width=20, height=20, terrain=[["." for _ in range(20)] for _ in range(20)], impassable={"^", "~", "#"}, # Add # as impassable ) # Add a wall for passability testing zone.terrain[5][6] = "#" player.location = zone return player class MockWriter: """Mock writer that captures output.""" def __init__(self): self.messages = [] # Mock option negotiation state from unittest.mock import MagicMock self.local_option = MagicMock() self.local_option.enabled = MagicMock(return_value=False) self.remote_option = MagicMock() self.remote_option.enabled = MagicMock(return_value=False) def write(self, message: str): self.messages.append(message) async def drain(self): pass def send_gmcp(self, package: str, data: dict): """Mock GMCP send (no-op for paint mode tests).""" pass def send_msdp(self, data: dict): """Mock MSDP send (no-op for paint mode tests).""" pass def get_output(self) -> str: return "".join(self.messages) def clear(self): self.messages = [] @pytest.mark.asyncio async def test_enter_paint_mode(player): """@paint command sets player.paint_mode = True and sends confirmation.""" from mudlib.commands.paint import cmd_paint await cmd_paint(player, "") assert player.paint_mode is True output = player.writer.get_output() assert "paint mode" in output.lower() @pytest.mark.asyncio async def test_exit_paint_mode(player): """@paint when already in paint mode exits it.""" from mudlib.commands.paint import cmd_paint # Enter paint mode first await cmd_paint(player, "") player.writer.clear() # Exit paint mode await cmd_paint(player, "") assert player.paint_mode is False output = player.writer.get_output() assert "exit" in output.lower() or "off" in output.lower() @pytest.mark.asyncio async def test_paint_mode_default_survey(player): """Entering paint mode starts in survey state (player.painting = False).""" from mudlib.commands.paint import cmd_paint await cmd_paint(player, "") assert player.paint_mode is True assert player.painting is False @pytest.mark.asyncio async def test_toggle_painting(player): """p command toggles player.painting between True and False.""" from mudlib.commands.paint import cmd_toggle_painting # Must be in paint mode first player.paint_mode = True # Toggle to painting await cmd_toggle_painting(player, "") assert player.painting is True player.writer.clear() # Toggle back to survey await cmd_toggle_painting(player, "") assert player.painting is False @pytest.mark.asyncio async def test_toggle_painting_requires_paint_mode(player): """p command only works in paint mode.""" from mudlib.commands.paint import cmd_toggle_painting player.paint_mode = False await cmd_toggle_painting(player, "") output = player.writer.get_output() assert "paint mode" in output.lower() @pytest.mark.asyncio async def test_set_brush(player): """Typing a single character while in paint mode sets player.paint_brush.""" from mudlib.commands.paint import cmd_set_brush player.paint_mode = True await cmd_set_brush(player, "#") assert player.paint_brush == "#" await cmd_set_brush(player, "~") assert player.paint_brush == "~" @pytest.mark.asyncio async def test_set_brush_requires_paint_mode(player): """Brush command only works in paint mode.""" from mudlib.commands.paint import cmd_set_brush player.paint_mode = False await cmd_set_brush(player, "#") output = player.writer.get_output() assert "paint mode" in output.lower() @pytest.mark.asyncio async def test_set_brush_requires_single_char(player): """Brush must be a single character.""" from mudlib.commands.paint import cmd_set_brush player.paint_mode = True await cmd_set_brush(player, "##") output = player.writer.get_output() assert "single character" in output.lower() @pytest.mark.asyncio async def test_paint_mode_movement_ignores_passability(player): """Movement in paint mode doesn't check passability.""" from mudlib.commands.movement import move_player # There's a wall at (6, 5) - normally can't move there player.paint_mode = True # Should be able to move into the wall await move_player(player, 1, 0, "east") assert player.x == 6 assert player.y == 5 @pytest.mark.asyncio async def test_painting_places_tile(player): """Moving while painting sets terrain tile at old position to brush char.""" from mudlib.commands.movement import move_player player.paint_mode = True player.painting = True player.paint_brush = "~" # Move from (5,5) to (6,5) old_x, old_y = player.x, player.y await move_player(player, 1, 0, "east") # Check that the old position now has the brush character zone = player.location assert isinstance(zone, Zone) assert zone.terrain[old_y][old_x] == "~" @pytest.mark.asyncio async def test_painting_only_in_paint_mode(player): """Painting flag has no effect outside paint mode.""" from mudlib.commands.movement import move_player player.paint_mode = False player.painting = True player.paint_brush = "~" # Try to move into wall at (6, 5) await move_player(player, 1, 0, "east") # Should have failed - player still at (5, 5) assert player.x == 5 assert player.y == 5 @pytest.mark.asyncio async def test_survey_mode_does_not_paint(player): """Survey mode (painting=False) allows movement but doesn't paint.""" from mudlib.commands.movement import move_player player.paint_mode = True player.painting = False player.paint_brush = "~" old_x, old_y = player.x, player.y await move_player(player, 1, 0, "east") # Movement should have happened assert player.x == 6 assert player.y == 5 # But no painting should have occurred zone = player.location assert isinstance(zone, Zone) assert zone.terrain[old_y][old_x] == "." # Still the original tile