Cockatrice/tests/command_zone/command_zone_integration_test.cpp
DawnFire42 6e83d64622 feat(command-zone): add graphics implementation with integration tests
Implement CommandZone - the main Qt graphics class for rendering and
interacting with command zones.

This commit brings together all previous components:
- Uses CommandZoneState for visibility management
- Uses CommandZoneLogic for card data handling
- Uses ZoneToggleButton for visibility controls
- Uses CommanderTaxCounter for tax display
- Uses z_values.h for proper visual layering

Zone modifications for command zone support:
- SelectZone: add command zone to zone selection
- StackZone: support command zone in stack operations
- TableZone: command zone positioning integration
- ViewZoneWidget: command zone viewing support

Integration tests verify:
- Counter parenting behavior
- Zone state coordination
- Full graphics stack interaction
2026-02-26 20:13:32 -05:00

916 lines
30 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 command_zone_integration_test.cpp
* @brief Integration tests for CommandZone counter coordination
*
* Tests the interaction between command zones and tax counters:
* - Counter parenting to correct zone
* - Counter positioning follows zone movement
* - Primary, partner, companion, and background zone coordination
*/
#include "command_zone_test_common.h"
#include <QPointF>
#include <QRectF>
#include <gtest/gtest.h>
/**
* @brief Mock counter for testing parenting and positioning.
*
* Simulates CommanderTaxCounter without QGraphicsScene dependencies.
* Tracks parent relationship and position for verification.
*/
class MockTaxCounter
{
void *parentItem_;
QPointF pos_;
qreal zValue_;
bool visible_;
public:
explicit MockTaxCounter(void *parent = nullptr) : parentItem_(parent), pos_(0, 0), zValue_(0), visible_(true)
{
}
void setParentItem(void *parent)
{
parentItem_ = parent;
}
[[nodiscard]] void *parentItem() const
{
return parentItem_;
}
void setPos(qreal x, qreal y)
{
pos_ = QPointF(x, y);
}
void setPos(const QPointF &p)
{
pos_ = p;
}
[[nodiscard]] QPointF pos() const
{
return pos_;
}
[[nodiscard]] QPointF scenePos() const
{
if (parentItem_) {
return pos_;
}
return pos_;
}
void setZValue(qreal z)
{
zValue_ = z;
}
[[nodiscard]] qreal zValue() const
{
return zValue_;
}
void setVisible(bool v)
{
visible_ = v;
}
[[nodiscard]] bool isVisible() const
{
return visible_;
}
};
/**
* @brief Mock command zone for testing counter coordination.
*
* Simulates CommandZone state management without QGraphicsScene.
* Supports all 4 zone types: Primary, Partner, Companion, Background.
*/
class MockCommandZone
{
CommandZoneType zoneType_;
ZoneVisibility visibility_;
bool visible_;
QPointF pos_;
qreal height_;
public:
explicit MockCommandZone(CommandZoneType zoneType, qreal height = 100.0)
: zoneType_(zoneType),
visibility_(zoneType == CommandZoneType::Primary ? ZoneVisibility::Expanded : ZoneVisibility::Collapsed),
visible_(true), pos_(0, 0), height_(height)
{
}
[[nodiscard]] bool isPrimary() const
{
return zoneType_ == CommandZoneType::Primary;
}
[[nodiscard]] CommandZoneType getZoneType() const
{
return zoneType_;
}
[[nodiscard]] bool isExpanded() const
{
return visibility_ != ZoneVisibility::Collapsed;
}
void setExpanded(bool e)
{
visibility_ = e ? ZoneVisibility::Expanded : ZoneVisibility::Collapsed;
}
[[nodiscard]] bool isMinimized() const
{
return visibility_ == ZoneVisibility::Minimized;
}
void setMinimized(bool m)
{
if (visibility_ != ZoneVisibility::Collapsed) {
visibility_ = m ? ZoneVisibility::Minimized : ZoneVisibility::Expanded;
}
}
[[nodiscard]] bool isVisible() const
{
return visible_;
}
void setVisible(bool v)
{
visible_ = v;
}
void setPos(const QPointF &p)
{
pos_ = p;
}
[[nodiscard]] QPointF pos() const
{
return pos_;
}
[[nodiscard]] qreal currentHeight() const
{
if (visibility_ == ZoneVisibility::Collapsed) {
return 0;
}
return visibility_ == ZoneVisibility::Minimized ? (height_ * 0.25) : height_;
}
};
/**
* @brief Test harness for counter-zone integration logic.
*
* Extracts the counter positioning and visibility logic from
* PlayerGraphicsItem::rearrangeCounters for isolated testing.
*
* @warning SYNC REQUIRED: This harness duplicates logic from
* player_graphics_item.cpp. Update if implementation changes.
*/
class CounterZoneIntegrationHarness
{
public:
MockCommandZone commandZone{CommandZoneType::Primary, 100.0};
MockCommandZone partnerZone{CommandZoneType::Partner, 100.0};
MockCommandZone companionZone{CommandZoneType::Companion, 100.0};
MockCommandZone backgroundZone{CommandZoneType::Background, 100.0};
MockTaxCounter commanderTaxCounter{nullptr};
MockTaxCounter partnerTaxCounter{nullptr};
static constexpr qreal TAX_COUNTERS_Z = 200000002.0;
CounterZoneIntegrationHarness()
{
}
void setupCounterParenting()
{
commanderTaxCounter.setParentItem(static_cast<void *>(&commandZone));
partnerTaxCounter.setParentItem(static_cast<void *>(&partnerZone));
}
void rearrangeCounters()
{
bool commandZonesVisible = commandZone.isVisible();
if (commandZonesVisible) {
commanderTaxCounter.setPos(2, 2);
commanderTaxCounter.setZValue(TAX_COUNTERS_Z);
commanderTaxCounter.setVisible(true);
} else {
commanderTaxCounter.setVisible(false);
}
if (commandZonesVisible && partnerZone.isExpanded()) {
partnerTaxCounter.setPos(2, 2);
partnerTaxCounter.setZValue(TAX_COUNTERS_Z);
partnerTaxCounter.setVisible(true);
} else {
partnerTaxCounter.setVisible(false);
}
}
};
class CommandZoneIntegrationTest : public ::testing::Test
{
protected:
CounterZoneIntegrationHarness *harness;
void SetUp() override
{
harness = new CounterZoneIntegrationHarness();
harness->setupCounterParenting();
}
void TearDown() override
{
delete harness;
}
};
// ============ Counter Parenting Tests ============
TEST_F(CommandZoneIntegrationTest, CommanderTaxCounter_ParentedToCommandZone)
{
EXPECT_NE(nullptr, harness->commanderTaxCounter.parentItem()) << "Commander tax counter should have a parent";
EXPECT_EQ(static_cast<void *>(&harness->commandZone), harness->commanderTaxCounter.parentItem())
<< "Commander tax counter should be parented to command zone";
}
TEST_F(CommandZoneIntegrationTest, PartnerTaxCounter_ParentedToPartnerZone)
{
EXPECT_NE(nullptr, harness->partnerTaxCounter.parentItem()) << "Partner tax counter should have a parent";
EXPECT_EQ(static_cast<void *>(&harness->partnerZone), harness->partnerTaxCounter.parentItem())
<< "Partner tax counter should be parented to partner zone";
}
// ============ Counter Positioning Tests ============
TEST_F(CommandZoneIntegrationTest, Counters_PositionedAtTopLeft)
{
harness->commandZone.setVisible(true);
harness->partnerZone.setExpanded(true);
harness->rearrangeCounters();
EXPECT_EQ(QPointF(2, 2), harness->commanderTaxCounter.pos())
<< "Commander tax counter should be at (2, 2) in command zone";
EXPECT_EQ(QPointF(2, 2), harness->partnerTaxCounter.pos())
<< "Partner tax counter should be at (2, 2) in partner zone";
}
TEST_F(CommandZoneIntegrationTest, Counters_CorrectZValue)
{
harness->commandZone.setVisible(true);
harness->partnerZone.setExpanded(true);
harness->rearrangeCounters();
EXPECT_EQ(CounterZoneIntegrationHarness::TAX_COUNTERS_Z, harness->commanderTaxCounter.zValue())
<< "Commander tax counter should have TAX_COUNTERS z-value";
EXPECT_EQ(CounterZoneIntegrationHarness::TAX_COUNTERS_Z, harness->partnerTaxCounter.zValue())
<< "Partner tax counter should have TAX_COUNTERS z-value";
}
// ============ Zone Type Tests ============
TEST_F(CommandZoneIntegrationTest, ZoneTypes_CorrectlyIdentified)
{
EXPECT_EQ(CommandZoneType::Primary, harness->commandZone.getZoneType());
EXPECT_EQ(CommandZoneType::Partner, harness->partnerZone.getZoneType());
EXPECT_EQ(CommandZoneType::Companion, harness->companionZone.getZoneType());
EXPECT_EQ(CommandZoneType::Background, harness->backgroundZone.getZoneType());
}
TEST_F(CommandZoneIntegrationTest, ZoneTypes_IsPrimaryCorrect)
{
EXPECT_TRUE(harness->commandZone.isPrimary());
EXPECT_FALSE(harness->partnerZone.isPrimary());
EXPECT_FALSE(harness->companionZone.isPrimary());
EXPECT_FALSE(harness->backgroundZone.isPrimary());
}
// ============ Zone Coordination Tests ============
TEST_F(CommandZoneIntegrationTest, ZoneInitialState)
{
EXPECT_TRUE(harness->commandZone.isExpanded()) << "Command zone should start expanded";
EXPECT_FALSE(harness->partnerZone.isExpanded()) << "Partner zone should start collapsed";
EXPECT_FALSE(harness->companionZone.isExpanded()) << "Companion zone should start collapsed";
EXPECT_FALSE(harness->backgroundZone.isExpanded()) << "Background zone should start collapsed";
}
TEST_F(CommandZoneIntegrationTest, PartnerZone_CollapsedHeight)
{
EXPECT_FALSE(harness->partnerZone.isExpanded());
EXPECT_EQ(0, harness->partnerZone.currentHeight()) << "Collapsed partner zone should have 0 height";
}
TEST_F(CommandZoneIntegrationTest, PartnerZone_ExpandedHeight)
{
harness->partnerZone.setExpanded(true);
EXPECT_EQ(100.0, harness->partnerZone.currentHeight()) << "Expanded partner zone should have full height";
}
TEST_F(CommandZoneIntegrationTest, CompanionZone_ExpandedHeight)
{
harness->companionZone.setExpanded(true);
EXPECT_EQ(100.0, harness->companionZone.currentHeight()) << "Expanded companion zone should have full height";
}
TEST_F(CommandZoneIntegrationTest, BackgroundZone_ExpandedHeight)
{
harness->backgroundZone.setExpanded(true);
EXPECT_EQ(100.0, harness->backgroundZone.currentHeight()) << "Expanded background zone should have full height";
}
TEST_F(CommandZoneIntegrationTest, Zone_MinimizedHeight)
{
harness->commandZone.setMinimized(true);
EXPECT_EQ(25.0, harness->commandZone.currentHeight()) << "Minimized zone should have 25% height";
}
// ============ Counter Visibility During Transitions ============
TEST_F(CommandZoneIntegrationTest, AllCountersVisible_WhenZonesExpanded)
{
harness->commandZone.setVisible(true);
harness->partnerZone.setExpanded(true);
harness->rearrangeCounters();
EXPECT_TRUE(harness->commanderTaxCounter.isVisible()) << "Commander tax counter should be visible";
EXPECT_TRUE(harness->partnerTaxCounter.isVisible()) << "Partner tax counter should be visible";
}
TEST_F(CommandZoneIntegrationTest, PartnerCounter_HiddenWhenCollapsed)
{
harness->commandZone.setVisible(true);
harness->partnerZone.setExpanded(false);
harness->rearrangeCounters();
EXPECT_TRUE(harness->commanderTaxCounter.isVisible()) << "Commander tax counter should remain visible";
EXPECT_FALSE(harness->partnerTaxCounter.isVisible()) << "Partner tax counter should be hidden when collapsed";
}
TEST_F(CommandZoneIntegrationTest, AllCountersHidden_WhenZonesNotVisible)
{
harness->commandZone.setVisible(false);
harness->rearrangeCounters();
EXPECT_FALSE(harness->commanderTaxCounter.isVisible())
<< "Commander tax counter should be hidden when zones not visible";
EXPECT_FALSE(harness->partnerTaxCounter.isVisible())
<< "Partner tax counter should be hidden when zones not visible";
}
TEST_F(CommandZoneIntegrationTest, CommanderCounter_VisibleWhenMinimized)
{
harness->commandZone.setVisible(true);
harness->commandZone.setMinimized(true);
harness->rearrangeCounters();
EXPECT_TRUE(harness->commanderTaxCounter.isVisible())
<< "Commander tax counter should remain visible when zone minimized";
}
TEST_F(CommandZoneIntegrationTest, VisibilityUpdates_DuringToggleCycle)
{
harness->commandZone.setVisible(true);
harness->partnerZone.setExpanded(false);
harness->rearrangeCounters();
EXPECT_FALSE(harness->partnerTaxCounter.isVisible()) << "Partner counter hidden when collapsed";
harness->partnerZone.setExpanded(true);
harness->rearrangeCounters();
EXPECT_TRUE(harness->partnerTaxCounter.isVisible()) << "Partner counter visible when expanded";
harness->partnerZone.setExpanded(false);
harness->rearrangeCounters();
EXPECT_FALSE(harness->partnerTaxCounter.isVisible()) << "Partner counter hidden after collapse";
}
// ============ Companion/Background Zone Tests (No Tax Counters) ============
TEST_F(CommandZoneIntegrationTest, CompanionZone_NoTaxCounter)
{
// Companion zone has no tax counter - this test verifies zone behavior independently
harness->companionZone.setExpanded(true);
EXPECT_TRUE(harness->companionZone.isExpanded());
EXPECT_EQ(100.0, harness->companionZone.currentHeight());
}
TEST_F(CommandZoneIntegrationTest, BackgroundZone_NoTaxCounter)
{
// Background zone has no tax counter - this test verifies zone behavior independently
harness->backgroundZone.setExpanded(true);
EXPECT_TRUE(harness->backgroundZone.isExpanded());
EXPECT_EQ(100.0, harness->backgroundZone.currentHeight());
}
// ============ Zone Positioning Tests ============
/**
* @brief Test harness for zone positioning calculation.
*
* Uses a HYBRID architecture matching the actual implementation:
* - Partner Zone: Qt child of Command Zone (uses relative coordinates)
* - Companion/Background Zones: Qt siblings of Command Zone (use absolute coordinates)
*
* This distinction matters because Qt transforms work differently for children vs siblings.
*/
class ZonePositioningHarness
{
public:
MockCommandZone commandZone{CommandZoneType::Primary, 100.0};
MockCommandZone partnerZone{CommandZoneType::Partner, 100.0};
MockCommandZone companionZone{CommandZoneType::Companion, 100.0};
MockCommandZone backgroundZone{CommandZoneType::Background, 100.0};
/**
* @brief Calculates scene position for a zone in the parent chain.
*
* Command zone position is the base. Each subsequent zone is offset
* by the cumulative height of all ancestor zones.
*/
QPointF scenePositionOf(const MockCommandZone &zone) const
{
QPointF base = commandZone.pos();
if (&zone == &commandZone) {
return base;
}
qreal offset = commandZone.currentHeight();
if (&zone == &partnerZone) {
return base + QPointF(0, offset);
}
offset += partnerZone.currentHeight();
if (&zone == &companionZone) {
return base + QPointF(0, offset);
}
offset += companionZone.currentHeight();
if (&zone == &backgroundZone) {
return base + QPointF(0, offset);
}
return base;
}
/**
* @brief Calculates the actual position for a zone based on hybrid architecture.
*
* - Partner Zone: Returns relative position (Qt child of Command Zone)
* - Companion/Background: Returns absolute position (Qt siblings)
*/
QPointF positionFor(const MockCommandZone &zone) const
{
QPointF base = commandZone.pos();
if (&zone == &commandZone) {
return base;
}
if (&zone == &partnerZone) {
return QPointF(0, commandZone.currentHeight()); // Relative (Qt child)
}
// Companion and Background use ABSOLUTE positioning (Qt siblings)
qreal runningY = commandZone.currentHeight() + partnerZone.currentHeight();
if (&zone == &companionZone) {
return QPointF(base.x(), base.y() + runningY);
}
runningY += companionZone.currentHeight();
if (&zone == &backgroundZone) {
return QPointF(base.x(), base.y() + runningY);
}
return QPointF(0, 0);
}
/**
* @brief Calculates total zone height for stack zone positioning.
*
* This matches the visibility-aware logic in PlayerGraphicsItem::totalCommandZoneHeight().
* Command zone and partner use Qt parent-child relationship, so checking command visibility
* implicitly covers partner (Qt hides children when parent is hidden).
* Companion and background are Qt siblings with independent visibility.
*/
qreal totalHeight() const
{
qreal total = 0;
// Command zone and partner: Qt parent-child relationship
if (commandZone.isVisible()) {
total += commandZone.currentHeight();
if (partnerZone.isExpanded()) {
total += partnerZone.currentHeight();
}
}
// Sibling zones: independent visibility
if (companionZone.isVisible()) {
total += companionZone.currentHeight();
}
if (backgroundZone.isVisible()) {
total += backgroundZone.currentHeight();
}
return total;
}
/**
* @brief Legacy total height calculation (non-visibility-aware).
*
* Used for tests that need the original behavior for comparison.
*/
qreal totalHeightIgnoringVisibility() const
{
return commandZone.currentHeight() + partnerZone.currentHeight() + companionZone.currentHeight() +
backgroundZone.currentHeight();
}
};
class ZonePositioningTest : public ::testing::Test
{
protected:
ZonePositioningHarness harness;
};
TEST_F(ZonePositioningTest, AllZonesExpanded_CorrectScenePositions)
{
harness.commandZone.setPos(QPointF(50, 0));
harness.partnerZone.setExpanded(true);
harness.companionZone.setExpanded(true);
harness.backgroundZone.setExpanded(true);
EXPECT_EQ(QPointF(50, 0), harness.scenePositionOf(harness.commandZone));
EXPECT_EQ(QPointF(50, 100), harness.scenePositionOf(harness.partnerZone));
EXPECT_EQ(QPointF(50, 200), harness.scenePositionOf(harness.companionZone));
EXPECT_EQ(QPointF(50, 300), harness.scenePositionOf(harness.backgroundZone));
}
TEST_F(ZonePositioningTest, AllZonesExpanded_CorrectPositions)
{
harness.commandZone.setPos(QPointF(0, 0));
harness.partnerZone.setExpanded(true);
harness.companionZone.setExpanded(true);
harness.backgroundZone.setExpanded(true);
// Partner: relative position (Qt child of Command)
EXPECT_EQ(QPointF(0, 100), harness.positionFor(harness.partnerZone));
// Companion: absolute position at (0, 200) - base + command height + partner height
EXPECT_EQ(QPointF(0, 200), harness.positionFor(harness.companionZone));
// Background: absolute position at (0, 300) - base + command + partner + companion
EXPECT_EQ(QPointF(0, 300), harness.positionFor(harness.backgroundZone));
}
TEST_F(ZonePositioningTest, PartnerCollapsed_CompanionAtCommandBottom)
{
harness.commandZone.setPos(QPointF(50, 0));
harness.partnerZone.setExpanded(false);
harness.companionZone.setExpanded(true);
harness.backgroundZone.setExpanded(true);
EXPECT_EQ(0, harness.partnerZone.currentHeight()) << "Collapsed partner should have 0 height";
EXPECT_EQ(QPointF(50, 100), harness.scenePositionOf(harness.companionZone))
<< "Companion should be at command bottom when partner is collapsed";
EXPECT_EQ(QPointF(50, 200), harness.scenePositionOf(harness.backgroundZone));
}
TEST_F(ZonePositioningTest, PartnerCollapsed_CompanionPosition)
{
harness.commandZone.setPos(QPointF(0, 0));
harness.partnerZone.setExpanded(false);
harness.companionZone.setExpanded(true);
// Companion: absolute position = base + command height + partner height (0)
EXPECT_EQ(QPointF(0, 100), harness.positionFor(harness.companionZone))
<< "Companion at (0, 100) when partner collapsed (0 + 100 + 0)";
}
TEST_F(ZonePositioningTest, AllCollapsed_TotalHeightIsCommandOnly)
{
harness.partnerZone.setExpanded(false);
harness.companionZone.setExpanded(false);
harness.backgroundZone.setExpanded(false);
EXPECT_EQ(100, harness.totalHeight()) << "Only command zone height when others collapsed";
}
TEST_F(ZonePositioningTest, AllExpanded_TotalHeightIs400)
{
harness.partnerZone.setExpanded(true);
harness.companionZone.setExpanded(true);
harness.backgroundZone.setExpanded(true);
EXPECT_EQ(400, harness.totalHeight()) << "4 zones × 100 = 400";
}
TEST_F(ZonePositioningTest, MixedStates_CorrectTotalHeight)
{
harness.partnerZone.setExpanded(true);
harness.partnerZone.setMinimized(true);
harness.companionZone.setExpanded(true);
harness.backgroundZone.setExpanded(false);
EXPECT_EQ(100 + 25 + 100 + 0, harness.totalHeight())
<< "Command (100) + Partner minimized (25) + Companion (100) + Background collapsed (0)";
}
// ============ Position Verification After State Changes ============
TEST_F(ZonePositioningTest, MinimizeCommand_CompanionMovesUp)
{
harness.commandZone.setPos(QPointF(50, 0));
harness.partnerZone.setExpanded(true);
harness.companionZone.setExpanded(true);
EXPECT_EQ(QPointF(50, 200), harness.scenePositionOf(harness.companionZone));
harness.commandZone.setMinimized(true);
EXPECT_EQ(QPointF(50, 125), harness.scenePositionOf(harness.companionZone))
<< "Companion should move up when command zone minimizes (25 + 100 = 125)";
}
TEST_F(ZonePositioningTest, CollapsePartner_CompanionMovesUp)
{
harness.commandZone.setPos(QPointF(50, 0));
harness.partnerZone.setExpanded(true);
harness.companionZone.setExpanded(true);
EXPECT_EQ(QPointF(50, 200), harness.scenePositionOf(harness.companionZone));
harness.partnerZone.setExpanded(false);
EXPECT_EQ(QPointF(50, 100), harness.scenePositionOf(harness.companionZone))
<< "Companion should move up when partner collapses (100 + 0 = 100)";
}
// ============ Visibility-Aware totalHeight Tests ============
TEST_F(ZonePositioningTest, CommandZoneHidden_HeightExcludesCommandAndPartner)
{
harness.commandZone.setVisible(false);
harness.partnerZone.setExpanded(true);
harness.companionZone.setExpanded(true);
harness.backgroundZone.setExpanded(true);
EXPECT_EQ(200, harness.totalHeight()) << "Only companion (100) + background (100) when command zone hidden";
}
TEST_F(ZonePositioningTest, CommandZoneHidden_PartnerExpandedIgnored)
{
harness.commandZone.setVisible(false);
harness.partnerZone.setExpanded(true);
EXPECT_EQ(0, harness.totalHeight())
<< "Partner height ignored when command zone is Qt-hidden (Qt cascades visibility to children)";
}
TEST_F(ZonePositioningTest, OnlyCompanionVisible_HeightIsCompanionOnly)
{
harness.commandZone.setVisible(false);
harness.companionZone.setVisible(true);
harness.companionZone.setExpanded(true);
harness.backgroundZone.setVisible(false);
EXPECT_EQ(100, harness.totalHeight()) << "Only companion height (100) when it's the only visible zone";
}
TEST_F(ZonePositioningTest, OnlyBackgroundVisible_HeightIsBackgroundOnly)
{
harness.commandZone.setVisible(false);
harness.companionZone.setVisible(false);
harness.backgroundZone.setVisible(true);
harness.backgroundZone.setExpanded(true);
EXPECT_EQ(100, harness.totalHeight()) << "Only background height (100) when it's the only visible zone";
}
TEST_F(ZonePositioningTest, CompanionAndBackgroundVisible_HeightIsBoth)
{
harness.commandZone.setVisible(false);
harness.companionZone.setVisible(true);
harness.companionZone.setExpanded(true);
harness.backgroundZone.setVisible(true);
harness.backgroundZone.setExpanded(true);
EXPECT_EQ(200, harness.totalHeight()) << "Companion (100) + Background (100) when both visible, command hidden";
}
TEST_F(ZonePositioningTest, AllZonesVisible_HeightIncludesAll)
{
harness.commandZone.setVisible(true);
harness.partnerZone.setExpanded(true);
harness.companionZone.setVisible(true);
harness.companionZone.setExpanded(true);
harness.backgroundZone.setVisible(true);
harness.backgroundZone.setExpanded(true);
EXPECT_EQ(400, harness.totalHeight()) << "All zones visible and expanded: 4 × 100 = 400";
}
TEST_F(ZonePositioningTest, CompanionCollapsed_NoHeightContribution)
{
harness.commandZone.setVisible(false);
harness.companionZone.setVisible(true);
harness.companionZone.setExpanded(false);
harness.backgroundZone.setVisible(true);
harness.backgroundZone.setExpanded(true);
EXPECT_EQ(100, harness.totalHeight()) << "Only background (100) contributes when companion is collapsed";
}
TEST_F(ZonePositioningTest, CompanionMinimized_ReducedHeightContribution)
{
harness.commandZone.setVisible(false);
harness.companionZone.setVisible(true);
harness.companionZone.setExpanded(true);
harness.companionZone.setMinimized(true);
harness.backgroundZone.setVisible(false);
EXPECT_EQ(25, harness.totalHeight()) << "Minimized companion contributes 25% height (25)";
}
// ============ Toggle Visibility Tests ============
/**
* @brief Test harness for toggle visibility logic.
*
* Simulates the toggle visibility rules from positionCommandZones()
* to test behavior when intermediate zones are disabled.
*
* @warning SYNC REQUIRED: This harness duplicates logic from
* player_graphics_item.cpp. Update if implementation changes.
*/
class ToggleVisibilityHarness
{
public:
MockCommandZone commandZone{CommandZoneType::Primary, 100.0};
MockCommandZone partnerZone{CommandZoneType::Partner, 100.0};
MockCommandZone companionZone{CommandZoneType::Companion, 100.0};
MockCommandZone backgroundZone{CommandZoneType::Background, 100.0};
bool partnerToggleVisible = false;
bool companionToggleVisible = false;
bool backgroundToggleVisible = false;
/**
* @brief Calculates toggle visibility using the same logic as positionCommandZones().
*/
void updateToggleVisibility()
{
bool commandZoneEnabled = commandZone.isVisible();
bool companionZoneEnabled = companionZone.isVisible();
bool backgroundZoneEnabled = backgroundZone.isVisible();
if (!commandZoneEnabled && !companionZoneEnabled && !backgroundZoneEnabled) {
partnerToggleVisible = false;
companionToggleVisible = false;
backgroundToggleVisible = false;
return;
}
bool partnerCollapsed = !partnerZone.isExpanded();
bool companionCollapsed = !companionZone.isExpanded();
bool backgroundCollapsed = !backgroundZone.isExpanded();
bool partnerIsOpen = !partnerCollapsed;
bool companionIsOpen = !companionCollapsed;
bool backgroundIsOpen = !backgroundCollapsed;
bool noDeeperZonesOpen = companionCollapsed && backgroundCollapsed;
partnerToggleVisible = commandZoneEnabled && (partnerIsOpen || noDeeperZonesOpen);
companionToggleVisible = companionZoneEnabled && (companionIsOpen || (partnerIsOpen && backgroundCollapsed) ||
(!commandZoneEnabled && noDeeperZonesOpen));
backgroundToggleVisible =
backgroundZoneEnabled && (backgroundIsOpen || companionIsOpen || (partnerIsOpen && !companionZoneEnabled) ||
(!commandZoneEnabled && !companionZoneEnabled));
}
};
class ToggleVisibilityTest : public ::testing::Test
{
protected:
ToggleVisibilityHarness harness;
void SetUp() override
{
harness.commandZone.setVisible(true);
harness.partnerZone.setVisible(true);
harness.companionZone.setVisible(true);
harness.backgroundZone.setVisible(true);
}
};
// Original bug: Companion disabled, command zone enabled
TEST_F(ToggleVisibilityTest, CompanionDisabled_PartnerOpens_BackgroundToggleAppears)
{
harness.companionZone.setVisible(false);
harness.updateToggleVisibility();
EXPECT_TRUE(harness.partnerToggleVisible) << "Partner toggle at game start";
harness.partnerZone.setExpanded(true);
harness.updateToggleVisibility();
EXPECT_TRUE(harness.backgroundToggleVisible)
<< "Background toggle should appear when partner opens (skipping disabled companion)";
}
// Bug 2: Command zone disabled, companion enabled
TEST_F(ToggleVisibilityTest, CommandZoneDisabled_CompanionEnabled_CompanionIsEntryPoint)
{
harness.commandZone.setVisible(false);
harness.updateToggleVisibility();
EXPECT_FALSE(harness.partnerToggleVisible) << "Partner toggle hidden when command zone disabled";
EXPECT_TRUE(harness.companionToggleVisible) << "Companion toggle should be entry point when command zone disabled";
}
// Edge case: Only background enabled
TEST_F(ToggleVisibilityTest, OnlyBackgroundEnabled_BackgroundIsEntryPoint)
{
harness.commandZone.setVisible(false);
harness.companionZone.setVisible(false);
harness.updateToggleVisibility();
EXPECT_FALSE(harness.partnerToggleVisible);
EXPECT_FALSE(harness.companionToggleVisible);
EXPECT_TRUE(harness.backgroundToggleVisible)
<< "Background toggle should be entry point when it's the only zone enabled";
}
// Regression: All zones enabled, normal cascade
TEST_F(ToggleVisibilityTest, AllEnabled_NormalCascade)
{
harness.updateToggleVisibility();
EXPECT_TRUE(harness.partnerToggleVisible) << "Partner toggle at game start";
EXPECT_FALSE(harness.companionToggleVisible) << "Companion toggle hidden at start";
EXPECT_FALSE(harness.backgroundToggleVisible) << "Background toggle hidden at start";
harness.partnerZone.setExpanded(true);
harness.updateToggleVisibility();
EXPECT_TRUE(harness.companionToggleVisible) << "Companion toggle after partner opens";
EXPECT_FALSE(harness.backgroundToggleVisible) << "Background still hidden";
harness.companionZone.setExpanded(true);
harness.updateToggleVisibility();
EXPECT_TRUE(harness.backgroundToggleVisible) << "Background toggle after companion opens";
}
// Edge case: Partner opens but all deeper zones disabled (dead-end cascade)
TEST_F(ToggleVisibilityTest, AllDeeperZonesDisabled_PartnerOpens_NoFurtherToggle)
{
harness.companionZone.setVisible(false);
harness.backgroundZone.setVisible(false);
harness.partnerZone.setExpanded(true);
harness.updateToggleVisibility();
EXPECT_TRUE(harness.partnerToggleVisible) << "Partner toggle visible (open)";
EXPECT_FALSE(harness.companionToggleVisible) << "Companion disabled";
EXPECT_FALSE(harness.backgroundToggleVisible) << "Background disabled";
}
// Verify toggle stays visible when zone is open
TEST_F(ToggleVisibilityTest, OpenZone_ToggleRemainsVisible)
{
harness.partnerZone.setExpanded(true);
harness.companionZone.setExpanded(true);
harness.backgroundZone.setExpanded(true);
harness.updateToggleVisibility();
EXPECT_TRUE(harness.partnerToggleVisible) << "Partner toggle visible when open";
EXPECT_TRUE(harness.companionToggleVisible) << "Companion toggle visible when open";
EXPECT_TRUE(harness.backgroundToggleVisible) << "Background toggle visible when open";
}
int main(int argc, char **argv)
{
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}