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.
303 lines
8.6 KiB
303 lines
8.6 KiB
/** |
|
* @file char_panel_test.cpp |
|
* |
|
* Tests for the character panel (stat display and stat point allocation). |
|
* |
|
* Covers: open/close/toggle, stat point allocation via CheckChrBtns + |
|
* ReleaseChrBtns, no-allocation when stat points are zero, and stat |
|
* value consistency checks. |
|
* |
|
* All assertions are on game state (CharFlag, stat values, stat points). |
|
* No assertions on rendering, pixel positions, or widget layout. |
|
* |
|
* NOTE: The actual stat increase (e.g. +1 Strength) is applied via |
|
* NetSendCmdParam1 → loopback → OnAddStrength → ModifyPlrStr. With |
|
* SELCONN_LOOPBACK the message is queued but not processed synchronously |
|
* (there is no message pump in the test harness). Therefore stat |
|
* allocation tests verify the LOCAL side-effects — _pStatPts decreasing |
|
* and CheckChrBtns/ReleaseChrBtns flow — rather than the final stat value. |
|
*/ |
|
|
|
#include <gtest/gtest.h> |
|
|
|
#include "ui_test.hpp" |
|
|
|
#include "control/control.hpp" |
|
#include "control/control_panel.hpp" |
|
#include "diablo.h" |
|
#include "player.h" |
|
|
|
namespace devilution { |
|
namespace { |
|
|
|
// --------------------------------------------------------------------------- |
|
// Test fixture |
|
// --------------------------------------------------------------------------- |
|
|
|
class CharPanelTest : public UITest { |
|
protected: |
|
void SetUp() override |
|
{ |
|
UITest::SetUp(); |
|
|
|
// Reset stat allocation state. |
|
CharPanelButtonActive = false; |
|
for (int i = 0; i < 4; i++) |
|
CharPanelButton[i] = false; |
|
} |
|
|
|
/** |
|
* @brief Simulate pressing and releasing a stat button for the given |
|
* attribute. |
|
* |
|
* Positions MousePosition inside the adjusted button rect, calls |
|
* CheckChrBtns() to "press", then ReleaseChrBtns() to "release" |
|
* (which triggers the stat decrease locally and sends a net command). |
|
*/ |
|
void ClickStatButton(CharacterAttribute attribute) |
|
{ |
|
auto buttonId = static_cast<size_t>(attribute); |
|
Rectangle button = CharPanelButtonRect[buttonId]; |
|
SetPanelObjectPosition(UiPanels::Character, button); |
|
|
|
// Position mouse in the centre of the button. |
|
MousePosition = Point { |
|
button.position.x + button.size.width / 2, |
|
button.position.y + button.size.height / 2 |
|
}; |
|
|
|
CheckChrBtns(); |
|
ReleaseChrBtns(false); |
|
} |
|
}; |
|
|
|
// =========================================================================== |
|
// Open / Close / Toggle |
|
// =========================================================================== |
|
|
|
TEST_F(CharPanelTest, Open_SetsCharFlag) |
|
{ |
|
ASSERT_FALSE(CharFlag); |
|
|
|
OpenCharPanel(); |
|
|
|
EXPECT_TRUE(CharFlag); |
|
} |
|
|
|
TEST_F(CharPanelTest, Close_ClearsCharFlag) |
|
{ |
|
OpenCharPanel(); |
|
ASSERT_TRUE(CharFlag); |
|
|
|
CloseCharPanel(); |
|
|
|
EXPECT_FALSE(CharFlag); |
|
} |
|
|
|
TEST_F(CharPanelTest, Toggle_OpensWhenClosed) |
|
{ |
|
ASSERT_FALSE(CharFlag); |
|
|
|
ToggleCharPanel(); |
|
|
|
EXPECT_TRUE(CharFlag); |
|
} |
|
|
|
TEST_F(CharPanelTest, Toggle_ClosesWhenOpen) |
|
{ |
|
OpenCharPanel(); |
|
ASSERT_TRUE(CharFlag); |
|
|
|
ToggleCharPanel(); |
|
|
|
EXPECT_FALSE(CharFlag); |
|
} |
|
|
|
TEST_F(CharPanelTest, Toggle_DoubleToggle_ReturnsToClosed) |
|
{ |
|
ASSERT_FALSE(CharFlag); |
|
|
|
ToggleCharPanel(); |
|
ToggleCharPanel(); |
|
|
|
EXPECT_FALSE(CharFlag); |
|
} |
|
|
|
// =========================================================================== |
|
// Stat point allocation — verify _pStatPts decrease (local effect) |
|
// =========================================================================== |
|
|
|
TEST_F(CharPanelTest, StatAllocation_StrengthDecreasesStatPoints) |
|
{ |
|
MyPlayer->_pStatPts = 5; |
|
const int ptsBefore = MyPlayer->_pStatPts; |
|
|
|
ClickStatButton(CharacterAttribute::Strength); |
|
|
|
EXPECT_EQ(MyPlayer->_pStatPts, ptsBefore - 1) |
|
<< "Stat points should decrease by 1 after allocating to Strength"; |
|
} |
|
|
|
TEST_F(CharPanelTest, StatAllocation_MagicDecreasesStatPoints) |
|
{ |
|
MyPlayer->_pStatPts = 5; |
|
const int ptsBefore = MyPlayer->_pStatPts; |
|
|
|
ClickStatButton(CharacterAttribute::Magic); |
|
|
|
EXPECT_EQ(MyPlayer->_pStatPts, ptsBefore - 1) |
|
<< "Stat points should decrease by 1 after allocating to Magic"; |
|
} |
|
|
|
TEST_F(CharPanelTest, StatAllocation_DexterityDecreasesStatPoints) |
|
{ |
|
MyPlayer->_pStatPts = 5; |
|
const int ptsBefore = MyPlayer->_pStatPts; |
|
|
|
ClickStatButton(CharacterAttribute::Dexterity); |
|
|
|
EXPECT_EQ(MyPlayer->_pStatPts, ptsBefore - 1) |
|
<< "Stat points should decrease by 1 after allocating to Dexterity"; |
|
} |
|
|
|
TEST_F(CharPanelTest, StatAllocation_VitalityDecreasesStatPoints) |
|
{ |
|
MyPlayer->_pStatPts = 5; |
|
const int ptsBefore = MyPlayer->_pStatPts; |
|
|
|
ClickStatButton(CharacterAttribute::Vitality); |
|
|
|
EXPECT_EQ(MyPlayer->_pStatPts, ptsBefore - 1) |
|
<< "Stat points should decrease by 1 after allocating to Vitality"; |
|
} |
|
|
|
TEST_F(CharPanelTest, StatAllocation_CheckChrBtnsActivatesButton) |
|
{ |
|
MyPlayer->_pStatPts = 5; |
|
|
|
auto buttonId = static_cast<size_t>(CharacterAttribute::Strength); |
|
Rectangle button = CharPanelButtonRect[buttonId]; |
|
SetPanelObjectPosition(UiPanels::Character, button); |
|
MousePosition = Point { |
|
button.position.x + button.size.width / 2, |
|
button.position.y + button.size.height / 2 |
|
}; |
|
|
|
CheckChrBtns(); |
|
|
|
EXPECT_TRUE(CharPanelButtonActive) |
|
<< "CharPanelButtonActive should be true after CheckChrBtns with stat points"; |
|
EXPECT_TRUE(CharPanelButton[buttonId]) |
|
<< "The specific button should be marked as pressed"; |
|
} |
|
|
|
// =========================================================================== |
|
// No stat points available |
|
// =========================================================================== |
|
|
|
TEST_F(CharPanelTest, NoStatPoints_CheckChrBtnsDoesNothing) |
|
{ |
|
MyPlayer->_pStatPts = 0; |
|
|
|
// Position mouse over the first button. |
|
Rectangle button = CharPanelButtonRect[0]; |
|
SetPanelObjectPosition(UiPanels::Character, button); |
|
MousePosition = Point { |
|
button.position.x + button.size.width / 2, |
|
button.position.y + button.size.height / 2 |
|
}; |
|
|
|
CheckChrBtns(); |
|
|
|
EXPECT_FALSE(CharPanelButtonActive) |
|
<< "Buttons should not activate when there are no stat points"; |
|
} |
|
|
|
TEST_F(CharPanelTest, NoStatPoints_StatPointsUnchanged) |
|
{ |
|
MyPlayer->_pStatPts = 0; |
|
|
|
ClickStatButton(CharacterAttribute::Strength); |
|
|
|
EXPECT_EQ(MyPlayer->_pStatPts, 0) |
|
<< "Stat points should remain zero"; |
|
} |
|
|
|
// =========================================================================== |
|
// Stat values match player |
|
// =========================================================================== |
|
|
|
TEST_F(CharPanelTest, StatValues_MatchPlayerStruct) |
|
{ |
|
// The level-25 Warrior created by UITest should have known stat values. |
|
// Just verify the getter returns matching values. |
|
EXPECT_EQ(MyPlayer->GetBaseAttributeValue(CharacterAttribute::Strength), |
|
MyPlayer->_pBaseStr); |
|
EXPECT_EQ(MyPlayer->GetBaseAttributeValue(CharacterAttribute::Magic), |
|
MyPlayer->_pBaseMag); |
|
EXPECT_EQ(MyPlayer->GetBaseAttributeValue(CharacterAttribute::Dexterity), |
|
MyPlayer->_pBaseDex); |
|
EXPECT_EQ(MyPlayer->GetBaseAttributeValue(CharacterAttribute::Vitality), |
|
MyPlayer->_pBaseVit); |
|
} |
|
|
|
// =========================================================================== |
|
// Multiple allocations |
|
// =========================================================================== |
|
|
|
TEST_F(CharPanelTest, MultipleAllocations_AllStatPointsUsed) |
|
{ |
|
MyPlayer->_pStatPts = 3; |
|
|
|
ClickStatButton(CharacterAttribute::Strength); |
|
ClickStatButton(CharacterAttribute::Strength); |
|
ClickStatButton(CharacterAttribute::Strength); |
|
|
|
EXPECT_EQ(MyPlayer->_pStatPts, 0) |
|
<< "All 3 stat points should be consumed"; |
|
} |
|
|
|
TEST_F(CharPanelTest, MultipleAllocations_DifferentStats) |
|
{ |
|
MyPlayer->_pStatPts = 4; |
|
|
|
ClickStatButton(CharacterAttribute::Strength); |
|
ClickStatButton(CharacterAttribute::Magic); |
|
ClickStatButton(CharacterAttribute::Dexterity); |
|
ClickStatButton(CharacterAttribute::Vitality); |
|
|
|
EXPECT_EQ(MyPlayer->_pStatPts, 0) |
|
<< "All 4 stat points should be consumed across different stats"; |
|
} |
|
|
|
// =========================================================================== |
|
// Edge case: allocation stops at max stat |
|
// =========================================================================== |
|
|
|
TEST_F(CharPanelTest, AllocationStopsAtMaxStat) |
|
{ |
|
// Set strength to the maximum. |
|
const int maxStr = MyPlayer->GetMaximumAttributeValue(CharacterAttribute::Strength); |
|
MyPlayer->_pBaseStr = maxStr; |
|
MyPlayer->_pStatPts = 5; |
|
|
|
// Position mouse and try to press — CheckChrBtns should skip the button |
|
// because the stat is already at max. |
|
auto buttonId = static_cast<size_t>(CharacterAttribute::Strength); |
|
Rectangle button = CharPanelButtonRect[buttonId]; |
|
SetPanelObjectPosition(UiPanels::Character, button); |
|
MousePosition = Point { |
|
button.position.x + button.size.width / 2, |
|
button.position.y + button.size.height / 2 |
|
}; |
|
|
|
CheckChrBtns(); |
|
|
|
EXPECT_FALSE(CharPanelButton[buttonId]) |
|
<< "Strength button should not activate when stat is at maximum"; |
|
EXPECT_EQ(MyPlayer->_pStatPts, 5) |
|
<< "Stat points should be unchanged"; |
|
} |
|
|
|
} // namespace |
|
} // namespace devilution
|