Files
DevilutionX/test/inventory_ui_test.cpp
T

359 lines
10 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @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