You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

359 lines
10 KiB

/**
* @file inventory_ui_test.cpp
*
* Tests for inventory operations that are not covered by inv_test.cpp.
*
* Covers: AutoEquip, AutoPlaceItemInInventory, CanFitItemInInventory,
* belt placement, RemoveEquipment, ReorganizeInventory, and
* TransferItemToStash.
*/
#include <gtest/gtest.h>
#include "ui_test.hpp"
#include "inv.h"
#include "items.h"
#include "player.h"
#include "qol/stash.h"
namespace devilution {
namespace {
// ---------------------------------------------------------------------------
// Test fixture
// ---------------------------------------------------------------------------
class InventoryUITest : public UITest {
protected:
void SetUp() override
{
UITest::SetUp();
// Each test starts with a completely stripped player and clean stash.
StripPlayer();
Stash = {};
Stash.gold = 0;
Stash.dirty = false;
}
// --- helpers ---
/** @brief Create a sword (2×1 one-hand weapon, requires 15 Str). */
static Item MakeSword()
{
Item item {};
InitializeItem(item, IDI_BARDSWORD);
item._iIdentified = true;
return item;
}
/** @brief Create a healing potion (1×1, beltable). */
static Item MakePotion()
{
Item item {};
InitializeItem(item, IDI_HEAL);
return item;
}
/** @brief Create a short staff (1×3 two-hand weapon, requires 0 Str). */
static Item MakeStaff()
{
Item item {};
InitializeItem(item, IDI_SHORTSTAFF);
item._iIdentified = true;
return item;
}
/**
* @brief Fill the entire inventory grid with 1×1 healing potions.
*
* After this call every one of the 40 inventory cells is occupied.
*/
void FillInventory()
{
for (int i = 0; i < InventoryGridCells; i++) {
Item potion = MakePotion();
ASSERT_TRUE(AutoPlaceItemInInventory(*MyPlayer, potion))
<< "Failed to place potion at cell " << i;
}
}
/** @brief Fill all 8 belt slots with healing potions. */
void FillBelt()
{
for (int i = 0; i < MaxBeltItems; i++) {
Item potion = MakePotion();
ASSERT_TRUE(AutoPlaceItemInBelt(*MyPlayer, potion, /*persistItem=*/true))
<< "Failed to place potion in belt slot " << i;
}
}
};
// ===========================================================================
// AutoEquip tests
// ===========================================================================
TEST_F(InventoryUITest, AutoEquip_SwordGoesToHand)
{
Item sword = MakeSword();
sword._iStatFlag = true; // Player meets stat requirements
bool equipped = AutoEquip(*MyPlayer, sword);
EXPECT_TRUE(equipped) << "AutoEquip should succeed for a usable sword";
EXPECT_FALSE(MyPlayer->InvBody[INVLOC_HAND_LEFT].isEmpty())
<< "Sword should be placed in the left hand slot";
EXPECT_EQ(MyPlayer->InvBody[INVLOC_HAND_LEFT].IDidx, sword.IDidx)
<< "Equipped item should match the sword we created";
}
TEST_F(InventoryUITest, AutoEquip_ReturnsFalseForUnusableItem)
{
Item sword = MakeSword();
sword._iStatFlag = false; // Player does NOT meet stat requirements
bool equipped = AutoEquip(*MyPlayer, sword);
EXPECT_FALSE(equipped)
<< "AutoEquip should return false when _iStatFlag is false";
EXPECT_TRUE(MyPlayer->InvBody[INVLOC_HAND_LEFT].isEmpty())
<< "Left hand slot should remain empty";
EXPECT_TRUE(MyPlayer->InvBody[INVLOC_HAND_RIGHT].isEmpty())
<< "Right hand slot should remain empty";
}
TEST_F(InventoryUITest, AutoEquip_FailsWhenSlotOccupied)
{
// Equip the first sword.
Item sword1 = MakeSword();
sword1._iStatFlag = true;
ASSERT_TRUE(AutoEquip(*MyPlayer, sword1));
// Also fill the right hand so neither hand slot is free.
Item sword2 = MakeSword();
sword2._iStatFlag = true;
MyPlayer->InvBody[INVLOC_HAND_RIGHT] = sword2;
// Now try to auto-equip a third sword — both hand slots are occupied,
// so CanEquip should reject it (AutoEquip does NOT swap).
Item sword3 = MakeSword();
sword3._iStatFlag = true;
bool equipped = AutoEquip(*MyPlayer, sword3);
EXPECT_FALSE(equipped)
<< "AutoEquip should return false when all valid body slots are occupied";
}
// ===========================================================================
// AutoPlaceItemInInventory / CanFitItemInInventory tests
// ===========================================================================
TEST_F(InventoryUITest, AutoPlaceItem_EmptyInventory)
{
Item sword = MakeSword();
bool placed = AutoPlaceItemInInventory(*MyPlayer, sword);
EXPECT_TRUE(placed) << "Should be able to place an item in an empty inventory";
EXPECT_EQ(MyPlayer->_pNumInv, 1)
<< "Inventory count should be 1 after placing one item";
// Verify the item is actually stored.
EXPECT_EQ(MyPlayer->InvList[0].IDidx, sword.IDidx)
<< "InvList[0] should contain the sword";
// Verify at least one grid cell is set (non-zero).
bool gridSet = false;
for (int i = 0; i < InventoryGridCells; i++) {
if (MyPlayer->InvGrid[i] != 0) {
gridSet = true;
break;
}
}
EXPECT_TRUE(gridSet) << "At least one InvGrid cell should be non-zero";
}
TEST_F(InventoryUITest, AutoPlaceItem_FullInventory)
{
FillInventory();
// Inventory is full — a new item should not fit.
Item extraPotion = MakePotion();
bool placed = AutoPlaceItemInInventory(*MyPlayer, extraPotion);
EXPECT_FALSE(placed)
<< "Should not be able to place an item in a completely full inventory";
}
TEST_F(InventoryUITest, CanFitItem_EmptyInventory)
{
Item sword = MakeSword();
EXPECT_TRUE(CanFitItemInInventory(*MyPlayer, sword))
<< "An empty inventory should have room for a sword";
}
TEST_F(InventoryUITest, CanFitItem_FullInventory)
{
FillInventory();
Item extraPotion = MakePotion();
EXPECT_FALSE(CanFitItemInInventory(*MyPlayer, extraPotion))
<< "A completely full inventory should report no room";
}
// ===========================================================================
// Belt tests
// ===========================================================================
TEST_F(InventoryUITest, CanBePlacedOnBelt_Potion)
{
Item potion = MakePotion();
EXPECT_TRUE(CanBePlacedOnBelt(*MyPlayer, potion))
<< "A healing potion (1×1) should be placeable on the belt";
}
TEST_F(InventoryUITest, CanBePlacedOnBelt_Sword)
{
Item sword = MakeSword();
EXPECT_FALSE(CanBePlacedOnBelt(*MyPlayer, sword))
<< "A sword (2×1 weapon) should NOT be placeable on the belt";
}
TEST_F(InventoryUITest, AutoPlaceBelt_Success)
{
Item potion = MakePotion();
bool placed = AutoPlaceItemInBelt(*MyPlayer, potion, /*persistItem=*/true);
EXPECT_TRUE(placed) << "Should be able to place a potion in an empty belt";
// Verify at least one belt slot contains the item.
bool found = false;
for (int i = 0; i < MaxBeltItems; i++) {
if (!MyPlayer->SpdList[i].isEmpty()) {
found = true;
break;
}
}
EXPECT_TRUE(found) << "Potion should appear in one of the belt slots";
}
TEST_F(InventoryUITest, AutoPlaceBelt_Full)
{
FillBelt();
Item extraPotion = MakePotion();
bool placed = AutoPlaceItemInBelt(*MyPlayer, extraPotion, /*persistItem=*/true);
EXPECT_FALSE(placed)
<< "Should not be able to place a potion when all 8 belt slots are occupied";
}
// ===========================================================================
// RemoveEquipment test
// ===========================================================================
TEST_F(InventoryUITest, RemoveEquipment_ClearsBodySlot)
{
// Equip a sword in the left hand.
Item sword = MakeSword();
sword._iStatFlag = true;
ASSERT_TRUE(AutoEquip(*MyPlayer, sword));
ASSERT_FALSE(MyPlayer->InvBody[INVLOC_HAND_LEFT].isEmpty())
<< "Precondition: left hand should have the sword";
RemoveEquipment(*MyPlayer, INVLOC_HAND_LEFT, false);
EXPECT_TRUE(MyPlayer->InvBody[INVLOC_HAND_LEFT].isEmpty())
<< "Left hand slot should be empty after RemoveEquipment";
}
// ===========================================================================
// ReorganizeInventory test
// ===========================================================================
TEST_F(InventoryUITest, ReorganizeInventory_DefragmentsGrid)
{
// Place three potions via AutoPlace so the grid is properly populated.
Item p1 = MakePotion();
Item p2 = MakePotion();
Item p3 = MakePotion();
ASSERT_TRUE(AutoPlaceItemInInventory(*MyPlayer, p1));
ASSERT_TRUE(AutoPlaceItemInInventory(*MyPlayer, p2));
ASSERT_TRUE(AutoPlaceItemInInventory(*MyPlayer, p3));
ASSERT_EQ(MyPlayer->_pNumInv, 3);
// Remove the middle item to create a gap.
MyPlayer->RemoveInvItem(1);
ASSERT_EQ(MyPlayer->_pNumInv, 2);
// Reorganize should keep all remaining items and defragment the grid.
ReorganizeInventory(*MyPlayer);
EXPECT_EQ(MyPlayer->_pNumInv, 2)
<< "Item count should be preserved after reorganization";
// After reorganization, a potion should still fit (there are 38 free cells).
Item extra = MakePotion();
EXPECT_TRUE(CanFitItemInInventory(*MyPlayer, extra))
<< "Should be able to fit another item after reorganization";
// Verify no InvList entries in the active range are empty.
for (int i = 0; i < MyPlayer->_pNumInv; i++) {
EXPECT_FALSE(MyPlayer->InvList[i].isEmpty())
<< "InvList[" << i << "] should not be empty within active range";
}
}
// ===========================================================================
// TransferItemToStash tests
// ===========================================================================
TEST_F(InventoryUITest, TransferToStash_FromInventory)
{
IsStashOpen = true;
// Place a sword in the inventory.
Item sword = MakeSword();
int idx = PlaceItemInInventory(sword);
ASSERT_GE(idx, 0) << "Failed to place sword in inventory";
ASSERT_EQ(MyPlayer->_pNumInv, 1);
int invLocation = INVITEM_INV_FIRST + idx;
TransferItemToStash(*MyPlayer, invLocation);
// Item should now be in the stash.
EXPECT_FALSE(Stash.stashList.empty())
<< "Stash should contain the transferred item";
// Item should be removed from inventory.
EXPECT_EQ(MyPlayer->_pNumInv, 0)
<< "Inventory should be empty after transferring the only item";
}
TEST_F(InventoryUITest, TransferToStash_InvalidLocation)
{
IsStashOpen = true;
size_t stashSizeBefore = Stash.stashList.size();
int invCountBefore = MyPlayer->_pNumInv;
// Passing -1 should be a no-op (early return), not a crash.
TransferItemToStash(*MyPlayer, -1);
EXPECT_EQ(Stash.stashList.size(), stashSizeBefore)
<< "Stash should be unchanged after invalid transfer";
EXPECT_EQ(MyPlayer->_pNumInv, invCountBefore)
<< "Inventory should be unchanged after invalid transfer";
}
} // namespace
} // namespace devilution