mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-07-07 05:53:59 -07:00
feat(command-zone): add state machine with tests
Implement CommandZoneState - a pure C++ state machine managing zone visibility transitions (Hidden, Revealed, Peeking states). The state machine is intentionally decoupled from Qt graphics: - Pure logic with no Qt dependencies in the state class - Testable in isolation without a GUI - Clear state transition table with StateChangeResult outcomes Test coverage verifies: - Initial state is Hidden - Valid transitions: Hidden->Revealed, Hidden->Peeking, etc. - Invalid transitions return NoChange - Peek timeout triggers return to previous state This establishes the foundation for command zone visibility management while keeping graphics concerns separate.
This commit is contained in:
parent
ba1a388e5d
commit
3c2e4a3fb2
6 changed files with 678 additions and 0 deletions
|
|
@ -103,6 +103,7 @@ set(cockatrice_SOURCES
|
|||
src/game/player/player_target.cpp
|
||||
src/game/replay.cpp
|
||||
src/game/zones/card_zone.cpp
|
||||
src/game/zones/command_zone_state.cpp
|
||||
src/game/zones/hand_zone.cpp
|
||||
src/game/zones/logic/card_zone_logic.cpp
|
||||
src/game/zones/logic/hand_zone_logic.cpp
|
||||
|
|
|
|||
121
cockatrice/src/game/zones/command_zone_state.cpp
Normal file
121
cockatrice/src/game/zones/command_zone_state.cpp
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
/**
|
||||
* @file command_zone_state.cpp
|
||||
* @brief Implementation of CommandZoneState pure state machine.
|
||||
*
|
||||
* @see CommandZoneState for class documentation
|
||||
* @see CommandZone for the graphics layer that owns this state
|
||||
*/
|
||||
|
||||
#include "command_zone_state.h"
|
||||
|
||||
CommandZoneState::CommandZoneState(qreal zoneHeight, CommandZoneType zoneType)
|
||||
: m_zoneHeight(zoneHeight), m_zoneType(zoneType),
|
||||
m_visibility(zoneType == CommandZoneType::Primary ? ZoneVisibility::Expanded : ZoneVisibility::Collapsed)
|
||||
{
|
||||
}
|
||||
|
||||
bool CommandZoneState::isPrimary() const
|
||||
{
|
||||
return m_zoneType == CommandZoneType::Primary;
|
||||
}
|
||||
|
||||
bool CommandZoneState::isMinimized() const
|
||||
{
|
||||
return m_visibility == ZoneVisibility::Minimized;
|
||||
}
|
||||
|
||||
bool CommandZoneState::isExpanded() const
|
||||
{
|
||||
return m_visibility != ZoneVisibility::Collapsed;
|
||||
}
|
||||
|
||||
bool CommandZoneState::isCollapsed() const
|
||||
{
|
||||
return m_visibility == ZoneVisibility::Collapsed;
|
||||
}
|
||||
|
||||
qreal CommandZoneState::currentHeight() const
|
||||
{
|
||||
if (isCollapsed()) {
|
||||
return 0;
|
||||
}
|
||||
return isMinimized() ? (m_zoneHeight * CommandZoneConstants::MINIMIZED_HEIGHT_RATIO) : m_zoneHeight;
|
||||
}
|
||||
|
||||
CommandZoneType CommandZoneState::getZoneType() const
|
||||
{
|
||||
return m_zoneType;
|
||||
}
|
||||
|
||||
qreal CommandZoneState::getZoneHeight() const
|
||||
{
|
||||
return m_zoneHeight;
|
||||
}
|
||||
|
||||
StateChangeResult CommandZoneState::trySetExpanded(bool expanded, bool collapseBlocked)
|
||||
{
|
||||
if (isPrimary()) {
|
||||
return StateChangeResult::noChange();
|
||||
}
|
||||
|
||||
bool currentlyExpanded = isExpanded();
|
||||
if (currentlyExpanded == expanded) {
|
||||
return StateChangeResult::noChange();
|
||||
}
|
||||
|
||||
if (!expanded && collapseBlocked) {
|
||||
return StateChangeResult::noChange();
|
||||
}
|
||||
|
||||
bool wasMinimized = isMinimized();
|
||||
|
||||
if (expanded) {
|
||||
m_visibility = ZoneVisibility::Expanded;
|
||||
} else {
|
||||
m_visibility = ZoneVisibility::Collapsed;
|
||||
}
|
||||
|
||||
return {
|
||||
true, // geometryChanged
|
||||
true, // shouldEmitExpanded
|
||||
wasMinimized && !expanded // shouldEmitMinimized (only if we were minimized and collapsed)
|
||||
};
|
||||
}
|
||||
|
||||
StateChangeResult CommandZoneState::tryToggleExpanded(bool collapseBlocked)
|
||||
{
|
||||
if (isPrimary()) {
|
||||
return StateChangeResult::noChange();
|
||||
}
|
||||
|
||||
if (m_lastToggle.isValid() && m_lastToggle.elapsed() < TOGGLE_DEBOUNCE_MS) {
|
||||
return StateChangeResult::noChange();
|
||||
}
|
||||
|
||||
if (isExpanded() && collapseBlocked) {
|
||||
return StateChangeResult::noChange();
|
||||
}
|
||||
|
||||
m_lastToggle.start();
|
||||
return trySetExpanded(!isExpanded(), collapseBlocked);
|
||||
}
|
||||
|
||||
StateChangeResult CommandZoneState::tryToggleMinimized()
|
||||
{
|
||||
if (isCollapsed()) {
|
||||
return StateChangeResult::noChange();
|
||||
}
|
||||
|
||||
m_visibility = isMinimized() ? ZoneVisibility::Expanded : ZoneVisibility::Minimized;
|
||||
|
||||
return {
|
||||
true, // geometryChanged
|
||||
false, // shouldEmitExpanded (minimized toggle doesn't change expanded state)
|
||||
true // shouldEmitMinimized
|
||||
};
|
||||
}
|
||||
|
||||
void CommandZoneState::resetDebounce()
|
||||
{
|
||||
m_lastToggle.invalidate();
|
||||
}
|
||||
128
cockatrice/src/game/zones/command_zone_state.h
Normal file
128
cockatrice/src/game/zones/command_zone_state.h
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
/**
|
||||
* @file command_zone_state.h
|
||||
* @ingroup GameGraphicsZones
|
||||
* @brief Pure state machine for command zone visibility management.
|
||||
*/
|
||||
|
||||
#ifndef COCKATRICE_COMMAND_ZONE_STATE_H
|
||||
#define COCKATRICE_COMMAND_ZONE_STATE_H
|
||||
|
||||
#include "command_zone_types.h"
|
||||
|
||||
#include <QElapsedTimer>
|
||||
|
||||
/**
|
||||
* @class CommandZoneState
|
||||
* @brief Manages visibility state (expanded/minimized/collapsed) for command zones.
|
||||
*
|
||||
* This class extracts pure state logic from CommandZone for testability.
|
||||
* It has no Qt graphics dependencies and can be tested in isolation.
|
||||
*
|
||||
* The state machine supports three visibility states:
|
||||
* - **Expanded**: Full height, cards visible
|
||||
* - **Minimized**: 25% height, cards clipped
|
||||
* - **Collapsed**: Zero height, only toggle button visible (not valid for Primary)
|
||||
*
|
||||
* All mutation methods return a StateChangeResult that tells callers what
|
||||
* side effects to trigger (geometry changes, signal emissions), following
|
||||
* the information hiding principle.
|
||||
*
|
||||
* @see CommandZone for the graphics layer that owns this state
|
||||
* @see CommandZoneLogic for card data management
|
||||
* @see StateChangeResult for mutation result semantics
|
||||
*/
|
||||
class CommandZoneState
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a CommandZoneState with the given height and type.
|
||||
* @param zoneHeight Full height in pixels when expanded
|
||||
* @param zoneType Type of command zone (Primary, Partner, Companion, Background)
|
||||
*
|
||||
* Primary zones start Expanded; all other types start Collapsed.
|
||||
*/
|
||||
CommandZoneState(qreal zoneHeight, CommandZoneType zoneType);
|
||||
|
||||
// === Query Methods ===
|
||||
|
||||
[[nodiscard]] bool isPrimary() const;
|
||||
[[nodiscard]] bool isMinimized() const;
|
||||
|
||||
/**
|
||||
* @brief Returns whether the zone is currently expanded (not collapsed).
|
||||
*
|
||||
* Note: A minimized zone is still considered "expanded" because it has
|
||||
* non-zero height. This method returns false only for Collapsed visibility.
|
||||
*/
|
||||
[[nodiscard]] bool isExpanded() const;
|
||||
|
||||
[[nodiscard]] bool isCollapsed() const;
|
||||
|
||||
/// Returns current visual height: 0 (collapsed), 25% (minimized), or full.
|
||||
[[nodiscard]] qreal currentHeight() const;
|
||||
|
||||
/**
|
||||
* @brief Returns the zone type.
|
||||
* @return The CommandZoneType of this zone
|
||||
*/
|
||||
[[nodiscard]] CommandZoneType getZoneType() const;
|
||||
|
||||
/**
|
||||
* @brief Returns the full (non-minimized) height of the zone.
|
||||
* @return Full height in pixels
|
||||
*/
|
||||
[[nodiscard]] qreal getZoneHeight() const;
|
||||
|
||||
// === Mutation Methods ===
|
||||
|
||||
/**
|
||||
* @brief Attempts to set the expanded state of a collapsible zone.
|
||||
*
|
||||
* No-op for Primary zone. When collapsing, transitions from Expanded/Minimized
|
||||
* to Collapsed (emitting minimizedChanged if was minimized). When expanding,
|
||||
* transitions to Expanded.
|
||||
*
|
||||
* @param expanded True to expand, false to collapse
|
||||
* @param collapseBlocked True if external conditions prevent collapsing (e.g., cards present)
|
||||
* @return StateChangeResult indicating what side effects to trigger
|
||||
*/
|
||||
StateChangeResult trySetExpanded(bool expanded, bool collapseBlocked = false);
|
||||
|
||||
/**
|
||||
* @brief Attempts to toggle the expanded/collapsed state.
|
||||
*
|
||||
* No-op for Primary zone. Subject to debounce protection (200ms).
|
||||
*
|
||||
* @param collapseBlocked True if external conditions prevent collapsing
|
||||
* @return StateChangeResult indicating what side effects to trigger
|
||||
*/
|
||||
StateChangeResult tryToggleExpanded(bool collapseBlocked = false);
|
||||
|
||||
/**
|
||||
* @brief Attempts to toggle the minimized state.
|
||||
*
|
||||
* No-op for collapsed zones. Toggles between Expanded and Minimized.
|
||||
*
|
||||
* @return StateChangeResult indicating what side effects to trigger
|
||||
*/
|
||||
StateChangeResult tryToggleMinimized();
|
||||
|
||||
// === Test Support ===
|
||||
|
||||
/**
|
||||
* @brief Resets the debounce timer to allow immediate toggle.
|
||||
*
|
||||
* @warning For testing only. Do not call in production code.
|
||||
*/
|
||||
void resetDebounce();
|
||||
|
||||
private:
|
||||
qreal m_zoneHeight; ///< Full height in pixels when expanded
|
||||
CommandZoneType m_zoneType; ///< Type of command zone
|
||||
ZoneVisibility m_visibility; ///< Current visibility state
|
||||
QElapsedTimer m_lastToggle; ///< Debounce timer for toggle operations
|
||||
|
||||
static constexpr qint64 TOGGLE_DEBOUNCE_MS = 200;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_COMMAND_ZONE_STATE_H
|
||||
|
|
@ -61,5 +61,6 @@ target_link_libraries(
|
|||
)
|
||||
|
||||
add_subdirectory(carddatabase)
|
||||
add_subdirectory(command_zone)
|
||||
add_subdirectory(loading_from_clipboard)
|
||||
add_subdirectory(oracle)
|
||||
|
|
|
|||
40
tests/command_zone/CMakeLists.txt
Normal file
40
tests/command_zone/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# ------------------------
|
||||
# Qt modules for command zone tests
|
||||
# Qt::Gui required for QColor in z_values.h
|
||||
# Qt::Widgets required for QGraphicsItem in abstract_card_item.h
|
||||
# ------------------------
|
||||
set(TEST_QT_MODULES
|
||||
${COCKATRICE_QT_VERSION_NAME}::Core
|
||||
${COCKATRICE_QT_VERSION_NAME}::Test
|
||||
${COCKATRICE_QT_VERSION_NAME}::Gui
|
||||
${COCKATRICE_QT_VERSION_NAME}::Widgets
|
||||
)
|
||||
|
||||
# ------------------------
|
||||
# Command Zone State Test
|
||||
# Tests the extracted CommandZoneState pure state machine
|
||||
# ------------------------
|
||||
add_executable(command_zone_state_test
|
||||
command_zone_state_test.cpp
|
||||
${CMAKE_SOURCE_DIR}/cockatrice/src/game/zones/command_zone_state.cpp
|
||||
)
|
||||
|
||||
target_include_directories(command_zone_state_test
|
||||
PRIVATE ${CMAKE_SOURCE_DIR}
|
||||
PRIVATE ${CMAKE_SOURCE_DIR}/cockatrice/src/game/zones
|
||||
)
|
||||
|
||||
target_link_libraries(command_zone_state_test
|
||||
PRIVATE Threads::Threads
|
||||
PRIVATE ${GTEST_BOTH_LIBRARIES}
|
||||
PRIVATE ${TEST_QT_MODULES}
|
||||
)
|
||||
|
||||
add_test(NAME command_zone_state_test COMMAND command_zone_state_test)
|
||||
|
||||
# ------------------------
|
||||
# Dependencies on gtest
|
||||
# ------------------------
|
||||
if(NOT GTEST_FOUND)
|
||||
add_dependencies(command_zone_state_test gtest)
|
||||
endif()
|
||||
387
tests/command_zone/command_zone_state_test.cpp
Normal file
387
tests/command_zone/command_zone_state_test.cpp
Normal file
|
|
@ -0,0 +1,387 @@
|
|||
/**
|
||||
* @file command_zone_state_test.cpp
|
||||
* @brief Unit tests for CommandZoneState pure state machine
|
||||
*
|
||||
* Tests the extracted visibility/height state logic:
|
||||
* - Primary zone: always expanded, cannot be collapsed
|
||||
* - Partner/Companion/Background zones: start collapsed, can be toggled
|
||||
* - All zones: can be minimized to 25% height
|
||||
* - Toggle debouncing: rejects rapid toggles within 200ms
|
||||
* - StateChangeResult: flags correctly indicate required side effects
|
||||
*
|
||||
* @see CommandZoneState for the class under test
|
||||
*/
|
||||
|
||||
#include "cockatrice/src/game/zones/command_zone_state.h"
|
||||
#include "command_zone_test_common.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
// ============ Parameterized Tests for Non-Primary Zones ============
|
||||
|
||||
class NonPrimaryZoneStateTest : public ::testing::TestWithParam<CommandZoneType>
|
||||
{
|
||||
protected:
|
||||
CommandZoneState *zone = nullptr;
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
delete zone;
|
||||
}
|
||||
|
||||
void createZone(qreal height = 100.0)
|
||||
{
|
||||
delete zone;
|
||||
zone = new CommandZoneState(height, GetParam());
|
||||
}
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(NonPrimaryZones,
|
||||
NonPrimaryZoneStateTest,
|
||||
::testing::Values(CommandZoneType::Partner,
|
||||
CommandZoneType::Companion,
|
||||
CommandZoneType::Background),
|
||||
[](const ::testing::TestParamInfo<CommandZoneType> &info) {
|
||||
switch (info.param) {
|
||||
case CommandZoneType::Partner:
|
||||
return "Partner";
|
||||
case CommandZoneType::Companion:
|
||||
return "Companion";
|
||||
case CommandZoneType::Background:
|
||||
return "Background";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
});
|
||||
|
||||
TEST_P(NonPrimaryZoneStateTest, StartsCollapsed)
|
||||
{
|
||||
createZone();
|
||||
EXPECT_FALSE(zone->isExpanded()) << "Non-primary zone should start collapsed";
|
||||
EXPECT_FALSE(zone->isPrimary());
|
||||
EXPECT_EQ(GetParam(), zone->getZoneType());
|
||||
}
|
||||
|
||||
TEST_P(NonPrimaryZoneStateTest, TryToggleExpanded)
|
||||
{
|
||||
createZone();
|
||||
ASSERT_FALSE(zone->isExpanded());
|
||||
|
||||
auto result = zone->tryToggleExpanded();
|
||||
|
||||
EXPECT_TRUE(zone->isExpanded()) << "Should expand after toggle";
|
||||
EXPECT_TRUE(result.geometryChanged);
|
||||
}
|
||||
|
||||
TEST_P(NonPrimaryZoneStateTest, CurrentHeight_Expanded)
|
||||
{
|
||||
createZone(100.0);
|
||||
zone->trySetExpanded(true);
|
||||
EXPECT_DOUBLE_EQ(100.0, zone->currentHeight()) << "Expanded zone should have full height";
|
||||
}
|
||||
|
||||
class CommandZoneStateTest : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
CommandZoneState *zone;
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
zone = nullptr;
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
delete zone;
|
||||
}
|
||||
|
||||
void createPrimaryZone(qreal height = 100.0)
|
||||
{
|
||||
delete zone;
|
||||
zone = new CommandZoneState(height, CommandZoneType::Primary);
|
||||
}
|
||||
|
||||
void createPartnerZone(qreal height = 100.0)
|
||||
{
|
||||
delete zone;
|
||||
zone = new CommandZoneState(height, CommandZoneType::Partner);
|
||||
}
|
||||
|
||||
void createCompanionZone(qreal height = 100.0)
|
||||
{
|
||||
delete zone;
|
||||
zone = new CommandZoneState(height, CommandZoneType::Companion);
|
||||
}
|
||||
|
||||
void createBackgroundZone(qreal height = 100.0)
|
||||
{
|
||||
delete zone;
|
||||
zone = new CommandZoneState(height, CommandZoneType::Background);
|
||||
}
|
||||
};
|
||||
|
||||
// ============ Primary Zone Tests ============
|
||||
|
||||
TEST_F(CommandZoneStateTest, PrimaryZone_AlwaysExpanded)
|
||||
{
|
||||
createPrimaryZone();
|
||||
|
||||
EXPECT_TRUE(zone->isExpanded()) << "Primary zone should start expanded";
|
||||
EXPECT_TRUE(zone->isPrimary()) << "Primary zone should report isPrimary() true";
|
||||
}
|
||||
|
||||
TEST_F(CommandZoneStateTest, PrimaryZone_TryToggleExpanded_NoOp)
|
||||
{
|
||||
createPrimaryZone();
|
||||
ASSERT_TRUE(zone->isExpanded());
|
||||
|
||||
auto result = zone->tryToggleExpanded();
|
||||
|
||||
EXPECT_TRUE(zone->isExpanded()) << "Primary zone should remain expanded after tryToggleExpanded";
|
||||
EXPECT_FALSE(result.geometryChanged) << "No geometry change for Primary zone toggle";
|
||||
EXPECT_FALSE(result.shouldEmitExpanded) << "No expanded signal for Primary zone toggle";
|
||||
}
|
||||
|
||||
TEST_F(CommandZoneStateTest, CurrentHeight_PrimaryFull)
|
||||
{
|
||||
createPrimaryZone(100.0);
|
||||
|
||||
EXPECT_DOUBLE_EQ(100.0, zone->currentHeight()) << "Primary zone should have full height";
|
||||
}
|
||||
|
||||
TEST_F(CommandZoneStateTest, CurrentHeight_PrimaryMinimized)
|
||||
{
|
||||
createPrimaryZone(100.0);
|
||||
|
||||
auto result = zone->tryToggleMinimized();
|
||||
|
||||
EXPECT_DOUBLE_EQ(25.0, zone->currentHeight()) << "Minimized primary zone should be 25% height";
|
||||
EXPECT_TRUE(result.geometryChanged);
|
||||
EXPECT_TRUE(result.shouldEmitMinimized);
|
||||
}
|
||||
|
||||
// ============ Partner Zone Tests (Partner-specific behaviors) ============
|
||||
|
||||
TEST_F(CommandZoneStateTest, PartnerZone_TryToggleExpanded_TwiceCollapses)
|
||||
{
|
||||
createPartnerZone();
|
||||
zone->tryToggleExpanded();
|
||||
ASSERT_TRUE(zone->isExpanded());
|
||||
|
||||
zone->resetDebounce();
|
||||
auto result = zone->tryToggleExpanded();
|
||||
|
||||
EXPECT_FALSE(zone->isExpanded()) << "Partner zone should collapse after second toggle";
|
||||
EXPECT_TRUE(result.geometryChanged);
|
||||
EXPECT_TRUE(result.shouldEmitExpanded);
|
||||
}
|
||||
|
||||
TEST_F(CommandZoneStateTest, CurrentHeight_PartnerCollapsed)
|
||||
{
|
||||
createPartnerZone(100.0);
|
||||
ASSERT_FALSE(zone->isExpanded());
|
||||
|
||||
EXPECT_DOUBLE_EQ(0.0, zone->currentHeight()) << "Collapsed partner zone should have 0 height";
|
||||
}
|
||||
|
||||
TEST_F(CommandZoneStateTest, CurrentHeight_PartnerExpandedMinimized)
|
||||
{
|
||||
createPartnerZone(100.0);
|
||||
zone->trySetExpanded(true);
|
||||
|
||||
zone->tryToggleMinimized();
|
||||
|
||||
EXPECT_DOUBLE_EQ(25.0, zone->currentHeight()) << "Expanded+minimized partner zone should be 25% height";
|
||||
}
|
||||
|
||||
// ============ Debounce Tests ============
|
||||
|
||||
TEST_F(CommandZoneStateTest, TryToggleDebounce_RejectsRapidCalls)
|
||||
{
|
||||
createPartnerZone();
|
||||
ASSERT_FALSE(zone->isExpanded());
|
||||
|
||||
zone->tryToggleExpanded();
|
||||
ASSERT_TRUE(zone->isExpanded());
|
||||
|
||||
auto result = zone->tryToggleExpanded();
|
||||
|
||||
EXPECT_TRUE(zone->isExpanded()) << "Rapid toggle should be rejected (debounced)";
|
||||
EXPECT_FALSE(result.geometryChanged) << "Debounced toggle should not change geometry";
|
||||
}
|
||||
|
||||
TEST_F(CommandZoneStateTest, TryToggleDebounce_AcceptsAfterReset)
|
||||
{
|
||||
createPartnerZone();
|
||||
|
||||
zone->tryToggleExpanded();
|
||||
ASSERT_TRUE(zone->isExpanded());
|
||||
|
||||
zone->resetDebounce();
|
||||
|
||||
auto result = zone->tryToggleExpanded();
|
||||
|
||||
EXPECT_FALSE(zone->isExpanded()) << "Toggle after reset should work";
|
||||
EXPECT_TRUE(result.geometryChanged);
|
||||
}
|
||||
|
||||
// ============ Minimize Tests ============
|
||||
|
||||
TEST_F(CommandZoneStateTest, Zone_StartsNotMinimized)
|
||||
{
|
||||
createPrimaryZone();
|
||||
|
||||
EXPECT_FALSE(zone->isMinimized()) << "Zone should start not minimized";
|
||||
}
|
||||
|
||||
TEST_F(CommandZoneStateTest, TryToggleMinimized_TogglesState)
|
||||
{
|
||||
createPrimaryZone();
|
||||
ASSERT_FALSE(zone->isMinimized());
|
||||
|
||||
auto result = zone->tryToggleMinimized();
|
||||
|
||||
EXPECT_TRUE(zone->isMinimized()) << "Zone should be minimized after toggle";
|
||||
EXPECT_TRUE(result.geometryChanged);
|
||||
EXPECT_TRUE(result.shouldEmitMinimized);
|
||||
}
|
||||
|
||||
TEST_F(CommandZoneStateTest, TryToggleMinimized_Twice_RestoresState)
|
||||
{
|
||||
createPrimaryZone();
|
||||
zone->tryToggleMinimized();
|
||||
ASSERT_TRUE(zone->isMinimized());
|
||||
|
||||
auto result = zone->tryToggleMinimized();
|
||||
|
||||
EXPECT_FALSE(zone->isMinimized()) << "Zone should be restored after second toggle";
|
||||
EXPECT_TRUE(result.geometryChanged);
|
||||
EXPECT_TRUE(result.shouldEmitMinimized);
|
||||
}
|
||||
|
||||
TEST_F(CommandZoneStateTest, TryToggleMinimized_NoOpWhenCollapsed)
|
||||
{
|
||||
createPartnerZone();
|
||||
ASSERT_TRUE(zone->isCollapsed());
|
||||
|
||||
auto result = zone->tryToggleMinimized();
|
||||
|
||||
EXPECT_FALSE(result.geometryChanged) << "Collapsed zone should not toggle minimized";
|
||||
EXPECT_FALSE(result.shouldEmitMinimized);
|
||||
}
|
||||
|
||||
// ============ StateChangeResult Tests ============
|
||||
|
||||
TEST_F(CommandZoneStateTest, TrySetExpanded_ReturnsCorrectResult_OnExpand)
|
||||
{
|
||||
createPartnerZone();
|
||||
ASSERT_FALSE(zone->isExpanded());
|
||||
|
||||
auto result = zone->trySetExpanded(true);
|
||||
|
||||
EXPECT_TRUE(result.geometryChanged);
|
||||
EXPECT_TRUE(result.shouldEmitExpanded);
|
||||
EXPECT_FALSE(result.shouldEmitMinimized);
|
||||
}
|
||||
|
||||
TEST_F(CommandZoneStateTest, TrySetExpanded_ReturnsCorrectResult_OnCollapse)
|
||||
{
|
||||
createPartnerZone();
|
||||
zone->trySetExpanded(true);
|
||||
ASSERT_TRUE(zone->isExpanded());
|
||||
|
||||
auto result = zone->trySetExpanded(false);
|
||||
|
||||
EXPECT_TRUE(result.geometryChanged);
|
||||
EXPECT_TRUE(result.shouldEmitExpanded);
|
||||
EXPECT_FALSE(result.shouldEmitMinimized) << "Not minimized before collapse";
|
||||
}
|
||||
|
||||
TEST_F(CommandZoneStateTest, TrySetExpanded_ReturnsCorrectResult_OnCollapseWhileMinimized)
|
||||
{
|
||||
createPartnerZone();
|
||||
zone->trySetExpanded(true);
|
||||
zone->tryToggleMinimized();
|
||||
ASSERT_TRUE(zone->isMinimized());
|
||||
|
||||
auto result = zone->trySetExpanded(false);
|
||||
|
||||
EXPECT_TRUE(result.geometryChanged);
|
||||
EXPECT_TRUE(result.shouldEmitExpanded);
|
||||
EXPECT_TRUE(result.shouldEmitMinimized) << "Was minimized before collapse";
|
||||
}
|
||||
|
||||
TEST_F(CommandZoneStateTest, TrySetExpanded_NoChange_ReturnsNoChangeResult)
|
||||
{
|
||||
createPartnerZone();
|
||||
zone->trySetExpanded(true);
|
||||
ASSERT_TRUE(zone->isExpanded());
|
||||
|
||||
auto result = zone->trySetExpanded(true);
|
||||
|
||||
EXPECT_FALSE(result.geometryChanged) << "No change when already in target state";
|
||||
EXPECT_FALSE(result.shouldEmitExpanded);
|
||||
EXPECT_FALSE(result.shouldEmitMinimized);
|
||||
}
|
||||
|
||||
TEST_F(CommandZoneStateTest, TrySetExpanded_Blocked_ReturnsNoChangeResult)
|
||||
{
|
||||
createPartnerZone();
|
||||
zone->trySetExpanded(true);
|
||||
ASSERT_TRUE(zone->isExpanded());
|
||||
|
||||
auto result = zone->trySetExpanded(false, true);
|
||||
|
||||
EXPECT_FALSE(result.geometryChanged) << "Collapse blocked";
|
||||
EXPECT_TRUE(zone->isExpanded()) << "Should still be expanded";
|
||||
}
|
||||
|
||||
TEST_F(CommandZoneStateTest, TryToggleMinimized_ReturnsCorrectResult)
|
||||
{
|
||||
createPrimaryZone();
|
||||
|
||||
auto result = zone->tryToggleMinimized();
|
||||
|
||||
EXPECT_TRUE(result.geometryChanged);
|
||||
EXPECT_FALSE(result.shouldEmitExpanded) << "Minimized toggle doesn't change expanded state";
|
||||
EXPECT_TRUE(result.shouldEmitMinimized);
|
||||
}
|
||||
|
||||
// ============ Edge Cases ============
|
||||
|
||||
TEST_F(CommandZoneStateTest, PartnerCollapsedMinimized_ZeroHeight)
|
||||
{
|
||||
createPartnerZone(100.0);
|
||||
ASSERT_FALSE(zone->isExpanded());
|
||||
|
||||
zone->tryToggleMinimized();
|
||||
|
||||
EXPECT_DOUBLE_EQ(0.0, zone->currentHeight()) << "Collapsed partner zone should be 0 height even when minimized";
|
||||
}
|
||||
|
||||
TEST_F(CommandZoneStateTest, DifferentZoneHeight)
|
||||
{
|
||||
createPrimaryZone(200.0);
|
||||
|
||||
EXPECT_DOUBLE_EQ(200.0, zone->currentHeight());
|
||||
EXPECT_DOUBLE_EQ(200.0, zone->getZoneHeight());
|
||||
|
||||
zone->tryToggleMinimized();
|
||||
|
||||
EXPECT_DOUBLE_EQ(50.0, zone->currentHeight()) << "25% of 200 should be 50";
|
||||
}
|
||||
|
||||
TEST_F(CommandZoneStateTest, NoChange_StaticMethod)
|
||||
{
|
||||
auto result = StateChangeResult::noChange();
|
||||
|
||||
EXPECT_FALSE(result.geometryChanged);
|
||||
EXPECT_FALSE(result.shouldEmitExpanded);
|
||||
EXPECT_FALSE(result.shouldEmitMinimized);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue