255 lines
8.2 KiB
Python
255 lines
8.2 KiB
Python
"""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 contain at least movement and other groups
|
|
assert "Movement:" in output
|
|
assert "Other:" in output
|
|
|
|
# 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
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_commands_shows_aliases(player):
|
|
"""Test that aliases are shown in parens after command names."""
|
|
await commands.dispatch(player, "commands")
|
|
output = "".join([call[0][0] for call in player.writer.write.call_args_list])
|
|
|
|
# Check that some aliases are shown in parentheses
|
|
assert "north(n)" in output or "north (n)" in output
|
|
assert "look(l)" in output or "look (l)" in output
|
|
assert "quit(q)" in output or "quit (q)" in output
|
|
assert "commands(cmds)" in output or "commands (cmds)" 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_groups_correctly(player):
|
|
"""Test that commands are grouped by type."""
|
|
await commands.dispatch(player, "commands")
|
|
output = "".join([call[0][0] for call in player.writer.write.call_args_list])
|
|
|
|
# Find the groups
|
|
movement_pos = output.find("Movement:")
|
|
other_pos = output.find("Other:")
|
|
|
|
# At minimum, movement and other should exist
|
|
assert movement_pos != -1
|
|
assert other_pos != -1
|
|
|
|
# Check that movement commands appear in the movement section
|
|
# (before the "Other:" section)
|
|
movement_section = output[movement_pos:other_pos]
|
|
assert "north" in movement_section
|
|
assert "east" in movement_section
|
|
|
|
# Check that non-movement commands appear in other section
|
|
other_section = output[other_pos:]
|
|
assert "quit" in other_section
|
|
assert "look" in other_section
|
|
|
|
|
|
@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 structure
|
|
assert "Movement:" in output
|
|
assert "Other:" 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 "aliases: rh" 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} spins into a roundhouse kick!" 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 "aliases: pl" in output
|
|
assert "{attacker} winds up a left hook!" in output
|
|
assert "countered by: dodge right, parry high" in output
|
|
|
|
assert "punch right" in output
|
|
assert "aliases: pr" in output
|
|
assert "{attacker} winds up a right hook!" 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 "aliases: pl" 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} winds up a left hook!" 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_alias(player, combat_moves):
|
|
"""Test commands detail view via an alias."""
|
|
await commands.dispatch(player, "commands rh")
|
|
output = "".join([call[0][0] for call in player.writer.write.call_args_list])
|
|
|
|
# Should show the full command details, not the alias
|
|
assert "roundhouse" in output
|
|
assert "aliases: rh" in output
|
|
assert "type: attack" in output
|