Removed identical local copies from 45 test files. These fixtures are already defined in conftest.py.
228 lines
6.5 KiB
Python
228 lines
6.5 KiB
Python
"""Tests for the verb system on Object."""
|
|
|
|
import pytest
|
|
|
|
from mudlib.object import Object
|
|
from mudlib.player import Player
|
|
from mudlib.thing import Thing
|
|
from mudlib.verbs import find_object, verb
|
|
from mudlib.zone import Zone
|
|
|
|
|
|
@pytest.fixture
|
|
def test_zone():
|
|
terrain = [["." for _ in range(10)] for _ in range(10)]
|
|
return Zone(name="testzone", width=10, height=10, terrain=terrain)
|
|
|
|
|
|
@pytest.fixture
|
|
def player(mock_reader, mock_writer, test_zone):
|
|
return Player(
|
|
name="TestPlayer",
|
|
x=5,
|
|
y=5,
|
|
reader=mock_reader,
|
|
writer=mock_writer,
|
|
location=test_zone,
|
|
)
|
|
|
|
|
|
def test_object_starts_with_empty_verbs():
|
|
"""Object starts with empty _verbs dict."""
|
|
obj = Object(name="rock")
|
|
assert obj._verbs == {}
|
|
|
|
|
|
def test_register_verb():
|
|
"""register_verb adds a verb to the _verbs dict."""
|
|
|
|
async def test_handler(obj, player, args):
|
|
pass
|
|
|
|
obj = Object(name="fountain")
|
|
obj.register_verb("drink", test_handler)
|
|
assert "drink" in obj._verbs
|
|
assert obj._verbs["drink"] is test_handler
|
|
|
|
|
|
def test_get_verb_returns_handler():
|
|
"""get_verb returns the registered handler."""
|
|
|
|
async def test_handler(obj, player, args):
|
|
pass
|
|
|
|
obj = Object(name="fountain")
|
|
obj.register_verb("drink", test_handler)
|
|
assert obj.get_verb("drink") is test_handler
|
|
|
|
|
|
def test_get_verb_returns_none_for_unknown():
|
|
"""get_verb returns None for unknown verb."""
|
|
obj = Object(name="rock")
|
|
assert obj.get_verb("nonexistent") is None
|
|
|
|
|
|
def test_has_verb_returns_true_when_present():
|
|
"""has_verb returns True when verb is registered."""
|
|
|
|
async def test_handler(obj, player, args):
|
|
pass
|
|
|
|
obj = Object(name="fountain")
|
|
obj.register_verb("drink", test_handler)
|
|
assert obj.has_verb("drink") is True
|
|
|
|
|
|
def test_has_verb_returns_false_when_absent():
|
|
"""has_verb returns False when verb is not registered."""
|
|
obj = Object(name="rock")
|
|
assert obj.has_verb("drink") is False
|
|
|
|
|
|
def test_verb_decorator_marks_method():
|
|
"""@verb decorator marks method with _verb_name attribute."""
|
|
|
|
class TestObject(Object):
|
|
@verb("test")
|
|
async def test_verb(self, player, args):
|
|
pass
|
|
|
|
assert hasattr(TestObject.test_verb, "_verb_name")
|
|
assert TestObject.test_verb._verb_name == "test"
|
|
|
|
|
|
def test_verb_decorator_auto_registers_on_instantiation():
|
|
"""Class with @verb methods auto-registers verbs on instantiation."""
|
|
|
|
class Fountain(Thing):
|
|
@verb("drink")
|
|
async def drink(self, player, args):
|
|
pass
|
|
|
|
fountain = Fountain(name="fountain")
|
|
assert fountain.has_verb("drink")
|
|
assert fountain.get_verb("drink") is not None
|
|
|
|
|
|
def test_multiple_verbs_on_same_class():
|
|
"""Multiple verbs on same class all register."""
|
|
|
|
class Fountain(Thing):
|
|
@verb("drink")
|
|
async def drink(self, player, args):
|
|
pass
|
|
|
|
@verb("splash")
|
|
async def splash(self, player, args):
|
|
pass
|
|
|
|
fountain = Fountain(name="fountain")
|
|
assert fountain.has_verb("drink")
|
|
assert fountain.has_verb("splash")
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_verb_handler_can_be_called(player):
|
|
"""Verb handler can be called as an async function."""
|
|
called = False
|
|
received_args = None
|
|
|
|
class Fountain(Thing):
|
|
@verb("drink")
|
|
async def drink(self, player, args):
|
|
nonlocal called, received_args
|
|
called = True
|
|
received_args = args
|
|
|
|
fountain = Fountain(name="fountain")
|
|
handler = fountain.get_verb("drink")
|
|
assert handler is not None
|
|
# handler is a bound method, so we don't pass self/fountain again
|
|
await handler(player, "from the left side")
|
|
|
|
assert called
|
|
assert received_args == "from the left side"
|
|
|
|
|
|
def test_verb_registered_on_parent_class_propagates_to_subclass():
|
|
"""Verb registered on parent class propagates to subclass instances."""
|
|
|
|
class BaseFountain(Thing):
|
|
@verb("drink")
|
|
async def drink(self, player, args):
|
|
pass
|
|
|
|
class FancyFountain(BaseFountain):
|
|
@verb("splash")
|
|
async def splash(self, player, args):
|
|
pass
|
|
|
|
fancy = FancyFountain(name="fancy fountain")
|
|
assert fancy.has_verb("drink")
|
|
assert fancy.has_verb("splash")
|
|
|
|
|
|
def test_find_object_by_name_in_inventory(player):
|
|
"""find_object finds object by name in inventory."""
|
|
sword = Thing(name="sword", location=player)
|
|
found = find_object("sword", player)
|
|
assert found is sword
|
|
|
|
|
|
def test_find_object_by_alias_in_inventory(player):
|
|
"""find_object finds object by alias in inventory."""
|
|
can = Thing(name="pepsi can", aliases=["can", "pepsi"], location=player)
|
|
found = find_object("can", player)
|
|
assert found is can
|
|
|
|
|
|
def test_find_object_by_name_on_ground(player, test_zone):
|
|
"""find_object finds object by name on ground."""
|
|
rock = Thing(name="rock", location=test_zone, x=5, y=5)
|
|
found = find_object("rock", player)
|
|
assert found is rock
|
|
|
|
|
|
def test_find_object_by_alias_on_ground(player, test_zone):
|
|
"""find_object finds object by alias on ground."""
|
|
can = Thing(
|
|
name="pepsi can", aliases=["can", "pepsi"], location=test_zone, x=5, y=5
|
|
)
|
|
found = find_object("pepsi", player)
|
|
assert found is can
|
|
|
|
|
|
def test_find_object_prefers_inventory_over_ground(player, test_zone):
|
|
"""find_object prefers inventory over ground when matching object in both."""
|
|
Thing(name="sword", location=test_zone, x=5, y=5)
|
|
inv_sword = Thing(name="sword", location=player)
|
|
found = find_object("sword", player)
|
|
assert found is inv_sword
|
|
|
|
|
|
def test_find_object_returns_none_when_nothing_matches(player):
|
|
"""find_object returns None when nothing matches."""
|
|
found = find_object("nonexistent", player)
|
|
assert found is None
|
|
|
|
|
|
def test_find_object_searches_all_objects_not_just_things(player, test_zone):
|
|
"""find_object searches all Objects, not just Things."""
|
|
|
|
class CustomObject(Object):
|
|
def __init__(self, name, aliases=None, **kwargs):
|
|
super().__init__(name=name, **kwargs)
|
|
self.aliases = aliases or []
|
|
|
|
custom = CustomObject(name="widget", aliases=["gadget"], location=player)
|
|
found = find_object("widget", player)
|
|
assert found is custom
|
|
found_by_alias = find_object("gadget", player)
|
|
assert found_by_alias is custom
|
|
|
|
|
|
def test_find_object_skips_self(player, test_zone):
|
|
"""find_object should not return the querying player."""
|
|
player.move_to(test_zone, x=0, y=0)
|
|
result = find_object(player.name, player)
|
|
assert result is None
|