using Theriapolis.Core.Data; using Theriapolis.Core.Items; using Theriapolis.Core.Rules.Stats; using Xunit; namespace Theriapolis.Tests.Items; public sealed class InventoryTests { private readonly ContentResolver _content = new(new ContentLoader(TestHelpers.DataDirectory)); [Fact] public void Add_AppendsItemAndIncreasesWeight() { var inv = new Inventory(); var sword = inv.Add(_content.Items["rend_sword"]); Assert.Single(inv.Items); Assert.Equal(_content.Items["rend_sword"].WeightLb, inv.TotalWeightLb); Assert.Equal(1, sword.Qty); } [Fact] public void TryEquip_PutsItemInRequestedSlot() { var inv = new Inventory(); var sword = inv.Add(_content.Items["rend_sword"]); bool ok = inv.TryEquip(sword, EquipSlot.MainHand, out var err); Assert.True(ok, err); Assert.Equal(EquipSlot.MainHand, sword.EquippedAt); Assert.Same(sword, inv.GetEquipped(EquipSlot.MainHand)); } [Fact] public void TryEquip_RefusesIfItemNotInInventory() { var inv = new Inventory(); var sword = new ItemInstance(_content.Items["rend_sword"]); // not added to inv bool ok = inv.TryEquip(sword, EquipSlot.MainHand, out var err); Assert.False(ok); Assert.Contains("not in this inventory", err); } [Fact] public void TryEquip_TwoHandedWeaponBlocksWhenOffHandOccupied() { var inv = new Inventory(); var shield = inv.Add(_content.Items["buckler"]); var lance = inv.Add(_content.Items["gore_lance"]); // two_handed Assert.True(inv.TryEquip(shield, EquipSlot.OffHand, out _)); bool ok = inv.TryEquip(lance, EquipSlot.MainHand, out var err); Assert.False(ok); Assert.Contains("two-handed", err.ToLowerInvariant()); } [Fact] public void TryEquip_OffHandBlockedWhenMainHandHoldsTwoHanded() { var inv = new Inventory(); var lance = inv.Add(_content.Items["gore_lance"]); var shield = inv.Add(_content.Items["buckler"]); Assert.True(inv.TryEquip(lance, EquipSlot.MainHand, out _)); bool ok = inv.TryEquip(shield, EquipSlot.OffHand, out var err); Assert.False(ok); Assert.Contains("two-handed", err.ToLowerInvariant()); } [Fact] public void TryEquip_NaturalWeaponEnhancerRequiresMatchingSlot() { var inv = new Inventory(); var fangCaps = inv.Add(_content.Items["fang_caps_steel"]); // Wrong slot: bool ok = inv.TryEquip(fangCaps, EquipSlot.NaturalWeaponClaw, out var err); Assert.False(ok); Assert.Contains("fits", err.ToLowerInvariant()); // Correct slot: Assert.True(inv.TryEquip(fangCaps, EquipSlot.NaturalWeaponFang, out _)); } [Fact] public void TryUnequip_ClearsSlot() { var inv = new Inventory(); var sword = inv.Add(_content.Items["rend_sword"]); inv.TryEquip(sword, EquipSlot.MainHand, out _); Assert.True(inv.TryUnequip(EquipSlot.MainHand, out _)); Assert.Null(sword.EquippedAt); Assert.Null(inv.GetEquipped(EquipSlot.MainHand)); } [Fact] public void Remove_AlsoUnregistersFromEquippedSlot() { var inv = new Inventory(); var sword = inv.Add(_content.Items["rend_sword"]); inv.TryEquip(sword, EquipSlot.MainHand, out _); Assert.True(inv.Remove(sword)); Assert.Empty(inv.Items); Assert.Null(inv.GetEquipped(EquipSlot.MainHand)); } // ── SizeMatch ──────────────────────────────────────────────────────── [Fact] public void SizeMatch_DirectFitReturnsMatch() { var rendSword = _content.Items["rend_sword"]; // medium + large Assert.Equal(SizeMatch.MatchResult.Match, SizeMatch.Check(rendSword, SizeCategory.Medium)); } [Fact] public void SizeMatch_AdaptivePropertyOverridesMissingSize() { var pack = _content.Items["adaptive_pack"]; // adaptive_pack lists s/m/l explicitly, so match — but we test the Adaptive // path by querying with a Tiny wearer that's not in the list. var result = SizeMatch.Check(pack, SizeCategory.Tiny); // adaptive_pack lists "small" so Tiny falls through to "tiny" not in list, // but its "adaptive" property kicks in for the Adaptive branch. Assert.NotEqual(SizeMatch.MatchResult.WrongSize, result); } [Fact] public void SizeMatch_NonAdaptiveWrongSizeIsFlagged() { var rendSword = _content.Items["rend_sword"]; // medium + large only // Small wearer: not in sizes, no adaptive property → WrongSize. Assert.Equal(SizeMatch.MatchResult.WrongSize, SizeMatch.Check(rendSword, SizeCategory.Small)); } }