Preview token edits live and preserve custom drafts

This commit is contained in:
Bryce! 2026-11-01 20:08:04 -05:00
parent e08402ac94
commit 7e9c209e9a
7 changed files with 296 additions and 29 deletions

View file

@ -8,12 +8,25 @@
#include "gtest/gtest.h"
#include <QCoreApplication>
#include <QList>
#include <QLoggingCategory>
#include <QSignalSpy>
#include <QStandardPaths>
#include <QTemporaryDir>
#include <libcockatrice/card/card_info.h>
#include <libcockatrice/card/database/card_database.h>
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/game/abstract_game.h>
#include <libcockatrice/game/board/card_state.h>
#include <libcockatrice/game/game_state.h>
#include <libcockatrice/game/player/player_actions.h>
#include <libcockatrice/game/player/player_logic.h>
#include <libcockatrice/game/player/player_manager.h>
#include <libcockatrice/game/zones/hand_zone_logic.h>
#include <libcockatrice/network/client/abstract/abstract_client.h>
#include <libcockatrice/protocol/pb/command_create_token.pb.h>
#include <libcockatrice/protocol/pb/commands.pb.h>
#include <libcockatrice/protocol/pending_command.h>
namespace
{
@ -25,6 +38,57 @@ CardRef cardRefFor(const QString &name)
return cardRef;
}
// Capture the real serialized command at the transport boundary. PlayerActions,
// GameEventHandler and PendingCommand retain their production behavior.
class CapturingClient : public AbstractClient
{
public:
QList<CommandContainer> commands;
~CapturingClient() override
{
for (auto *command : pendingCommands) {
delete command;
}
}
protected:
void sendCommandContainer(const CommandContainer &command) override
{
commands.append(command);
}
};
class TokenCommandTest : public ::testing::Test
{
protected:
// The client outlives the game and its pending-command signal receivers.
CapturingClient client;
AbstractGame game{nullptr};
PlayerLogic *player = nullptr;
CardInfoPtr knownToken;
void SetUp() override
{
knownToken = CardInfo::newInstance(QStringLiteral("Token Command Test Goblin"), "", true, {}, {}, {}, {},
{.tableRow = 2});
knownToken->setColors(QStringLiteral("R"));
knownToken->setPowTough(QStringLiteral("2/2"));
CardDatabaseManager::getInstance()->addCard(knownToken);
game.gameState = new GameState(&game, 0, 0, false, {&client}, true, false, 0, false);
game.playerManager = new PlayerManager(&game, 0, false, false);
ServerInfo_User user;
user.set_name("Token command test player");
player = game.playerManager->addPlayer(0, user);
}
void TearDown() override
{
CardDatabaseManager::getInstance()->removeCard(knownToken);
}
};
} // namespace
TEST(CardStateTest, AttachmentGraphKeepsBothEndsConsistent)
@ -86,6 +150,64 @@ TEST(CardZoneLogicTest, AsksTheGraphicsSideToCreateCards)
EXPECT_TRUE(arguments.at(1).toBool());
}
TEST_F(TokenCommandTest, FaceDownKnownTokenDoesNotRestoreDatabasePowerToughness)
{
TokenInfo token;
token.name = knownToken->getName();
token.color = QString(QChar());
token.faceDown = true;
player->getPlayerActions()->actCreateToken(token);
ASSERT_EQ(client.commands.size(), 1);
ASSERT_EQ(client.commands.first().game_command_size(), 1);
const auto &gameCommand = client.commands.first().game_command(0);
ASSERT_TRUE(gameCommand.HasExtension(Command_CreateToken::ext));
const auto &command = gameCommand.GetExtension(Command_CreateToken::ext);
EXPECT_EQ(command.card_name(), knownToken->getName().toStdString());
EXPECT_TRUE(command.face_down());
EXPECT_TRUE(command.pt().empty());
player->getPlayerActions()->actCreateAnotherToken();
ASSERT_EQ(client.commands.size(), 2);
const auto &repeat = client.commands.last().game_command(0).GetExtension(Command_CreateToken::ext);
EXPECT_TRUE(repeat.face_down());
EXPECT_TRUE(repeat.pt().empty());
}
TEST_F(TokenCommandTest, FaceUpKnownAliasKeepsCanonicalNameAndDefaultPowerToughness)
{
TokenInfo token;
token.name = QStringLiteral("token command test goblin");
player->getPlayerActions()->actCreateToken(token);
ASSERT_EQ(client.commands.size(), 1);
ASSERT_EQ(client.commands.first().game_command_size(), 1);
const auto &gameCommand = client.commands.first().game_command(0);
ASSERT_TRUE(gameCommand.HasExtension(Command_CreateToken::ext));
const auto &command = gameCommand.GetExtension(Command_CreateToken::ext);
EXPECT_EQ(command.card_name(), knownToken->getName().toStdString());
EXPECT_FALSE(command.face_down());
EXPECT_EQ(command.pt(), "2/2");
}
TEST_F(TokenCommandTest, FaceUpKnownTokenPreservesExplicitPowerToughness)
{
TokenInfo token;
token.name = knownToken->getName();
token.pt = QStringLiteral("7/9");
token.annotation = QStringLiteral("Flying");
player->getPlayerActions()->actCreateToken(token);
ASSERT_EQ(client.commands.size(), 1);
ASSERT_EQ(client.commands.first().game_command_size(), 1);
const auto &gameCommand = client.commands.first().game_command(0);
ASSERT_TRUE(gameCommand.HasExtension(Command_CreateToken::ext));
const auto &command = gameCommand.GetExtension(Command_CreateToken::ext);
EXPECT_FALSE(command.face_down());
EXPECT_EQ(command.pt(), "7/9");
EXPECT_EQ(command.annotation(), "Flying");
}
int main(int argc, char **argv)
{
// Keep SettingsCache reads and writes away from the real user profile: the game logic still