mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-07 10:33:24 -08:00
Seeding themes/<name>/fonts.toml made getAvailableThemes() resolve the user dir, which shadowed the shipped theme.cfg and stylesheet: styleName came back empty and QSS was cleared. Fall back to the shipped theme dir for theme.cfg and QSS when the user dir exists, and resolve CardText sizes through FontConfig's 6..72 range so a migrated 80..100 renders at the size the editor shows. Insert CardTextMaxSize inside [Fonts] when another section follows.
1237 lines
55 KiB
C++
1237 lines
55 KiB
C++
#include "gtest/gtest.h"
|
|
#include <QByteArray>
|
|
#include <QByteArrayView>
|
|
#include <QChar>
|
|
#include <QColor>
|
|
#include <QCoreApplication>
|
|
#include <QDate>
|
|
#include <QDir>
|
|
#include <QFile>
|
|
#include <QIODevice>
|
|
#include <QList>
|
|
#include <QSettings>
|
|
#include <QString>
|
|
#include <QStringList>
|
|
#include <QStringLiteral>
|
|
#include <QTemporaryDir>
|
|
#include <QVariant>
|
|
#include <QtGlobal>
|
|
#include <libcockatrice/settings/settings_migration.h>
|
|
#include <qnamespace.h>
|
|
#include <string>
|
|
|
|
namespace
|
|
{
|
|
|
|
class SettingsMigrationTest : public ::testing::Test
|
|
{
|
|
protected:
|
|
QTemporaryDir tempDir;
|
|
QString settingsPath;
|
|
|
|
void SetUp() override
|
|
{
|
|
// Mirror the production layout: the settings folder lives inside the data folder, so
|
|
// migration backups (siblings of settings/) stay isolated inside tempDir instead of
|
|
// landing in the shared system temp directory.
|
|
settingsPath = tempDir.path() + "/settings/";
|
|
QDir().mkpath(settingsPath);
|
|
|
|
// Isolate the settings used by the legacy migration tests inside the temporary
|
|
// directory so the tests never read or write the real user config (registry on
|
|
// Windows, plist on macOS, .conf on Linux), which would otherwise be shared
|
|
// across CI jobs and flaky. Default-format QSettings is forced to IniFormat and
|
|
// its UserScope path is redirected here; setPath wins over the XDG_CONFIG_HOME
|
|
// default on Unix, so every platform resolves to <tempDir>/config/...
|
|
const QString configDir = tempDir.path() + "/config";
|
|
QDir().mkpath(configDir);
|
|
qputenv("XDG_CONFIG_HOME", configDir.toUtf8());
|
|
QSettings::setPath(QSettings::IniFormat, QSettings::UserScope, configDir);
|
|
}
|
|
|
|
bool fileExists(const QString &name) const
|
|
{
|
|
return QFile::exists(settingsPath + name);
|
|
}
|
|
|
|
QVariant readFromIni(const QString &fileName, const QString &key) const
|
|
{
|
|
QSettings ini(settingsPath + fileName, QSettings::IniFormat);
|
|
return ini.value(key);
|
|
}
|
|
|
|
// Checks for the exact legacy key in the raw INI content. On Windows, INI keys are
|
|
// case-insensitive, so reading back a lower-case legacy key would match its migrated
|
|
// camelCase counterpart and hide regressions. Checking the file bytes directly keeps
|
|
// the comparison case-sensitive on every platform.
|
|
bool iniFileHasKeyCaseSensitive(const QString &fileName, const QString &key) const
|
|
{
|
|
QFile f(settingsPath + fileName);
|
|
if (!f.open(QIODevice::ReadOnly)) {
|
|
return false;
|
|
}
|
|
const QString valueName = key.section('/', -1);
|
|
const QStringList lines = QString::fromUtf8(f.readAll()).split('\n');
|
|
for (const auto &line : lines) {
|
|
if (line.startsWith(valueName + '=')) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// The migration backups are timestamped, so tests address them by index rather than by name.
|
|
QStringList backupFolderNames() const
|
|
{
|
|
return QDir(tempDir.path())
|
|
.entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name)
|
|
.filter(QStringLiteral("settings-backup-"));
|
|
}
|
|
|
|
QVariant readFromBackup(const QString &folderName, const QString &fileName, const QString &key) const
|
|
{
|
|
QSettings ini(QDir::cleanPath(tempDir.path() + '/' + folderName + '/' + fileName), QSettings::IniFormat);
|
|
return ini.value(key);
|
|
}
|
|
|
|
bool backupFileExists(const QString &folderName, const QString &fileName) const
|
|
{
|
|
return QFile::exists(QDir::cleanPath(tempDir.path() + '/' + folderName + '/' + fileName));
|
|
}
|
|
};
|
|
|
|
TEST_F(SettingsMigrationTest, NoGlobalIniDoesNothing)
|
|
{
|
|
bool result = SettingsMigration::migrateSettingsFromGlobalIni(settingsPath);
|
|
ASSERT_FALSE(result);
|
|
}
|
|
|
|
TEST_F(SettingsMigrationTest, EmptyGlobalIniGetsBackedUp)
|
|
{
|
|
{
|
|
QFile f(settingsPath + "global.ini");
|
|
ASSERT_TRUE(f.open(QIODevice::WriteOnly));
|
|
f.close();
|
|
}
|
|
bool result = SettingsMigration::migrateSettingsFromGlobalIni(settingsPath);
|
|
ASSERT_TRUE(result);
|
|
ASSERT_TRUE(fileExists("global.ini.old"));
|
|
ASSERT_TRUE(fileExists("global.ini"));
|
|
|
|
// No per-file INIs should be created
|
|
ASSERT_FALSE(fileExists("tabs.ini"));
|
|
ASSERT_FALSE(fileExists("sound.ini"));
|
|
|
|
// Sentinel should be in the new global.ini
|
|
ASSERT_EQ(readFromIni("global.ini", "migration/perfile_complete"), QVariant(true));
|
|
}
|
|
|
|
TEST_F(SettingsMigrationTest, MigratesAllSettingsGroups)
|
|
{
|
|
{
|
|
QSettings g(settingsPath + "global.ini", QSettings::IniFormat);
|
|
|
|
// tabs
|
|
g.setValue("tabs/visualDeckStorage", false);
|
|
g.setValue("tabs/server", true);
|
|
g.setValue("tabs/account", true);
|
|
g.setValue("tabs/deckStorage", true);
|
|
g.setValue("tabs/replays", false);
|
|
g.setValue("tabs/admin", false);
|
|
g.setValue("tabs/log", true);
|
|
|
|
// sound
|
|
g.setValue("sound/enabled", true);
|
|
g.setValue("sound/theme", "custom_theme");
|
|
g.setValue("sound/mastervolume", 75);
|
|
|
|
// game
|
|
g.beginGroup("game");
|
|
g.setValue("maxplayers", 4);
|
|
g.setValue("gamedescription", "test game");
|
|
g.setValue("remembergamesettings", false);
|
|
g.setValue("gametypes", "commander");
|
|
g.setValue("onlybuddies", true);
|
|
g.endGroup();
|
|
|
|
// localgameoptions
|
|
g.beginGroup("localgameoptions");
|
|
g.setValue("maxplayers", 2);
|
|
g.setValue("startinglifetotal", 40);
|
|
g.endGroup();
|
|
|
|
// chat
|
|
g.beginGroup("chat");
|
|
g.setValue("mention", false);
|
|
g.setValue("mentioncolor", "FF0000");
|
|
g.setValue("showmessagepopups", false);
|
|
g.setValue("mentioncompleter", false);
|
|
g.setValue("roomhistory", false);
|
|
g.setValue("highlightcolor", "00FF00");
|
|
g.endGroup();
|
|
|
|
// legacy highlight words (under [personal])
|
|
g.setValue("personal/highlightWords", "alpha beta");
|
|
|
|
// cache storage (under [personal] group)
|
|
g.setValue("personal/pixmapCacheSize", 1024);
|
|
g.setValue("personal/networkCacheSize", 2048);
|
|
g.setValue("personal/redirectCacheTtl", 5);
|
|
g.setValue("personal/cardPictureLoaderCacheMethod", 1);
|
|
g.setValue("personal/localCardImageStorageNamingScheme", 2);
|
|
|
|
// updates (under [personal] group)
|
|
g.setValue("personal/startupUpdateCheck", false);
|
|
g.setValue("personal/startupCardUpdateCheckPromptForUpdate", false);
|
|
g.setValue("personal/startupCardUpdateCheckAlwaysUpdate", true);
|
|
g.setValue("personal/cardUpdateCheckInterval", 14);
|
|
g.setValue("personal/lastCardUpdateCheck", QDate(2024, 1, 1));
|
|
g.setValue("personal/alwaysEnableNewSets", true);
|
|
g.setValue("personal/updatenotification", false);
|
|
g.setValue("personal/newversionnotification", false);
|
|
|
|
// personal
|
|
g.setValue("personal/lang", "de");
|
|
|
|
// downloads (previously under [personal])
|
|
g.setValue("personal/picturedownload", true);
|
|
g.setValue("personal/downloadspoilers", true);
|
|
|
|
// interface (previously under [personal])
|
|
g.setValue("personal/showStatusBar", true);
|
|
|
|
// theme
|
|
g.setValue("theme/name", "custom_theme");
|
|
g.setValue("game/maxfontsize", 14);
|
|
|
|
// appearance
|
|
g.setValue("appearance/styleUserList", false);
|
|
g.setValue("home/background/displayCardName", false);
|
|
g.setValue("menu/showshortcuts", false);
|
|
g.setValue("menu/showgameselectorfiltertoolbar", false);
|
|
|
|
// deck editor
|
|
g.setValue("editor/openDeckInNewTab", false);
|
|
g.setValue("interface/deckeditortagswidgetvisible", false);
|
|
g.setValue("interface/defaultDeckEditorType", 0);
|
|
g.setValue("interface/visualdeckeditorsamplehandsize", 5);
|
|
|
|
// personal home
|
|
g.setValue("home/background", "custom_bg");
|
|
g.setValue("home/background/shuffleTimer", 30);
|
|
|
|
// personal tipOfDay
|
|
g.setValue("tipOfDay/showTips", false);
|
|
g.setValue("tipOfDay/seenTips", QStringList{"1", "2", "3"});
|
|
|
|
// network
|
|
g.setValue("personal/keepalive", 10);
|
|
g.setValue("personal/timeout", 30);
|
|
g.setValue("personal/clientid", "test-client-id");
|
|
g.setValue("personal/clientversion", "test-client-version");
|
|
g.setValue("interface/knownmissingfeatures", "feature1,feature2");
|
|
|
|
// cards
|
|
g.setValue("cards/displaycardnames", false);
|
|
g.setValue("cards/roundcardcorners", false);
|
|
g.setValue("cards/overrideallcardartwithpersonalpreference", true);
|
|
g.setValue("cards/bumpsetswithcardsindecktotop", false);
|
|
g.setValue("cards/autorotatesidewayslayoutcards", false);
|
|
g.setValue("cards/tapanimation", true);
|
|
g.setValue("cards/scaleCards", false);
|
|
g.setValue("cards/verticalCardOverlapPercent", 42);
|
|
g.setValue("cards/cardinfoviewmode", 1);
|
|
g.setValue("cards/printingselectorcardsize", 90);
|
|
g.setValue("cards/printingselectorsortorder", 3);
|
|
g.setValue("cards/printingselectornavigationbuttonsvisible", false);
|
|
// card counters (migrate into card_counters.ini)
|
|
g.setValue("cards/counters/0/color", QColor(Qt::red));
|
|
|
|
// interface
|
|
g.setValue("interface/usetearoffmenus", true);
|
|
g.setValue("interface/cardViewInitialRowsMax", 8);
|
|
g.setValue("interface/cardViewExpandedRowsMax", 12);
|
|
g.setValue("interface/closeEmptyCardView", false);
|
|
g.setValue("interface/focusCardViewSearchBar", false);
|
|
g.setValue("interface/keepGameChatFocus", true);
|
|
g.setValue("interface/notificationsenabled", false);
|
|
g.setValue("interface/specnotificationsenabled", true);
|
|
g.setValue("interface/buddyconnectnotificationsenabled", false);
|
|
g.setValue("interface/doubleclicktoplay", false);
|
|
g.setValue("interface/clickPlaysAllSelected", false);
|
|
g.setValue("interface/playtostack", false);
|
|
g.setValue("interface/doNotDeleteArrowsInSubPhases", false);
|
|
g.setValue("interface/startinghandsize", 5);
|
|
g.setValue("interface/annotatetokens", true);
|
|
g.setValue("interface/showlassoselectioncount", false);
|
|
g.setValue("interface/showpersistentselectioncount", false);
|
|
g.setValue("interface/tallyType", 2);
|
|
g.setValue("interface/leftjustified", true);
|
|
g.setValue("interface/min_players_multicolumn", 6);
|
|
g.setValue("interface/deckeditorbannercardcomboboxvisible", false);
|
|
// hand/table/replay/zoneview
|
|
g.setValue("hand/horizontal", true);
|
|
g.setValue("table/invert_vertical", true);
|
|
g.setValue("replay/rewindBufferingMs", 6000);
|
|
g.setValue("replay/fastForwardSpeed", 5);
|
|
g.setValue("zoneview/groupby", 2);
|
|
g.setValue("zoneview/sortby", 1);
|
|
g.setValue("zoneview/pileview", false);
|
|
|
|
// paths
|
|
g.beginGroup("paths");
|
|
g.setValue("decks", "/custom/decks");
|
|
g.setValue("pics", "/custom/pics");
|
|
g.endGroup();
|
|
|
|
// visual deck storage (under [interface] group)
|
|
g.setValue("interface/visualdeckstoragecardsize", 150);
|
|
g.setValue("interface/visualdeckstoragesortingorder", 2);
|
|
g.setValue("interface/visualdeckstorageshowfolders", false);
|
|
g.setValue("interface/visualdeckstorageshowtagfilter", false);
|
|
g.setValue("interface/visualdeckstoragedefaulttagslist", QStringList{"Alpha", "Beta"});
|
|
g.setValue("interface/visualdeckstoragesearchfoldernames", false);
|
|
g.setValue("interface/visualdeckstorageshowcoloridentity", false);
|
|
g.setValue("interface/visualdeckstorageshowbannercardcombobox", false);
|
|
g.setValue("interface/visualdeckstorageshowtagsondeckpreviews", false);
|
|
g.setValue("interface/visualdeckstoragedrawunusedcoloridentities", false);
|
|
g.setValue("interface/visualdeckstorageunusedcoloridentitiesopacity", 35);
|
|
g.setValue("interface/visualdeckstoragetooltiptype", 1);
|
|
g.setValue("interface/visualdeckstoragepromptforconversion", false);
|
|
g.setValue("interface/visualdeckstoragealwaysconvert", true);
|
|
g.setValue("interface/visualdeckstorageingame", false);
|
|
g.setValue("interface/visualdeckstorageselectionanimation", false);
|
|
g.setValue("interface/visualdatabasedisplayfiltertomostrecentsetsenabled", true);
|
|
g.setValue("interface/visualdatabasedisplayfiltertomostrecentsetsamount", 25);
|
|
|
|
// card sizes (migrate into cards_display.ini)
|
|
g.setValue("interface/visualdatabasedisplaycardsize", 80);
|
|
g.setValue("interface/visualdeckeditorcardsize", 70);
|
|
g.setValue("interface/edhreccardsize", 60);
|
|
g.setValue("interface/archidektpreviewsize", 50);
|
|
|
|
g.sync();
|
|
}
|
|
|
|
bool result = SettingsMigration::migrateSettingsFromGlobalIni(settingsPath);
|
|
ASSERT_TRUE(result);
|
|
|
|
// Original global.ini should be backed up
|
|
ASSERT_TRUE(fileExists("global.ini.old"));
|
|
|
|
// Verify each per-file INI was created and has correct values
|
|
ASSERT_TRUE(fileExists("tabs.ini"));
|
|
ASSERT_EQ(readFromIni("tabs.ini", "tabs/visualDeckStorage"), QVariant(false));
|
|
ASSERT_EQ(readFromIni("tabs.ini", "tabs/server"), QVariant(true));
|
|
ASSERT_EQ(readFromIni("tabs.ini", "tabs/log"), QVariant(true));
|
|
|
|
ASSERT_TRUE(fileExists("sound.ini"));
|
|
ASSERT_EQ(readFromIni("sound.ini", "sound/enabled"), QVariant(true));
|
|
ASSERT_EQ(readFromIni("sound.ini", "sound/theme"), QVariant("custom_theme"));
|
|
ASSERT_EQ(readFromIni("sound.ini", "sound/masterVolume"), QVariant(75));
|
|
|
|
ASSERT_TRUE(fileExists("game.ini"));
|
|
ASSERT_EQ(readFromIni("game.ini", "game/maxPlayers"), QVariant(4));
|
|
ASSERT_EQ(readFromIni("game.ini", "game/gameDescription"), QVariant("test game"));
|
|
ASSERT_EQ(readFromIni("game.ini", "game/rememberGameSettings"), QVariant(false));
|
|
ASSERT_EQ(readFromIni("game.ini", "game/gameTypes"), QVariant("commander"));
|
|
ASSERT_EQ(readFromIni("game.ini", "game/onlyBuddies"), QVariant(true));
|
|
ASSERT_EQ(readFromIni("game.ini", "localgameoptions/maxPlayers"), QVariant(2));
|
|
ASSERT_EQ(readFromIni("game.ini", "localgameoptions/startingLifeTotal"), QVariant(40));
|
|
|
|
ASSERT_TRUE(fileExists("chat.ini"));
|
|
ASSERT_EQ(readFromIni("chat.ini", "chat/mention"), QVariant(false));
|
|
ASSERT_EQ(readFromIni("chat.ini", "chat/mentionColor"), QVariant("FF0000"));
|
|
ASSERT_EQ(readFromIni("chat.ini", "chat/showMessagePopups"), QVariant(false));
|
|
ASSERT_EQ(readFromIni("chat.ini", "chat/mentionCompleter"), QVariant(false));
|
|
ASSERT_EQ(readFromIni("chat.ini", "chat/roomHistory"), QVariant(false));
|
|
ASSERT_EQ(readFromIni("chat.ini", "chat/highlightColor"), QVariant("00FF00"));
|
|
ASSERT_EQ(readFromIni("chat.ini", "chat/highlightWords"), QVariant("alpha beta"));
|
|
|
|
ASSERT_TRUE(fileExists("cache_storage.ini"));
|
|
ASSERT_EQ(readFromIni("cache_storage.ini", "cache_storage/pixmapCacheSize"), QVariant(1024));
|
|
ASSERT_EQ(readFromIni("cache_storage.ini", "cache_storage/networkCacheSize"), QVariant(2048));
|
|
ASSERT_EQ(readFromIni("cache_storage.ini", "cache_storage/redirectCacheTtl"), QVariant(5));
|
|
ASSERT_EQ(readFromIni("cache_storage.ini", "cache_storage/cardPictureLoaderCacheMethod"), QVariant(1));
|
|
ASSERT_EQ(readFromIni("cache_storage.ini", "cache_storage/localCardImageStorageNamingScheme"), QVariant(2));
|
|
|
|
ASSERT_TRUE(fileExists("updates.ini"));
|
|
ASSERT_EQ(readFromIni("updates.ini", "updates/startupUpdateCheck"), QVariant(false));
|
|
ASSERT_EQ(readFromIni("updates.ini", "updates/startupCardUpdateCheckPromptForUpdate"), QVariant(false));
|
|
ASSERT_EQ(readFromIni("updates.ini", "updates/startupCardUpdateCheckAlwaysUpdate"), QVariant(true));
|
|
ASSERT_EQ(readFromIni("updates.ini", "updates/cardUpdateCheckInterval"), QVariant(14));
|
|
ASSERT_EQ(readFromIni("updates.ini", "updates/lastCardUpdateCheck"), QVariant(QDate(2024, 1, 1)));
|
|
ASSERT_EQ(readFromIni("updates.ini", "updates/alwaysEnableNewSets"), QVariant(true));
|
|
ASSERT_EQ(readFromIni("updates.ini", "updates/updateNotification"), QVariant(false));
|
|
ASSERT_EQ(readFromIni("updates.ini", "updates/newVersionNotification"), QVariant(false));
|
|
|
|
ASSERT_TRUE(fileExists("personal.ini"));
|
|
ASSERT_EQ(readFromIni("personal.ini", "personal/lang"), QVariant("de"));
|
|
ASSERT_EQ(readFromIni("personal.ini", "tipOfDay/showTips"), QVariant(false));
|
|
ASSERT_EQ(readFromIni("personal.ini", "tipOfDay/seenTips"), QVariant(QStringList{"1", "2", "3"}));
|
|
|
|
ASSERT_TRUE(fileExists("downloads.ini"));
|
|
ASSERT_EQ(readFromIni("downloads.ini", "downloads/pictureDownload"), QVariant(true));
|
|
ASSERT_EQ(readFromIni("downloads.ini", "downloads/downloadSpoilers"), QVariant(true));
|
|
|
|
ASSERT_TRUE(fileExists("appearance.ini"));
|
|
ASSERT_EQ(readFromIni("appearance.ini", "appearance/themeName"), QVariant("custom_theme"));
|
|
ASSERT_EQ(readFromIni("appearance.ini", "appearance/maxFontSize"), QVariant(14));
|
|
ASSERT_EQ(readFromIni("appearance.ini", "appearance/styleUserList"), QVariant(false));
|
|
ASSERT_EQ(readFromIni("appearance.ini", "appearance/homeTabBackgroundSource"), QVariant("custom_bg"));
|
|
ASSERT_EQ(readFromIni("appearance.ini", "appearance/homeTabBackgroundShuffleFrequency"), QVariant(30));
|
|
ASSERT_EQ(readFromIni("appearance.ini", "appearance/homeTabDisplayCardName"), QVariant(false));
|
|
|
|
ASSERT_TRUE(fileExists("network.ini"));
|
|
ASSERT_EQ(readFromIni("network.ini", "network/keepAlive"), QVariant(10));
|
|
ASSERT_EQ(readFromIni("network.ini", "network/timeout"), QVariant(30));
|
|
ASSERT_EQ(readFromIni("network.ini", "network/clientId"), QVariant("test-client-id"));
|
|
ASSERT_EQ(readFromIni("network.ini", "network/clientVersion"), QVariant("test-client-version"));
|
|
ASSERT_EQ(readFromIni("network.ini", "network/knownMissingFeatures"), QVariant("feature1,feature2"));
|
|
|
|
ASSERT_TRUE(fileExists("cards_display.ini"));
|
|
ASSERT_EQ(readFromIni("cards_display.ini", "cards/displayCardNames"), QVariant(false));
|
|
ASSERT_EQ(readFromIni("cards_display.ini", "cards/roundCardCorners"), QVariant(false));
|
|
ASSERT_EQ(readFromIni("cards_display.ini", "cards/overrideAllCardArtWithPersonalPreference"), QVariant(true));
|
|
ASSERT_EQ(readFromIni("cards_display.ini", "cards/bumpSetsWithCardsInDeckToTop"), QVariant(false));
|
|
ASSERT_EQ(readFromIni("cards_display.ini", "cards/autoRotateSidewaysLayoutCards"), QVariant(false));
|
|
ASSERT_EQ(readFromIni("cards_display.ini", "cards/tapAnimation"), QVariant(true));
|
|
ASSERT_EQ(readFromIni("cards_display.ini", "cards/scaleCards"), QVariant(false));
|
|
ASSERT_EQ(readFromIni("cards_display.ini", "cards/verticalCardOverlapPercent"), QVariant(42));
|
|
ASSERT_EQ(readFromIni("cards_display.ini", "cards/cardInfoViewMode"), QVariant(1));
|
|
ASSERT_EQ(readFromIni("cards_display.ini", "cards/cardSize/printingSelector"), QVariant(90));
|
|
ASSERT_EQ(readFromIni("cards_display.ini", "cards/printingSelector/sortOrder"), QVariant(3));
|
|
ASSERT_EQ(readFromIni("cards_display.ini", "cards/printingSelector/navigationButtonsVisible"), QVariant(false));
|
|
ASSERT_EQ(readFromIni("cards_display.ini", "cards/cardSize/visualDeckStorage"), QVariant(150));
|
|
ASSERT_EQ(readFromIni("cards_display.ini", "cards/cardSize/visualDatabaseDisplay"), QVariant(80));
|
|
ASSERT_EQ(readFromIni("cards_display.ini", "cards/cardSize/visualDeckEditor"), QVariant(70));
|
|
ASSERT_EQ(readFromIni("cards_display.ini", "cards/cardSize/edhrec"), QVariant(60));
|
|
ASSERT_EQ(readFromIni("cards_display.ini", "cards/cardSize/archidektPreview"), QVariant(50));
|
|
ASSERT_EQ(readFromIni("cards_display.ini", "cards/cardSize/sampleHandSize"), QVariant(5));
|
|
|
|
ASSERT_TRUE(fileExists("card_counters.ini"));
|
|
ASSERT_EQ(readFromIni("card_counters.ini", "cards/counters/0/color").toString(), QColor(Qt::red).name());
|
|
ASSERT_FALSE(readFromIni("global.ini", "cards/counters/0/color").isValid());
|
|
|
|
ASSERT_TRUE(fileExists("interface.ini"));
|
|
ASSERT_EQ(readFromIni("interface.ini", "interface/useTearOffMenus"), QVariant(true));
|
|
ASSERT_EQ(readFromIni("interface.ini", "interface/cardViewInitialRowsMax"), QVariant(8));
|
|
ASSERT_EQ(readFromIni("interface.ini", "interface/cardViewExpandedRowsMax"), QVariant(12));
|
|
ASSERT_EQ(readFromIni("interface.ini", "interface/closeEmptyCardView"), QVariant(false));
|
|
ASSERT_EQ(readFromIni("interface.ini", "interface/focusCardViewSearchBar"), QVariant(false));
|
|
ASSERT_EQ(readFromIni("interface.ini", "interface/keepGameChatFocus"), QVariant(true));
|
|
ASSERT_EQ(readFromIni("interface.ini", "interface/notifications/enabled"), QVariant(false));
|
|
ASSERT_EQ(readFromIni("interface.ini", "interface/notifications/spectatorsEnabled"), QVariant(true));
|
|
ASSERT_EQ(readFromIni("interface.ini", "interface/notifications/buddyConnectEnabled"), QVariant(false));
|
|
ASSERT_EQ(readFromIni("interface.ini", "interface/doubleClickToPlay"), QVariant(false));
|
|
ASSERT_EQ(readFromIni("interface.ini", "interface/clickPlaysAllSelected"), QVariant(false));
|
|
ASSERT_EQ(readFromIni("interface.ini", "interface/playToStack"), QVariant(false));
|
|
ASSERT_EQ(readFromIni("interface.ini", "interface/doNotDeleteArrowsInSubPhases"), QVariant(false));
|
|
ASSERT_EQ(readFromIni("interface.ini", "interface/startingHandSize"), QVariant(5));
|
|
ASSERT_EQ(readFromIni("interface.ini", "interface/annotateTokens"), QVariant(true));
|
|
ASSERT_EQ(readFromIni("interface.ini", "interface/showLassoSelectionCount"), QVariant(false));
|
|
ASSERT_EQ(readFromIni("interface.ini", "interface/showPersistentSelectionCount"), QVariant(false));
|
|
ASSERT_EQ(readFromIni("interface.ini", "interface/tallyType"), QVariant(2));
|
|
ASSERT_EQ(readFromIni("interface.ini", "interface/leftJustified"), QVariant(true));
|
|
ASSERT_EQ(readFromIni("interface.ini", "interface/minPlayersMulticolumn"), QVariant(6));
|
|
ASSERT_EQ(readFromIni("interface.ini", "interface/showStatusBar"), QVariant(true));
|
|
ASSERT_EQ(readFromIni("interface.ini", "interface/showShortcuts"), QVariant(false));
|
|
ASSERT_EQ(readFromIni("interface.ini", "interface/showGameSelectorFilterToolbar"), QVariant(false));
|
|
ASSERT_EQ(readFromIni("interface.ini", "hand/horizontal"), QVariant(true));
|
|
ASSERT_EQ(readFromIni("interface.ini", "table/invertVertical"), QVariant(true));
|
|
ASSERT_EQ(readFromIni("interface.ini", "replay/rewindBufferingMs"), QVariant(6000));
|
|
ASSERT_EQ(readFromIni("interface.ini", "replay/fastForwardSpeed"), QVariant(5));
|
|
ASSERT_EQ(readFromIni("interface.ini", "zoneview/groupBy"), QVariant(2));
|
|
ASSERT_EQ(readFromIni("interface.ini", "zoneview/sortBy"), QVariant(1));
|
|
ASSERT_EQ(readFromIni("interface.ini", "zoneview/pileView"), QVariant(false));
|
|
|
|
ASSERT_TRUE(fileExists("deck_editor.ini"));
|
|
ASSERT_EQ(readFromIni("deck_editor.ini", "deckeditor/openDeckInNewTab"), QVariant(false));
|
|
ASSERT_EQ(readFromIni("deck_editor.ini", "deckeditor/bannerCardComboBoxVisible"), QVariant(false));
|
|
ASSERT_EQ(readFromIni("deck_editor.ini", "deckeditor/tagsWidgetVisible"), QVariant(false));
|
|
ASSERT_EQ(readFromIni("deck_editor.ini", "deckeditor/defaultDeckEditorType"), QVariant(0));
|
|
|
|
ASSERT_TRUE(fileExists("paths.ini"));
|
|
ASSERT_EQ(readFromIni("paths.ini", "paths/decks"), QVariant("/custom/decks"));
|
|
ASSERT_EQ(readFromIni("paths.ini", "paths/pics"), QVariant("/custom/pics"));
|
|
|
|
ASSERT_TRUE(fileExists("visual_deck_storage.ini"));
|
|
ASSERT_EQ(readFromIni("visual_deck_storage.ini", "interface/visualDeckStorage/sortingOrder"), QVariant(2));
|
|
ASSERT_EQ(readFromIni("visual_deck_storage.ini", "interface/visualDeckStorage/showFolders"), QVariant(false));
|
|
ASSERT_EQ(readFromIni("visual_deck_storage.ini", "interface/visualDeckStorage/showTagFilter"), QVariant(false));
|
|
ASSERT_EQ(readFromIni("visual_deck_storage.ini", "interface/visualDeckStorage/defaultTagsList"),
|
|
QVariant(QStringList{"Alpha", "Beta"}));
|
|
ASSERT_EQ(readFromIni("visual_deck_storage.ini", "interface/visualDeckStorage/searchFolderNames"), QVariant(false));
|
|
ASSERT_EQ(readFromIni("visual_deck_storage.ini", "interface/visualDeckStorage/showColorIdentity"), QVariant(false));
|
|
ASSERT_EQ(readFromIni("visual_deck_storage.ini", "interface/visualDeckStorage/showBannerCardComboBox"),
|
|
QVariant(false));
|
|
ASSERT_EQ(readFromIni("visual_deck_storage.ini", "interface/visualDeckStorage/showTagsOnDeckPreviews"),
|
|
QVariant(false));
|
|
ASSERT_EQ(readFromIni("visual_deck_storage.ini", "interface/visualDeckStorage/drawUnusedColorIdentities"),
|
|
QVariant(false));
|
|
ASSERT_EQ(readFromIni("visual_deck_storage.ini", "interface/visualDeckStorage/unusedColorIdentitiesOpacity"),
|
|
QVariant(35));
|
|
ASSERT_EQ(readFromIni("visual_deck_storage.ini", "interface/visualDeckStorage/tooltipType"), QVariant(1));
|
|
ASSERT_EQ(readFromIni("visual_deck_storage.ini", "interface/visualDeckStorage/promptForConversion"),
|
|
QVariant(false));
|
|
ASSERT_EQ(readFromIni("visual_deck_storage.ini", "interface/visualDeckStorage/alwaysConvert"), QVariant(true));
|
|
ASSERT_EQ(readFromIni("visual_deck_storage.ini", "interface/visualDeckStorage/inGame"), QVariant(false));
|
|
ASSERT_EQ(readFromIni("visual_deck_storage.ini", "interface/visualDeckStorage/selectionAnimation"),
|
|
QVariant(false));
|
|
ASSERT_EQ(readFromIni("visual_deck_storage.ini", "interface/visualDatabaseDisplay/filterToMostRecentSetsEnabled"),
|
|
QVariant(true));
|
|
ASSERT_EQ(readFromIni("visual_deck_storage.ini", "interface/visualDatabaseDisplay/filterToMostRecentSetsAmount"),
|
|
QVariant(25));
|
|
|
|
// No legacy flat keys should remain in the per-file INIs
|
|
ASSERT_FALSE(iniFileHasKeyCaseSensitive("visual_deck_storage.ini", "interface/visualdeckstorageshowfolders"));
|
|
ASSERT_FALSE(iniFileHasKeyCaseSensitive("visual_deck_storage.ini", "interface/visualdeckstoragecardsize"));
|
|
ASSERT_FALSE(iniFileHasKeyCaseSensitive("deck_editor.ini", "deckeditor/sampleHandSize"));
|
|
ASSERT_FALSE(iniFileHasKeyCaseSensitive("deck_editor.ini", "deckeditor/cardSize"));
|
|
ASSERT_FALSE(iniFileHasKeyCaseSensitive("interface.ini", "interface/notificationsenabled"));
|
|
ASSERT_FALSE(iniFileHasKeyCaseSensitive("cards_display.ini", "cards/printingselectorsortorder"));
|
|
ASSERT_FALSE(iniFileHasKeyCaseSensitive("cards_display.ini", "cards/visualDeckStorage/cardSize"));
|
|
|
|
// No legacy non-camelCase keys should remain in the per-file INIs
|
|
ASSERT_FALSE(iniFileHasKeyCaseSensitive("game.ini", "game/gamedescription"));
|
|
ASSERT_FALSE(iniFileHasKeyCaseSensitive("game.ini", "localgameoptions/maxplayers"));
|
|
ASSERT_FALSE(iniFileHasKeyCaseSensitive("chat.ini", "chat/roomhistory"));
|
|
ASSERT_FALSE(iniFileHasKeyCaseSensitive("chat.ini", "chat/highlightwords"));
|
|
ASSERT_FALSE(iniFileHasKeyCaseSensitive("sound.ini", "sound/mastervolume"));
|
|
ASSERT_FALSE(iniFileHasKeyCaseSensitive("downloads.ini", "downloads/picturedownload"));
|
|
ASSERT_FALSE(iniFileHasKeyCaseSensitive("network.ini", "network/keepalive"));
|
|
ASSERT_FALSE(iniFileHasKeyCaseSensitive("network.ini", "network/knownmissingfeatures"));
|
|
ASSERT_FALSE(iniFileHasKeyCaseSensitive("updates.ini", "updates/updatenotification"));
|
|
ASSERT_FALSE(iniFileHasKeyCaseSensitive("cards_display.ini", "cards/displaycardnames"));
|
|
ASSERT_FALSE(iniFileHasKeyCaseSensitive("cards_display.ini", "cards/cardinfoviewmode"));
|
|
ASSERT_FALSE(iniFileHasKeyCaseSensitive("interface.ini", "interface/usetearoffmenus"));
|
|
ASSERT_FALSE(iniFileHasKeyCaseSensitive("interface.ini", "interface/doubleclicktoplay"));
|
|
ASSERT_FALSE(iniFileHasKeyCaseSensitive("interface.ini", "interface/min_players_multicolumn"));
|
|
ASSERT_FALSE(iniFileHasKeyCaseSensitive("interface.ini", "table/invert_vertical"));
|
|
ASSERT_FALSE(iniFileHasKeyCaseSensitive("interface.ini", "zoneview/groupby"));
|
|
ASSERT_FALSE(iniFileHasKeyCaseSensitive("interface.ini", "zoneview/pileview"));
|
|
|
|
// Verify sentinel was written
|
|
ASSERT_EQ(readFromIni("global.ini", "migration/perfile_complete"), QVariant(true));
|
|
}
|
|
|
|
TEST_F(SettingsMigrationTest, SecondCallDoesNothing)
|
|
{
|
|
{
|
|
QSettings g(settingsPath + "global.ini", QSettings::IniFormat);
|
|
g.setValue("tabs/visualDeckStorage", true);
|
|
g.setValue("sound/enabled", true);
|
|
g.sync();
|
|
}
|
|
|
|
ASSERT_TRUE(SettingsMigration::migrateSettingsFromGlobalIni(settingsPath));
|
|
ASSERT_FALSE(SettingsMigration::migrateSettingsFromGlobalIni(settingsPath));
|
|
}
|
|
|
|
TEST_F(SettingsMigrationTest, SecondCallDoesNotOverwriteUserChanges)
|
|
{
|
|
{
|
|
QSettings g(settingsPath + "global.ini", QSettings::IniFormat);
|
|
g.setValue("tabs/visualDeckStorage", true);
|
|
g.setValue("sound/enabled", true);
|
|
g.sync();
|
|
}
|
|
|
|
ASSERT_TRUE(SettingsMigration::migrateSettingsFromGlobalIni(settingsPath));
|
|
|
|
// User changes the value after migration
|
|
{
|
|
QSettings tabsIni(settingsPath + "tabs.ini", QSettings::IniFormat);
|
|
tabsIni.setValue("tabs/visualDeckStorage", false);
|
|
tabsIni.sync();
|
|
}
|
|
|
|
// Second migration should NOT overwrite the user's change
|
|
ASSERT_FALSE(SettingsMigration::migrateSettingsFromGlobalIni(settingsPath));
|
|
|
|
ASSERT_EQ(readFromIni("tabs.ini", "tabs/visualDeckStorage"), QVariant(false));
|
|
}
|
|
|
|
TEST_F(SettingsMigrationTest, PartialMigrationOnlyMigratesPresentKeys)
|
|
{
|
|
{
|
|
QSettings g(settingsPath + "global.ini", QSettings::IniFormat);
|
|
g.setValue("tabs/visualDeckStorage", true);
|
|
g.setValue("tabs/server", false);
|
|
g.sync();
|
|
}
|
|
|
|
ASSERT_TRUE(SettingsMigration::migrateSettingsFromGlobalIni(settingsPath));
|
|
|
|
ASSERT_TRUE(fileExists("global.ini.old"));
|
|
ASSERT_TRUE(fileExists("tabs.ini"));
|
|
ASSERT_EQ(readFromIni("tabs.ini", "tabs/visualDeckStorage"), QVariant(true));
|
|
ASSERT_EQ(readFromIni("tabs.ini", "tabs/server"), QVariant(false));
|
|
|
|
// Other files should NOT exist since no keys were present for them
|
|
ASSERT_FALSE(fileExists("sound.ini"));
|
|
ASSERT_FALSE(fileExists("game.ini"));
|
|
ASSERT_FALSE(fileExists("chat.ini"));
|
|
}
|
|
|
|
TEST_F(SettingsMigrationTest, ThemeNameDefaultIsRewrittenToSystem)
|
|
{
|
|
{
|
|
QSettings g(settingsPath + "global.ini", QSettings::IniFormat);
|
|
g.setValue("theme/name", "Default");
|
|
g.sync();
|
|
}
|
|
|
|
ASSERT_TRUE(SettingsMigration::migrateSettingsFromGlobalIni(settingsPath));
|
|
|
|
ASSERT_TRUE(fileExists("appearance.ini"));
|
|
ASSERT_EQ(readFromIni("appearance.ini", "appearance/themeName"), QVariant("System"));
|
|
}
|
|
|
|
TEST_F(SettingsMigrationTest, OtherThemeNamesAreLeftAlone)
|
|
{
|
|
{
|
|
QSettings g(settingsPath + "global.ini", QSettings::IniFormat);
|
|
g.setValue("theme/name", "VelvetMarble");
|
|
g.sync();
|
|
}
|
|
|
|
ASSERT_TRUE(SettingsMigration::migrateSettingsFromGlobalIni(settingsPath));
|
|
|
|
ASSERT_TRUE(fileExists("appearance.ini"));
|
|
ASSERT_EQ(readFromIni("appearance.ini", "appearance/themeName"), QVariant("VelvetMarble"));
|
|
}
|
|
|
|
TEST_F(SettingsMigrationTest, KeyMapTranslationIsCorrect)
|
|
{
|
|
{
|
|
QSettings g(settingsPath + "global.ini", QSettings::IniFormat);
|
|
g.setValue("personal/cardUpdateCheckInterval", 30);
|
|
g.setValue("personal/updatereleasechannel", 1);
|
|
g.sync();
|
|
}
|
|
|
|
ASSERT_TRUE(SettingsMigration::migrateSettingsFromGlobalIni(settingsPath));
|
|
|
|
ASSERT_TRUE(fileExists("global.ini.old"));
|
|
ASSERT_TRUE(fileExists("updates.ini"));
|
|
// The key should be translated from "personal/cardUpdateCheckInterval" to "updates/cardUpdateCheckInterval"
|
|
ASSERT_EQ(readFromIni("updates.ini", "updates/cardUpdateCheckInterval"), QVariant(30));
|
|
ASSERT_EQ(readFromIni("updates.ini", "updates/updateReleaseChannel"), QVariant(1));
|
|
}
|
|
|
|
TEST_F(SettingsMigrationTest, CardsKeysKeepGroupPrefix)
|
|
{
|
|
{
|
|
QSettings g(settingsPath + "global.ini", QSettings::IniFormat);
|
|
g.setValue("cards/displaycardnames", false);
|
|
g.setValue("interface/deckeditorbannercardcomboboxvisible", true);
|
|
g.sync();
|
|
}
|
|
|
|
ASSERT_TRUE(SettingsMigration::migrateSettingsFromGlobalIni(settingsPath));
|
|
|
|
ASSERT_TRUE(fileExists("global.ini.old"));
|
|
ASSERT_TRUE(fileExists("cards_display.ini"));
|
|
ASSERT_TRUE(fileExists("deck_editor.ini"));
|
|
// "cards/displaycardnames" should be stored with its group prefix
|
|
ASSERT_EQ(readFromIni("cards_display.ini", "cards/displayCardNames"), QVariant(false));
|
|
// deck editor keys belong to the deck editor settings now
|
|
ASSERT_EQ(readFromIni("deck_editor.ini", "deckeditor/bannerCardComboBoxVisible"), QVariant(true));
|
|
}
|
|
|
|
TEST_F(SettingsMigrationTest, LegacyMigrationIsIdempotent)
|
|
{
|
|
{
|
|
QSettings nativeSettings;
|
|
nativeSettings.setValue("server/previoushostlogin", "test_user");
|
|
nativeSettings.setValue("server/auto_connect", true);
|
|
nativeSettings.sync();
|
|
}
|
|
|
|
ASSERT_TRUE(SettingsMigration::migrateLegacySettings(settingsPath));
|
|
|
|
// Change the migrated value
|
|
{
|
|
QSettings serversIni(settingsPath + "servers.ini", QSettings::IniFormat);
|
|
serversIni.setValue("server/previousHostLogin", "modified_user");
|
|
serversIni.sync();
|
|
}
|
|
|
|
// Second migration should NOT overwrite the change
|
|
ASSERT_FALSE(SettingsMigration::migrateLegacySettings(settingsPath));
|
|
ASSERT_EQ(readFromIni("servers.ini", "server/previousHostLogin"), QVariant("modified_user"));
|
|
}
|
|
|
|
TEST_F(SettingsMigrationTest, LegacyMigrationCamelCasesKeys)
|
|
{
|
|
{
|
|
QSettings nativeSettings;
|
|
nativeSettings.setValue("sets/AAA/sortkey", 2);
|
|
nativeSettings.setValue("sets/AAA/enabled", false);
|
|
nativeSettings.setValue("sets/AAA/isknown", false);
|
|
nativeSettings.setValue("server/previoushostlogin", "legacy_user");
|
|
nativeSettings.setValue("server/auto_connect", 1);
|
|
nativeSettings.setValue("server/fpport", "5080");
|
|
nativeSettings.setValue("messages/count", 1);
|
|
nativeSettings.setValue("messages/msg0", "hello");
|
|
nativeSettings.setValue("filter_games/hide_full_games", true);
|
|
nativeSettings.setValue("filter_games/min_players", 3);
|
|
nativeSettings.setValue("filter_games/max_players", 5);
|
|
nativeSettings.setValue("filter_games/game_type/deadbeef", true);
|
|
nativeSettings.sync();
|
|
}
|
|
|
|
ASSERT_TRUE(SettingsMigration::migrateLegacySettings(settingsPath));
|
|
|
|
ASSERT_TRUE(fileExists("cardDatabase.ini"));
|
|
ASSERT_EQ(readFromIni("cardDatabase.ini", "sets/AAA/sortKey"), QVariant(2));
|
|
ASSERT_EQ(readFromIni("cardDatabase.ini", "sets/AAA/enabled"), QVariant(false));
|
|
ASSERT_EQ(readFromIni("cardDatabase.ini", "sets/AAA/isKnown"), QVariant(false));
|
|
|
|
ASSERT_TRUE(fileExists("servers.ini"));
|
|
ASSERT_EQ(readFromIni("servers.ini", "server/previousHostLogin"), QVariant("legacy_user"));
|
|
ASSERT_EQ(readFromIni("servers.ini", "server/autoConnect"), QVariant(1));
|
|
ASSERT_EQ(readFromIni("servers.ini", "server/fpPort"), QVariant("5080"));
|
|
|
|
ASSERT_TRUE(fileExists("messages.ini"));
|
|
ASSERT_EQ(readFromIni("messages.ini", "messages/count"), QVariant(1));
|
|
ASSERT_EQ(readFromIni("messages.ini", "messages/msg0"), QVariant("hello"));
|
|
|
|
ASSERT_TRUE(fileExists("gamefilters.ini"));
|
|
ASSERT_EQ(readFromIni("gamefilters.ini", "filter_games/hideFullGames"), QVariant(true));
|
|
ASSERT_EQ(readFromIni("gamefilters.ini", "filter_games/minPlayers"), QVariant(3));
|
|
ASSERT_EQ(readFromIni("gamefilters.ini", "filter_games/maxPlayers"), QVariant(5));
|
|
ASSERT_EQ(readFromIni("gamefilters.ini", "filter_games/gameType/deadbeef"), QVariant(true));
|
|
}
|
|
|
|
TEST_F(SettingsMigrationTest, LegacyMigrationMapsCreatorNameFilterToHostNameFilter)
|
|
{
|
|
{
|
|
QSettings nativeSettings;
|
|
nativeSettings.setValue("filter_games/creator_name_filter", QStringList({"Alice", "Bob"}));
|
|
nativeSettings.sync();
|
|
}
|
|
|
|
ASSERT_TRUE(SettingsMigration::migrateLegacySettings(settingsPath));
|
|
|
|
ASSERT_TRUE(fileExists("gamefilters.ini"));
|
|
ASSERT_EQ(readFromIni("gamefilters.ini", "filter_games/hostNameFilter"), QVariant(QStringList({"Alice", "Bob"})));
|
|
}
|
|
|
|
TEST_F(SettingsMigrationTest, GameFiltersIniSnakeCaseMigratesToCamelCase)
|
|
{
|
|
{
|
|
QSettings filtersIni(settingsPath + "gamefilters.ini", QSettings::IniFormat);
|
|
filtersIni.setValue("filter_games/hide_full_games", true);
|
|
filtersIni.setValue("filter_games/hide_games_that_started", true);
|
|
filtersIni.setValue("filter_games/game_name_filter", "test game");
|
|
filtersIni.setValue("filter_games/creator_name_filter", QStringList({"Alice"}));
|
|
filtersIni.setValue("filter_games/game_type/deadbeef", true);
|
|
filtersIni.sync();
|
|
}
|
|
|
|
ASSERT_TRUE(SettingsMigration::migrateGameFiltersToCamelCase(settingsPath));
|
|
|
|
ASSERT_EQ(readFromIni("gamefilters.ini", "filter_games/hideFullGames"), QVariant(true));
|
|
ASSERT_EQ(readFromIni("gamefilters.ini", "filter_games/hideGamesThatStarted"), QVariant(true));
|
|
ASSERT_EQ(readFromIni("gamefilters.ini", "filter_games/gameNameFilter"), QVariant("test game"));
|
|
ASSERT_EQ(readFromIni("gamefilters.ini", "filter_games/hostNameFilter"), QVariant(QStringList({"Alice"})));
|
|
ASSERT_EQ(readFromIni("gamefilters.ini", "filter_games/gameType/deadbeef"), QVariant(true));
|
|
|
|
// Idempotent: a second run has nothing left to do
|
|
ASSERT_FALSE(SettingsMigration::migrateGameFiltersToCamelCase(settingsPath));
|
|
}
|
|
|
|
TEST_F(SettingsMigrationTest, GameFiltersIniOrphanCreatorNameFilterMigratesToHostName)
|
|
{
|
|
{
|
|
QSettings filtersIni(settingsPath + "gamefilters.ini", QSettings::IniFormat);
|
|
filtersIni.setValue("filter_games/creatorNameFilter", QStringList({"Alice"}));
|
|
filtersIni.sync();
|
|
}
|
|
|
|
ASSERT_TRUE(SettingsMigration::migrateGameFiltersToCamelCase(settingsPath));
|
|
ASSERT_EQ(readFromIni("gamefilters.ini", "filter_games/hostNameFilter"), QVariant(QStringList({"Alice"})));
|
|
ASSERT_FALSE(SettingsMigration::migrateGameFiltersToCamelCase(settingsPath));
|
|
}
|
|
|
|
TEST_F(SettingsMigrationTest, GameFiltersIniKeepsNewerCamelCaseValues)
|
|
{
|
|
{
|
|
QSettings filtersIni(settingsPath + "gamefilters.ini", QSettings::IniFormat);
|
|
filtersIni.setValue("filter_games/hide_full_games", true);
|
|
filtersIni.setValue("filter_games/hideFullGames", false);
|
|
filtersIni.sync();
|
|
}
|
|
|
|
// Nothing to do: the current-format key already exists and must win
|
|
ASSERT_FALSE(SettingsMigration::migrateGameFiltersToCamelCase(settingsPath));
|
|
ASSERT_EQ(readFromIni("gamefilters.ini", "filter_games/hideFullGames"), QVariant(false));
|
|
}
|
|
|
|
TEST_F(SettingsMigrationTest, LegacyMigrationEmptyNativeFormatWritesSentinel)
|
|
{
|
|
QSettings().clear();
|
|
QSettings().sync();
|
|
|
|
ASSERT_TRUE(SettingsMigration::migrateLegacySettings(settingsPath));
|
|
ASSERT_TRUE(fileExists("personal.ini"));
|
|
ASSERT_EQ(readFromIni("personal.ini", "migration/legacy_complete"), QVariant(true));
|
|
ASSERT_FALSE(fileExists("messages.ini"));
|
|
ASSERT_FALSE(fileExists("gamefilters.ini"));
|
|
ASSERT_FALSE(fileExists("cardDatabase.ini"));
|
|
|
|
// Second call should be a no-op
|
|
ASSERT_FALSE(SettingsMigration::migrateLegacySettings(settingsPath));
|
|
}
|
|
|
|
// --- migrateSettingsFromGlobalIni rename-failure test ---
|
|
|
|
TEST_F(SettingsMigrationTest, RenameFailureWithExistingBackup)
|
|
{
|
|
{
|
|
QSettings g(settingsPath + "global.ini", QSettings::IniFormat);
|
|
g.setValue("tabs/visualDeckStorage", true);
|
|
g.setValue("sound/enabled", true);
|
|
g.sync();
|
|
}
|
|
|
|
// Create a stale backup
|
|
{
|
|
QFile oldBackup(settingsPath + "global.ini.old");
|
|
ASSERT_TRUE(oldBackup.open(QIODevice::WriteOnly));
|
|
oldBackup.close();
|
|
}
|
|
|
|
// Migration should still succeed
|
|
ASSERT_TRUE(SettingsMigration::migrateSettingsFromGlobalIni(settingsPath));
|
|
|
|
// Per-file INIs should be created
|
|
ASSERT_TRUE(fileExists("tabs.ini"));
|
|
ASSERT_TRUE(fileExists("sound.ini"));
|
|
|
|
// Sentinel should be set in the new global.ini
|
|
ASSERT_EQ(readFromIni("global.ini", "migration/perfile_complete"), QVariant(true));
|
|
|
|
// Original data should be backed up (stale backup was replaced)
|
|
ASSERT_TRUE(fileExists("global.ini.old"));
|
|
ASSERT_EQ(readFromIni("global.ini.old", "tabs/visualDeckStorage"), QVariant(true));
|
|
|
|
// Second call should be a no-op
|
|
ASSERT_FALSE(SettingsMigration::migrateSettingsFromGlobalIni(settingsPath));
|
|
}
|
|
|
|
// --- pre-migration backup of the settings folder ---
|
|
|
|
TEST_F(SettingsMigrationTest, BackupHoldsPreMigrationStateOfPerDomainFiles)
|
|
{
|
|
// A user already migrated once, changed a setting, then ran an older build that wrote to
|
|
// global.ini again. Re-migrating clobbers the per-domain file, so the backup has to hold the
|
|
// pre-migration value.
|
|
{
|
|
QSettings tabs(settingsPath + "tabs.ini", QSettings::IniFormat);
|
|
tabs.setValue("tabs/visualDeckStorage", false);
|
|
tabs.sync();
|
|
}
|
|
{
|
|
QSettings g(settingsPath + "global.ini", QSettings::IniFormat);
|
|
g.setValue("tabs/visualDeckStorage", true);
|
|
g.sync();
|
|
}
|
|
|
|
const QString backupPath = SettingsMigration::backupSettingsDirectory(settingsPath);
|
|
ASSERT_FALSE(backupPath.isEmpty());
|
|
ASSERT_TRUE(SettingsMigration::migrateSettingsFromGlobalIni(settingsPath));
|
|
|
|
// The migration overwrote the user's change ...
|
|
ASSERT_EQ(readFromIni("tabs.ini", "tabs/visualDeckStorage"), QVariant(true));
|
|
|
|
// ... but the backup still holds it, so it can be restored by hand.
|
|
const QStringList backups = backupFolderNames();
|
|
ASSERT_EQ(backups.size(), 1);
|
|
ASSERT_EQ(readFromBackup(backups.at(0), "tabs.ini", "tabs/visualDeckStorage"), QVariant(false));
|
|
ASSERT_EQ(readFromBackup(backups.at(0), "global.ini", "tabs/visualDeckStorage"), QVariant(true));
|
|
}
|
|
|
|
TEST_F(SettingsMigrationTest, BackupIsASiblingOfTheSettingsFolder)
|
|
{
|
|
{
|
|
QSettings g(settingsPath + "global.ini", QSettings::IniFormat);
|
|
g.setValue("sound/enabled", true);
|
|
g.sync();
|
|
}
|
|
|
|
const QString backupPath = SettingsMigration::backupSettingsDirectory(settingsPath);
|
|
ASSERT_FALSE(backupPath.isEmpty());
|
|
|
|
const QDir backupDir(backupPath);
|
|
ASSERT_TRUE(backupDir.dirName().startsWith("settings-backup-"));
|
|
// Same parent as the settings folder, and not inside it, so a backup is never copied into
|
|
// another backup and "Open settings folder" shows only live files.
|
|
ASSERT_EQ(QDir::cleanPath(backupDir.absolutePath() + "/.."), QDir::cleanPath(tempDir.path()));
|
|
ASSERT_FALSE(backupDir.absolutePath().startsWith(QDir::cleanPath(settingsPath) + '/'));
|
|
ASSERT_TRUE(backupFileExists(backupDir.dirName(), "global.ini"));
|
|
}
|
|
|
|
TEST_F(SettingsMigrationTest, BackupCopiesNestedFolders)
|
|
{
|
|
ASSERT_TRUE(QDir().mkpath(settingsPath + "nested"));
|
|
{
|
|
QFile f(settingsPath + "nested/deep.ini");
|
|
ASSERT_TRUE(f.open(QIODevice::WriteOnly));
|
|
f.write("[visualDeckStorage]\nfirstDeck=1234\n");
|
|
}
|
|
{
|
|
QSettings g(settingsPath + "global.ini", QSettings::IniFormat);
|
|
g.setValue("sound/enabled", true);
|
|
g.sync();
|
|
}
|
|
|
|
const QString backupPath = SettingsMigration::backupSettingsDirectory(settingsPath);
|
|
ASSERT_FALSE(backupPath.isEmpty());
|
|
|
|
const QStringList backups = backupFolderNames();
|
|
ASSERT_EQ(backups.size(), 1);
|
|
ASSERT_TRUE(backupFileExists(backups.at(0), "nested/deep.ini"));
|
|
ASSERT_EQ(readFromBackup(backups.at(0), "nested/deep.ini", "visualDeckStorage/firstDeck"), QVariant("1234"));
|
|
}
|
|
|
|
TEST_F(SettingsMigrationTest, NoBackupWhenSettingsFolderIsEmpty)
|
|
{
|
|
ASSERT_TRUE(SettingsMigration::backupSettingsDirectory(settingsPath).isEmpty());
|
|
ASSERT_TRUE(backupFolderNames().isEmpty());
|
|
}
|
|
|
|
TEST_F(SettingsMigrationTest, NoBackupWhenEverySettingsFileIsEmpty)
|
|
{
|
|
// A fresh profile: QSettings has created global.ini, but it holds nothing worth preserving.
|
|
{
|
|
QFile f(settingsPath + "global.ini");
|
|
ASSERT_TRUE(f.open(QIODevice::WriteOnly));
|
|
f.close();
|
|
}
|
|
|
|
ASSERT_TRUE(SettingsMigration::backupSettingsDirectory(settingsPath).isEmpty());
|
|
ASSERT_TRUE(backupFolderNames().isEmpty());
|
|
}
|
|
|
|
TEST_F(SettingsMigrationTest, BackupFailsGracefullyOnMissingSettingsFolder)
|
|
{
|
|
ASSERT_TRUE(SettingsMigration::backupSettingsDirectory(tempDir.path() + "/does-not-exist/").isEmpty());
|
|
}
|
|
|
|
TEST_F(SettingsMigrationTest, BackupKeepsEveryPreviousSnapshot)
|
|
{
|
|
{
|
|
QSettings g(settingsPath + "global.ini", QSettings::IniFormat);
|
|
g.setValue("sound/enabled", true);
|
|
g.sync();
|
|
}
|
|
|
|
ASSERT_FALSE(SettingsMigration::backupSettingsDirectory(settingsPath).isEmpty());
|
|
ASSERT_FALSE(SettingsMigration::backupSettingsDirectory(settingsPath).isEmpty());
|
|
|
|
// A second snapshot in the same second must not merge into the first one.
|
|
const QStringList backups = backupFolderNames();
|
|
ASSERT_EQ(backups.size(), 2);
|
|
ASSERT_NE(backups.at(0), backups.at(1));
|
|
}
|
|
|
|
TEST_F(SettingsMigrationTest, IsMigrationPendingWhenGlobalIniHasKeys)
|
|
{
|
|
{
|
|
QSettings g(settingsPath + "global.ini", QSettings::IniFormat);
|
|
g.setValue("sound/enabled", true);
|
|
g.sync();
|
|
}
|
|
|
|
ASSERT_TRUE(SettingsMigration::isMigrationPending(settingsPath));
|
|
}
|
|
|
|
TEST_F(SettingsMigrationTest, IsNotMigrationPendingAfterSentinelIsSet)
|
|
{
|
|
{
|
|
QSettings g(settingsPath + "global.ini", QSettings::IniFormat);
|
|
g.setValue("sound/enabled", true);
|
|
g.sync();
|
|
}
|
|
ASSERT_TRUE(SettingsMigration::migrateSettingsFromGlobalIni(settingsPath));
|
|
|
|
// The sentinel-only global.ini and the legacy sentinel leave nothing to migrate.
|
|
{
|
|
QSettings personal(settingsPath + "personal.ini", QSettings::IniFormat);
|
|
personal.setValue("migration/legacy_complete", true);
|
|
personal.sync();
|
|
}
|
|
ASSERT_FALSE(SettingsMigration::isMigrationPending(settingsPath));
|
|
}
|
|
|
|
TEST_F(SettingsMigrationTest, IsMigrationPendingWhenOnlyLegacySettingsRemain)
|
|
{
|
|
QSettings().setValue("server/host", "example.com");
|
|
QSettings().sync();
|
|
|
|
ASSERT_TRUE(SettingsMigration::isMigrationPending(settingsPath));
|
|
}
|
|
|
|
TEST_F(SettingsMigrationTest, IsNotMigrationPendingWithoutLegacyData)
|
|
{
|
|
// Nothing to migrate: a fresh profile must not leave a backup folder behind on every start.
|
|
QSettings().clear();
|
|
QSettings().sync();
|
|
|
|
ASSERT_FALSE(SettingsMigration::isMigrationPending(settingsPath));
|
|
}
|
|
|
|
TEST_F(SettingsMigrationTest, AppearanceMaxFontSize_NonDefaultSeedsThemeAndDeletesKey)
|
|
{
|
|
const QString themesPath = tempDir.path() + "/themes";
|
|
ASSERT_TRUE(QDir().mkpath(themesPath + "/MyTheme"));
|
|
{
|
|
QSettings paths(settingsPath + "paths.ini", QSettings::IniFormat);
|
|
paths.setValue("paths/themes", themesPath);
|
|
paths.sync();
|
|
}
|
|
{
|
|
QSettings appearance(settingsPath + "appearance.ini", QSettings::IniFormat);
|
|
appearance.setValue("appearance/maxFontSize", 18);
|
|
appearance.sync();
|
|
}
|
|
|
|
ASSERT_TRUE(SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath));
|
|
|
|
ASSERT_FALSE(readFromIni("appearance.ini", "appearance/maxFontSize").isValid());
|
|
|
|
const QString fontsPath = themesPath + "/MyTheme/fonts.toml";
|
|
ASSERT_TRUE(QFile::exists(fontsPath));
|
|
QFile f(fontsPath);
|
|
ASSERT_TRUE(f.open(QIODevice::ReadOnly | QIODevice::Text));
|
|
const QString content = QString::fromUtf8(f.readAll());
|
|
EXPECT_TRUE(content.contains("CardTextMaxSize"));
|
|
EXPECT_TRUE(content.contains("18"));
|
|
// Size-only: the migration must not pin the family, or every CardText
|
|
// surface switches to Serif for users who never chose it.
|
|
EXPECT_FALSE(content.contains("Serif"));
|
|
|
|
ASSERT_FALSE(SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath));
|
|
}
|
|
|
|
TEST_F(SettingsMigrationTest, AppearanceMaxFontSize_SeedsActiveStockTheme)
|
|
{
|
|
// A stock theme resolves to the system directory, so there is no user theme
|
|
// folder to seed. The migration has to create the active theme's folder,
|
|
// otherwise the retired size is deleted without landing anywhere.
|
|
const QString themesPath = tempDir.path() + "/themes";
|
|
{
|
|
QSettings paths(settingsPath + "paths.ini", QSettings::IniFormat);
|
|
paths.setValue("paths/themes", themesPath);
|
|
paths.sync();
|
|
}
|
|
{
|
|
QSettings appearance(settingsPath + "appearance.ini", QSettings::IniFormat);
|
|
appearance.setValue("appearance/themeName", "Fabric");
|
|
appearance.setValue("appearance/maxFontSize", 18);
|
|
appearance.sync();
|
|
}
|
|
|
|
ASSERT_TRUE(SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath));
|
|
|
|
ASSERT_FALSE(readFromIni("appearance.ini", "appearance/maxFontSize").isValid());
|
|
|
|
const QString fontsPath = themesPath + "/Fabric/fonts.toml";
|
|
ASSERT_TRUE(QFile::exists(fontsPath));
|
|
QFile f(fontsPath);
|
|
ASSERT_TRUE(f.open(QIODevice::ReadOnly | QIODevice::Text));
|
|
const QString content = QString::fromUtf8(f.readAll());
|
|
EXPECT_TRUE(content.contains("CardTextMaxSize"));
|
|
EXPECT_TRUE(content.contains("18"));
|
|
}
|
|
|
|
TEST_F(SettingsMigrationTest, AppearanceMaxFontSize_KeepsExistingActiveTheme)
|
|
{
|
|
const QString themesPath = tempDir.path() + "/themes";
|
|
ASSERT_TRUE(QDir().mkpath(themesPath + "/Fabric"));
|
|
{
|
|
QFile f(themesPath + "/Fabric/fonts.toml");
|
|
ASSERT_TRUE(f.open(QIODevice::WriteOnly | QIODevice::Text));
|
|
QTextStream out(&f);
|
|
out << "[Fonts]\nCardText = \"Inter, 10\"\n";
|
|
}
|
|
{
|
|
QSettings paths(settingsPath + "paths.ini", QSettings::IniFormat);
|
|
paths.setValue("paths/themes", themesPath);
|
|
paths.sync();
|
|
}
|
|
{
|
|
QSettings appearance(settingsPath + "appearance.ini", QSettings::IniFormat);
|
|
appearance.setValue("appearance/themeName", "Fabric");
|
|
appearance.setValue("appearance/maxFontSize", 18);
|
|
appearance.sync();
|
|
}
|
|
|
|
ASSERT_TRUE(SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath));
|
|
|
|
QFile f(themesPath + "/Fabric/fonts.toml");
|
|
ASSERT_TRUE(f.open(QIODevice::ReadOnly | QIODevice::Text));
|
|
const QString content = QString::fromUtf8(f.readAll());
|
|
EXPECT_TRUE(content.contains("Inter, 10"));
|
|
EXPECT_FALSE(content.contains("18"));
|
|
}
|
|
|
|
TEST_F(SettingsMigrationTest, AppearanceMaxFontSize_DefaultDeletesKeyWithoutFiles)
|
|
{
|
|
const QString themesPath = tempDir.path() + "/themes";
|
|
ASSERT_TRUE(QDir().mkpath(themesPath + "/MyTheme"));
|
|
{
|
|
QSettings paths(settingsPath + "paths.ini", QSettings::IniFormat);
|
|
paths.setValue("paths/themes", themesPath);
|
|
paths.sync();
|
|
}
|
|
{
|
|
QSettings appearance(settingsPath + "appearance.ini", QSettings::IniFormat);
|
|
appearance.setValue("appearance/maxFontSize", 12);
|
|
appearance.sync();
|
|
}
|
|
|
|
ASSERT_TRUE(SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath));
|
|
ASSERT_FALSE(readFromIni("appearance.ini", "appearance/maxFontSize").isValid());
|
|
ASSERT_FALSE(QFile::exists(themesPath + "/MyTheme/fonts.toml"));
|
|
}
|
|
|
|
TEST_F(SettingsMigrationTest, AppearanceMaxFontSize_DoesNotOverwriteExplicitMax)
|
|
{
|
|
const QString themesPath = tempDir.path() + "/themes";
|
|
ASSERT_TRUE(QDir().mkpath(themesPath + "/MyTheme"));
|
|
{
|
|
QFile f(themesPath + "/MyTheme/fonts.toml");
|
|
ASSERT_TRUE(f.open(QIODevice::WriteOnly | QIODevice::Text));
|
|
QTextStream out(&f);
|
|
out << "[Fonts]\nCardText = \"Inter, 10\"\n";
|
|
}
|
|
{
|
|
QSettings paths(settingsPath + "paths.ini", QSettings::IniFormat);
|
|
paths.setValue("paths/themes", themesPath);
|
|
paths.sync();
|
|
}
|
|
{
|
|
QSettings appearance(settingsPath + "appearance.ini", QSettings::IniFormat);
|
|
appearance.setValue("appearance/maxFontSize", 18);
|
|
appearance.sync();
|
|
}
|
|
|
|
ASSERT_TRUE(SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath));
|
|
|
|
QFile f(themesPath + "/MyTheme/fonts.toml");
|
|
ASSERT_TRUE(f.open(QIODevice::ReadOnly | QIODevice::Text));
|
|
const QString content = QString::fromUtf8(f.readAll());
|
|
EXPECT_TRUE(content.contains("10"));
|
|
EXPECT_FALSE(content.contains("18"));
|
|
}
|
|
|
|
TEST_F(SettingsMigrationTest, AppearanceMaxFontSize_MergesWithOtherRoles)
|
|
{
|
|
const QString themesPath = tempDir.path() + "/themes";
|
|
ASSERT_TRUE(QDir().mkpath(themesPath + "/MyTheme"));
|
|
{
|
|
QFile f(themesPath + "/MyTheme/fonts.toml");
|
|
ASSERT_TRUE(f.open(QIODevice::WriteOnly | QIODevice::Text));
|
|
QTextStream out(&f);
|
|
out << "[Fonts]\nInterfaceDefault = \"Inter, 10\"\n";
|
|
}
|
|
{
|
|
QSettings paths(settingsPath + "paths.ini", QSettings::IniFormat);
|
|
paths.setValue("paths/themes", themesPath);
|
|
paths.sync();
|
|
}
|
|
{
|
|
QSettings appearance(settingsPath + "appearance.ini", QSettings::IniFormat);
|
|
appearance.setValue("appearance/maxFontSize", 20);
|
|
appearance.sync();
|
|
}
|
|
|
|
ASSERT_TRUE(SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath));
|
|
|
|
QFile f(themesPath + "/MyTheme/fonts.toml");
|
|
ASSERT_TRUE(f.open(QIODevice::ReadOnly | QIODevice::Text));
|
|
const QString content = QString::fromUtf8(f.readAll());
|
|
EXPECT_TRUE(content.contains("InterfaceDefault"));
|
|
EXPECT_TRUE(content.contains("CardTextMaxSize"));
|
|
EXPECT_TRUE(content.contains("20"));
|
|
}
|
|
|
|
TEST_F(SettingsMigrationTest, AppearanceMaxFontSize_InsertsIntoFontsSectionNotAfterIt)
|
|
{
|
|
// A hand-edited fonts.toml may keep [Fonts] first and another section after
|
|
// it. Appending the seeded key at EOF would land it in that trailing
|
|
// section, where the font parser never reads it.
|
|
const QString themesPath = tempDir.path() + "/themes";
|
|
ASSERT_TRUE(QDir().mkpath(themesPath + "/MyTheme"));
|
|
{
|
|
QFile f(themesPath + "/MyTheme/fonts.toml");
|
|
ASSERT_TRUE(f.open(QIODevice::WriteOnly | QIODevice::Text));
|
|
QTextStream out(&f);
|
|
out << "[Fonts]\n"
|
|
"# keep this comment\n"
|
|
"InterfaceDefault = \"Inter, 10\"\n"
|
|
"\n"
|
|
"[Other]\n"
|
|
"Keep = \"me\"\n";
|
|
}
|
|
{
|
|
QSettings paths(settingsPath + "paths.ini", QSettings::IniFormat);
|
|
paths.setValue("paths/themes", themesPath);
|
|
paths.sync();
|
|
}
|
|
{
|
|
QSettings appearance(settingsPath + "appearance.ini", QSettings::IniFormat);
|
|
appearance.setValue("appearance/maxFontSize", 18);
|
|
appearance.sync();
|
|
}
|
|
|
|
ASSERT_TRUE(SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath));
|
|
|
|
QFile f(themesPath + "/MyTheme/fonts.toml");
|
|
ASSERT_TRUE(f.open(QIODevice::ReadOnly | QIODevice::Text));
|
|
const QString content = QString::fromUtf8(f.readAll());
|
|
ASSERT_TRUE(content.contains("CardTextMaxSize"));
|
|
EXPECT_TRUE(content.contains("18"));
|
|
// The new key belongs to [Fonts], not to the section that follows it ...
|
|
const int keyPos = content.indexOf("CardTextMaxSize");
|
|
const int otherSectionPos = content.indexOf("[Other]");
|
|
ASSERT_GE(keyPos, 0);
|
|
ASSERT_GE(otherSectionPos, 0);
|
|
EXPECT_LT(keyPos, otherSectionPos);
|
|
// ... and the file keeps its own comment, ordering and trailing section.
|
|
EXPECT_TRUE(content.contains("# keep this comment"));
|
|
EXPECT_TRUE(content.contains("InterfaceDefault = \"Inter, 10\""));
|
|
EXPECT_TRUE(content.endsWith("[Other]\nKeep = \"me\"\n"));
|
|
}
|
|
|
|
TEST_F(SettingsMigrationTest, AppearanceMaxFontSize_AbsentKeyDoesNothing)
|
|
{
|
|
ASSERT_FALSE(SettingsMigration::migrateAppearanceMaxFontSizeToTheme(settingsPath));
|
|
}
|
|
|
|
TEST_F(SettingsMigrationTest, IsMigrationPendingWhenMaxFontSizePresent)
|
|
{
|
|
{
|
|
QSettings appearance(settingsPath + "appearance.ini", QSettings::IniFormat);
|
|
appearance.setValue("appearance/maxFontSize", 18);
|
|
appearance.sync();
|
|
}
|
|
|
|
ASSERT_TRUE(SettingsMigration::isMigrationPending(settingsPath));
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
::testing::InitGoogleTest(&argc, argv);
|
|
QCoreApplication app(argc, argv);
|
|
app.setOrganizationName("CockatriceTest");
|
|
app.setApplicationName("SettingsMigrationTest");
|
|
QSettings::setDefaultFormat(QSettings::IniFormat);
|
|
return RUN_ALL_TESTS();
|
|
}
|