mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-07-07 05:53:59 -07:00
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
This commit is contained in:
parent
78db49a9b3
commit
6e83d64622
12 changed files with 1570 additions and 8 deletions
|
|
@ -106,6 +106,7 @@ set(cockatrice_SOURCES
|
|||
src/game/zones/card_zone.cpp
|
||||
src/game/zones/command_zone_state.cpp
|
||||
src/game/zones/hand_zone.cpp
|
||||
src/game/zones/command_zone.cpp
|
||||
src/game/zones/logic/command_zone_logic.cpp
|
||||
src/game/zones/logic/card_zone_logic.cpp
|
||||
src/game/zones/logic/hand_zone_logic.cpp
|
||||
|
|
|
|||
374
cockatrice/src/game/zones/command_zone.cpp
Normal file
374
cockatrice/src/game/zones/command_zone.cpp
Normal file
|
|
@ -0,0 +1,374 @@
|
|||
#include "command_zone.h"
|
||||
|
||||
// @see CommandZoneState for the extracted visibility/height state machine
|
||||
|
||||
#include "../../client/settings/cache_settings.h"
|
||||
#include "../../interface/theme_manager.h"
|
||||
#include "../board/abstract_counter.h"
|
||||
#include "../board/card_drag_item.h"
|
||||
#include "../board/card_item.h"
|
||||
#include "../player/player.h"
|
||||
#include "../player/player_actions.h"
|
||||
#include "../z_values.h"
|
||||
#include "select_zone.h"
|
||||
#include "zone_toggle_button.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QGraphicsRectItem>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QPainter>
|
||||
#include <libcockatrice/common/counter_ids.h>
|
||||
#include <libcockatrice/protocol/pb/command_move_card.pb.h>
|
||||
|
||||
CommandZone::CommandZone(CommandZoneLogic *_logic, int _zoneHeight, CommandZoneType _zoneType, QGraphicsItem *parent)
|
||||
: SelectZone(_logic, parent), m_state(_zoneHeight, _zoneType), toggleButton(nullptr), cardClipContainer(nullptr)
|
||||
{
|
||||
connect(themeManager, &ThemeManager::themeChanged, this, &CommandZone::updateBg);
|
||||
updateBg();
|
||||
setCacheMode(DeviceCoordinateCache);
|
||||
setFlag(QGraphicsItem::ItemClipsChildrenToShape, false);
|
||||
|
||||
cardClipContainer = new QGraphicsRectItem(this);
|
||||
cardClipContainer->setFlag(QGraphicsItem::ItemClipsChildrenToShape, true);
|
||||
cardClipContainer->setPen(Qt::NoPen);
|
||||
cardClipContainer->setBrush(Qt::NoBrush);
|
||||
cardClipContainer->setRect(boundingRect());
|
||||
cardClipContainer->setZValue(ZValues::CARD_BASE);
|
||||
|
||||
disconnect(getLogic(), &CardZoneLogic::cardAdded, this, &CardZone::onCardAdded);
|
||||
connect(getLogic(), &CardZoneLogic::cardAdded, this, [this](CardItem *card) {
|
||||
if (cardClipContainer && card) {
|
||||
card->setParentItem(cardClipContainer);
|
||||
card->update();
|
||||
}
|
||||
});
|
||||
|
||||
if (!isPrimary()) {
|
||||
toggleButton = new ZoneToggleButton(getZoneType(), this);
|
||||
connect(toggleButton, &ZoneToggleButton::clicked, this, &CommandZone::toggleExpanded);
|
||||
repositionToggleButton();
|
||||
}
|
||||
}
|
||||
|
||||
CommandZone::~CommandZone()
|
||||
{
|
||||
disconnect(getLogic(), &CardZoneLogic::cardAdded, this, nullptr);
|
||||
}
|
||||
|
||||
void CommandZone::updateBg()
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
QRectF CommandZone::boundingRect() const
|
||||
{
|
||||
return {0, 0, ZoneSizes::COMMAND_ZONE_WIDTH, currentHeight()};
|
||||
}
|
||||
|
||||
qreal CommandZone::currentHeight() const
|
||||
{
|
||||
return m_state.currentHeight();
|
||||
}
|
||||
|
||||
bool CommandZone::isPrimary() const
|
||||
{
|
||||
return m_state.isPrimary();
|
||||
}
|
||||
|
||||
bool CommandZone::isMinimized() const
|
||||
{
|
||||
return m_state.isMinimized();
|
||||
}
|
||||
|
||||
bool CommandZone::isExpanded() const
|
||||
{
|
||||
return m_state.isExpanded();
|
||||
}
|
||||
|
||||
bool CommandZone::isCollapsed() const
|
||||
{
|
||||
return m_state.isCollapsed();
|
||||
}
|
||||
|
||||
void CommandZone::toggleExpanded()
|
||||
{
|
||||
applyStateChange(m_state.tryToggleExpanded(shouldPreventCollapse()));
|
||||
}
|
||||
|
||||
void CommandZone::toggleMinimized()
|
||||
{
|
||||
applyStateChange(m_state.tryToggleMinimized(), true);
|
||||
}
|
||||
|
||||
void CommandZone::applyStateChange(const StateChangeResult &result, bool updateTogglePosition)
|
||||
{
|
||||
if (!result.geometryChanged) {
|
||||
return;
|
||||
}
|
||||
|
||||
prepareGeometryChange();
|
||||
if (cardClipContainer) {
|
||||
cardClipContainer->setRect(boundingRect());
|
||||
}
|
||||
if (toggleButton) {
|
||||
toggleButton->setExpanded(m_state.isExpanded());
|
||||
}
|
||||
reorganizeCards();
|
||||
if (updateTogglePosition) {
|
||||
repositionToggleButton();
|
||||
}
|
||||
update();
|
||||
|
||||
if (result.shouldEmitMinimized) {
|
||||
emit minimizedChanged(m_state.isMinimized());
|
||||
}
|
||||
if (result.shouldEmitExpanded) {
|
||||
emit expandedChanged(m_state.isExpanded());
|
||||
}
|
||||
}
|
||||
|
||||
void CommandZone::paint(QPainter *painter,
|
||||
[[maybe_unused]] const QStyleOptionGraphicsItem *option,
|
||||
[[maybe_unused]] QWidget *widget)
|
||||
{
|
||||
if (isCollapsed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QBrush brush = themeManager->getExtraBgBrush(ThemeManager::Command, getLogic()->getPlayer()->getZoneId());
|
||||
|
||||
QPointF scenePos = mapToScene(QPointF(0, 0));
|
||||
painter->setBrushOrigin(-scenePos);
|
||||
|
||||
painter->fillRect(boundingRect(), brush);
|
||||
}
|
||||
|
||||
void CommandZone::handleDropEvent(const QList<CardDragItem *> &dragItems,
|
||||
CardZoneLogic *startZone,
|
||||
const QPoint &dropPoint)
|
||||
{
|
||||
if (dragItems.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (startZone == nullptr || startZone->getPlayer() == nullptr) {
|
||||
qCWarning(CommandZoneLog) << "handleDropEvent: Invalid startZone or player";
|
||||
return;
|
||||
}
|
||||
|
||||
// Primary and Partner zones hold exactly one card; Companion and Background support multiple
|
||||
if (!supportsMultipleCards(getZoneType()) && !getLogic()->getCards().isEmpty()) {
|
||||
qCDebug(CommandZoneLog) << "handleDropEvent: Single-card zone already occupied, ignoring drop";
|
||||
return;
|
||||
}
|
||||
|
||||
Command_MoveCard cmd;
|
||||
cmd.set_start_player_id(startZone->getPlayer()->getPlayerInfo()->getId());
|
||||
cmd.set_start_zone(startZone->getName().toStdString());
|
||||
cmd.set_target_player_id(getLogic()->getPlayer()->getPlayerInfo()->getId());
|
||||
cmd.set_target_zone(getLogic()->getName().toStdString());
|
||||
|
||||
// Calculate insertion index from drop position for multi-card zones
|
||||
int index = 0;
|
||||
const auto &cards = getLogic()->getCards();
|
||||
if (!cards.isEmpty()) {
|
||||
const auto cardCount = static_cast<int>(cards.size());
|
||||
const qreal cardHeight = cards.at(0)->boundingRect().height();
|
||||
|
||||
index = qRound(divideCardSpaceInZone(dropPoint.y(), cardCount, boundingRect().height(), cardHeight, true));
|
||||
index = qBound(0, index, cardCount);
|
||||
}
|
||||
|
||||
cmd.set_x(index);
|
||||
cmd.set_y(0);
|
||||
|
||||
// Add all dragged cards to the move command
|
||||
for (const CardDragItem *item : dragItems) {
|
||||
if (item != nullptr) {
|
||||
cmd.mutable_cards_to_move()->add_card()->set_card_id(item->getId());
|
||||
}
|
||||
}
|
||||
|
||||
getLogic()->getPlayer()->getPlayerActions()->sendGameCommand(cmd);
|
||||
}
|
||||
|
||||
void CommandZone::reorganizeCards()
|
||||
{
|
||||
const auto &cards = getLogic()->getCards();
|
||||
|
||||
if (cardClipContainer) {
|
||||
cardClipContainer->setRect(boundingRect());
|
||||
}
|
||||
|
||||
// Hide all cards when collapsed
|
||||
if (isCollapsed()) {
|
||||
for (CardItem *card : cards) {
|
||||
if (card != nullptr) {
|
||||
card->setVisible(false);
|
||||
}
|
||||
}
|
||||
update();
|
||||
return;
|
||||
}
|
||||
|
||||
if (cards.isEmpty()) {
|
||||
update();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto cardCount = static_cast<int>(cards.size());
|
||||
const qreal totalWidth = boundingRect().width();
|
||||
const qreal totalHeight = boundingRect().height();
|
||||
const qreal cardWidth = cards.at(0)->boundingRect().width();
|
||||
const qreal cardHeight = cards.at(0)->boundingRect().height();
|
||||
|
||||
// Zigzag horizontal positioning (StackZone pattern)
|
||||
// Single card stays centered for visual consistency with Primary/Partner zones
|
||||
const qreal xMargin = 5;
|
||||
const qreal x1 = xMargin;
|
||||
const qreal x2 = totalWidth - xMargin - cardWidth;
|
||||
|
||||
for (int i = 0; i < cardCount; i++) {
|
||||
CardItem *card = cards.at(i);
|
||||
if (card == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Horizontal: center single card, zigzag for multiple
|
||||
qreal x = (cardCount == 1) ? (totalWidth - cardWidth) / 2.0 : ((i % 2) ? x2 : x1);
|
||||
|
||||
// Vertical: use divideCardSpaceInZone for proper stacking with overlap compression.
|
||||
// divideCardSpaceInZone can return negative Y when totalHeight < cardHeight.
|
||||
// Clamp to 0 so card tops remain visible when clipped by cardClipContainer.
|
||||
const qreal y = qMax(0.0, divideCardSpaceInZone(i, cardCount, totalHeight, cardHeight));
|
||||
|
||||
card->setVisible(true);
|
||||
card->setPos(x, y);
|
||||
card->setRealZValue(ZValues::CARD_BASE + i);
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
void CommandZone::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
toggleMinimized();
|
||||
event->accept();
|
||||
} else {
|
||||
SelectZone::mouseDoubleClickEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
void CommandZone::repositionToggleButton()
|
||||
{
|
||||
if (toggleButton) {
|
||||
QRectF btnRect = toggleButton->boundingRect();
|
||||
qreal btnX = (boundingRect().width() - btnRect.width()) / 2.0;
|
||||
qreal btnY = 0;
|
||||
toggleButton->setPos(btnX, btnY);
|
||||
}
|
||||
}
|
||||
|
||||
void CommandZone::setExpanded(bool _expanded)
|
||||
{
|
||||
applyStateChange(m_state.trySetExpanded(_expanded, shouldPreventCollapse()));
|
||||
}
|
||||
|
||||
bool CommandZone::shouldPreventCollapse() const
|
||||
{
|
||||
if (isPrimary())
|
||||
return false;
|
||||
|
||||
if (!getLogic()->getCards().isEmpty())
|
||||
return true;
|
||||
|
||||
if (getZoneType() == CommandZoneType::Partner) {
|
||||
Player *player = getLogic()->getPlayer();
|
||||
if (player) {
|
||||
AbstractCounter *partnerTax = player->getCounters().value(CounterIds::PartnerTax, nullptr);
|
||||
if (partnerTax && partnerTax->getValue() > 0)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check Qt children (Partner has toggle button as child, and Partner is nested under Command)
|
||||
for (QGraphicsItem *child : childItems()) {
|
||||
if (auto *childZone = dynamic_cast<CommandZone *>(child)) {
|
||||
if (childZone->shouldPreventCollapse())
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check logical children (for sibling zones: Companion is logical child of Partner,
|
||||
// Background is logical child of Companion)
|
||||
for (CommandZone *childZone : logicalChildZones) {
|
||||
if (childZone != nullptr && childZone->shouldPreventCollapse())
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CommandZone::tryAutoMinimize()
|
||||
{
|
||||
// Check Qt children first (Partner is nested under Command)
|
||||
for (QGraphicsItem *child : childItems()) {
|
||||
if (auto *childZone = dynamic_cast<CommandZone *>(child)) {
|
||||
if (childZone->tryAutoMinimize())
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check logical children (sibling zones: Companion under Partner, Background under Companion)
|
||||
for (CommandZone *childZone : logicalChildZones) {
|
||||
if (childZone != nullptr && childZone->tryAutoMinimize())
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isExpanded() && !isMinimized() && !isPrimary()) {
|
||||
toggleMinimized();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
CommandZone *CommandZone::getParentCommandZone() const
|
||||
{
|
||||
QGraphicsItem *parent = parentItem();
|
||||
while (parent != nullptr) {
|
||||
if (auto *parentZone = dynamic_cast<CommandZone *>(parent)) {
|
||||
return parentZone;
|
||||
}
|
||||
parent = parent->parentItem();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool CommandZone::tryMinimizeAbove()
|
||||
{
|
||||
// First check logical parent (for sibling zones: Companion, Background)
|
||||
CommandZone *parentZone = logicalParentZone;
|
||||
|
||||
// Fall back to Qt parent chain for Partner zone (still nested under Command)
|
||||
if (parentZone == nullptr) {
|
||||
parentZone = getParentCommandZone();
|
||||
}
|
||||
|
||||
if (parentZone != nullptr && parentZone->isExpanded() && !parentZone->isMinimized()) {
|
||||
parentZone->toggleMinimized();
|
||||
return true;
|
||||
}
|
||||
// If parent can't minimize, try grandparent (recursion bounded by zone depth)
|
||||
if (parentZone != nullptr) {
|
||||
return parentZone->tryMinimizeAbove();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CommandZone::setToggleButtonVisible(bool visible)
|
||||
{
|
||||
if (toggleButton) {
|
||||
toggleButton->setVisible(visible);
|
||||
}
|
||||
}
|
||||
190
cockatrice/src/game/zones/command_zone.h
Normal file
190
cockatrice/src/game/zones/command_zone.h
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
/**
|
||||
* @file command_zone.h
|
||||
* @ingroup GameGraphicsZones
|
||||
* @brief Graphics layer for the command zone, used for Commander format.
|
||||
*/
|
||||
|
||||
#ifndef COCKATRICE_COMMAND_ZONE_H
|
||||
#define COCKATRICE_COMMAND_ZONE_H
|
||||
|
||||
#include "command_zone_state.h"
|
||||
#include "command_zone_types.h"
|
||||
#include "logic/command_zone_logic.h"
|
||||
#include "select_zone.h"
|
||||
|
||||
#include <QLoggingCategory>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(CommandZoneLog, "command_zone");
|
||||
|
||||
class ZoneToggleButton;
|
||||
|
||||
/**
|
||||
* @class CommandZone
|
||||
* @brief Graphics layer for command zones in Commander format games.
|
||||
*
|
||||
* Supports primary (always visible) and collapsible (partner, companion, background)
|
||||
* variants. Primary/Partner hold one card each; Companion/Background support multiple
|
||||
* cards for edge-case mechanics. Single cards display centered; multiple cards use
|
||||
* zigzag horizontal positioning with vertical stacking.
|
||||
*
|
||||
* @see CommandZoneState for visibility/height state machine
|
||||
* @see supportsMultipleCards() in command_zone_types.h
|
||||
*/
|
||||
class CommandZone : public SelectZone
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
CommandZoneState m_state; ///< Pure state machine for visibility/height
|
||||
ZoneToggleButton *toggleButton; ///< Only created for collapsible zones
|
||||
QGraphicsRectItem *cardClipContainer; ///< Clips cards to zone bounds when minimized
|
||||
|
||||
/**
|
||||
* @brief Logical parent for Qt-sibling zones (Companion, Background).
|
||||
*
|
||||
* INVARIANT: A zone has EITHER a Qt parent CommandZone (Partner) OR this
|
||||
* logical parent, never both. tryMinimizeAbove() relies on this exclusivity.
|
||||
*/
|
||||
CommandZone *logicalParentZone = nullptr;
|
||||
|
||||
/**
|
||||
* @brief Logical child zones for sibling architecture.
|
||||
*
|
||||
* Tracks zones that are logically children but Qt siblings. Used by
|
||||
* shouldPreventCollapse() and tryAutoMinimize() to traverse the hierarchy.
|
||||
*/
|
||||
QList<CommandZone *> logicalChildZones;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a CommandZone graphics item.
|
||||
* @param _logic Logic layer managing card data
|
||||
* @param _zoneHeight Zone height in pixels
|
||||
* @param _zoneType Type of command zone (Primary, Partner, Companion, Background)
|
||||
* @param parent Parent graphics item
|
||||
*/
|
||||
CommandZone(CommandZoneLogic *_logic, int _zoneHeight, CommandZoneType _zoneType, QGraphicsItem *parent);
|
||||
~CommandZone() override;
|
||||
|
||||
/**
|
||||
* @brief Handles card drops, calculating insertion position from drop point.
|
||||
* @param dragItems Cards being dragged
|
||||
* @param startZone Source zone
|
||||
* @param dropPoint Drop position in local coordinates
|
||||
*/
|
||||
void
|
||||
handleDropEvent(const QList<CardDragItem *> &dragItems, CardZoneLogic *startZone, const QPoint &dropPoint) override;
|
||||
|
||||
[[nodiscard]] QRectF boundingRect() const override;
|
||||
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
|
||||
void reorganizeCards() override;
|
||||
|
||||
/**
|
||||
* @brief Sets expanded/collapsed state of a collapsible zone.
|
||||
*
|
||||
* When collapsed, background and cards are hidden but toggle button remains.
|
||||
* When expanded, zone renders normally with centered cards.
|
||||
* No-op for Primary zone.
|
||||
*
|
||||
* @param expanded True to show zone content, false to collapse
|
||||
*/
|
||||
void setExpanded(bool expanded);
|
||||
|
||||
void toggleMinimized();
|
||||
[[nodiscard]] bool isMinimized() const;
|
||||
[[nodiscard]] bool isExpanded() const;
|
||||
[[nodiscard]] bool isCollapsed() const;
|
||||
[[nodiscard]] bool isPrimary() const;
|
||||
[[nodiscard]] CommandZoneType getZoneType() const
|
||||
{
|
||||
return m_state.getZoneType();
|
||||
}
|
||||
[[nodiscard]] qreal currentHeight() const;
|
||||
|
||||
/**
|
||||
* @brief Sets the logical parent zone for sibling architecture.
|
||||
* @param parent The logical parent CommandZone, or nullptr for nested zones
|
||||
*/
|
||||
void setLogicalParentZone(CommandZone *parent)
|
||||
{
|
||||
logicalParentZone = parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the logical parent zone.
|
||||
* @return The logical parent, or nullptr if using Qt parent-child hierarchy
|
||||
*/
|
||||
[[nodiscard]] CommandZone *getLogicalParentZone() const
|
||||
{
|
||||
return logicalParentZone;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adds a logical child zone for sibling architecture.
|
||||
* @param child The zone that is logically a child but a Qt sibling
|
||||
*/
|
||||
void addLogicalChildZone(CommandZone *child)
|
||||
{
|
||||
logicalChildZones.append(child);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Attempts to auto-minimize this zone or a child zone (depth-first).
|
||||
*
|
||||
* Tries children first (depth-first, bottom-up) to minimize the
|
||||
* deepest non-minimized zone. Used as a fallback safety-net in
|
||||
* rearrangeZones() for non-expansion triggers (e.g., window resize).
|
||||
*
|
||||
* For user-initiated zone expansion, tryMinimizeAbove() is preferred
|
||||
* as it minimizes the zone above instead of below.
|
||||
*
|
||||
* @return True if a zone was minimized, false if no minimization possible
|
||||
* @see tryMinimizeAbove()
|
||||
*/
|
||||
bool tryAutoMinimize();
|
||||
|
||||
/**
|
||||
* @brief Returns the nearest CommandZone ancestor in the graphics item hierarchy.
|
||||
* @return Pointer to parent CommandZone, or nullptr if none exists.
|
||||
*/
|
||||
[[nodiscard]] CommandZone *getParentCommandZone() const;
|
||||
|
||||
/**
|
||||
* @brief Attempts to minimize the zone directly above this one.
|
||||
*
|
||||
* Walks up the parent chain to find the first ancestor CommandZone that
|
||||
* can be minimized (is expanded and not already minimized). The Primary
|
||||
* zone can be minimized (reduced to 25% height) but cannot be collapsed.
|
||||
*
|
||||
* Recursion is bounded by zone hierarchy depth (max 4 zones).
|
||||
*
|
||||
* @return True if an ancestor zone was minimized, false if none could be.
|
||||
* @see tryAutoMinimize()
|
||||
*/
|
||||
bool tryMinimizeAbove();
|
||||
|
||||
/**
|
||||
* @brief Sets toggle button visibility.
|
||||
* @param visible Whether toggle should be visible
|
||||
*/
|
||||
void setToggleButtonVisible(bool visible);
|
||||
|
||||
public slots:
|
||||
void toggleExpanded();
|
||||
|
||||
signals:
|
||||
void expandedChanged(bool isExpanded);
|
||||
void minimizedChanged(bool isMinimized);
|
||||
|
||||
protected:
|
||||
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
|
||||
private slots:
|
||||
void updateBg();
|
||||
|
||||
private:
|
||||
void repositionToggleButton();
|
||||
void applyStateChange(const StateChangeResult &result, bool updateTogglePosition = false);
|
||||
[[nodiscard]] bool shouldPreventCollapse() const;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_COMMAND_ZONE_H
|
||||
|
|
@ -9,6 +9,15 @@
|
|||
|
||||
qreal divideCardSpaceInZone(qreal index, int cardCount, qreal totalHeight, qreal cardHeight, bool reverse)
|
||||
{
|
||||
// Handle single card case - return centered position or index 0
|
||||
if (cardCount <= 1) {
|
||||
if (reverse) {
|
||||
return 0; // Single card maps to index 0
|
||||
} else {
|
||||
return (totalHeight - cardHeight) / 2.0; // Center the single card
|
||||
}
|
||||
}
|
||||
|
||||
qreal cardMinOverlap = cardHeight * SettingsCache::instance().getStackCardOverlapPercent() / 100;
|
||||
qreal desiredHeight = cardHeight * cardCount - cardMinOverlap * (cardCount - 1);
|
||||
qreal y;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* @file select_zone.h
|
||||
* @ingroup GameGraphicsZones
|
||||
* @brief TODO: Document this.
|
||||
* @brief Base class for zones with directly clickable, laid-out cards.
|
||||
*/
|
||||
|
||||
#ifndef SELECTZONE_H
|
||||
|
|
@ -12,7 +12,16 @@
|
|||
#include <QSet>
|
||||
|
||||
/**
|
||||
* A CardZone where the cards are laid out, with each card directly interactable by clicking.
|
||||
* @class SelectZone
|
||||
* @brief Base class for zones where cards are laid out and directly clickable.
|
||||
*
|
||||
* SelectZone provides mouse interaction handling for zones that display cards
|
||||
* in a visible layout (as opposed to hidden zones like the deck). It supports
|
||||
* click-to-select and rectangle selection via click-and-drag.
|
||||
*
|
||||
* Subclasses include TableZone, HandZone, and CommandZone.
|
||||
*
|
||||
* @see CardZone
|
||||
*/
|
||||
class SelectZone : public CardZone
|
||||
{
|
||||
|
|
|
|||
|
|
@ -33,6 +33,11 @@ QRectF StackZone::boundingRect() const
|
|||
void StackZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
|
||||
{
|
||||
QBrush brush = themeManager->getExtraBgBrush(ThemeManager::Stack, getLogic()->getPlayer()->getZoneId());
|
||||
|
||||
// Set brush origin to compensate for zone position, keeping texture fixed in scene coordinates
|
||||
QPointF scenePos = pos();
|
||||
painter->setBrushOrigin(-scenePos);
|
||||
|
||||
painter->fillRect(boundingRect(), brush);
|
||||
}
|
||||
|
||||
|
|
@ -106,3 +111,11 @@ void StackZone::reorganizeCards()
|
|||
}
|
||||
update();
|
||||
}
|
||||
|
||||
void StackZone::setHeight(qreal newHeight)
|
||||
{
|
||||
prepareGeometryChange();
|
||||
zoneHeight = newHeight;
|
||||
reorganizeCards();
|
||||
update();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ public:
|
|||
QRectF boundingRect() const override;
|
||||
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
|
||||
void reorganizeCards() override;
|
||||
void setHeight(qreal newHeight);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include "../board/card_item.h"
|
||||
#include "../player/player.h"
|
||||
#include "../player/player_actions.h"
|
||||
#include "../z_value_layer_manager.h"
|
||||
#include "logic/table_zone_logic.h"
|
||||
|
||||
#include <QGraphicsScene>
|
||||
|
|
@ -167,7 +168,9 @@ void TableZone::reorganizeCards()
|
|||
actualY += 15;
|
||||
|
||||
getLogic()->getCards()[i]->setPos(actualX, actualY);
|
||||
getLogic()->getCards()[i]->setRealZValue((actualY + CARD_HEIGHT) * 100000 + (actualX + 1) * 100);
|
||||
qreal cardZValue = (actualY + CARD_HEIGHT) * 100000 + (actualX + 1) * 100;
|
||||
Q_ASSERT(ZValueLayerManager::isValidCardZValue(cardZValue));
|
||||
getLogic()->getCards()[i]->setRealZValue(cardZValue);
|
||||
|
||||
QListIterator<CardItem *> attachedCardIterator(getLogic()->getCards()[i]->getAttachedCards());
|
||||
int j = 0;
|
||||
|
|
@ -177,7 +180,9 @@ void TableZone::reorganizeCards()
|
|||
qreal childX = actualX - j * STACKED_CARD_OFFSET_X;
|
||||
qreal childY = y + 5;
|
||||
attachedCard->setPos(childX, childY);
|
||||
attachedCard->setRealZValue((childY + CARD_HEIGHT) * 100000 + (childX + 1) * 100);
|
||||
qreal attachedZValue = (childY + CARD_HEIGHT) * 100000 + (childX + 1) * 100;
|
||||
Q_ASSERT(ZValueLayerManager::isValidCardZValue(attachedZValue));
|
||||
attachedCard->setRealZValue(attachedZValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include "../game_scene.h"
|
||||
#include "../player/player.h"
|
||||
#include "../player/player_actions.h"
|
||||
#include "../z_values.h"
|
||||
#include "view_zone.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
|
|
@ -47,7 +48,7 @@ ZoneViewWidget::ZoneViewWidget(Player *_player,
|
|||
{
|
||||
setAcceptHoverEvents(true);
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
setZValue(2000000006);
|
||||
setZValue(ZValues::ZONE_VIEW_WIDGET);
|
||||
setFlag(ItemIgnoresTransformations);
|
||||
|
||||
QGraphicsLinearLayout *vbox = new QGraphicsLinearLayout(Qt::Vertical);
|
||||
|
|
@ -71,7 +72,7 @@ ZoneViewWidget::ZoneViewWidget(Player *_player,
|
|||
|
||||
QGraphicsProxyWidget *searchEditProxy = new QGraphicsProxyWidget;
|
||||
searchEditProxy->setWidget(&searchEdit);
|
||||
searchEditProxy->setZValue(2000000007);
|
||||
searchEditProxy->setZValue(ZValues::DRAG_ITEM);
|
||||
vbox->addItem(searchEditProxy);
|
||||
|
||||
// top row
|
||||
|
|
@ -80,13 +81,13 @@ ZoneViewWidget::ZoneViewWidget(Player *_player,
|
|||
// groupBy options
|
||||
QGraphicsProxyWidget *groupBySelectorProxy = new QGraphicsProxyWidget;
|
||||
groupBySelectorProxy->setWidget(&groupBySelector);
|
||||
groupBySelectorProxy->setZValue(2000000008);
|
||||
groupBySelectorProxy->setZValue(ZValues::TOP_UI);
|
||||
hTopRow->addItem(groupBySelectorProxy);
|
||||
|
||||
// sortBy options
|
||||
QGraphicsProxyWidget *sortBySelectorProxy = new QGraphicsProxyWidget;
|
||||
sortBySelectorProxy->setWidget(&sortBySelector);
|
||||
sortBySelectorProxy->setZValue(2000000007);
|
||||
sortBySelectorProxy->setZValue(ZValues::DRAG_ITEM);
|
||||
hTopRow->addItem(sortBySelectorProxy);
|
||||
|
||||
vbox->addItem(hTopRow);
|
||||
|
|
|
|||
|
|
@ -69,6 +69,26 @@ target_link_libraries(command_zone_state_test
|
|||
|
||||
add_test(NAME command_zone_state_test COMMAND command_zone_state_test)
|
||||
|
||||
# ------------------------
|
||||
# Command Zone Integration Test
|
||||
# Tests counter parenting and zone coordination
|
||||
# ------------------------
|
||||
add_executable(command_zone_integration_test
|
||||
command_zone_integration_test.cpp
|
||||
)
|
||||
|
||||
target_include_directories(command_zone_integration_test
|
||||
PRIVATE ${CMAKE_SOURCE_DIR}
|
||||
)
|
||||
|
||||
target_link_libraries(command_zone_integration_test
|
||||
PRIVATE Threads::Threads
|
||||
PRIVATE ${GTEST_BOTH_LIBRARIES}
|
||||
PRIVATE ${TEST_QT_MODULES}
|
||||
)
|
||||
|
||||
add_test(NAME command_zone_integration_test COMMAND command_zone_integration_test)
|
||||
|
||||
# ------------------------
|
||||
# Counter Visibility Test
|
||||
# Tests counter visibility during zone state transitions
|
||||
|
|
@ -92,5 +112,6 @@ if(NOT GTEST_FOUND)
|
|||
add_dependencies(command_zone_logic_test gtest)
|
||||
add_dependencies(commander_tax_counter_test gtest)
|
||||
add_dependencies(command_zone_state_test gtest)
|
||||
add_dependencies(command_zone_integration_test gtest)
|
||||
add_dependencies(counter_visibility_test gtest)
|
||||
endif()
|
||||
|
|
|
|||
916
tests/command_zone/command_zone_integration_test.cpp
Normal file
916
tests/command_zone/command_zone_integration_test.cpp
Normal file
|
|
@ -0,0 +1,916 @@
|
|||
/**
|
||||
* @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();
|
||||
}
|
||||
22
tests/command_zone/command_zone_test_common.h
Normal file
22
tests/command_zone/command_zone_test_common.h
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* @file command_zone_test_common.h
|
||||
* @brief Shared types and utilities for command zone unit tests.
|
||||
*
|
||||
* Provides access to production CommandZoneType and ZoneVisibility enums
|
||||
* without pulling in full Qt graphics dependencies. Tests remain isolated
|
||||
* while using the canonical enum definitions.
|
||||
*/
|
||||
|
||||
#ifndef COMMAND_ZONE_TEST_COMMON_H
|
||||
#define COMMAND_ZONE_TEST_COMMON_H
|
||||
|
||||
#include "cockatrice/src/game/zones/command_zone_types.h"
|
||||
|
||||
// Re-export the minimized height ratio using the canonical constant name
|
||||
// that tests were already using (ZoneSizes vs CommandZoneConstants)
|
||||
namespace ZoneSizes
|
||||
{
|
||||
using namespace CommandZoneConstants;
|
||||
}
|
||||
|
||||
#endif // COMMAND_ZONE_TEST_COMMON_H
|
||||
Loading…
Add table
Add a link
Reference in a new issue