Load plain improvements (#3422)
* improve loading from plain text Fixes the loadFromStream_Plain function that is used to load plain text decklists. The rewritten function uses more regexes and is a bit cleaner. This fixes multiple bugs with loading the various sources of decklists. Note that the new function still has a few issues that are shared with the original version like creating duplicate cards. * clang format comments apparently clang-format even complains about the spacing in your comments * refactor loading_from_clipboard tests Remove all heap allocation and use references. Use std::pair and std::string so gtest will show the cardnames in error messages. (note that using QPair or QString does not work with gtest) Improve the last two testcases to include weird names; and name and comments testing. Remove empty header file. * fix compatibility with more formats skip "sideboard" line include everything in mainboard when there are multiple empty lines add removal of the mwdeck cardversion selector in round braces add replacal of lowercase ae combination that should never occur Set cardname to lowercase as apparently our checks are hardcoded to only accept lowercase. * remove bugged test The current load from plain is simply broken, removed checking the comments for correct contents. * rework load_from_clipboard tests again rework the test to have less code duplication add more tests and more special cases note that text is still all lowercase * improve loading from plain text Fixes the loadFromStream_Plain function that is used to load plain text decklists. The rewritten function uses more regexes and is a bit cleaner. This fixes multiple bugs with loading the various sources of decklists. Note that the new function still has a few issues that are shared with the original version like creating duplicate cards. * clang format comments apparently clang-format even complains about the spacing in your comments * refactor loading_from_clipboard tests Remove all heap allocation and use references. Use std::pair and std::string so gtest will show the cardnames in error messages. (note that using QPair or QString does not work with gtest) Improve the last two testcases to include weird names; and name and comments testing. Remove empty header file. * fix compatibility with more formats skip "sideboard" line include everything in mainboard when there are multiple empty lines add removal of the mwdeck cardversion selector in round braces add replacal of lowercase ae combination that should never occur Set cardname to lowercase as apparently our checks are hardcoded to only accept lowercase. * remove bugged test The current load from plain is simply broken, removed checking the comments for correct contents. * rework load_from_clipboard tests again rework the test to have less code duplication add more tests and more special cases note that text is still all lowercase * remove forcing of lowercase cardnames Cardnames in DeckList::loadFromStream_Plain will no longer be forced lowercase if they aren't found in the database. Empty lines in the comments of plaintext decklists will not be skipped. The loading_from_clipboard_test gets its functions declared in a separate header "clipboard_testing.h". Add more edgecase tests. Refactor code. * add old QHash version support QT 5.5 does not support using initializer lists for QHash. Implement a preprocessor version check for conditionally using inserts instead of a const with initializer list. * add old QHash version support QT 5.5 does not support using initializer lists for QHash. Implement a preprocessor version check for conditionally using [] access instead of a const with initializer list. * add qHash on QRegularExpression below QT 5.6 Apparently QRegularExpression can't be hashed in lower QT versions, so we add our own hash function, and everyone lived happily ever after, and none the wiser. * add header guards to clipboard_testing.h
This commit is contained in:
parent
8b7a287b44
commit
e1e9caf0ef
6 changed files with 296 additions and 324 deletions
|
@ -2,8 +2,17 @@
|
||||||
#include <QCryptographicHash>
|
#include <QCryptographicHash>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
|
#include <QRegularExpression>
|
||||||
#include <QTextStream>
|
#include <QTextStream>
|
||||||
|
|
||||||
|
#if QT_VERSION < 0x050600
|
||||||
|
// qHash on QRegularExpression was added in 5.6, FIX IT
|
||||||
|
uint qHash(const QRegularExpression &key, uint seed) noexcept
|
||||||
|
{
|
||||||
|
return qHash(key.pattern(), seed); // call qHash on pattern QString instead
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
SideboardPlan::SideboardPlan(const QString &_name, const QList<MoveCard_ToZone> &_moveList)
|
SideboardPlan::SideboardPlan(const QString &_name, const QList<MoveCard_ToZone> &_moveList)
|
||||||
: name(_name), moveList(_moveList)
|
: name(_name), moveList(_moveList)
|
||||||
{
|
{
|
||||||
|
@ -477,161 +486,131 @@ bool DeckList::saveToFile_Native(QIODevice *device)
|
||||||
|
|
||||||
bool DeckList::loadFromStream_Plain(QTextStream &in)
|
bool DeckList::loadFromStream_Plain(QTextStream &in)
|
||||||
{
|
{
|
||||||
|
const QRegularExpression reCardLine("^\\s*[\\w\\[\\(\\{].*$", QRegularExpression::UseUnicodePropertiesOption);
|
||||||
|
const QRegularExpression reEmpty("^\\s*$");
|
||||||
|
const QRegularExpression reComment("[\\w\\[\\(\\{].*$", QRegularExpression::UseUnicodePropertiesOption);
|
||||||
|
const QRegularExpression reSBMark("^\\s*sb:\\s*(.+)", QRegularExpression::CaseInsensitiveOption);
|
||||||
|
const QRegularExpression reSBComment("sideboard", QRegularExpression::CaseInsensitiveOption);
|
||||||
|
|
||||||
|
// simplified matches
|
||||||
|
const QRegularExpression reMultiplier("^[xX\\(\\[]*(\\d+)[xX\\*\\)\\]]* ?(.+)");
|
||||||
|
const QRegularExpression reBrace(" ?[\\[\\{][^\\]\\}]*[\\]\\}] ?"); // not nested
|
||||||
|
const QRegularExpression reRoundBrace("^\\([^\\)]*\\) ?"); // () are only matched at start of string
|
||||||
|
const QRegularExpression reDigitBrace(" ?\\(\\d*\\) ?"); // () are matched if containing digits
|
||||||
|
const QHash<QRegularExpression, QString> differences{{QRegularExpression("’"), QString("'")},
|
||||||
|
{QRegularExpression("Æ"), QString("Ae")},
|
||||||
|
{QRegularExpression("æ"), QString("ae")},
|
||||||
|
{QRegularExpression(" ?[|/]+ ?"), QString(" // ")},
|
||||||
|
{QRegularExpression("(?<![A-Z]) ?& ?"), QString(" // ")}};
|
||||||
|
|
||||||
cleanList();
|
cleanList();
|
||||||
QVector<QString> inputs; // QTextStream -> QVector
|
|
||||||
|
|
||||||
bool priorEntryIsBlank = true, isAtBeginning = true;
|
QStringList inputs = in.readAll().trimmed().split('\n');
|
||||||
int blankLines = 0;
|
int max_line = inputs.size();
|
||||||
while (!in.atEnd()) {
|
|
||||||
QString line = in.readLine().simplified().toLower();
|
|
||||||
|
|
||||||
/*
|
// start at the first empty line before the first cardline
|
||||||
* Removes all blank lines at start of inputs
|
int deckStart = inputs.indexOf(reCardLine);
|
||||||
* Ex: ("", "", "", "Card1", "Card2") => ("Card1", "Card2")
|
if (deckStart == -1) { // there are no cards?
|
||||||
*
|
if (inputs.indexOf(reComment) == -1)
|
||||||
* This will also concise multiple blank lines in a row to just one blank
|
return false; // input is empty
|
||||||
* Ex: ("Card1", "Card2", "", "", "", "Card3") => ("Card1", "Card2", "", "Card3")
|
deckStart = max_line;
|
||||||
*/
|
} else {
|
||||||
if (line.isEmpty()) {
|
deckStart = inputs.lastIndexOf(reEmpty, deckStart);
|
||||||
if (priorEntryIsBlank || isAtBeginning) {
|
if (deckStart == -1) {
|
||||||
continue;
|
deckStart = 0;
|
||||||
}
|
|
||||||
|
|
||||||
priorEntryIsBlank = true;
|
|
||||||
blankLines++;
|
|
||||||
} else {
|
|
||||||
isAtBeginning = false;
|
|
||||||
priorEntryIsBlank = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inputs.push_back(line);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// find sideboard position, if marks are used this won't be needed
|
||||||
* Removes blank line at end of inputs (if applicable)
|
int sBStart = -1;
|
||||||
* Ex: ("Card1", "Card2", "") => ("Card1", "Card2")
|
if (inputs.indexOf(reSBMark, deckStart) == -1) {
|
||||||
* NOTE: Any duplicates were taken care of above, so there can be
|
sBStart = inputs.indexOf(reSBComment, deckStart);
|
||||||
* at most one blank line at the very end
|
if (sBStart == -1) {
|
||||||
*/
|
sBStart = inputs.indexOf(reEmpty, deckStart + 1);
|
||||||
if (!inputs.empty() && inputs.last().isEmpty()) {
|
if (sBStart == -1) {
|
||||||
blankLines--;
|
sBStart = max_line;
|
||||||
inputs.erase(inputs.end() - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If "Sideboard" line appears in inputs, then blank lines mean nothing
|
|
||||||
if (inputs.contains("sideboard")) {
|
|
||||||
blankLines = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool inSideboard = false, titleFound = false, isSideboard;
|
|
||||||
int okRows = 0;
|
|
||||||
|
|
||||||
foreach (QString line, inputs) {
|
|
||||||
// This is a comment line, ignore it
|
|
||||||
if (line.startsWith("//")) {
|
|
||||||
if (!titleFound) // Set the title to the first comment
|
|
||||||
{
|
|
||||||
name = line.mid(2).trimmed();
|
|
||||||
titleFound = true;
|
|
||||||
} else if (okRows == 0) // We haven't processed any cards yet
|
|
||||||
{
|
|
||||||
comments += line.mid(2).trimmed() + "\n";
|
|
||||||
}
|
}
|
||||||
|
int nextCard = inputs.indexOf(reCardLine, sBStart + 1);
|
||||||
|
if (inputs.indexOf(reEmpty, nextCard + 1) != -1) {
|
||||||
|
sBStart = max_line; // if there is another empty line all cards are mainboard
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
QRegularExpressionMatch match;
|
||||||
|
|
||||||
|
// parse name and comments
|
||||||
|
while (index < deckStart) {
|
||||||
|
const QString current = inputs.at(index++);
|
||||||
|
if (!current.contains(reEmpty)) {
|
||||||
|
match = reComment.match(current);
|
||||||
|
name = match.captured();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (index < deckStart) {
|
||||||
|
const QString current = inputs.at(index++);
|
||||||
|
if (!current.contains(reEmpty)) {
|
||||||
|
match = reComment.match(current);
|
||||||
|
comments += match.captured() + '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
comments.chop(1); // remove last newline
|
||||||
|
|
||||||
|
// parse decklist
|
||||||
|
for (; index < max_line; ++index) {
|
||||||
|
|
||||||
|
// check if line is a card
|
||||||
|
match = reCardLine.match(inputs.at(index));
|
||||||
|
if (!match.hasMatch())
|
||||||
continue;
|
continue;
|
||||||
}
|
QString cardName = match.captured().simplified();
|
||||||
|
|
||||||
// If we have a blank line and it's the _ONLY_ blank line in the paste
|
// check if card should be sideboard
|
||||||
// and it follows at least one valid card
|
bool sideboard = false;
|
||||||
// Then we assume it means to start the sideboard section of the paste.
|
if (sBStart < 0) {
|
||||||
// If we have the word "Sideboard" appear on any line, then that will
|
match = reSBMark.match(cardName);
|
||||||
// also indicate the start of the sideboard.
|
if (match.hasMatch()) {
|
||||||
if ((line.isEmpty() && blankLines == 1 && okRows > 0) || line.startsWith("sideboard")) {
|
sideboard = true;
|
||||||
inSideboard = true;
|
cardName = match.captured(1);
|
||||||
continue; // The line isn't actually a card
|
|
||||||
}
|
|
||||||
|
|
||||||
isSideboard = inSideboard;
|
|
||||||
|
|
||||||
if (line.startsWith("sb:")) {
|
|
||||||
line = line.mid(3).trimmed();
|
|
||||||
isSideboard = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line.trimmed().isEmpty()) {
|
|
||||||
continue; // The line was " " instead of "\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter out MWS edition symbols and basic land extras
|
|
||||||
QRegExp rx("\\[.*\\]\\s?");
|
|
||||||
line.remove(rx);
|
|
||||||
rx.setPattern("\\s?\\(.*\\)");
|
|
||||||
line.remove(rx);
|
|
||||||
|
|
||||||
// Filter out post card name editions
|
|
||||||
rx.setPattern("\\|.*$");
|
|
||||||
line.remove(rx);
|
|
||||||
|
|
||||||
// If the user inputs "Quicksilver Elemental" then it will cut it off
|
|
||||||
// 1x Squishy Treaker
|
|
||||||
int i = line.indexOf(' ');
|
|
||||||
int cardNameStart = i + 1;
|
|
||||||
|
|
||||||
if (i > 0) {
|
|
||||||
// If the count ends with an 'x', ignore it. For example,
|
|
||||||
// "4x Storm Crow" will count 4 correctly.
|
|
||||||
if (line.at(i - 1) == 'x') {
|
|
||||||
i--;
|
|
||||||
} else if (!line.at(i - 1).isDigit()) {
|
|
||||||
// If the user inputs "Quicksilver Elemental" then it will work as 1x of that card
|
|
||||||
cardNameStart = 0;
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (index == sBStart) // skip sideboard line itself
|
||||||
|
continue;
|
||||||
|
sideboard = index > sBStart;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ok;
|
// check if a specific amount is mentioned
|
||||||
int number = line.left(i).toInt(&ok);
|
int amount = 1;
|
||||||
|
match = reMultiplier.match(cardName);
|
||||||
if (!ok) {
|
if (match.hasMatch()) {
|
||||||
number = 1; // If input is "cardName" assume it's "1x cardName"
|
amount = match.capturedRef(1).toInt();
|
||||||
|
cardName = match.captured(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString cardName = line.mid(cardNameStart);
|
// remove stuff inbetween braces
|
||||||
|
cardName.remove(reBrace);
|
||||||
|
cardName.remove(reRoundBrace); // I'll be entirely honest here, these are split to accommodate just three cards
|
||||||
|
cardName.remove(reDigitBrace); // all cards from un-sets that have a word in between round braces at the end
|
||||||
|
|
||||||
// Common differences between Cockatrice's card names
|
// replace common differences in cardnames
|
||||||
// and what's commonly used in decklists
|
for (auto diff = differences.constBegin(); diff != differences.constEnd(); ++diff) {
|
||||||
rx.setPattern("’");
|
cardName.replace(diff.key(), diff.value());
|
||||||
cardName.replace(rx, "'");
|
|
||||||
rx.setPattern("Æ");
|
|
||||||
cardName.replace(rx, "Ae");
|
|
||||||
rx.setPattern("\\s*[|/]{1,2}\\s*");
|
|
||||||
cardName.replace(rx, " // ");
|
|
||||||
|
|
||||||
// Replace only if the ampersand is preceded by a non-capital letter,
|
|
||||||
// as would happen with acronyms. So 'Fire & Ice' is replaced but not
|
|
||||||
// 'R&D' or 'R & D'.
|
|
||||||
// Qt regexes don't support lookbehind so we capture and replace instead.
|
|
||||||
rx.setPattern("([^A-Z])\\s*&\\s*");
|
|
||||||
if (rx.indexIn(cardName) != -1) {
|
|
||||||
cardName.replace(rx, QString("%1 // ").arg(rx.cap(1)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to get the name of the card from the database,
|
// get cardname, this function does nothing if the name is not found
|
||||||
// but we can't do that until we get the "real" name
|
|
||||||
// (name stored in database for the card)
|
|
||||||
// and establish a card info that is of the card, then it's
|
|
||||||
// a simple getting the _real_ name of the card
|
|
||||||
// (i.e. "STOrm, CrOW" => "Storm Crow")
|
|
||||||
cardName = getCompleteCardName(cardName);
|
cardName = getCompleteCardName(cardName);
|
||||||
|
|
||||||
// Look for the correct card zone of where to place the new card
|
// get zone name based on if it's in sideboard
|
||||||
QString zoneName = getCardZoneFromName(cardName, isSideboard ? DECK_ZONE_SIDE : DECK_ZONE_MAIN);
|
QString zoneName = getCardZoneFromName(cardName, sideboard ? DECK_ZONE_SIDE : DECK_ZONE_MAIN);
|
||||||
|
|
||||||
okRows++;
|
// make new entry in decklist
|
||||||
new DecklistCardNode(cardName, number, getZoneObjFromName(zoneName));
|
new DecklistCardNode(cardName, amount, getZoneObjFromName(zoneName));
|
||||||
}
|
}
|
||||||
|
|
||||||
updateDeckHash();
|
updateDeckHash();
|
||||||
return (okRows > 0);
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
InnerDecklistNode *DeckList::getZoneObjFromName(const QString zoneName)
|
InnerDecklistNode *DeckList::getZoneObjFromName(const QString zoneName)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
ADD_DEFINITIONS("-DCARDDB_DATADIR=\"${CMAKE_CURRENT_SOURCE_DIR}/data/\"")
|
ADD_DEFINITIONS("-DCARDDB_DATADIR=\"${CMAKE_CURRENT_SOURCE_DIR}/data/\"")
|
||||||
add_executable(loading_from_clipboard_test
|
add_executable(loading_from_clipboard_test
|
||||||
loading_from_clipboard_test.cpp
|
loading_from_clipboard_test.cpp
|
||||||
|
clipboard_testing.cpp
|
||||||
../../common/decklist.cpp
|
../../common/decklist.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -12,4 +13,4 @@ find_package(Qt5 COMPONENTS Concurrent Network Widgets REQUIRED)
|
||||||
set(TEST_QT_MODULES Qt5::Concurrent Qt5::Network Qt5::Widgets)
|
set(TEST_QT_MODULES Qt5::Concurrent Qt5::Network Qt5::Widgets)
|
||||||
|
|
||||||
target_link_libraries(loading_from_clipboard_test cockatrice_common ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES})
|
target_link_libraries(loading_from_clipboard_test cockatrice_common ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES})
|
||||||
add_test(NAME loading_from_clipboard_test COMMAND loading_from_clipboard_test)
|
add_test(NAME loading_from_clipboard_test COMMAND loading_from_clipboard_test)
|
||||||
|
|
40
tests/loading_from_clipboard/clipboard_testing.cpp
Normal file
40
tests/loading_from_clipboard/clipboard_testing.cpp
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
#include "clipboard_testing.h"
|
||||||
|
#include <QTextStream>
|
||||||
|
|
||||||
|
void Result::operator()(const InnerDecklistNode *innerDecklistNode, const DecklistCardNode *card)
|
||||||
|
{
|
||||||
|
if (innerDecklistNode->getName() == DECK_ZONE_MAIN) {
|
||||||
|
mainboard.append({card->getName().toStdString(), card->getNumber()});
|
||||||
|
} else if (innerDecklistNode->getName() == DECK_ZONE_SIDE) {
|
||||||
|
sideboard.append({card->getName().toStdString(), card->getNumber()});
|
||||||
|
} else {
|
||||||
|
FAIL();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void testEmpty(const QString &clipboard)
|
||||||
|
{
|
||||||
|
QString cp(clipboard);
|
||||||
|
DeckList deckList;
|
||||||
|
QTextStream stream(&cp); // text stream requires local copy
|
||||||
|
deckList.loadFromStream_Plain(stream);
|
||||||
|
|
||||||
|
ASSERT_TRUE(deckList.getCardList().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
void testDeck(const QString &clipboard, const Result &result)
|
||||||
|
{
|
||||||
|
QString cp(clipboard);
|
||||||
|
DeckList deckList;
|
||||||
|
QTextStream stream(&cp); // text stream requires local copy
|
||||||
|
deckList.loadFromStream_Plain(stream);
|
||||||
|
|
||||||
|
ASSERT_EQ(result.name, deckList.getName().toStdString());
|
||||||
|
ASSERT_EQ(result.comments, deckList.getComments().toStdString());
|
||||||
|
|
||||||
|
Result decklistBuilder;
|
||||||
|
deckList.forEachCard(decklistBuilder);
|
||||||
|
|
||||||
|
ASSERT_EQ(result.mainboard, decklistBuilder.mainboard);
|
||||||
|
ASSERT_EQ(result.sideboard, decklistBuilder.sideboard);
|
||||||
|
}
|
32
tests/loading_from_clipboard/clipboard_testing.h
Normal file
32
tests/loading_from_clipboard/clipboard_testing.h
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
#ifndef CLIPBOARD_TESTING_H
|
||||||
|
#define CLIPBOARD_TESTING_H
|
||||||
|
|
||||||
|
#include "../../common/decklist.h"
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
struct Result
|
||||||
|
{
|
||||||
|
// using std types because qt types aren't understood by gtest (without this you'll get less nice errors)
|
||||||
|
using CardRows = QVector<std::pair<std::string, int>>;
|
||||||
|
std::string name;
|
||||||
|
std::string comments;
|
||||||
|
CardRows mainboard;
|
||||||
|
CardRows sideboard;
|
||||||
|
|
||||||
|
Result()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Result(std::string _name, std::string _comments, CardRows _mainboard, CardRows _sideboard)
|
||||||
|
: name(_name), comments(_comments), mainboard(_mainboard), sideboard(_sideboard)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator()(const InnerDecklistNode *innerDecklistNode, const DecklistCardNode *card);
|
||||||
|
};
|
||||||
|
|
||||||
|
void testEmpty(const QString &clipboard);
|
||||||
|
|
||||||
|
void testDeck(const QString &clipboard, const Result &result);
|
||||||
|
|
||||||
|
#endif // CLIPBOARD_TESTING_H
|
|
@ -1,249 +1,169 @@
|
||||||
#include "loading_from_clipboard_test.h"
|
#include "clipboard_testing.h"
|
||||||
#include "../../common/decklist.h"
|
|
||||||
#include "gtest/gtest.h"
|
|
||||||
#include <QTextStream>
|
|
||||||
|
|
||||||
DeckList *fromClipboard(QString *clipboard);
|
// Testing is done by using the DeckList::loadFromString_Plain function in common/decklist.h
|
||||||
DeckList *fromClipboard(QString *clipboard)
|
// It does not check if cards are in the database at all, so no comparisons to the database will be made.
|
||||||
{
|
|
||||||
DeckList *deckList = new DeckList;
|
|
||||||
QTextStream *stream = new QTextStream(clipboard);
|
|
||||||
deckList->loadFromStream_Plain(*stream);
|
|
||||||
return deckList;
|
|
||||||
}
|
|
||||||
|
|
||||||
using CardRows = QMap<QString, int>;
|
|
||||||
|
|
||||||
struct DecklistBuilder
|
|
||||||
{
|
|
||||||
CardRows actualMainboard;
|
|
||||||
CardRows actualSideboard;
|
|
||||||
|
|
||||||
explicit DecklistBuilder() : actualMainboard({}), actualSideboard({})
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void operator()(const InnerDecklistNode *innerDecklistNode, const DecklistCardNode *card)
|
|
||||||
{
|
|
||||||
if (innerDecklistNode->getName() == DECK_ZONE_MAIN) {
|
|
||||||
actualMainboard[card->getName()] += card->getNumber();
|
|
||||||
} else if (innerDecklistNode->getName() == DECK_ZONE_SIDE) {
|
|
||||||
actualSideboard[card->getName()] += card->getNumber();
|
|
||||||
} else {
|
|
||||||
FAIL();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CardRows mainboard()
|
|
||||||
{
|
|
||||||
return actualMainboard;
|
|
||||||
}
|
|
||||||
|
|
||||||
CardRows sideboard()
|
|
||||||
{
|
|
||||||
return actualSideboard;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
TEST(LoadingFromClipboardTest, EmptyDeck)
|
TEST(LoadingFromClipboardTest, EmptyDeck)
|
||||||
{
|
{
|
||||||
DeckList *deckList = fromClipboard(new QString(""));
|
testEmpty("");
|
||||||
ASSERT_TRUE(deckList->getCardList().isEmpty());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(LoadingFromClipboardTest, EmptySideboard)
|
TEST(LoadingFromClipboardTest, EmptySideboard)
|
||||||
{
|
{
|
||||||
DeckList *deckList = fromClipboard(new QString("Sideboard"));
|
testEmpty("Sideboard");
|
||||||
ASSERT_TRUE(deckList->getCardList().isEmpty());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(LoadingFromClipboardTest, QuantityPrefixed)
|
TEST(LoadingFromClipboardTest, QuantityPrefixed)
|
||||||
{
|
{
|
||||||
QString *clipboard = new QString("1 Mountain\n"
|
QString clipboard("1 Mountain\n"
|
||||||
"2x Island\n"
|
"2x Island\n"
|
||||||
"3X FOREST\n");
|
"3x Forest\n");
|
||||||
DeckList *deckList = fromClipboard(clipboard);
|
Result result("", "", {{"Mountain", 1}, {"Island", 2}, {"Forest", 3}}, {});
|
||||||
|
testDeck(clipboard, result);
|
||||||
DecklistBuilder decklistBuilder = DecklistBuilder();
|
|
||||||
deckList->forEachCard(decklistBuilder);
|
|
||||||
|
|
||||||
CardRows expectedMainboard = CardRows({{"mountain", 1}, {"island", 2}, {"forest", 3}});
|
|
||||||
CardRows expectedSideboard = CardRows({});
|
|
||||||
|
|
||||||
ASSERT_EQ(expectedMainboard, decklistBuilder.mainboard());
|
|
||||||
ASSERT_EQ(expectedSideboard, decklistBuilder.sideboard());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(LoadingFromClipboardTest, CommentsAreIgnored)
|
TEST(LoadingFromClipboardTest, CommentsAreIgnored)
|
||||||
{
|
{
|
||||||
QString *clipboard = new QString("//1 Mountain\n"
|
QString clipboard("//1 Mountain\n"
|
||||||
"//2x Island\n"
|
"//2x Island\n"
|
||||||
"//SB:2x Island\n");
|
"//SB:2x Island\n");
|
||||||
|
testEmpty(clipboard);
|
||||||
DeckList *deckList = fromClipboard(clipboard);
|
|
||||||
|
|
||||||
DecklistBuilder decklistBuilder = DecklistBuilder();
|
|
||||||
deckList->forEachCard(decklistBuilder);
|
|
||||||
|
|
||||||
CardRows expectedMainboard = CardRows({});
|
|
||||||
CardRows expectedSideboard = CardRows({});
|
|
||||||
|
|
||||||
ASSERT_EQ(expectedMainboard, decklistBuilder.mainboard());
|
|
||||||
ASSERT_EQ(expectedSideboard, decklistBuilder.sideboard());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(LoadingFromClipboardTest, SideboardPrefix)
|
TEST(LoadingFromClipboardTest, SideboardPrefix)
|
||||||
{
|
{
|
||||||
QString *clipboard = new QString("1 Mountain\n"
|
QString clipboard("1 Mountain\n"
|
||||||
"SB: 1 Mountain\n"
|
"SB: 1 Mountain\n"
|
||||||
"SB: 2x Island\n");
|
"sb: 2x Island\n"
|
||||||
DeckList *deckList = fromClipboard(clipboard);
|
"2 Swamp\n"
|
||||||
|
"\n"
|
||||||
|
"3 Plains\n");
|
||||||
|
Result result("", "", {{"Mountain", 1}, {"Swamp", 2}, {"Plains", 3}}, {{"Mountain", 1}, {"Island", 2}});
|
||||||
|
testDeck(clipboard, result);
|
||||||
|
}
|
||||||
|
|
||||||
DecklistBuilder decklistBuilder = DecklistBuilder();
|
TEST(LoadingFromClipboardTest, SideboardLine)
|
||||||
deckList->forEachCard(decklistBuilder);
|
{
|
||||||
|
QString clipboard("1 Mountain\n"
|
||||||
CardRows expectedMainboard = CardRows({{"mountain", 1}});
|
"2 Swamp\n"
|
||||||
CardRows expectedSideboard = CardRows({{"mountain", 1}, {"island", 2}});
|
"\n"
|
||||||
|
"3 Plains\n"
|
||||||
ASSERT_EQ(expectedMainboard, decklistBuilder.mainboard());
|
"sideboard\n"
|
||||||
ASSERT_EQ(expectedSideboard, decklistBuilder.sideboard());
|
"1 Mountain\n"
|
||||||
|
"2x Island\n");
|
||||||
|
Result result("", "", {{"Mountain", 1}, {"Swamp", 2}, {"Plains", 3}}, {{"Mountain", 1}, {"Island", 2}});
|
||||||
|
testDeck(clipboard, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(LoadingFromClipboardTest, UnknownCardsAreNotDiscarded)
|
TEST(LoadingFromClipboardTest, UnknownCardsAreNotDiscarded)
|
||||||
{
|
{
|
||||||
QString *clipboard = new QString("1 CardThatDoesNotExistInCardsXml\n");
|
QString clipboard("1 CardThatDoesNotExistInCardsXml\n");
|
||||||
DeckList *deckList = fromClipboard(clipboard);
|
Result result("", "", {{"CardThatDoesNotExistInCardsXml", 1}}, {});
|
||||||
|
testDeck(clipboard, result);
|
||||||
|
}
|
||||||
|
|
||||||
DecklistBuilder decklistBuilder = DecklistBuilder();
|
TEST(LoadingFromClipboardTest, WeirdWhitespaceIsIgnored)
|
||||||
deckList->forEachCard(decklistBuilder);
|
{
|
||||||
|
QString clipboard(
|
||||||
CardRows expectedMainboard = CardRows({{"cardthatdoesnotexistincardsxml", 1}});
|
"\t\tSb:\t1\tOur Market Research Shows That Players Like Really Long Card Names So We Made "
|
||||||
CardRows expectedSideboard = CardRows({});
|
" This Card to Have\tthe Absolute \t Longest Card Name \tEver Elemental\t\n\t");
|
||||||
|
Result result("", "", {},
|
||||||
ASSERT_EQ(expectedMainboard, decklistBuilder.mainboard());
|
{{"Our Market Research Shows That Players Like Really Long Card Names So We Made This Card to Have "
|
||||||
ASSERT_EQ(expectedSideboard, decklistBuilder.sideboard());
|
"the Absolute Longest Card Name Ever Elemental",
|
||||||
|
1}});
|
||||||
|
testDeck(clipboard, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(LoadingFromClipboardTest, RemoveBlankEntriesFromBeginningAndEnd)
|
TEST(LoadingFromClipboardTest, RemoveBlankEntriesFromBeginningAndEnd)
|
||||||
{
|
{
|
||||||
QString *clipboard = new QString("\n"
|
QString clipboard("\n"
|
||||||
"\n"
|
"\n"
|
||||||
"\n"
|
"\n"
|
||||||
"1x Algae Gharial\n"
|
"1x Algae Gharial\n"
|
||||||
"3x CardThatDoesNotExistInCardsXml\n"
|
"3x CardThatDoesNotExistInCardsXml\n"
|
||||||
"2x Phelddagrif\n"
|
"2x Phelddagrif\n"
|
||||||
"\n"
|
"\n"
|
||||||
"\n");
|
"\n");
|
||||||
|
|
||||||
DeckList *deckList = fromClipboard(clipboard);
|
Result result("", "", {{"Algae Gharial", 1}, {"CardThatDoesNotExistInCardsXml", 3}, {"Phelddagrif", 2}}, {});
|
||||||
|
testDeck(clipboard, result);
|
||||||
DecklistBuilder decklistBuilder = DecklistBuilder();
|
|
||||||
deckList->forEachCard(decklistBuilder);
|
|
||||||
|
|
||||||
CardRows expectedMainboard =
|
|
||||||
CardRows({{"algae gharial", 1}, {"cardthatdoesnotexistincardsxml", 3}, {"phelddagrif", 2}});
|
|
||||||
CardRows expectedSideboard = CardRows({});
|
|
||||||
|
|
||||||
ASSERT_EQ(expectedMainboard, decklistBuilder.mainboard());
|
|
||||||
ASSERT_EQ(expectedSideboard, decklistBuilder.sideboard());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(LoadingFromClipboardTest, UseFirstBlankIfOnlyOneBlankToSplitSideboard)
|
TEST(LoadingFromClipboardTest, UseFirstBlankIfOnlyOneBlankToSplitSideboard)
|
||||||
{
|
{
|
||||||
QString *clipboard = new QString("1x Algae Gharial\n"
|
QString clipboard("1x Algae Gharial\n"
|
||||||
"3x CardThatDoesNotExistInCardsXml\n"
|
"3x CardThatDoesNotExistInCardsXml\n"
|
||||||
"\n"
|
"\n"
|
||||||
"2x Phelddagrif\n");
|
"2x Phelddagrif\n");
|
||||||
|
|
||||||
DeckList *deckList = fromClipboard(clipboard);
|
Result result("", "", {{"Algae Gharial", 1}, {"CardThatDoesNotExistInCardsXml", 3}}, {{"Phelddagrif", 2}});
|
||||||
|
testDeck(clipboard, result);
|
||||||
DecklistBuilder decklistBuilder = DecklistBuilder();
|
|
||||||
deckList->forEachCard(decklistBuilder);
|
|
||||||
|
|
||||||
CardRows expectedMainboard = CardRows({{"algae gharial", 1}, {"cardthatdoesnotexistincardsxml", 3}});
|
|
||||||
CardRows expectedSideboard = CardRows({{"phelddagrif", 2}});
|
|
||||||
|
|
||||||
ASSERT_EQ(expectedMainboard, decklistBuilder.mainboard());
|
|
||||||
ASSERT_EQ(expectedSideboard, decklistBuilder.sideboard());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(LoadingFromClipboardTest, IfMultipleScatteredBlanksAllMainBoard)
|
TEST(LoadingFromClipboardTest, IfMultipleScatteredBlanksAllMainBoard)
|
||||||
{
|
{
|
||||||
QString *clipboard = new QString("1x Algae Gharial\n"
|
QString clipboard("1x Algae Gharial\n"
|
||||||
"3x CardThatDoesNotExistInCardsXml\n"
|
"3x CardThatDoesNotExistInCardsXml\n"
|
||||||
"\n"
|
"\n"
|
||||||
"2x Phelddagrif\n"
|
"2x Phelddagrif\n"
|
||||||
"\n"
|
"\n"
|
||||||
"3 Giant Growth\n");
|
"3 Giant Growth\n");
|
||||||
|
|
||||||
DeckList *deckList = fromClipboard(clipboard);
|
Result result(
|
||||||
|
"", "", {{"Algae Gharial", 1}, {"CardThatDoesNotExistInCardsXml", 3}, {"Phelddagrif", 2}, {"Giant Growth", 3}},
|
||||||
DecklistBuilder decklistBuilder = DecklistBuilder();
|
{});
|
||||||
deckList->forEachCard(decklistBuilder);
|
testDeck(clipboard, result);
|
||||||
|
|
||||||
CardRows expectedMainboard = CardRows(
|
|
||||||
{{"algae gharial", 1}, {"cardthatdoesnotexistincardsxml", 3}, {"phelddagrif", 2}, {"giant growth", 3}});
|
|
||||||
CardRows expectedSideboard = CardRows({});
|
|
||||||
|
|
||||||
ASSERT_EQ(expectedMainboard, decklistBuilder.mainboard());
|
|
||||||
ASSERT_EQ(expectedSideboard, decklistBuilder.sideboard());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(LoadingFromClipboardTest, LotsOfStuffInBulkTesting)
|
TEST(LoadingFromClipboardTest, EdgeCaseTesting)
|
||||||
{
|
{
|
||||||
QString *clipboard = new QString("\n"
|
QString clipboard(R"(
|
||||||
"\n"
|
// DeckName
|
||||||
"\n"
|
|
||||||
"1x test1\n"
|
|
||||||
"testNoValueMB\n"
|
|
||||||
"2x test2\n"
|
|
||||||
"SB: 10 testSB\n"
|
|
||||||
"3 test3\n"
|
|
||||||
"4X test4\n"
|
|
||||||
"\n"
|
|
||||||
"\n"
|
|
||||||
"\n"
|
|
||||||
"\n"
|
|
||||||
"5x test5\n"
|
|
||||||
"6X test6\n"
|
|
||||||
"testNoValueSB\n"
|
|
||||||
"\n"
|
|
||||||
"\n"
|
|
||||||
"\n"
|
|
||||||
"\n");
|
|
||||||
|
|
||||||
DeckList *deckList = fromClipboard(clipboard);
|
// Comment 1
|
||||||
|
|
||||||
DecklistBuilder decklistBuilder = DecklistBuilder();
|
//
|
||||||
deckList->forEachCard(decklistBuilder);
|
//Comment [two]
|
||||||
|
//(test) Æ ’ | / (3)
|
||||||
|
|
||||||
CardRows expectedMainboard = CardRows({{"test1", 1}, {"test2", 2}, {"test3", 3}, {"test4", 4}, {"testnovaluemb", 1}
|
|
||||||
|
|
||||||
});
|
// Mainboard (10 cards)
|
||||||
CardRows expectedSideboard = CardRows({{"testsb", 10}, {"test5", 5}, {"test6", 6}, {"testnovaluesb", 1}
|
Æther Adept
|
||||||
|
2x Fire & Ice
|
||||||
|
3 Pain/Suffering
|
||||||
|
4X [B] Forest (3)
|
||||||
|
|
||||||
});
|
|
||||||
ASSERT_EQ(expectedMainboard, decklistBuilder.mainboard());
|
// Sideboard (11 cards)
|
||||||
ASSERT_EQ(expectedSideboard, decklistBuilder.sideboard());
|
|
||||||
|
5x [WTH] Nature’s Resurgence
|
||||||
|
6X Gaea's Skyfolk
|
||||||
|
7 B.F.M. (Big Furry Monster)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
)");
|
||||||
|
|
||||||
|
Result result("DeckName", "Comment 1\n\nComment [two]\n(test) Æ ’ | / (3)",
|
||||||
|
{{"Aether Adept", 1}, {"Fire // Ice", 2}, {"Pain // Suffering", 3}, {"Forest", 4}},
|
||||||
|
{{"Nature's Resurgence", 5}, {"Gaea's Skyfolk", 6}, {"B.F.M. (Big Furry Monster)", 7}});
|
||||||
|
testDeck(clipboard, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(LoadingFromClipboardTest, CommentsBeforeCardsTesting)
|
TEST(LoadingFromClipboardTest, CommentsBeforeCardsTesting)
|
||||||
{
|
{
|
||||||
QString *clipboard = new QString("//NAME: Title from Website.com\n"
|
QString clipboard("// Title from website.com\n"
|
||||||
"\n"
|
"// A nice deck\n"
|
||||||
"//Main\n"
|
"// With nice cards\n"
|
||||||
"1 test1\n");
|
"\n"
|
||||||
DeckList *decklist = fromClipboard(clipboard);
|
"// Mainboard\n"
|
||||||
DecklistBuilder decklistBuilder = DecklistBuilder();
|
"1 test1\n"
|
||||||
decklist->forEachCard(decklistBuilder);
|
"Sideboard\n"
|
||||||
CardRows expectedMainboard = CardRows({{"test1", 1}});
|
"2 test2\n");
|
||||||
CardRows expectedSideboard = CardRows({});
|
|
||||||
ASSERT_EQ(expectedMainboard, decklistBuilder.mainboard());
|
Result result("Title from website.com", "A nice deck\nWith nice cards", {{"test1", 1}}, {{"test2", 2}});
|
||||||
ASSERT_EQ(expectedSideboard, decklistBuilder.sideboard());
|
testDeck(clipboard, result);
|
||||||
}
|
}
|
||||||
} // namespace
|
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in a new issue