"""Tests for target resolution system.""" from mudlib.entity import Mob from mudlib.targeting import ( find_entity_on_tile, find_in_inventory, find_thing_on_tile, parse_target, resolve_target, ) from mudlib.thing import Thing class TestParseTarget: """Test ordinal prefix parsing.""" def test_plain_name(self): """Plain name returns ordinal 1.""" ordinal, name = parse_target("goblin") assert ordinal == 1 assert name == "goblin" def test_ordinal_prefix(self): """Parse '2.goblin' into (2, 'goblin').""" ordinal, name = parse_target("2.goblin") assert ordinal == 2 assert name == "goblin" def test_first_ordinal(self): """Parse '1.sword' into (1, 'sword').""" ordinal, name = parse_target("1.sword") assert ordinal == 1 assert name == "sword" def test_large_ordinal(self): """Parse large ordinal numbers.""" ordinal, name = parse_target("10.rat") assert ordinal == 10 assert name == "rat" def test_zero_ordinal_invalid(self): """Zero ordinal is invalid, treat as plain name.""" ordinal, name = parse_target("0.sword") assert ordinal == 1 assert name == "0.sword" def test_negative_ordinal_invalid(self): """Negative ordinal is invalid, treat as plain name.""" ordinal, name = parse_target("-1.sword") assert ordinal == 1 assert name == "-1.sword" class TestResolveTarget: """Test target resolution with name matching.""" def test_exact_match(self): """Find exact name match.""" candidates = [Thing(name="sword"), Thing(name="shield")] result = resolve_target("sword", candidates) assert result is candidates[0] def test_exact_match_case_insensitive(self): """Exact match is case-insensitive.""" candidates = [Thing(name="Sword")] result = resolve_target("sword", candidates) assert result is candidates[0] def test_prefix_match(self): """Find by name prefix.""" candidates = [Thing(name="longsword")] result = resolve_target("long", candidates) assert result is candidates[0] def test_prefix_match_case_insensitive(self): """Prefix match is case-insensitive.""" candidates = [Thing(name="LongSword")] result = resolve_target("long", candidates) assert result is candidates[0] def test_alias_exact_match(self): """Find by exact alias match.""" candidates = [Thing(name="longsword", aliases=["blade", "weapon"])] result = resolve_target("blade", candidates) assert result is candidates[0] def test_alias_prefix_match(self): """Find by alias prefix.""" candidates = [Thing(name="longsword", aliases=["greatsword"])] result = resolve_target("great", candidates) assert result is candidates[0] def test_ordinal_disambiguation(self): """Use ordinal to select Nth match.""" candidates = [ Thing(name="goblin"), Thing(name="goblin"), Thing(name="goblin"), ] result = resolve_target("2.goblin", candidates) assert result is candidates[1] def test_ordinal_out_of_range(self): """Ordinal beyond available matches returns None.""" candidates = [Thing(name="goblin")] result = resolve_target("2.goblin", candidates) assert result is None def test_no_match(self): """No matching candidates returns None.""" candidates = [Thing(name="sword")] result = resolve_target("shield", candidates) assert result is None def test_custom_key_function(self): """Use custom key function to extract name.""" class CustomObject: def __init__(self, label): self.label = label candidates = [CustomObject("sword"), CustomObject("shield")] result = resolve_target("sword", candidates, key=lambda obj: obj.label) assert result is candidates[0] def test_object_without_aliases(self): """Handle objects without aliases attribute.""" class SimpleObject: def __init__(self, name): self.name = name candidates = [SimpleObject("sword")] result = resolve_target("sword", candidates) assert result is candidates[0] def test_exact_match_preferred_over_prefix(self): """Exact match takes precedence over prefix.""" candidates = [Thing(name="sword"), Thing(name="swordfish")] result = resolve_target("sword", candidates) assert result is candidates[0] def test_ordinal_with_prefix_match(self): """Ordinal works with prefix matching.""" candidates = [ Thing(name="longsword"), Thing(name="longbow"), ] result = resolve_target("2.long", candidates) assert result is candidates[1] class TestFindEntityOnTile: """Test finding entities on the same tile.""" def test_find_nearby_player(self, player, nearby_player, test_zone): """Find another player on same tile.""" player.location = test_zone nearby_player.location = test_zone result = find_entity_on_tile("vegeta", player) assert result is nearby_player def test_find_mob_on_tile(self, player, test_zone): """Find mob on same tile.""" player.location = test_zone goblin = Mob(name="goblin", x=0, y=0, alive=True) goblin.location = test_zone test_zone._contents.append(goblin) result = find_entity_on_tile("goblin", player) assert result is goblin def test_skip_dead_mobs(self, player, test_zone): """Skip dead mobs.""" player.location = test_zone dead_goblin = Mob(name="goblin", x=0, y=0, alive=False) dead_goblin.location = test_zone test_zone._contents.append(dead_goblin) result = find_entity_on_tile("goblin", player) assert result is None def test_skip_self(self, player, test_zone): """Don't return the player themselves.""" player.location = test_zone result = find_entity_on_tile("goku", player) assert result is None def test_z_axis_check_both_grounded(self, player, nearby_player, test_zone): """Find entity when both grounded.""" player.location = test_zone nearby_player.location = test_zone player.flying = False nearby_player.flying = False result = find_entity_on_tile("vegeta", player) assert result is nearby_player def test_z_axis_check_both_flying(self, player, nearby_player, test_zone): """Find entity when both flying.""" player.location = test_zone nearby_player.location = test_zone player.flying = True nearby_player.flying = True result = find_entity_on_tile("vegeta", player) assert result is nearby_player def test_z_axis_check_mismatch(self, player, nearby_player, test_zone): """Don't find entity on different z-axis.""" player.location = test_zone nearby_player.location = test_zone player.flying = False nearby_player.flying = True result = find_entity_on_tile("vegeta", player) assert result is None def test_ordinal_with_multiple_entities(self, player, test_zone): """Use ordinal to select specific entity.""" player.location = test_zone goblin1 = Mob(name="goblin", x=0, y=0, alive=True) goblin2 = Mob(name="goblin", x=0, y=0, alive=True) goblin1.location = test_zone test_zone._contents.append(goblin1) goblin2.location = test_zone test_zone._contents.append(goblin2) result = find_entity_on_tile("2.goblin", player) assert result is goblin2 def test_prefix_match_entity(self, player, test_zone): """Find entity by prefix.""" player.location = test_zone goblin = Mob(name="goblin", x=0, y=0, alive=True) goblin.location = test_zone test_zone._contents.append(goblin) result = find_entity_on_tile("gob", player) assert result is goblin def test_no_match(self, player, test_zone): """Return None when no match.""" player.location = test_zone result = find_entity_on_tile("dragon", player) assert result is None class TestFindThingOnTile: """Test finding Things on the ground.""" def test_find_by_name(self, test_zone): """Find thing by exact name.""" sword = Thing(name="sword", x=5, y=5) sword.location = test_zone test_zone._contents.append(sword) result = find_thing_on_tile("sword", test_zone, 5, 5) assert result is sword def test_find_by_alias(self, test_zone): """Find thing by alias.""" sword = Thing(name="longsword", aliases=["blade"], x=5, y=5) sword.location = test_zone test_zone._contents.append(sword) result = find_thing_on_tile("blade", test_zone, 5, 5) assert result is sword def test_prefix_match(self, test_zone): """Find thing by name prefix.""" sword = Thing(name="longsword", x=5, y=5) sword.location = test_zone test_zone._contents.append(sword) result = find_thing_on_tile("long", test_zone, 5, 5) assert result is sword def test_ordinal(self, test_zone): """Use ordinal to select specific thing.""" rock1 = Thing(name="rock", x=5, y=5) rock2 = Thing(name="rock", x=5, y=5) rock1.location = test_zone test_zone._contents.append(rock1) rock2.location = test_zone test_zone._contents.append(rock2) result = find_thing_on_tile("2.rock", test_zone, 5, 5) assert result is rock2 def test_skip_non_things(self, test_zone): """Only match Thing instances, skip entities.""" mob = Mob(name="sword", x=5, y=5, alive=True) mob.location = test_zone test_zone._contents.append(mob) result = find_thing_on_tile("sword", test_zone, 5, 5) assert result is None def test_no_match(self, test_zone): """Return None when no match.""" result = find_thing_on_tile("shield", test_zone, 5, 5) assert result is None class TestFindInInventory: """Test finding Things in player inventory.""" def test_find_by_name(self, player): """Find item by exact name.""" sword = Thing(name="sword") sword.location = player player._contents.append(sword) result = find_in_inventory("sword", player) assert result is sword def test_find_by_alias(self, player): """Find item by alias.""" sword = Thing(name="longsword", aliases=["blade"]) sword.location = player player._contents.append(sword) result = find_in_inventory("blade", player) assert result is sword def test_prefix_match(self, player): """Find item by name prefix.""" sword = Thing(name="longsword") sword.location = player player._contents.append(sword) result = find_in_inventory("long", player) assert result is sword def test_ordinal(self, player): """Use ordinal to select specific item.""" potion1 = Thing(name="potion") potion2 = Thing(name="potion") potion1.location = player player._contents.append(potion1) potion2.location = player player._contents.append(potion2) result = find_in_inventory("2.potion", player) assert result is potion2 def test_no_match(self, player): """Return None when no match.""" result = find_in_inventory("shield", player) assert result is None