File indexing completed on 2024-11-10 04:49:58
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"