"""Tests for the commands listing command.""" from pathlib import Path from unittest.mock import AsyncMock, MagicMock import pytest from mudlib import commands # Import command modules to register their commands from mudlib.commands import ( edit, # noqa: F401 fly, # noqa: F401 help, # noqa: F401 look, # noqa: F401 movement, # noqa: F401 quit, # noqa: F401 ) @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): from mudlib.player import Player return Player( name="TestPlayer", x=5, y=5, reader=mock_reader, writer=mock_writer, ) @pytest.fixture def combat_moves(): """Load and register combat moves from content directory.""" from mudlib.combat.commands import register_combat_commands combat_dir = Path(__file__).resolve().parents[1] / "content" / "combat" register_combat_commands(combat_dir) from mudlib.combat import commands as combat_cmds yield combat_cmds.combat_moves # Clean up combat_cmds.combat_moves = {} # Note: commands stay registered in _registry, but that's ok for tests @pytest.mark.asyncio async def test_commands_command_exists(): """Test that commands command is registered.""" assert "commands" in commands._registry assert "cmds" in commands._registry assert commands._registry["commands"] is commands._registry["cmds"] @pytest.mark.asyncio async def test_commands_has_wildcard_mode(): """Test that commands works from any mode.""" assert commands._registry["commands"].mode == "*" @pytest.mark.asyncio async def test_commands_lists_all_commands(player): """Test that commands command lists all unique commands.""" # Execute the command await commands.dispatch(player, "commands") # Collect all output output = "".join([call[0][0] for call in player.writer.write.call_args_list]) # Should show some specific commands with their primary names assert "north" in output assert "look" in output assert "quit" in output assert "commands" in output # Should not contain combat moves assert "punch" not in output assert "roundhouse" not in output @pytest.mark.asyncio async def test_commands_clean_names_no_parens(player): """Test that command names are shown cleanly without alias parens.""" await commands.dispatch(player, "commands") output = "".join([call[0][0] for call in player.writer.write.call_args_list]) # Commands should appear without parens assert "north" in output assert "look" in output assert "quit" in output assert "commands" in output # Should NOT show aliases in parens assert "north(n)" not in output assert "look(l)" not in output assert "quit(q)" not in output assert "commands(cmds)" not in output @pytest.mark.asyncio async def test_commands_deduplicates_entries(player): """Test that aliases don't create duplicate command entries.""" await commands.dispatch(player, "commands") output = "".join([call[0][0] for call in player.writer.write.call_args_list]) # Count how many times "north" appears as a primary command # It should appear once in the movement group, not multiple times # (Note: it might appear in parens as part of another command, but # we're checking it doesn't appear as a standalone entry multiple times) lines_with_north_command = [ line for line in output.split("\n") if line.strip().startswith("north") ] # Should only have one line starting with "north" as a command assert len(lines_with_north_command) <= 1 @pytest.mark.asyncio async def test_commands_sorted_alphabetically(player): """Test that commands are sorted alphabetically.""" await commands.dispatch(player, "commands") output = "".join([call[0][0] for call in player.writer.write.call_args_list]) # All commands should appear in a single sorted list assert "north" in output assert "east" in output assert "quit" in output assert "look" in output @pytest.mark.asyncio async def test_commands_via_alias(player): """Test that the cmds alias works.""" await commands.dispatch(player, "cmds") output = "".join([call[0][0] for call in player.writer.write.call_args_list]) # Should produce the same output as commands assert "north" in output assert "look" in output @pytest.mark.asyncio async def test_commands_detail_regular_command(player): """Test commands detail view for a regular command.""" await commands.dispatch(player, "commands look") output = "".join([call[0][0] for call in player.writer.write.call_args_list]) assert "look" in output assert "aliases: l" in output assert "mode: normal" in output @pytest.mark.asyncio async def test_commands_detail_simple_combat_move(player, combat_moves): """Test commands detail view for a simple combat move.""" await commands.dispatch(player, "commands roundhouse") output = "".join([call[0][0] for call in player.writer.write.call_args_list]) assert "roundhouse" in output assert "type: attack" in output assert "stamina: 8.0" in output assert "timing window: 2000ms" in output assert "damage: 25%" in output assert "{attacker} shifts {his} weight back..." in output assert "countered by: duck, parry high, parry low" in output @pytest.mark.asyncio async def test_commands_detail_variant_base(player, combat_moves): """Test commands detail view for a variant base command.""" await commands.dispatch(player, "commands punch") output = "".join([call[0][0] for call in player.writer.write.call_args_list]) assert "punch" in output assert "type: attack" in output # Should show both variants assert "punch left" in output assert "{attacker} retracts {his} left arm..." in output assert "countered by: dodge right, parry high" in output assert "punch right" in output assert "{attacker} retracts {his} right arm..." in output assert "countered by: dodge left, parry high" in output # Should show shared properties in each variant assert "stamina: 5.0" in output assert "timing window: 1800ms" in output assert "damage: 15%" in output @pytest.mark.asyncio async def test_commands_detail_specific_variant(player, combat_moves): """Test commands detail view for a specific variant.""" await commands.dispatch(player, "commands punch left") output = "".join([call[0][0] for call in player.writer.write.call_args_list]) assert "punch left" in output assert "type: attack" in output assert "stamina: 5.0" in output assert "timing window: 1800ms" in output assert "damage: 15%" in output assert "{attacker} retracts {his} left arm..." in output assert "countered by: dodge right, parry high" in output # Should NOT show "punch right" assert "punch right" not in output @pytest.mark.asyncio async def test_commands_detail_unknown_command(player): """Test commands detail view for an unknown command.""" await commands.dispatch(player, "commands nonexistent") output = "".join([call[0][0] for call in player.writer.write.call_args_list]) assert "Unknown command: nonexistent" in output @pytest.mark.asyncio async def test_commands_detail_via_prefix(player, combat_moves): """Test commands detail view via prefix matching.""" await commands.dispatch(player, "commands ro") output = "".join([call[0][0] for call in player.writer.write.call_args_list]) # Should show the full command details via prefix match assert "roundhouse" in output assert "type: attack" in output