diff --git a/src/field.cpp b/src/field.cpp index 9e0eac59..57a03f0b 100644 --- a/src/field.cpp +++ b/src/field.cpp @@ -1,322 +1,341 @@ /*************************************************************************** Copyright (C) 2001-2009 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ #include "field.h" #include "utils/string_utils.h" #include "tellico_debug.h" #include +#include + using namespace Tellico; using Tellico::Data::Field; // this constructor is for anything but Choice type Field::Field(const QString& name_, const QString& title_, Type type_/*=Line*/) : QSharedData(), m_name(name_), m_title(title_), m_category(i18n("General")), m_desc(title_), m_type(type_), m_flags(0), m_formatType(FieldFormat::FormatNone) { Q_ASSERT(m_type != Choice); // a paragraph's category is always its title, along with tables if(isSingleCategory()) { m_category = m_title; } switch(m_type) { case Table: case Table2: m_flags = AllowMultiple; if(m_type == Table2) { m_type = Table; setProperty(QStringLiteral("columns"), QStringLiteral("2")); } else { setProperty(QStringLiteral("columns"), QStringLiteral("1")); } break; case Date: // hidden from user m_formatType = FieldFormat::FormatDate; break; case Rating: setProperty(QStringLiteral("minimum"), QStringLiteral("1")); setProperty(QStringLiteral("maximum"), QStringLiteral("5")); break; case ReadOnly: m_flags = NoEdit; m_type = Line; break; case Dependent: m_flags = Derived; m_type = Line; break; default: // ssshhhhhhhh break; } } // if this constructor is called, the type is necessarily Choice Field::Field(const QString& name_, const QString& title_, const QStringList& allowed_) : QSharedData(), m_name(name_), m_title(title_), m_category(i18n("General")), m_desc(title_), m_type(Field::Choice), m_allowed(allowed_), m_flags(0), m_formatType(FieldFormat::FormatNone) { } Field::Field(const Field& field_) : QSharedData(field_), m_name(field_.name()), m_title(field_.title()), m_category(field_.category()), m_desc(field_.description()), m_type(field_.type()), m_allowed(field_.allowed()), m_flags(field_.flags()), m_formatType(field_.formatType()), m_properties(field_.propertyList()) { } Field& Field::operator=(const Field& field_) { if(this == &field_) return *this; // static_cast(*this) = static_cast(field_); m_name = field_.name(); m_title = field_.title(); m_category = field_.category(); m_desc = field_.description(); m_type = field_.type(); m_allowed = field_.allowed(); m_flags = field_.flags(); m_formatType = field_.formatType(); m_properties = field_.propertyList(); return *this; } Field::~Field() { } void Field::setTitle(const QString& title_) { m_title = title_; if(isSingleCategory()) { m_category = title_; } } void Field::setType(Field::Type type_) { m_type = type_; if(m_type != Field::Choice) { m_allowed = QStringList(); } switch(m_type) { case Table: case Table2: m_flags |= AllowMultiple; if(m_type == Table2) { m_type = Table; setProperty(QStringLiteral("columns"), QStringLiteral("2")); } if(property(QStringLiteral("columns")).isEmpty()) { setProperty(QStringLiteral("columns"), QStringLiteral("1")); } break; case Date: m_formatType = FieldFormat::FormatDate; break; case ReadOnly: m_flags |= NoEdit; m_type = Line; break; case Dependent: m_flags |= Derived; m_type = Line; break; default: // ssshhhhhhhh break; } if(isSingleCategory()) { m_category = m_title; } } void Field::setCategory(const QString& category_) { if(!isSingleCategory()) { m_category = category_; } } void Field::setFlags(int flags_) { // tables always have multiple allowed if(m_type == Table) { m_flags = AllowMultiple | flags_; } else { m_flags = flags_; } } bool Field::hasFlag(FieldFlag flag_) const { return m_flags & flag_; } void Field::setFormatType(FieldFormat::Type type_) { // Choice and Data fields are not allowed a format type if(m_type != Choice && m_type != Date) { m_formatType = type_; } } QString Field::defaultValue() const { return property(QStringLiteral("default")); } void Field::setDefaultValue(const QString& value_) { if(value_.isEmpty() || m_type != Choice || m_allowed.contains(value_)) { setProperty(QStringLiteral("default"), value_); } } bool Field::isSingleCategory() const { return (m_type == Para || m_type == Table || m_type == Image); } // if these are changed, then CollectionFieldsDialog should be checked since it // checks for equality against some of these strings Field::FieldMap Field::typeMap() { FieldMap map; map[Field::Line] = i18n("Simple Text"); map[Field::Para] = i18n("Paragraph"); map[Field::Choice] = i18n("Choice"); map[Field::Bool] = i18n("Checkbox"); map[Field::Number] = i18n("Number"); map[Field::URL] = i18n("URL"); map[Field::Table] = i18n("Table"); map[Field::Image] = i18n("Image"); map[Field::Date] = i18n("Date"); map[Field::Rating] = i18n("Rating"); return map; } // just for formatting's sake QStringList Field::typeTitles() { const FieldMap& map = typeMap(); QStringList list; list.append(map[Field::Line]); list.append(map[Field::Para]); list.append(map[Field::Choice]); list.append(map[Field::Bool]); list.append(map[Field::Number]); list.append(map[Field::URL]); list.append(map[Field::Date]); list.append(map[Field::Table]); list.append(map[Field::Image]); list.append(map[Field::Rating]); return list; } void Field::addAllowed(const QString& value_) { if(m_type != Choice) { return; } if(!m_allowed.contains(value_)) { m_allowed += value_; } } void Field::setProperty(const QString& key_, const QString& value_) { if(value_.isEmpty()) { m_properties.remove(key_); } else { m_properties.insert(key_, value_); } } void Field::setPropertyList(const Tellico::StringMap& props_) { m_properties = props_; } QString Field::property(const QString& key_) const { return m_properties.contains(key_) ? m_properties.value(key_) : QString(); } void Field::convertOldRating(Tellico::Data::FieldPtr field_) { if(field_->type() != Data::Field::Choice) { return; // nothing to do } if(field_->name() != QLatin1String("rating") && field_->property(QStringLiteral("rating")) != QLatin1String("true")) { return; // nothing to do } int min = 10; int max = 1; bool ok; const QStringList& allow = field_->allowed(); for(QStringList::ConstIterator it = allow.begin(); it != allow.end(); ++it) { int n = Tellico::toUInt(*it, &ok); if(!ok) { return; // no need to convert } min = qMin(min, n); max = qMax(max, n); } min = qMax(max, 1); max = qMin(max, 10); if(min >= max) { min = 1; max = 5; } field_->setProperty(QStringLiteral("minimum"), QString::number(min)); field_->setProperty(QStringLiteral("maximum"), QString::number(max)); //remove any old property field_->setProperty(QStringLiteral("rating"), QString()); field_->setType(Rating); } Tellico::Data::FieldPtr Field::createDefaultField(DefaultField fieldEnum) { Data::FieldPtr field; switch(fieldEnum) { case IDField: field = new Field(QStringLiteral("id"), i18nc("ID # of the entry", "ID"), Field::Number); field->setCategory(i18n("Personal")); field->setProperty(QStringLiteral("template"), QStringLiteral("%{@id}")); field->setFlags(Field::Derived); field->setFormatType(FieldFormat::FormatNone); break; case TitleField: field = new Field(QStringLiteral("title"), i18n("Title")); field->setCategory(i18n("General")); field->setFlags(Field::NoDelete); field->setFormatType(FieldFormat::FormatTitle); break; case CreatedDateField: field = new Field(QStringLiteral("cdate"), i18n("Date Created"), Field::Date); field->setCategory(i18n("Personal")); field->setFlags(Field::NoEdit); break; case ModifiedDateField: field = new Field(QStringLiteral("mdate"), i18n("Date Modified"), Field::Date); field->setCategory(i18n("Personal")); field->setFlags(Field::NoEdit); break; case IsbnField: field = new Field(QStringLiteral("isbn"), i18n("ISBN#")); field->setCategory(i18n("Publishing")); field->setDescription(i18n("International Standard Book Number")); break; } Q_ASSERT(field); return field; } Tellico::Data::FieldList Tellico::listIntersection(const Tellico::Data::FieldList& list1, const Tellico::Data::FieldList& list2) { +#if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0)) + // QList::toSet is deprecated return list1.toSet().intersect(list2.toSet()).values(); +#else + // std::set_intersection requires sorted lists. Just do the set intersection manually + Data::FieldList returnList; + QSet set(list1.begin(), list1.end()); + + std::for_each(list2.begin(), list2.end(), + [&set, &returnList](const Data::FieldPtr& f) { + if(set.contains(f)) { + returnList.append(f); + set.remove(f); + } + } + ); + return returnList; +#endif } diff --git a/src/tests/collectiontest.cpp b/src/tests/collectiontest.cpp index e6cd702e..bf5dd250 100644 --- a/src/tests/collectiontest.cpp +++ b/src/tests/collectiontest.cpp @@ -1,743 +1,758 @@ /*************************************************************************** Copyright (C) 2009-2016 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ #include "collectiontest.h" #include "../collection.h" #include "../field.h" #include "../entry.h" #include "../collectionfactory.h" #include "../collections/collectioninitializer.h" #include "../collections/bookcollection.h" #include "../collections/gamecollection.h" #include "../translators/tellicoxmlexporter.h" #include "../translators/tellicoimporter.h" #include "../images/imagefactory.h" #include "../document.h" #include "../entrycomparison.h" #include #include #include QTEST_GUILESS_MAIN( CollectionTest ) Q_DECLARE_METATYPE(Tellico::EntryComparison::MatchValue) class TestResolver : public Tellico::MergeConflictResolver { public: TestResolver(Tellico::MergeConflictResolver::Result ret) : m_ret(ret) {}; Tellico::MergeConflictResolver::Result resolve(Tellico::Data::EntryPtr, Tellico::Data::EntryPtr, Tellico::Data::FieldPtr, const QString& value1 = QString(), const QString& value2 = QString()) Q_DECL_OVERRIDE { Q_UNUSED(value1); Q_UNUSED(value2); return m_ret; } private: Tellico::MergeConflictResolver::Result m_ret; }; void CollectionTest::initTestCase() { qRegisterMetaType(); Tellico::ImageFactory::init(); // need to register the collection types Tellico::CollectionInitializer ci; // create the collection and entry used for testMatchScore() m_coll = Tellico::Data::CollPtr(new Tellico::Data::BookCollection(true)); m_entry = Tellico::Data::EntryPtr(new Tellico::Data::Entry(m_coll)); m_entry->setField(QStringLiteral("title"), QStringLiteral("title1")); m_entry->setField(QStringLiteral("author"), QStringLiteral("John Doe")); m_entry->setField(QStringLiteral("isbn"), QStringLiteral("1234367890")); m_entry->setField(QStringLiteral("lccn"), QStringLiteral("89456")); Tellico::Data::FieldPtr f(new Tellico::Data::Field(QStringLiteral("arxiv"), QStringLiteral("Arxiv ID"))); m_coll->addField(f); m_entry->setField(QStringLiteral("arxiv"), QStringLiteral("hep-lat/0110180")); m_coll->addEntries(m_entry); } void CollectionTest::cleanupTestCase() { Tellico::ImageFactory::clean(true); } void CollectionTest::testEmpty() { Tellico::Data::CollPtr nullColl; QVERIFY(!nullColl); Tellico::Data::Collection coll(false, QStringLiteral("Title")); QCOMPARE(coll.id(), 2); // ID is 2 since ID of 1 is created in initTestCase() QCOMPARE(coll.entryCount(), 0); QCOMPARE(coll.type(), Tellico::Data::Collection::Base); QVERIFY(coll.fields().isEmpty()); QCOMPARE(coll.title(), QStringLiteral("Title")); } void CollectionTest::testCollection() { Tellico::Data::CollPtr coll(new Tellico::Data::Collection(true)); // add default fields QCOMPARE(coll->entryCount(), 0); QCOMPARE(coll->type(), Tellico::Data::Collection::Base); QCOMPARE(coll->fields().count(), 4); QVERIFY(coll->hasField(QStringLiteral("title"))); QVERIFY(coll->hasField(QStringLiteral("id"))); QVERIFY(coll->hasField(QStringLiteral("cdate"))); QVERIFY(coll->hasField(QStringLiteral("mdate"))); QVERIFY(coll->peopleFields().isEmpty()); QVERIFY(coll->imageFields().isEmpty()); QVERIFY(!coll->hasImages()); Tellico::Data::EntryPtr entry1(new Tellico::Data::Entry(coll)); coll->addEntries(entry1); // check derived value QCOMPARE(entry1->field(QStringLiteral("id")), QStringLiteral("1")); // check created and modified values QCOMPARE(entry1->field(QStringLiteral("cdate")), QDate::currentDate().toString(Qt::ISODate)); QCOMPARE(entry1->field(QStringLiteral("mdate")), QDate::currentDate().toString(Qt::ISODate)); // also verify that the empty string is included in list of group names Tellico::Data::FieldPtr field1(new Tellico::Data::Field(QStringLiteral("test"), QStringLiteral("test"))); coll->addField(field1); QStringList groupNames = entry1->groupNamesByFieldName(QStringLiteral("test")); QCOMPARE(groupNames.count(), 1); QVERIFY(groupNames.at(0).isEmpty()); Tellico::Data::EntryPtr entry2(new Tellico::Data::Entry(coll)); // add created and modified dates from earlier, to make sure they don't get overwritten QDate weekAgo = QDate::currentDate().addDays(-7); QDate yesterday = QDate::currentDate().addDays(-1); entry2->setField(QStringLiteral("cdate"), weekAgo.toString(Qt::ISODate)); entry2->setField(QStringLiteral("mdate"), yesterday.toString(Qt::ISODate)); coll->addEntries(entry2); // check derived value QCOMPARE(entry2->field(QStringLiteral("id")), QStringLiteral("2")); // check created and modified values QCOMPARE(entry2->field(QStringLiteral("cdate")), weekAgo.toString(Qt::ISODate)); QCOMPARE(entry2->field(QStringLiteral("mdate")), yesterday.toString(Qt::ISODate)); // check that mdate gets updates entry2->setField(QStringLiteral("title"), QStringLiteral("new title")); QCOMPARE(entry2->field(QStringLiteral("cdate")), weekAgo.toString(Qt::ISODate)); QCOMPARE(entry2->field(QStringLiteral("mdate")), QDate::currentDate().toString(Qt::ISODate)); // check Bug 361622 - properly handling empty rows in table Tellico::Data::FieldPtr tableField(new Tellico::Data::Field(QStringLiteral("table"), QStringLiteral("Table"), Tellico::Data::Field::Table)); tableField->setFormatType(Tellico::FieldFormat::FormatName); coll->addField(tableField); QString tableValue = QStringLiteral("Value1") + Tellico::FieldFormat::rowDelimiterString() + Tellico::FieldFormat::rowDelimiterString() + QStringLiteral("Value2"); entry2->setField(QStringLiteral("table"), tableValue); QCOMPARE(entry2->formattedField(QStringLiteral("table")), tableValue); groupNames = entry2->groupNamesByFieldName(QStringLiteral("table")); QCOMPARE(groupNames.count(), 2); QVERIFY(groupNames.contains(QStringLiteral("Value1"))); QVERIFY(groupNames.contains(QStringLiteral("Value2"))); } void CollectionTest::testFields() { Tellico::Data::CollPtr coll(new Tellico::Data::Collection(true)); // add default fields QCOMPARE(coll->fields().count(), 4); QVERIFY(coll->peopleFields().isEmpty()); QVERIFY(coll->imageFields().isEmpty()); Tellico::Data::FieldPtr aField(new Tellico::Data::Field(QStringLiteral("author"), QStringLiteral("Author"))); aField->setFlags(Tellico::Data::Field::AllowMultiple | Tellico::Data::Field::AllowGrouped); aField->setFormatType(Tellico::FieldFormat::FormatName); QCOMPARE(coll->addField(aField), true); QVERIFY(coll->hasField(QStringLiteral("author"))); QCOMPARE(coll->defaultGroupField(), QStringLiteral("author")); QCOMPARE(coll->fields().count(), 5); QCOMPARE(coll->peopleFields().count(), 1); QVERIFY(coll->imageFields().isEmpty()); QVERIFY(!coll->hasImages()); QCOMPARE(coll->fieldsByCategory(QStringLiteral("General")).size(), 2); Tellico::Data::FieldPtr bField(new Tellico::Data::Field(QStringLiteral("cover"), QStringLiteral("Cover"), Tellico::Data::Field::Image)); QCOMPARE(coll->addField(bField), true); QVERIFY(coll->hasField(QStringLiteral("cover"))); QVERIFY(!coll->hasField(QStringLiteral("Ccover"))); QCOMPARE(coll->fields().count(), 6); QCOMPARE(coll->peopleFields().count(), 1); QCOMPARE(coll->imageFields().count(), 1); QVERIFY(coll->hasImages()); QStringList cats = coll->fieldCategories(); QCOMPARE(cats.size(), 3); QVERIFY(cats.contains(QStringLiteral("General"))); QVERIFY(cats.contains(QStringLiteral("Personal"))); QVERIFY(cats.contains(QStringLiteral("Cover"))); const QStringList names = coll->fieldNames(); QCOMPARE(names.size(), 6); QVERIFY(names.contains(QStringLiteral("author"))); QVERIFY(names.contains(QStringLiteral("cover"))); const QStringList titles = coll->fieldTitles(); QCOMPARE(titles.size(), 6); QVERIFY(titles.contains(QStringLiteral("Author"))); QVERIFY(titles.contains(QStringLiteral("Cover"))); QCOMPARE(coll->fieldByName(QStringLiteral("author")), aField); QCOMPARE(coll->fieldByTitle(QStringLiteral("Author")), aField); QCOMPARE(coll->fieldNameByTitle(QStringLiteral("Author")), QStringLiteral("author")); QCOMPARE(coll->fieldNameByTitle(QStringLiteral("author")), QString()); QCOMPARE(coll->fieldTitleByName(QStringLiteral("Author")), QString()); QCOMPARE(coll->fieldTitleByName(QStringLiteral("author")), QStringLiteral("Author")); QVERIFY(coll->removeField(QStringLiteral("cover"))); QVERIFY(!coll->hasField(QStringLiteral("cover"))); QCOMPARE(coll->fields().count(), 5); QVERIFY(!coll->hasImages()); QCOMPARE(coll->fieldTitleByName(QStringLiteral("cover")), QString()); QCOMPARE(coll->fieldCategories().size(), 2); Tellico::Data::FieldPtr cField(new Tellico::Data::Field(QStringLiteral("editor"), QStringLiteral("Editor"))); cField->setFlags(Tellico::Data::Field::AllowGrouped); cField->setFormatType(Tellico::FieldFormat::FormatName); cField->setCategory(QStringLiteral("People")); // since the field name does not match an existing field, modifying should fail QVERIFY(!coll->modifyField(cField)); cField->setName(QStringLiteral("author")); QVERIFY(coll->modifyField(cField)); QCOMPARE(coll->fieldByName(QStringLiteral("author")), cField); QCOMPARE(coll->fieldByTitle(QStringLiteral("Author")), Tellico::Data::FieldPtr()); QCOMPARE(coll->fieldByTitle(QStringLiteral("Editor")), cField); QCOMPARE(coll->peopleFields().count(), 1); cats = coll->fieldCategories(); QCOMPARE(cats.size(), 3); QVERIFY(cats.contains(QStringLiteral("General"))); QVERIFY(cats.contains(QStringLiteral("Personal"))); QVERIFY(cats.contains(QStringLiteral("People"))); QCOMPARE(coll->fieldsByCategory(QStringLiteral("General")).size(), 1); QCOMPARE(coll->fieldsByCategory(QStringLiteral("People")).size(), 1); coll->clear(); QVERIFY(coll->fields().isEmpty()); QVERIFY(coll->peopleFields().isEmpty()); QVERIFY(coll->imageFields().isEmpty()); QVERIFY(coll->fieldCategories().isEmpty()); QVERIFY(coll->defaultGroupField().isEmpty()); QCOMPARE(coll->fieldByName(QStringLiteral("author")), Tellico::Data::FieldPtr()); QCOMPARE(coll->fieldByTitle(QStringLiteral("Editor")), Tellico::Data::FieldPtr()); } void CollectionTest::testDerived() { Tellico::Data::CollPtr coll(new Tellico::Data::Collection(true)); // add default field Tellico::Data::FieldPtr aField(new Tellico::Data::Field(QStringLiteral("author"), QStringLiteral("Author"))); aField->setFlags(Tellico::Data::Field::AllowMultiple); aField->setFormatType(Tellico::FieldFormat::FormatName); coll->addField(aField); Tellico::Data::EntryPtr entry(new Tellico::Data::Entry(coll)); entry->setField(QStringLiteral("author"), QStringLiteral("Albert Einstein; Niels Bohr")); coll->addEntries(entry); Tellico::Data::FieldPtr field(new Tellico::Data::Field(QStringLiteral("test"), QStringLiteral("Test"))); field->setProperty(QStringLiteral("template"), QStringLiteral("%{author}")); field->setFlags(Tellico::Data::Field::Derived); field->setFormatType(Tellico::FieldFormat::FormatName); coll->addField(field); QCOMPARE(entry->field(QStringLiteral("test")), QStringLiteral("Albert Einstein; Niels Bohr")); field->setProperty(QStringLiteral("template"), QStringLiteral("%{test3}")); Tellico::Data::FieldPtr field2(new Tellico::Data::Field(QStringLiteral("test2"), QStringLiteral("Test"))); field2->setProperty(QStringLiteral("template"), QStringLiteral("%{test}")); field2->setFlags(Tellico::Data::Field::Derived); coll->addField(field2); Tellico::Data::FieldPtr field3(new Tellico::Data::Field(QStringLiteral("test3"), QStringLiteral("Test"))); field3->setProperty(QStringLiteral("template"), QStringLiteral("%{test3:1}")); field3->setFlags(Tellico::Data::Field::Derived); coll->addField(field3); // recursive, so template should be empty now QCOMPARE(field3->property(QStringLiteral("template")), QString()); // now test all the possible format options field->setProperty(QStringLiteral("template"), QStringLiteral("%{author:1}")); QCOMPARE(entry->field(QStringLiteral("test")), QStringLiteral("Albert Einstein")); field->setProperty(QStringLiteral("template"), QStringLiteral("%{author:1/l}")); QCOMPARE(entry->field(QStringLiteral("test")), QStringLiteral("albert einstein")); field->setProperty(QStringLiteral("template"), QStringLiteral("%{author:1/u}")); QCOMPARE(entry->field(QStringLiteral("test")), QStringLiteral("ALBERT EINSTEIN")); field->setProperty(QStringLiteral("template"), QStringLiteral("%{author:1}")); QCOMPARE(entry->formattedField(QStringLiteral("test"), Tellico::FieldFormat::ForceFormat), QStringLiteral("Einstein, Albert")); field->setProperty(QStringLiteral("template"), QStringLiteral("%{author:2}")); QCOMPARE(entry->field(QStringLiteral("test")), QStringLiteral("Niels Bohr")); field->setProperty(QStringLiteral("template"), QStringLiteral("%{author:-1}")); QCOMPARE(entry->field(QStringLiteral("test")), QStringLiteral("Niels Bohr")); field->setProperty(QStringLiteral("template"), QStringLiteral("%{author:-1}")); QCOMPARE(entry->formattedField(QStringLiteral("test"), Tellico::FieldFormat::ForceFormat), QStringLiteral("Bohr, Niels")); field->setProperty(QStringLiteral("template"), QStringLiteral("%{author:-2}")); QCOMPARE(entry->field(QStringLiteral("test")), QStringLiteral("Albert Einstein")); } void CollectionTest::testValue() { QFETCH(QString, string); QFETCH(QString, formatted); QFETCH(int, typeInt); Tellico::FieldFormat::Type type = static_cast(typeInt); Tellico::Data::CollPtr coll(new Tellico::Data::Collection(true)); // add default field Tellico::Data::FieldPtr field1(new Tellico::Data::Field(QStringLiteral("test"), QStringLiteral("Test"))); field1->setFlags(Tellico::Data::Field::AllowMultiple); field1->setFormatType(type); coll->addField(field1); Tellico::Data::FieldPtr field2(new Tellico::Data::Field(QStringLiteral("table"), QStringLiteral("Table"), Tellico::Data::Field::Table)); field2->setFormatType(type); coll->addField(field2); Tellico::Data::EntryPtr entry(new Tellico::Data::Entry(coll)); coll->addEntries(entry); entry->setField(field1, string); const QString dummy = Tellico::FieldFormat::columnDelimiterString() + QStringLiteral("dummy, the; Dummy, The; the dummy"); entry->setField(field2, string + dummy); QCOMPARE(entry->formattedField(field1, Tellico::FieldFormat::ForceFormat), formatted); QCOMPARE(entry->formattedField(field2, Tellico::FieldFormat::ForceFormat), formatted.append(dummy)); } void CollectionTest::testValue_data() { QTest::addColumn("string"); QTest::addColumn("formatted"); QTest::addColumn("typeInt"); QTest::newRow("test1") << "name" << "Name" << int(Tellico::FieldFormat::FormatName); QTest::newRow("test2") << "name1; name2" << "Name1; Name2" << int(Tellico::FieldFormat::FormatName); QTest::newRow("test3") << "Bob Dylan;Randy Quaid" << "Dylan, Bob; Quaid, Randy" << int(Tellico::FieldFormat::FormatName); QTest::newRow("test4") << "the return of the king" << "Return of the King, The" << int(Tellico::FieldFormat::FormatTitle); QTest::newRow("test5") << "the return of the king;the who" << "Return of the King, The; Who, The" << int(Tellico::FieldFormat::FormatTitle); } void CollectionTest::testDtd() { const QString xmllint = QStandardPaths::findExecutable(QStringLiteral("xmllint")); if(xmllint.isEmpty()) { QSKIP("This test requires xmllint", SkipAll); } // xmllint doesn't seem to support spaces in path. Is this an XML thing? if(QFINDTESTDATA("../../tellico.dtd").contains(QRegExp(QStringLiteral("\\s")))) { QSKIP("This test prohibits whitespace in the build path", SkipAll); } QFETCH(int, typeInt); Tellico::Data::Collection::Type type = static_cast(typeInt); Tellico::Data::CollPtr coll = Tellico::CollectionFactory::collection(type, true); QVERIFY(coll); Tellico::Data::EntryPtr entry1(new Tellico::Data::Entry(coll)); coll->addEntries(entry1); foreach(Tellico::Data::FieldPtr field, coll->fields()) { switch(field->type()) { case Tellico::Data::Field::Line: entry1->setField(field, field->title()); break; case Tellico::Data::Field::Para: entry1->setField(field, field->title()); break; case Tellico::Data::Field::URL: entry1->setField(field, field->title()); break; case Tellico::Data::Field::Table: entry1->setField(field, field->title()); break; case Tellico::Data::Field::Image: entry1->setField(field, field->title()); break; case Tellico::Data::Field::Number: entry1->setField(field, QStringLiteral("1")); break; case Tellico::Data::Field::Rating: entry1->setField(field, QStringLiteral("1")); break; case Tellico::Data::Field::Date: entry1->setField(field, QStringLiteral("2009-01-10")); break; case Tellico::Data::Field::Bool: entry1->setField(field, QStringLiteral("true")); break; case Tellico::Data::Field::Choice: entry1->setField(field, field->allowed().first()); break; default: break; } } Tellico::Export::TellicoXMLExporter exporter(coll); exporter.setEntries(coll->entries()); KProcess proc; proc.setProgram(QStringLiteral("xmllint"), QStringList() << QStringLiteral("--noout") << QStringLiteral("--nonet") << QStringLiteral("--nowarning") << QStringLiteral("--dtdvalid") << QFINDTESTDATA("../../tellico.dtd") << QStringLiteral("-")); proc.start(); proc.write(exporter.text().toUtf8()); proc.closeWriteChannel(); proc.waitForFinished(); QCOMPARE(proc.exitCode(), 0); } void CollectionTest::testDtd_data() { QTest::addColumn("typeInt"); QTest::newRow("book") << int(Tellico::Data::Collection::Book); QTest::newRow("video") << int(Tellico::Data::Collection::Video); QTest::newRow("album") << int(Tellico::Data::Collection::Album); QTest::newRow("bibtex") << int(Tellico::Data::Collection::Bibtex); QTest::newRow("comic") << int(Tellico::Data::Collection::ComicBook); QTest::newRow("wine") << int(Tellico::Data::Collection::Wine); QTest::newRow("coin") << int(Tellico::Data::Collection::Coin); QTest::newRow("stamp") << int(Tellico::Data::Collection::Stamp); QTest::newRow("card") << int(Tellico::Data::Collection::Card); QTest::newRow("game") << int(Tellico::Data::Collection::Game); QTest::newRow("file") << int(Tellico::Data::Collection::File); QTest::newRow("board") << int(Tellico::Data::Collection::BoardGame); } void CollectionTest::testDuplicate() { Tellico::Data::CollPtr coll(new Tellico::Data::Collection(true)); QCOMPARE(coll->entryCount(), 0); Tellico::Data::EntryPtr entry1(new Tellico::Data::Entry(coll)); entry1->setField(QStringLiteral("title"), QStringLiteral("title1")); entry1->setField(QStringLiteral("cdate"), QStringLiteral("2019-01-01")); coll->addEntries(entry1); QCOMPARE(coll->entryCount(), 1); // this is how Controller::slotCopySelectedEntries() does it Tellico::Data::EntryPtr entry2(new Tellico::Data::Entry(*entry1)); coll->addEntries(entry2); QCOMPARE(coll->entryCount(), 2); QCOMPARE(entry1->title(), entry2->title()); QVERIFY(entry1->id() != entry2->id()); // creation date should reflect current date in the duplicated entry QVERIFY(entry1->field(QStringLiteral("cdate")) != entry2->field(QStringLiteral("cdate"))); QCOMPARE(entry2->field(QStringLiteral("cdate")), QDate::currentDate().toString(Qt::ISODate)); // also test operator= which is how ModifyEntries::swapValues() works Tellico::Data::Entry* entryPtr = new Tellico::Data::Entry(coll); *entryPtr = *entry1; Tellico::Data::EntryPtr entry3(entryPtr); coll->addEntries(entry3); QCOMPARE(coll->entryCount(), 3); QCOMPARE(entry1->title(), entry3->title()); // entry id should be different QVERIFY(entry1->id() != entry3->id()); // creation date should reflect current date in the duplicated entry QVERIFY(entry1->field(QStringLiteral("cdate")) != entry3->field(QStringLiteral("cdate"))); QCOMPARE(entry3->field(QStringLiteral("cdate")), QDate::currentDate().toString(Qt::ISODate)); bool ret = Tellico::Data::Document::mergeEntry(entry1, entry2); QCOMPARE(ret, true); TestResolver cancelMerge(Tellico::MergeConflictResolver::CancelMerge); ret = Tellico::Data::Document::mergeEntry(entry1, entry2, &cancelMerge); QCOMPARE(ret, true); entry2->setField(QStringLiteral("title"), QStringLiteral("title2")); ret = Tellico::Data::Document::mergeEntry(entry1, entry2, &cancelMerge); QCOMPARE(ret, false); QCOMPARE(entry1->title(), QStringLiteral("title1")); QCOMPARE(entry2->title(), QStringLiteral("title2")); TestResolver keepFirst(Tellico::MergeConflictResolver::KeepFirst); ret = Tellico::Data::Document::mergeEntry(entry1, entry2, &keepFirst); QCOMPARE(ret, true); QCOMPARE(entry1->title(), QStringLiteral("title1")); // the second entry never gets changed QCOMPARE(entry2->title(), QStringLiteral("title2")); entry2->setField(QStringLiteral("title"), QStringLiteral("title2")); TestResolver keepSecond(Tellico::MergeConflictResolver::KeepSecond); ret = Tellico::Data::Document::mergeEntry(entry1, entry2, &keepSecond); QCOMPARE(ret, true); QCOMPARE(entry1->title(), QStringLiteral("title2")); QCOMPARE(entry2->title(), QStringLiteral("title2")); entry1->setField(QStringLiteral("title"), QStringLiteral("title1")); // returns true, ("merge successful") even if values were not merged ret = Tellico::Data::Document::mergeEntry(entry1, entry2); QCOMPARE(ret, true); QCOMPARE(entry1->title(), QStringLiteral("title1")); QCOMPARE(entry2->title(), QStringLiteral("title2")); } void CollectionTest::testMergeFields() { // here, we want to verify that when entries and fields from outside a collection are merged in // the allowed values for the Choice fields are retained in the same order, and new values are only // added if they are used Tellico::Data::CollPtr coll1 = Tellico::CollectionFactory::collection(Tellico::Data::Collection::Game, true); Tellico::Data::CollPtr coll2 = Tellico::CollectionFactory::collection(Tellico::Data::Collection::Game, true); // modify the allowed values for "platform" in collection 1 Tellico::Data::FieldPtr platform1 = coll1->fieldByName(QStringLiteral("platform")); QVERIFY(platform1); QStringList newValues1 = QStringList() << QStringLiteral("PSP") << QStringLiteral("Xbox 360"); platform1->setAllowed(newValues1); QVERIFY(coll1->modifyField(platform1)); QCOMPARE(platform1->allowed(), newValues1); Tellico::Data::EntryPtr entry2(new Tellico::Data::Entry(coll2)); entry2->setField(QStringLiteral("platform"), QStringLiteral("PlayStation")); QCOMPARE(entry2->field(QStringLiteral("platform")), QStringLiteral("PlayStation")); coll2->addEntries(entry2); QPair p = Tellico::Data::Document::mergeFields(coll1, Tellico::Data::FieldList() << coll2->fieldByName(QStringLiteral("platform")), Tellico::Data::EntryList() << entry2); Tellico::Data::FieldList modifiedFields = p.first; QCOMPARE(modifiedFields.count(), 1); // this is the zinger right here. The list of allowed values should be the original // with only the new existing value tacked on the end QCOMPARE(modifiedFields.first()->allowed(), newValues1 << entry2->field(QStringLiteral("platform"))); Tellico::Data::FieldList addedFields = p.second; QVERIFY(addedFields.isEmpty()); } +void CollectionTest::testFieldsIntersection() { + // simple test for the list intersection utility method + Tellico::Data::CollPtr coll(new Tellico::Data::BookCollection(true)); + Tellico::Data::FieldList imageFields = coll->imageFields(); + + Tellico::Data::FieldList list = Tellico::listIntersection(imageFields, coll->fields()); + QCOMPARE(imageFields.count(), list.count()); + + QBENCHMARK { + // should be something less than 0.020 msecs :) + Tellico::Data::FieldList list = Tellico::listIntersection(coll->fields(), coll->fields()); + Q_UNUSED(list); + } +} + void CollectionTest::testAppendCollection() { // appending a collection adds new fields, merges existing one, and add new entries // the new entries should belong to the original collection and the existing entries should // remain in the source collection Tellico::Data::CollPtr coll1 = Tellico::CollectionFactory::collection(Tellico::Data::Collection::Game, true); Tellico::Data::CollPtr coll2 = Tellico::CollectionFactory::collection(Tellico::Data::Collection::Game, true); // modify the allowed values for "platform" in collection 1 Tellico::Data::FieldPtr platform1 = coll1->fieldByName(QStringLiteral("platform")); QVERIFY(platform1); QStringList newValues1 = QStringList() << QStringLiteral("My Box"); platform1->setAllowed(newValues1); QVERIFY(coll1->modifyField(platform1)); // add a new field Tellico::Data::FieldPtr field1(new Tellico::Data::Field(QStringLiteral("test"), QStringLiteral("test"))); QVERIFY(coll1->addField(field1)); Tellico::Data::EntryPtr entry1(new Tellico::Data::Entry(coll1)); QCOMPARE(entry1->collection(), coll1); coll1->addEntries(entry1); Tellico::Data::EntryPtr entry2(new Tellico::Data::Entry(coll2)); QCOMPARE(entry2->collection(), coll2); coll2->addEntries(entry2); // append coll1 into coll2 Tellico::Data::Document::appendCollection(coll2, coll1); // verify that the test field was added QVERIFY(coll2->hasField(QStringLiteral("test"))); // verified that the modified field was merged Tellico::Data::FieldPtr platform2 = coll2->fieldByName(QStringLiteral("platform")); QVERIFY(platform2); QVERIFY(platform2->allowed().contains(QStringLiteral("My Box"))); // coll2 should have two entries now, both with proper parent QCOMPARE(coll2->entryCount(), 2); Tellico::Data::EntryList e2 = coll2->entries(); QCOMPARE(e2.at(0)->collection(), coll2); QCOMPARE(e2.at(1)->collection(), coll2); QCOMPARE(coll1->entryCount(), 1); Tellico::Data::EntryList e1 = coll1->entries(); QCOMPARE(e1.at(0)->collection(), coll1); } void CollectionTest::testMergeCollection() { QUrl url = QUrl::fromLocalFile(QFINDTESTDATA("data/movies-many.tc")); Tellico::Import::TellicoImporter importer1(url); Tellico::Data::CollPtr coll1 = importer1.collection(); QVERIFY(coll1); Tellico::Import::TellicoImporter importer2(url); Tellico::Data::CollPtr coll2 = importer2.collection(); QVERIFY(coll2); Tellico::Data::EntryPtr entryToAdd(new Tellico::Data::Entry(coll2)); QCOMPARE(entryToAdd->collection(), coll2); coll2->addEntries(entryToAdd); QCOMPARE(coll1->entryCount()+1, coll2->entryCount()); // merge coll2 into coll1 // first item is a vector of all entries that got added in the merge process // second item is a pair of entries that had their table field modified // typedef QVector< QPair > PairVector; // typedef QPair MergePair; Tellico::Data::MergePair mergePair = Tellico::Data::Document::mergeCollection(coll1, coll2); // one new entry was added QCOMPARE(mergePair.first.count(), 1); // no table fields edited either QVERIFY(mergePair.second.isEmpty()); // check item count QCOMPARE(coll1->fields().count(), coll2->fields().count()); QCOMPARE(coll1->entryCount(), coll2->entryCount()); } void CollectionTest::testBookMatch() { Tellico::Data::CollPtr c(new Tellico::Data::BookCollection(true)); // first check merging with same isbn Tellico::Data::EntryPtr e1(new Tellico::Data::Entry(c)); e1->setField(QStringLiteral("title"), QStringLiteral("title1")); e1->setField(QStringLiteral("author"), QStringLiteral("author1")); e1->setField(QStringLiteral("edition"), QStringLiteral("edition1")); e1->setField(QStringLiteral("pur_price"), QStringLiteral("price1")); e1->setField(QStringLiteral("isbn"), QStringLiteral("1234567890")); c->addEntries(e1); Tellico::Data::EntryPtr e2(new Tellico::Data::Entry(c)); e2->setField(QStringLiteral("title"), QStringLiteral("title2")); e2->setField(QStringLiteral("author"), QStringLiteral("author2")); e2->setField(QStringLiteral("edition"), QStringLiteral("edition2")); e2->setField(QStringLiteral("pur_price"), QStringLiteral("price2")); e2->setField(QStringLiteral("isbn"), QStringLiteral("000000000")); // not a good match QVERIFY(c->sameEntry(e1, e2) < Tellico::EntryComparison::ENTRY_GOOD_MATCH); // perfect match now e2->setField(QStringLiteral("isbn"), QStringLiteral("1234567890")); QCOMPARE(c->sameEntry(e1, e2), int(Tellico::EntryComparison::ENTRY_PERFECT_MATCH)); QBENCHMARK { QCOMPARE(c->sameEntry(e1, e2), int(Tellico::EntryComparison::ENTRY_PERFECT_MATCH)); } } void CollectionTest::testMergeBenchmark() { QUrl url = QUrl::fromLocalFile(QFINDTESTDATA("data/movies-many.tc")); Tellico::Data::EntryList entriesToAdd; QBENCHMARK { Tellico::Import::TellicoImporter importer1(url, false /* load all images */); Tellico::Data::CollPtr coll1 = importer1.collection(); QVERIFY(coll1); Tellico::Import::TellicoImporter importer2(url); Tellico::Data::CollPtr coll2 = importer2.collection(); QVERIFY(coll2); entriesToAdd.clear(); for(int i = 0; i < 500; ++i) { Tellico::Data::EntryPtr entryToAdd(new Tellico::Data::Entry(coll2)); entryToAdd->setField(QStringLiteral("title"), QString::number(qrand())); entryToAdd->setField(QStringLiteral("studio"), QString::number(i)); entriesToAdd += entryToAdd; } coll2->addEntries(entriesToAdd); Tellico::Data::Document::mergeCollection(coll1, coll2); } } void CollectionTest::testMatchScore() { QFETCH(QString, field); QFETCH(QString, value); QFETCH(Tellico::EntryComparison::MatchValue, score); QVERIFY(m_coll); Tellico::Data::EntryPtr e(new Tellico::Data::Entry(m_coll)); e->setField(field, value); QCOMPARE(Tellico::EntryComparison::score(m_entry, e, field, m_coll.data()), int(score)); } void CollectionTest::testMatchScore_data() { QTest::addColumn("field"); QTest::addColumn("value"); QTest::addColumn("score"); QTest::newRow("empty title") << QStringLiteral("title") << QString() << Tellico::EntryComparison::MATCH_VALUE_NONE; QTest::newRow("title match") << QStringLiteral("title") << QStringLiteral("title1") << Tellico::EntryComparison::MATCH_VALUE_STRONG; QTest::newRow("title match case") << QStringLiteral("title") << QStringLiteral("TITLE1") << Tellico::EntryComparison::MATCH_VALUE_STRONG; // QTest::newRow("title match articles") << QStringLiteral("title") << QStringLiteral("THE TITLE1") << Tellico::EntryComparison::MATCH_VALUE_WEAK; QTest::newRow("title match non alphanum") << QStringLiteral("title") << QStringLiteral("title1.") << Tellico::EntryComparison::MATCH_VALUE_STRONG; // QTest::newRow("title match paren") << QStringLiteral("title") << QStringLiteral("title1 (old)") << Tellico::EntryComparison::MATCH_VALUE_WEAK; QTest::newRow("isbn match") << QStringLiteral("isbn") << QStringLiteral("1234367890") << Tellico::EntryComparison::MATCH_VALUE_STRONG; QTest::newRow("isbn match formatted") << QStringLiteral("isbn") << QStringLiteral("1-234-36789-0") << Tellico::EntryComparison::MATCH_VALUE_STRONG; QTest::newRow("lccn match") << QStringLiteral("lccn") << QStringLiteral("89456") << Tellico::EntryComparison::MATCH_VALUE_STRONG; QTest::newRow("lccn match formatted") << QStringLiteral("lccn") << QStringLiteral("89-456") << Tellico::EntryComparison::MATCH_VALUE_STRONG; QTest::newRow("arxiv") << QStringLiteral("arxiv") << QStringLiteral("hep-lat/0110180") << Tellico::EntryComparison::MATCH_VALUE_STRONG; QTest::newRow("arxiv format1") << QStringLiteral("arxiv") << QStringLiteral("hep-lat/0110180v1") << Tellico::EntryComparison::MATCH_VALUE_STRONG; QTest::newRow("arxiv format2") << QStringLiteral("arxiv") << QStringLiteral("arxiv:hep-lat/0110180v1") << Tellico::EntryComparison::MATCH_VALUE_STRONG; QTest::newRow("author") << QStringLiteral("author") << QStringLiteral("John Doe") << Tellico::EntryComparison::MATCH_VALUE_STRONG; QTest::newRow("author formatted") << QStringLiteral("author") << QStringLiteral("Doe, John") << Tellico::EntryComparison::MATCH_VALUE_STRONG; QTest::newRow("author formatted2") << QStringLiteral("author") << QStringLiteral("doe, john") << Tellico::EntryComparison::MATCH_VALUE_STRONG; QTest::newRow("author multiple") << QStringLiteral("author") << QStringLiteral("John Doe; Jane Doe") << Tellico::EntryComparison::MATCH_VALUE_STRONG; } void CollectionTest::testGamePlatform() { // test that the platform name guessing heuristic works on its own names for(int i = 1; i < Tellico::Data::GameCollection::LastPlatform; i++) { QString pName = Tellico::Data::GameCollection::platformName(Tellico::Data::GameCollection::GamePlatform(i)); int pGuess = Tellico::Data::GameCollection::guessPlatform(pName); QCOMPARE(i, pGuess); } // test some specific platform names that some data sources might return // thegamesdb.net returns "Nintendo Game Boy" int gameBoy = Tellico::Data::GameCollection::guessPlatform(QStringLiteral("Nintendo Game Boy")); QCOMPARE(gameBoy, int(Tellico::Data::GameCollection::GameBoy)); gameBoy = Tellico::Data::GameCollection::guessPlatform(QStringLiteral("gameboy")); QCOMPARE(gameBoy, int(Tellico::Data::GameCollection::GameBoy)); gameBoy = Tellico::Data::GameCollection::guessPlatform(QStringLiteral("gameboy color")); QCOMPARE(gameBoy, int(Tellico::Data::GameCollection::GameBoyColor)); gameBoy = Tellico::Data::GameCollection::guessPlatform(QStringLiteral("Gameboy Advance")); QCOMPARE(gameBoy, int(Tellico::Data::GameCollection::GameBoyAdvance)); // don't match Nintendo Virtual Boy with Nintendo int guess = Tellico::Data::GameCollection::guessPlatform(QStringLiteral("Nintendo Virtual Boy")); QCOMPARE(guess, int(Tellico::Data::GameCollection::UnknownPlatform)); guess = Tellico::Data::GameCollection::guessPlatform(QStringLiteral("Nintendo Entertainment System")); QCOMPARE(guess, int(Tellico::Data::GameCollection::Nintendo)); } diff --git a/src/tests/collectiontest.h b/src/tests/collectiontest.h index 8986f65f..93e88ffa 100644 --- a/src/tests/collectiontest.h +++ b/src/tests/collectiontest.h @@ -1,62 +1,63 @@ /*************************************************************************** Copyright (C) 2009-2016 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ #ifndef COLLECTIONTEST_H #define COLLECTIONTEST_H #include #include "../datavectors.h" class CollectionTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void cleanupTestCase(); void testEmpty(); void testCollection(); void testFields(); void testDerived(); void testValue(); void testValue_data(); void testDtd(); void testDtd_data(); void testDuplicate(); void testMergeFields(); + void testFieldsIntersection(); void testAppendCollection(); void testMergeCollection(); void testBookMatch(); void testMergeBenchmark(); void testMatchScore(); void testMatchScore_data(); void testGamePlatform(); private: Tellico::Data::CollPtr m_coll; Tellico::Data::EntryPtr m_entry; }; #endif