File indexing completed on 2024-05-12 05:23:17

0001 /*
0002   SPDX-FileCopyrightText: 2016 David Faure <faure@kde.org>
0003 
0004   SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #undef QT_NO_CAST_FROM_ASCII
0008 
0009 #include <akonadi/qtest_akonadi.h>
0010 
0011 #include <Akonadi/Collection>
0012 #include <Akonadi/Item>
0013 #include <Akonadi/ItemFetchScope>
0014 
0015 #include <Akonadi/AgentInstanceCreateJob>
0016 #include <Akonadi/AgentManager>
0017 #include <Akonadi/AgentType>
0018 #include <Akonadi/CollectionCreateJob>
0019 #include <Akonadi/CollectionDeleteJob>
0020 #include <Akonadi/CollectionFetchJob>
0021 #include <Akonadi/CollectionMoveJob>
0022 #include <Akonadi/EntityOrderProxyModel>
0023 #include <Akonadi/SearchCreateJob>
0024 #include <Akonadi/SearchQuery>
0025 
0026 #include <KMime/Message>
0027 
0028 #include "folder/entitycollectionorderproxymodel.h"
0029 #include <MailCommon/FolderTreeView>
0030 #include <MailCommon/FolderTreeWidget>
0031 #include <MailCommon/MailKernel>
0032 
0033 #include <QDebug>
0034 #include <QStandardPaths>
0035 #include <QTest>
0036 #include <QTreeView>
0037 
0038 #include "dummykernel.cpp"
0039 
0040 // #define SHOW_WIDGET
0041 
0042 using namespace Akonadi;
0043 
0044 class FolderTreeWidgetTest : public QObject
0045 {
0046     Q_OBJECT
0047 private Q_SLOTS:
0048     void initTestCase()
0049     {
0050         AkonadiTest::checkTestIsIsolated();
0051 
0052         auto kernel = new DummyKernel(nullptr);
0053         CommonKernel->registerKernelIf(kernel);
0054         CommonKernel->registerSettingsIf(kernel);
0055 
0056         mFolderTreeWidget = new MailCommon::FolderTreeWidget(nullptr);
0057         const QStringList resourceOrder{"akonadi_knut_resource_2", "akonadi_knut_resource_0"}; // _1 isn't specified so it goes at the end
0058         mFolderTreeWidget->entityOrderProxy()->setTopLevelOrder(resourceOrder);
0059 
0060         mCollectionModel = KernelIf->collectionModel();
0061         mTopModel = mFolderTreeWidget->folderTreeView()->model();
0062 
0063         // One knut resource is already defined in the unittestenv, so that it's below "Search" in the ETM.
0064         QTRY_COMPARE(mCollectionModel->rowCount(), 4);
0065         QCOMPARE(mTopModel->rowCount(), 3); // Search doesn't appear yet
0066         mFolderNames = QStringList{QStringLiteral("res3"), QStringLiteral("res1"), QStringLiteral("res2")};
0067         QCOMPARE(collectNames(mTopModel), mFolderNames);
0068     }
0069 
0070     void createAndDeleteSearchCollection()
0071     {
0072 #ifdef SHOW_WIDGET
0073         mFolderTreeWidget->resize(1000, 1000);
0074         mFolderTreeWidget->show();
0075 #endif
0076 
0077         // Create search folder
0078         Akonadi::SearchQuery query;
0079         query.addTerm(Akonadi::SearchTerm(QStringLiteral("plugin"), 1));
0080         auto create = new SearchCreateJob(QStringLiteral("search123456"), query, this);
0081         create->setSearchMimeTypes({KMime::Message::mimeType()});
0082         create->setRemoteSearchEnabled(false);
0083         AKVERIFYEXEC(create);
0084         const Collection searchCol = create->createdCollection();
0085         QVERIFY(searchCol.isValid());
0086 
0087         // Check it appeared in the tree, under a Search toplevel item
0088         QTRY_COMPARE(mCollectionModel->rowCount(), 4);
0089         QTRY_COMPARE(mTopModel->rowCount(), 4);
0090         const QStringList names = collectNames(mTopModel);
0091         const QStringList expectedNames{"res3", "res1", "res2", "Search"};
0092         QCOMPARE(names, expectedNames);
0093         const int rowOfSearch = names.indexOf("Search");
0094         const QModelIndex searchParent = mTopModel->index(rowOfSearch, 0);
0095         QCOMPARE(mTopModel->rowCount(searchParent), 1); // the actual child search folder
0096 
0097 #ifdef SHOW_WIDGET
0098         QTest::qWait(500);
0099 #endif
0100 
0101         QSignalSpy rATBRSpy_CollectionModel(mCollectionModel, &QAbstractItemModel::rowsAboutToBeRemoved);
0102         QSignalSpy rATBRSpy_FolderTreeProxyModel(mFolderTreeWidget->folderTreeWidgetProxyModel(), &QAbstractItemModel::rowsAboutToBeRemoved);
0103         QSignalSpy rATBRSpy_TopModel(mTopModel, &QAbstractItemModel::rowsAboutToBeRemoved);
0104 
0105         // Now delete it
0106         auto delJob = new Akonadi::CollectionDeleteJob(searchCol);
0107         AKVERIFYEXEC(delJob);
0108 
0109         // Check it disappeared from the tree, as well as the toplevel item
0110         QTRY_COMPARE(mTopModel->rowCount(), 3);
0111         QCOMPARE(collectNames(mTopModel), mFolderNames);
0112         QCOMPARE(collectNames(mFolderTreeWidget->entityOrderProxy()), mFolderNames);
0113 
0114         QCOMPARE(rATBRSpy_CollectionModel.count(), 2); // one for the child, one for the parent
0115         QCOMPARE(rATBRSpy_FolderTreeProxyModel.count(), 2); // one for the child, one for the parent
0116         QCOMPARE(rATBRSpy_TopModel.count(), 2); // one for the child, one for the parent
0117 
0118         checkMailFolders(mTopModel->index(0, 0));
0119 
0120 #ifdef SHOW_WIDGET
0121         QTest::qWait(1000);
0122 #endif
0123     }
0124 
0125     void testCreateResources()
0126     {
0127         // Test creating more knut resources.
0128         // This tests that ETM and proxies on top update correctly, and it tests toplevel collection order.
0129         const QList<int> numFolders{1, 5, 2};
0130         QList<Collection> topLevelCollections;
0131         const AgentType agentType = AgentManager::self()->type(QStringLiteral("akonadi_knut_resource"));
0132         QVERIFY(agentType.isValid());
0133         mFolderNames = QStringList{"res3", "res1", "res2"}; // according to resourceOrder above (the folder names are defined in testdata-res*.xml)
0134 
0135         // Create resources
0136         const int numResources = numFolders.count();
0137         for (int i = 3 /*first three already created*/; i < numResources; ++i) {
0138             auto agentCreateJob = new AgentInstanceCreateJob(agentType);
0139             AKVERIFYEXEC(agentCreateJob);
0140             const QString identifier = agentCreateJob->instance().identifier();
0141 
0142             QTRY_COMPARE(mCollectionModel->rowCount(), i + 1);
0143             QTRY_COMPARE(mTopModel->rowCount(), i + 1);
0144 
0145             Collection topLevelCollection;
0146             QTRY_VERIFY((topLevelCollection = topLevelCollectionForResource(identifier)).isValid());
0147             topLevelCollections.append(topLevelCollection);
0148 
0149             // Now create some folders
0150             for (int number = 0; number < numFolders[i]; ++number) {
0151                 Collection mailCollection;
0152                 mailCollection.setParentCollection(topLevelCollection);
0153                 mailCollection.setName(QStringLiteral("mailCollection_%1_%2").arg(i).arg(number));
0154                 auto collCreateJob = new CollectionCreateJob(mailCollection);
0155                 AKVERIFYEXEC(collCreateJob);
0156             }
0157             const int resourceRow = collectNames(mTopModel).indexOf("res" + QString::number(i + 1));
0158             QModelIndex parent = mTopModel->index(resourceRow, 0);
0159             QTRY_COMPARE(mTopModel->rowCount(parent), numFolders[i]);
0160 
0161             checkMailFolders(parent);
0162         }
0163         QCOMPARE(collectNames(mTopModel), mFolderNames);
0164     }
0165 
0166     void testMoveFolder()
0167     {
0168         // Given a source folder with 2 levels of parents (res1/sub1/sub2)
0169         const QStringList folderNames = collectNames(mCollectionModel);
0170         const QModelIndex res1Index = mCollectionModel->index(folderNames.indexOf("res1"), 0, QModelIndex());
0171         const auto topLevelCollection = res1Index.data(EntityTreeModel::CollectionRole).value<Collection>();
0172         QCOMPARE(topLevelCollection.name(), QStringLiteral("res1"));
0173         const int parentCount = 2;
0174         Collection currentColl = topLevelCollection;
0175         for (int number = 0; number < parentCount; ++number) {
0176             Collection mailCollection;
0177             mailCollection.setParentCollection(currentColl);
0178             mailCollection.setName(QStringLiteral("sub%1").arg(number + 1));
0179             auto collCreateJob = new CollectionCreateJob(mailCollection);
0180             AKVERIFYEXEC(collCreateJob);
0181             currentColl = collCreateJob->collection();
0182         }
0183         QTRY_COMPARE(mCollectionModel->rowCount(res1Index), 1);
0184 
0185         // ... and a dest folder in another resource
0186         const QPersistentModelIndex res2Index = mCollectionModel->index(folderNames.indexOf("res2"), 0, QModelIndex());
0187         const int origRowCount = mCollectionModel->rowCount(res2Index);
0188         const auto newParentCollection = res2Index.data(EntityTreeModel::CollectionRole).value<Collection>();
0189         QCOMPARE(newParentCollection.name(), QStringLiteral("res2"));
0190 
0191         QTest::qWait(100); // #### akonadi bug? Without this, a warning "Only resources can modify remote identifiers" appears
0192 
0193         // When moving the source folder (sub2) to the dest folder
0194         auto collMoveJob = new CollectionMoveJob(currentColl, newParentCollection);
0195         AKVERIFYEXEC(collMoveJob);
0196 
0197         // wait for Akonadi::Monitor::collectionMoved
0198         QTRY_COMPARE(mCollectionModel->rowCount(res2Index), origRowCount + 1);
0199 
0200 #ifdef SHOW_WIDGET
0201         QTest::qWait(1000);
0202 #endif
0203     }
0204 
0205     void testFiltering()
0206     {
0207         const auto model = mFolderTreeWidget->entityOrderProxy();
0208         QCOMPARE(collectNamesRecursive(model), (QStringList{"res3", "res1", "sub1", "res2", "sub2"}));
0209 
0210         mFolderTreeWidget->applyFilter(QStringLiteral("sub"));
0211         // matches all folders matching "sub"
0212         QCOMPARE(collectNamesRecursive(model), (QStringList{"res1", "sub1", "res2", "sub2"}));
0213         QCOMPARE(mFolderTreeWidget->currentIndex().data().toString(), "sub1");
0214 
0215         mFolderTreeWidget->applyFilter(QStringLiteral("res"));
0216         // matches all folders matching "res"
0217         QCOMPARE(collectNamesRecursive(model), (QStringList{"res3", "res1", "res2"}));
0218         // "res1" is current because it became current when previous current "sub1" was filtered out
0219         QCOMPARE(mFolderTreeWidget->currentIndex().data().toString(), "res1");
0220 
0221         mFolderTreeWidget->applyFilter(QStringLiteral("foo"));
0222         // matches nothing
0223         QCOMPARE(collectNamesRecursive(model), (QStringList{}));
0224         QVERIFY(!mFolderTreeWidget->currentIndex().isValid());
0225 
0226         mFolderTreeWidget->applyFilter(QStringLiteral("res/sub"));
0227         // matches folders matching "sub" with parents matching "res"
0228         QCOMPARE(collectNamesRecursive(model), (QStringList{"res1", "sub1", "res2", "sub2"}));
0229         QCOMPARE(mFolderTreeWidget->currentIndex().data().toString(), "sub1");
0230 
0231         mFolderTreeWidget->applyFilter(QStringLiteral("res/1"));
0232         // matches folders matching "1" with parents matching "res"
0233         QCOMPARE(collectNamesRecursive(model), (QStringList{"res1", "sub1"}));
0234         QCOMPARE(mFolderTreeWidget->currentIndex().data().toString(), "sub1");
0235 
0236         mFolderTreeWidget->applyFilter(QStringLiteral("res/"));
0237         // matches folders matching anything ("" always matches) with parents matching "res"
0238         QCOMPARE(collectNamesRecursive(model), (QStringList{"res1", "sub1", "res2", "sub2"}));
0239         QCOMPARE(mFolderTreeWidget->currentIndex().data().toString(), "sub1");
0240 
0241         mFolderTreeWidget->applyFilter(QStringLiteral("sub/"));
0242         // matches nothing (there are no folders matching "sub" that have subfolders)
0243         QCOMPARE(collectNamesRecursive(model), (QStringList{}));
0244         QVERIFY(!mFolderTreeWidget->currentIndex().isValid());
0245 
0246         mFolderTreeWidget->applyFilter(QStringLiteral("/sub"));
0247         // matches folders matching "sub" with parents matching anything
0248         QCOMPARE(collectNamesRecursive(model), (QStringList{"res1", "sub1", "res2", "sub2"}));
0249         QCOMPARE(mFolderTreeWidget->currentIndex().data().toString(), "sub1");
0250 
0251         mFolderTreeWidget->applyFilter(QStringLiteral("/res"));
0252         // matches nothing (there are no subfolders matching "res")
0253         QCOMPARE(collectNamesRecursive(model), (QStringList{}));
0254         QVERIFY(!mFolderTreeWidget->currentIndex().isValid());
0255 
0256         mFolderTreeWidget->applyFilter(QStringLiteral("//sub"));
0257         // matches nothing (there are no subsubfolders matching "sub")
0258         QCOMPARE(collectNamesRecursive(model), (QStringList{}));
0259         QVERIFY(!mFolderTreeWidget->currentIndex().isValid());
0260 
0261         mFolderTreeWidget->applyFilter(QStringLiteral("res//"));
0262         // matches nothing (there are no folders matching "res" that have subsubfolders)
0263         QCOMPARE(collectNamesRecursive(model), (QStringList{}));
0264         QVERIFY(!mFolderTreeWidget->currentIndex().isValid());
0265 
0266         mFolderTreeWidget->applyFilter(QStringLiteral("1/1"));
0267         // matches folders matching "1" with parents matching "1"
0268         QCOMPARE(collectNamesRecursive(model), (QStringList{"res1", "sub1"}));
0269         QCOMPARE(mFolderTreeWidget->currentIndex().data().toString(), "sub1");
0270 
0271         mFolderTreeWidget->applyFilter(QStringLiteral("2/"));
0272         // matches folders matching anything with parents matching "2"
0273         QCOMPARE(collectNamesRecursive(model), (QStringList{"res2", "sub2"}));
0274         QCOMPARE(mFolderTreeWidget->currentIndex().data().toString(), "sub2");
0275 
0276         mFolderTreeWidget->applyFilter(QStringLiteral("1/2"));
0277         // matches nothing (there are no folders matching "2" with parents matching "1")
0278         QCOMPARE(collectNamesRecursive(model), (QStringList{}));
0279         QVERIFY(!mFolderTreeWidget->currentIndex().isValid());
0280     }
0281 
0282 private:
0283     static Collection topLevelCollectionForResource(const QString &identifier)
0284     {
0285         // Find out the collection for the resource (as defined in unittestenv/xdglocal/testdata-res*.xml)
0286         auto job = new CollectionFetchJob(Collection::root(), CollectionFetchJob::FirstLevel);
0287         if (job->exec()) {
0288             const Collection::List collections = job->collections();
0289             for (const Collection &col : collections) {
0290                 // qDebug() << col.resource() << col.mimeType();
0291                 if (col.resource() == identifier) {
0292                     return col;
0293                 }
0294             }
0295         } else {
0296             qWarning() << job->errorString();
0297         }
0298         return {};
0299     }
0300 
0301     // Check that every child of @p parent has a valid CollectionRole.
0302     static void checkMailFolders(const QModelIndex &parent)
0303     {
0304         const QAbstractItemModel *model = parent.model();
0305         QVERIFY(model);
0306         for (int row = 0; row < model->rowCount(parent); ++row) {
0307             QModelIndex idx = model->index(row, 0, parent);
0308             QModelIndex col1idx = model->index(row, 1, parent);
0309             QCOMPARE(col1idx.sibling(col1idx.row(), 0), idx);
0310             auto collection = idx.data(EntityTreeModel::CollectionRole).value<Collection>();
0311             QVERIFY2(collection.isValid(), qPrintable(idx.data().toString()));
0312         }
0313     }
0314 
0315     static QStringList collectNamesRecursive(const QAbstractItemModel *model, const QModelIndex &parent = QModelIndex{})
0316     {
0317         QStringList ret;
0318         ret.reserve(model->rowCount(parent));
0319         for (int row = 0; row < model->rowCount(parent); ++row) {
0320             QModelIndex idx = model->index(row, 0, parent);
0321             ret.append(idx.data().toString());
0322             ret.append(collectNamesRecursive(model, idx));
0323         }
0324         return ret;
0325     }
0326 
0327     static QStringList collectNames(QAbstractItemModel *model);
0328     EntityMimeTypeFilterModel *mCollectionModel = nullptr;
0329     QAbstractItemModel *mTopModel = nullptr;
0330     MailCommon::FolderTreeWidget *mFolderTreeWidget = nullptr;
0331     QStringList mFolderNames;
0332 };
0333 
0334 QStringList FolderTreeWidgetTest::collectNames(QAbstractItemModel *model)
0335 {
0336     QStringList ret;
0337     ret.reserve(model->rowCount());
0338     for (int row = 0; row < model->rowCount(); ++row) {
0339         ret.append(model->index(row, 0).data().toString());
0340     }
0341     return ret;
0342 }
0343 
0344 QTEST_AKONADIMAIN(FolderTreeWidgetTest)
0345 
0346 #include "foldertreewidgettest.moc"