]> cloud.milkyroute.net Git - dolphin.git/blob - src/tests/kfileitemmodeltest.cpp
Ensure that the "Sort by Type" setting is respected
[dolphin.git] / src / tests / kfileitemmodeltest.cpp
1 /***************************************************************************
2 * Copyright (C) 2011 by Peter Penz <peter.penz19@gmail.com> *
3 * Copyright (C) 2011 by Frank Reininghaus <frank78ac@googlemail.com> *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
19 ***************************************************************************/
20
21 #include <qtest_kde.h>
22
23 #include <KDirLister>
24 #include "kitemviews/kfileitemmodel.h"
25 #include "kitemviews/private/kfileitemmodeldirlister.h"
26 #include "testdir.h"
27
28 void myMessageOutput(QtMsgType type, const char* msg)
29 {
30 switch (type) {
31 case QtDebugMsg:
32 break;
33 case QtWarningMsg:
34 break;
35 case QtCriticalMsg:
36 fprintf(stderr, "Critical: %s\n", msg);
37 break;
38 case QtFatalMsg:
39 fprintf(stderr, "Fatal: %s\n", msg);
40 abort();
41 default:
42 break;
43 }
44 }
45
46 namespace {
47 const int DefaultTimeout = 5000;
48 };
49
50 Q_DECLARE_METATYPE(KItemRangeList)
51 Q_DECLARE_METATYPE(QList<int>)
52
53 class KFileItemModelTest : public QObject
54 {
55 Q_OBJECT
56
57 private slots:
58 void init();
59 void cleanup();
60
61 void testDefaultRoles();
62 void testDefaultSortRole();
63 void testDefaultGroupedSorting();
64 void testNewItems();
65 void testRemoveItems();
66 void testDirLoadingCompleted();
67 void testSetData();
68 void testSetDataWithModifiedSortRole_data();
69 void testSetDataWithModifiedSortRole();
70 void testChangeSortRole();
71 void testModelConsistencyWhenInsertingItems();
72 void testItemRangeConsistencyWhenInsertingItems();
73 void testExpandItems();
74 void testExpandParentItems();
75 void testSorting();
76 void testIndexForKeyboardSearch();
77 void testNameFilter();
78 void testEmptyPath();
79 void testRefreshExpandedItem();
80 void testRemoveHiddenItems();
81 void collapseParentOfHiddenItems();
82 void removeParentOfHiddenItems();
83
84 private:
85 bool isModelConsistent() const;
86 QStringList itemsInModel() const;
87
88 private:
89 KFileItemModel* m_model;
90 TestDir* m_testDir;
91 };
92
93 void KFileItemModelTest::init()
94 {
95 // The item-model tests result in a huge number of debugging
96 // output from kdelibs. Only show critical and fatal messages.
97 qInstallMsgHandler(myMessageOutput);
98
99 qRegisterMetaType<KItemRange>("KItemRange");
100 qRegisterMetaType<KItemRangeList>("KItemRangeList");
101 qRegisterMetaType<KFileItemList>("KFileItemList");
102
103 m_testDir = new TestDir();
104 m_model = new KFileItemModel();
105 m_model->m_dirLister->setAutoUpdate(false);
106 }
107
108 void KFileItemModelTest::cleanup()
109 {
110 delete m_model;
111 m_model = 0;
112
113 delete m_testDir;
114 m_testDir = 0;
115 }
116
117 void KFileItemModelTest::testDefaultRoles()
118 {
119 const QSet<QByteArray> roles = m_model->roles();
120 QCOMPARE(roles.count(), 3);
121 QVERIFY(roles.contains("text"));
122 QVERIFY(roles.contains("isDir"));
123 QVERIFY(roles.contains("isLink"));
124 }
125
126 void KFileItemModelTest::testDefaultSortRole()
127 {
128 QCOMPARE(m_model->sortRole(), QByteArray("text"));
129
130 QStringList files;
131 files << "c.txt" << "a.txt" << "b.txt";
132
133 m_testDir->createFiles(files);
134
135 m_model->loadDirectory(m_testDir->url());
136 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
137
138 QCOMPARE(m_model->count(), 3);
139 QCOMPARE(m_model->data(0)["text"].toString(), QString("a.txt"));
140 QCOMPARE(m_model->data(1)["text"].toString(), QString("b.txt"));
141 QCOMPARE(m_model->data(2)["text"].toString(), QString("c.txt"));
142 }
143
144 void KFileItemModelTest::testDefaultGroupedSorting()
145 {
146 QCOMPARE(m_model->groupedSorting(), false);
147 }
148
149 void KFileItemModelTest::testNewItems()
150 {
151 QStringList files;
152 files << "a.txt" << "b.txt" << "c.txt";
153 m_testDir->createFiles(files);
154
155 m_model->loadDirectory(m_testDir->url());
156 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
157
158 QCOMPARE(m_model->count(), 3);
159
160 QVERIFY(isModelConsistent());
161 }
162
163 void KFileItemModelTest::testRemoveItems()
164 {
165 m_testDir->createFile("a.txt");
166 m_testDir->createFile("b.txt");
167 m_model->loadDirectory(m_testDir->url());
168 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
169 QCOMPARE(m_model->count(), 2);
170 QVERIFY(isModelConsistent());
171
172 m_testDir->removeFile("a.txt");
173 m_model->m_dirLister->updateDirectory(m_testDir->url());
174 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsRemoved(KItemRangeList)), DefaultTimeout));
175 QCOMPARE(m_model->count(), 1);
176 QVERIFY(isModelConsistent());
177 }
178
179 void KFileItemModelTest::testDirLoadingCompleted()
180 {
181 QSignalSpy loadingCompletedSpy(m_model, SIGNAL(directoryLoadingCompleted()));
182 QSignalSpy itemsInsertedSpy(m_model, SIGNAL(itemsInserted(KItemRangeList)));
183 QSignalSpy itemsRemovedSpy(m_model, SIGNAL(itemsRemoved(KItemRangeList)));
184
185 m_testDir->createFiles(QStringList() << "a.txt" << "b.txt" << "c.txt");
186
187 m_model->loadDirectory(m_testDir->url());
188 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(directoryLoadingCompleted()), DefaultTimeout));
189 QCOMPARE(loadingCompletedSpy.count(), 1);
190 QCOMPARE(itemsInsertedSpy.count(), 1);
191 QCOMPARE(itemsRemovedSpy.count(), 0);
192 QCOMPARE(m_model->count(), 3);
193
194 m_testDir->createFiles(QStringList() << "d.txt" << "e.txt");
195 m_model->m_dirLister->updateDirectory(m_testDir->url());
196 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(directoryLoadingCompleted()), DefaultTimeout));
197 QCOMPARE(loadingCompletedSpy.count(), 2);
198 QCOMPARE(itemsInsertedSpy.count(), 2);
199 QCOMPARE(itemsRemovedSpy.count(), 0);
200 QCOMPARE(m_model->count(), 5);
201
202 m_testDir->removeFile("a.txt");
203 m_testDir->createFile("f.txt");
204 m_model->m_dirLister->updateDirectory(m_testDir->url());
205 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(directoryLoadingCompleted()), DefaultTimeout));
206 QCOMPARE(loadingCompletedSpy.count(), 3);
207 QCOMPARE(itemsInsertedSpy.count(), 3);
208 QCOMPARE(itemsRemovedSpy.count(), 1);
209 QCOMPARE(m_model->count(), 5);
210
211 m_testDir->removeFile("b.txt");
212 m_model->m_dirLister->updateDirectory(m_testDir->url());
213 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsRemoved(KItemRangeList)), DefaultTimeout));
214 QCOMPARE(loadingCompletedSpy.count(), 4);
215 QCOMPARE(itemsInsertedSpy.count(), 3);
216 QCOMPARE(itemsRemovedSpy.count(), 2);
217 QCOMPARE(m_model->count(), 4);
218
219 QVERIFY(isModelConsistent());
220 }
221
222 void KFileItemModelTest::testSetData()
223 {
224 m_testDir->createFile("a.txt");
225
226 m_model->loadDirectory(m_testDir->url());
227 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
228
229 QHash<QByteArray, QVariant> values;
230 values.insert("customRole1", "Test1");
231 values.insert("customRole2", "Test2");
232
233 QSignalSpy itemsChangedSpy(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)));
234 m_model->setData(0, values);
235 QCOMPARE(itemsChangedSpy.count(), 1);
236
237 values = m_model->data(0);
238 QCOMPARE(values.value("customRole1").toString(), QString("Test1"));
239 QCOMPARE(values.value("customRole2").toString(), QString("Test2"));
240 QVERIFY(isModelConsistent());
241 }
242
243 void KFileItemModelTest::testSetDataWithModifiedSortRole_data()
244 {
245 QTest::addColumn<int>("changedIndex");
246 QTest::addColumn<int>("changedRating");
247 QTest::addColumn<bool>("expectMoveSignal");
248 QTest::addColumn<int>("ratingIndex0");
249 QTest::addColumn<int>("ratingIndex1");
250 QTest::addColumn<int>("ratingIndex2");
251
252 // Default setup:
253 // Index 0 = rating 2
254 // Index 1 = rating 4
255 // Index 2 = rating 6
256
257 QTest::newRow("Index 0: Rating 3") << 0 << 3 << false << 3 << 4 << 6;
258 QTest::newRow("Index 0: Rating 5") << 0 << 5 << true << 4 << 5 << 6;
259 QTest::newRow("Index 0: Rating 8") << 0 << 8 << true << 4 << 6 << 8;
260
261 QTest::newRow("Index 2: Rating 1") << 2 << 1 << true << 1 << 2 << 4;
262 QTest::newRow("Index 2: Rating 3") << 2 << 3 << true << 2 << 3 << 4;
263 QTest::newRow("Index 2: Rating 5") << 2 << 5 << false << 2 << 4 << 5;
264 }
265
266 void KFileItemModelTest::testSetDataWithModifiedSortRole()
267 {
268 QFETCH(int, changedIndex);
269 QFETCH(int, changedRating);
270 QFETCH(bool, expectMoveSignal);
271 QFETCH(int, ratingIndex0);
272 QFETCH(int, ratingIndex1);
273 QFETCH(int, ratingIndex2);
274
275 // Changing the value of a sort-role must result in
276 // a reordering of the items.
277 QCOMPARE(m_model->sortRole(), QByteArray("text"));
278 m_model->setSortRole("rating");
279 QCOMPARE(m_model->sortRole(), QByteArray("rating"));
280
281 QStringList files;
282 files << "a.txt" << "b.txt" << "c.txt";
283 m_testDir->createFiles(files);
284
285 m_model->loadDirectory(m_testDir->url());
286 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
287
288 // Fill the "rating" role of each file:
289 // a.txt -> 2
290 // b.txt -> 4
291 // c.txt -> 6
292
293 QHash<QByteArray, QVariant> ratingA;
294 ratingA.insert("rating", 2);
295 m_model->setData(0, ratingA);
296
297 QHash<QByteArray, QVariant> ratingB;
298 ratingB.insert("rating", 4);
299 m_model->setData(1, ratingB);
300
301 QHash<QByteArray, QVariant> ratingC;
302 ratingC.insert("rating", 6);
303 m_model->setData(2, ratingC);
304
305 QCOMPARE(m_model->data(0).value("rating").toInt(), 2);
306 QCOMPARE(m_model->data(1).value("rating").toInt(), 4);
307 QCOMPARE(m_model->data(2).value("rating").toInt(), 6);
308
309 // Now change the rating from a.txt. This usually results
310 // in reordering of the items.
311 QHash<QByteArray, QVariant> rating;
312 rating.insert("rating", changedRating);
313 m_model->setData(changedIndex, rating);
314
315 if (expectMoveSignal) {
316 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsMoved(KItemRange,QList<int>)), DefaultTimeout));
317 }
318
319 QCOMPARE(m_model->data(0).value("rating").toInt(), ratingIndex0);
320 QCOMPARE(m_model->data(1).value("rating").toInt(), ratingIndex1);
321 QCOMPARE(m_model->data(2).value("rating").toInt(), ratingIndex2);
322 QVERIFY(isModelConsistent());
323 }
324
325 void KFileItemModelTest::testChangeSortRole()
326 {
327 QCOMPARE(m_model->sortRole(), QByteArray("text"));
328
329 QStringList files;
330 files << "a.txt" << "b.jpg" << "c.txt";
331 m_testDir->createFiles(files);
332
333 m_model->loadDirectory(m_testDir->url());
334 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
335 QCOMPARE(itemsInModel(), QStringList() << "a.txt" << "b.jpg" << "c.txt");
336
337 // Simulate that KFileItemModelRolesUpdater determines the mime type.
338 // Resorting the files by 'type' will only work immediately if their
339 // mime types are known.
340 for (int index = 0; index < m_model->count(); ++index) {
341 m_model->fileItem(index).determineMimeType();
342 }
343
344 // Now: sort by type.
345 QSignalSpy spyItemsMoved(m_model, SIGNAL(itemsMoved(KItemRange,QList<int>)));
346 m_model->setSortRole("type");
347 QCOMPARE(m_model->sortRole(), QByteArray("type"));
348 QVERIFY(!spyItemsMoved.isEmpty());
349
350 // The actual order of the files might depend on the translation of the
351 // result of KFileItem::mimeComment() in the user's language.
352 QStringList version1;
353 version1 << "b.jpg" << "a.txt" << "c.txt";
354
355 QStringList version2;
356 version2 << "a.txt" << "c.txt" << "b.jpg";
357
358 const bool ok1 = (itemsInModel() == version1);
359 const bool ok2 = (itemsInModel() == version2);
360
361 QVERIFY(ok1 || ok2);
362 }
363
364 void KFileItemModelTest::testModelConsistencyWhenInsertingItems()
365 {
366 //QSKIP("Temporary disabled", SkipSingle);
367
368 // KFileItemModel prevents that inserting a punch of items sequentially
369 // results in an itemsInserted()-signal for each item. Instead internally
370 // a timeout is given that collects such operations and results in only
371 // one itemsInserted()-signal. However in this test we want to stress
372 // KFileItemModel to do a lot of insert operation and hence decrease
373 // the timeout to 1 millisecond.
374 m_testDir->createFile("1");
375 m_model->loadDirectory(m_testDir->url());
376 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
377 QCOMPARE(m_model->count(), 1);
378
379 // Insert 10 items for 20 times. After each insert operation the model consistency
380 // is checked.
381 QSet<int> insertedItems;
382 for (int i = 0; i < 20; ++i) {
383 QSignalSpy spy(m_model, SIGNAL(itemsInserted(KItemRangeList)));
384
385 for (int j = 0; j < 10; ++j) {
386 int itemName = qrand();
387 while (insertedItems.contains(itemName)) {
388 itemName = qrand();
389 }
390 insertedItems.insert(itemName);
391
392 m_testDir->createFile(QString::number(itemName));
393 }
394
395 m_model->m_dirLister->updateDirectory(m_testDir->url());
396 if (spy.count() == 0) {
397 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
398 }
399
400 QVERIFY(isModelConsistent());
401 }
402
403 QCOMPARE(m_model->count(), 201);
404 }
405
406 void KFileItemModelTest::testItemRangeConsistencyWhenInsertingItems()
407 {
408 QStringList files;
409 files << "B" << "E" << "G";
410 m_testDir->createFiles(files);
411
412 // Due to inserting the 3 items one item-range with index == 0 and
413 // count == 3 must be given
414 QSignalSpy spy1(m_model, SIGNAL(itemsInserted(KItemRangeList)));
415 m_model->loadDirectory(m_testDir->url());
416 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
417
418 QCOMPARE(spy1.count(), 1);
419 QList<QVariant> arguments = spy1.takeFirst();
420 KItemRangeList itemRangeList = arguments.at(0).value<KItemRangeList>();
421 QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(0, 3));
422
423 // The indexes of the item-ranges must always be related to the model before
424 // the items have been inserted. Having:
425 // 0 1 2
426 // B E G
427 // and inserting A, C, D, F the resulting model will be:
428 // 0 1 2 3 4 5 6
429 // A B C D E F G
430 // and the item-ranges must be:
431 // index: 0, count: 1 for A
432 // index: 1, count: 2 for B, C
433 // index: 2, count: 1 for G
434
435 files.clear();
436 files << "A" << "C" << "D" << "F";
437 m_testDir->createFiles(files);
438
439 QSignalSpy spy2(m_model, SIGNAL(itemsInserted(KItemRangeList)));
440 m_model->m_dirLister->updateDirectory(m_testDir->url());
441 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
442
443 QCOMPARE(spy2.count(), 1);
444 arguments = spy2.takeFirst();
445 itemRangeList = arguments.at(0).value<KItemRangeList>();
446 QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(0, 1) << KItemRange(1, 2) << KItemRange(2, 1));
447 }
448
449 void KFileItemModelTest::testExpandItems()
450 {
451 // Test expanding subfolders in a folder with the items "a/", "a/a/", "a/a/1", "a/a-1/", "a/a-1/1".
452 // Besides testing the basic item expansion functionality, the test makes sure that
453 // KFileItemModel::expansionLevelsCompare(const KFileItem& a, const KFileItem& b)
454 // yields the correct result for "a/a/1" and "a/a-1/", whis is non-trivial because they share the
455 // first three characters.
456 QSet<QByteArray> modelRoles = m_model->roles();
457 modelRoles << "isExpanded" << "isExpandable" << "expandedParentsCount";
458 m_model->setRoles(modelRoles);
459
460 QStringList files;
461 files << "a/a/1" << "a/a-1/1"; // missing folders are created automatically
462 m_testDir->createFiles(files);
463
464 // Store the URLs of all folders in a set.
465 QSet<KUrl> allFolders;
466 allFolders << KUrl(m_testDir->name() + 'a') << KUrl(m_testDir->name() + "a/a") << KUrl(m_testDir->name() + "a/a-1");
467
468 m_model->loadDirectory(m_testDir->url());
469 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
470
471 // So far, the model contains only "a/"
472 QCOMPARE(m_model->count(), 1);
473 QVERIFY(m_model->isExpandable(0));
474 QVERIFY(!m_model->isExpanded(0));
475 QVERIFY(m_model->expandedDirectories().empty());
476
477 QSignalSpy spyInserted(m_model, SIGNAL(itemsInserted(KItemRangeList)));
478
479 // Expand the folder "a/" -> "a/a/" and "a/a-1/" become visible
480 m_model->setExpanded(0, true);
481 QVERIFY(m_model->isExpanded(0));
482 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
483 QCOMPARE(m_model->count(), 3); // 3 items: "a/", "a/a/", "a/a-1/"
484 QCOMPARE(m_model->expandedDirectories(), QSet<KUrl>() << KUrl(m_testDir->name() + 'a'));
485
486 QCOMPARE(spyInserted.count(), 1);
487 KItemRangeList itemRangeList = spyInserted.takeFirst().at(0).value<KItemRangeList>();
488 QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(1, 2)); // 2 new items "a/a/" and "a/a-1/" with indices 1 and 2
489
490 QVERIFY(m_model->isExpandable(1));
491 QVERIFY(!m_model->isExpanded(1));
492 QVERIFY(m_model->isExpandable(2));
493 QVERIFY(!m_model->isExpanded(2));
494
495 // Expand the folder "a/a/" -> "a/a/1" becomes visible
496 m_model->setExpanded(1, true);
497 QVERIFY(m_model->isExpanded(1));
498 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
499 QCOMPARE(m_model->count(), 4); // 4 items: "a/", "a/a/", "a/a/1", "a/a-1/"
500 QCOMPARE(m_model->expandedDirectories(), QSet<KUrl>() << KUrl(m_testDir->name() + 'a') << KUrl(m_testDir->name() + "a/a"));
501
502 QCOMPARE(spyInserted.count(), 1);
503 itemRangeList = spyInserted.takeFirst().at(0).value<KItemRangeList>();
504 QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(2, 1)); // 1 new item "a/a/1" with index 2
505
506 QVERIFY(!m_model->isExpandable(2));
507 QVERIFY(!m_model->isExpanded(2));
508
509 // Expand the folder "a/a-1/" -> "a/a-1/1" becomes visible
510 m_model->setExpanded(3, true);
511 QVERIFY(m_model->isExpanded(3));
512 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
513 QCOMPARE(m_model->count(), 5); // 5 items: "a/", "a/a/", "a/a/1", "a/a-1/", "a/a-1/1"
514 QCOMPARE(m_model->expandedDirectories(), allFolders);
515
516 QCOMPARE(spyInserted.count(), 1);
517 itemRangeList = spyInserted.takeFirst().at(0).value<KItemRangeList>();
518 QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(4, 1)); // 1 new item "a/a-1/1" with index 4
519
520 QVERIFY(!m_model->isExpandable(4));
521 QVERIFY(!m_model->isExpanded(4));
522
523 QSignalSpy spyRemoved(m_model, SIGNAL(itemsRemoved(KItemRangeList)));
524
525 // Collapse the top-level folder -> all other items should disappear
526 m_model->setExpanded(0, false);
527 QVERIFY(!m_model->isExpanded(0));
528 QCOMPARE(m_model->count(), 1);
529 QVERIFY(!m_model->expandedDirectories().contains(KUrl(m_testDir->name() + 'a'))); // TODO: Make sure that child URLs are also removed
530
531 QCOMPARE(spyRemoved.count(), 1);
532 itemRangeList = spyRemoved.takeFirst().at(0).value<KItemRangeList>();
533 QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(1, 4)); // 4 items removed
534
535 // Clear the model, reload the folder and try to restore the expanded folders.
536 m_model->clear();
537 QCOMPARE(m_model->count(), 0);
538 QVERIFY(m_model->expandedDirectories().empty());
539
540 m_model->loadDirectory(m_testDir->url());
541 m_model->restoreExpandedDirectories(allFolders);
542 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(directoryLoadingCompleted()), DefaultTimeout));
543 QCOMPARE(m_model->count(), 5); // 5 items: "a/", "a/a/", "a/a/1", "a/a-1/", "a/a-1/1"
544 QVERIFY(m_model->isExpanded(0));
545 QVERIFY(m_model->isExpanded(1));
546 QVERIFY(!m_model->isExpanded(2));
547 QVERIFY(m_model->isExpanded(3));
548 QVERIFY(!m_model->isExpanded(4));
549 QCOMPARE(m_model->expandedDirectories(), allFolders);
550
551 // Move to a sub folder, then call restoreExpandedFolders() *before* going back.
552 // This is how DolphinView restores the expanded folders when navigating in history.
553 m_model->loadDirectory(KUrl(m_testDir->name() + "a/a/"));
554 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(directoryLoadingCompleted()), DefaultTimeout));
555 QCOMPARE(m_model->count(), 1); // 1 item: "1"
556 m_model->restoreExpandedDirectories(allFolders);
557 m_model->loadDirectory(m_testDir->url());
558 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(directoryLoadingCompleted()), DefaultTimeout));
559 QCOMPARE(m_model->count(), 5); // 5 items: "a/", "a/a/", "a/a/1", "a/a-1/", "a/a-1/1"
560 QCOMPARE(m_model->expandedDirectories(), allFolders);
561 }
562
563 void KFileItemModelTest::testExpandParentItems()
564 {
565 // Create a tree structure of folders:
566 // a 1/
567 // a 1/b1/
568 // a 1/b1/c1/
569 // a2/
570 // a2/b2/
571 // a2/b2/c2/
572 // a2/b2/c2/d2/
573 QSet<QByteArray> modelRoles = m_model->roles();
574 modelRoles << "isExpanded" << "isExpandable" << "expandedParentsCount";
575 m_model->setRoles(modelRoles);
576
577 QStringList files;
578 files << "a 1/b1/c1/file.txt" << "a2/b2/c2/d2/file.txt"; // missing folders are created automatically
579 m_testDir->createFiles(files);
580
581 m_model->loadDirectory(m_testDir->url());
582 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
583
584 // So far, the model contains only "a 1/" and "a2/".
585 QCOMPARE(m_model->count(), 2);
586 QVERIFY(m_model->expandedDirectories().empty());
587
588 // Expand the parents of "a2/b2/c2".
589 m_model->expandParentDirectories(KUrl(m_testDir->name() + "a2/b2/c2"));
590 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(directoryLoadingCompleted()), DefaultTimeout));
591
592 // The model should now contain "a 1/", "a2/", "a2/b2/", and "a2/b2/c2/".
593 // It's important that only the parents of "a1/b1/c1" are expanded.
594 QCOMPARE(m_model->count(), 4);
595 QVERIFY(!m_model->isExpanded(0));
596 QVERIFY(m_model->isExpanded(1));
597 QVERIFY(m_model->isExpanded(2));
598 QVERIFY(!m_model->isExpanded(3));
599
600 // Expand the parents of "a 1/b1".
601 m_model->expandParentDirectories(KUrl(m_testDir->name() + "a 1/b1"));
602 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(directoryLoadingCompleted()), DefaultTimeout));
603
604 // The model should now contain "a 1/", "a 1/b1/", "a2/", "a2/b2", and "a2/b2/c2/".
605 // It's important that only the parents of "a 1/b1/" and "a2/b2/c2/" are expanded.
606 QCOMPARE(m_model->count(), 5);
607 QVERIFY(m_model->isExpanded(0));
608 QVERIFY(!m_model->isExpanded(1));
609 QVERIFY(m_model->isExpanded(2));
610 QVERIFY(m_model->isExpanded(3));
611 QVERIFY(!m_model->isExpanded(4));
612 }
613
614 void KFileItemModelTest::testSorting()
615 {
616 // Create some files with different sizes and modification times to check the different sorting options
617 QDateTime now = QDateTime::currentDateTime();
618
619 QSet<QByteArray> roles;
620 roles.insert("text");
621 roles.insert("isExpanded");
622 roles.insert("isExpandable");
623 roles.insert("expandedParentsCount");
624 m_model->setRoles(roles);
625
626 m_testDir->createDir("c/c-2");
627 m_testDir->createFile("c/c-2/c-3");
628 m_testDir->createFile("c/c-1");
629
630 m_testDir->createFile("a", "A file", now.addDays(-3));
631 m_testDir->createFile("b", "A larger file", now.addDays(0));
632 m_testDir->createDir("c", now.addDays(-2));
633 m_testDir->createFile("d", "The largest file in this directory", now.addDays(-1));
634 m_testDir->createFile("e", "An even larger file", now.addDays(-4));
635 m_testDir->createFile(".f");
636
637 m_model->loadDirectory(m_testDir->url());
638 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
639
640 int index = m_model->index(KUrl(m_testDir->url().url() + 'c'));
641 m_model->setExpanded(index, true);
642 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
643
644 index = m_model->index(KUrl(m_testDir->url().url() + "c/c-2"));
645 m_model->setExpanded(index, true);
646 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
647
648 // Default: Sort by Name, ascending
649 QCOMPARE(m_model->sortRole(), QByteArray("text"));
650 QCOMPARE(m_model->sortOrder(), Qt::AscendingOrder);
651 QVERIFY(m_model->sortDirectoriesFirst());
652 QVERIFY(!m_model->showHiddenFiles());
653 QCOMPARE(itemsInModel(), QStringList() << "c" << "c-2" << "c-3" << "c-1" << "a" << "b" << "d" << "e");
654
655 QSignalSpy spyItemsMoved(m_model, SIGNAL(itemsMoved(KItemRange,QList<int>)));
656
657 // Sort by Name, ascending, 'Sort Folders First' disabled
658 m_model->setSortDirectoriesFirst(false);
659 QCOMPARE(m_model->sortRole(), QByteArray("text"));
660 QCOMPARE(m_model->sortOrder(), Qt::AscendingOrder);
661 QCOMPARE(itemsInModel(), QStringList() << "a" << "b" << "c" << "c-1" << "c-2" << "c-3" << "d" << "e");
662 QCOMPARE(spyItemsMoved.count(), 1);
663 QCOMPARE(spyItemsMoved.takeFirst().at(1).value<QList<int> >(), QList<int>() << 2 << 4 << 5 << 3 << 0 << 1 << 6 << 7);
664
665 // Sort by Name, descending
666 m_model->setSortDirectoriesFirst(true);
667 m_model->setSortOrder(Qt::DescendingOrder);
668 QCOMPARE(m_model->sortRole(), QByteArray("text"));
669 QCOMPARE(m_model->sortOrder(), Qt::DescendingOrder);
670 QCOMPARE(itemsInModel(), QStringList() << "c" << "c-2" << "c-3" << "c-1" << "e" << "d" << "b" << "a");
671 QCOMPARE(spyItemsMoved.count(), 2);
672 QCOMPARE(spyItemsMoved.takeFirst().at(1).value<QList<int> >(), QList<int>() << 4 << 5 << 0 << 3 << 1 << 2 << 6 << 7);
673 QCOMPARE(spyItemsMoved.takeFirst().at(1).value<QList<int> >(), QList<int>() << 0 << 1 << 2 << 3 << 7 << 6 << 5 << 4);
674
675 // Sort by Date, descending
676 m_model->setSortDirectoriesFirst(true);
677 m_model->setSortRole("date");
678 QCOMPARE(m_model->sortRole(), QByteArray("date"));
679 QCOMPARE(m_model->sortOrder(), Qt::DescendingOrder);
680 QCOMPARE(itemsInModel(), QStringList() << "c" << "c-2" << "c-3" << "c-1" << "b" << "d" << "a" << "e");
681 QCOMPARE(spyItemsMoved.count(), 1);
682 QCOMPARE(spyItemsMoved.takeFirst().at(1).value<QList<int> >(), QList<int>() << 0 << 1 << 2 << 3 << 7 << 5 << 4 << 6);
683
684 // Sort by Date, ascending
685 m_model->setSortOrder(Qt::AscendingOrder);
686 QCOMPARE(m_model->sortRole(), QByteArray("date"));
687 QCOMPARE(m_model->sortOrder(), Qt::AscendingOrder);
688 QCOMPARE(itemsInModel(), QStringList() << "c" << "c-2" << "c-3" << "c-1" << "e" << "a" << "d" << "b");
689 QCOMPARE(spyItemsMoved.count(), 1);
690 QCOMPARE(spyItemsMoved.takeFirst().at(1).value<QList<int> >(), QList<int>() << 0 << 1 << 2 << 3 << 7 << 6 << 5 << 4);
691
692 // Sort by Date, ascending, 'Sort Folders First' disabled
693 m_model->setSortDirectoriesFirst(false);
694 QCOMPARE(m_model->sortRole(), QByteArray("date"));
695 QCOMPARE(m_model->sortOrder(), Qt::AscendingOrder);
696 QVERIFY(!m_model->sortDirectoriesFirst());
697 QCOMPARE(itemsInModel(), QStringList() << "e" << "a" << "c" << "c-1" << "c-2" << "c-3" << "d" << "b");
698 QCOMPARE(spyItemsMoved.count(), 1);
699 QCOMPARE(spyItemsMoved.takeFirst().at(1).value<QList<int> >(), QList<int>() << 2 << 4 << 5 << 3 << 0 << 1 << 6 << 7);
700
701 // Sort by Name, ascending, 'Sort Folders First' disabled
702 m_model->setSortRole("text");
703 QCOMPARE(m_model->sortOrder(), Qt::AscendingOrder);
704 QVERIFY(!m_model->sortDirectoriesFirst());
705 QCOMPARE(itemsInModel(), QStringList() << "a" << "b" << "c" << "c-1" << "c-2" << "c-3" << "d" << "e");
706 QCOMPARE(spyItemsMoved.count(), 1);
707 QCOMPARE(spyItemsMoved.takeFirst().at(1).value<QList<int> >(), QList<int>() << 7 << 0 << 2 << 3 << 4 << 5 << 6 << 1);
708
709 // Sort by Size, ascending, 'Sort Folders First' disabled
710 m_model->setSortRole("size");
711 QCOMPARE(m_model->sortRole(), QByteArray("size"));
712 QCOMPARE(m_model->sortOrder(), Qt::AscendingOrder);
713 QVERIFY(!m_model->sortDirectoriesFirst());
714 QCOMPARE(itemsInModel(), QStringList() << "c" << "c-2" << "c-3" << "c-1" << "a" << "b" << "e" << "d");
715 QCOMPARE(spyItemsMoved.count(), 1);
716 QCOMPARE(spyItemsMoved.takeFirst().at(1).value<QList<int> >(), QList<int>() << 4 << 5 << 0 << 3 << 1 << 2 << 7 << 6);
717
718 QSKIP("2 tests of testSorting() are temporary deactivated as in KFileItemModel resortAllItems() "
719 "always emits a itemsMoved() signal. Before adjusting the tests think about probably introducing "
720 "another signal", SkipSingle);
721 // Internal note: Check comment in KFileItemModel::resortAllItems() for details.
722
723 // In 'Sort by Size' mode, folders are always first -> changing 'Sort Folders First' does not resort the model
724 m_model->setSortDirectoriesFirst(true);
725 QCOMPARE(m_model->sortRole(), QByteArray("size"));
726 QCOMPARE(m_model->sortOrder(), Qt::AscendingOrder);
727 QVERIFY(m_model->sortDirectoriesFirst());
728 QCOMPARE(itemsInModel(), QStringList() << "c" << "a" << "b" << "e" << "d");
729 QCOMPARE(spyItemsMoved.count(), 0);
730
731 // Sort by Size, descending, 'Sort Folders First' enabled
732 m_model->setSortOrder(Qt::DescendingOrder);
733 QCOMPARE(m_model->sortRole(), QByteArray("size"));
734 QCOMPARE(m_model->sortOrder(), Qt::DescendingOrder);
735 QVERIFY(m_model->sortDirectoriesFirst());
736 QCOMPARE(itemsInModel(), QStringList() << "c" << "d" << "e" << "b" << "a");
737 QCOMPARE(spyItemsMoved.count(), 1);
738 QCOMPARE(spyItemsMoved.takeFirst().at(1).value<QList<int> >(), QList<int>() << 0 << 4 << 3 << 2 << 1);
739
740 // TODO: Sort by other roles; show/hide hidden files
741 }
742
743 void KFileItemModelTest::testIndexForKeyboardSearch()
744 {
745 QStringList files;
746 files << "a" << "aa" << "Image.jpg" << "Image.png" << "Text" << "Text1" << "Text2" << "Text11";
747 m_testDir->createFiles(files);
748
749 m_model->loadDirectory(m_testDir->url());
750 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
751
752 // Search from index 0
753 QCOMPARE(m_model->indexForKeyboardSearch("a", 0), 0);
754 QCOMPARE(m_model->indexForKeyboardSearch("aa", 0), 1);
755 QCOMPARE(m_model->indexForKeyboardSearch("i", 0), 2);
756 QCOMPARE(m_model->indexForKeyboardSearch("image", 0), 2);
757 QCOMPARE(m_model->indexForKeyboardSearch("image.jpg", 0), 2);
758 QCOMPARE(m_model->indexForKeyboardSearch("image.png", 0), 3);
759 QCOMPARE(m_model->indexForKeyboardSearch("t", 0), 4);
760 QCOMPARE(m_model->indexForKeyboardSearch("text", 0), 4);
761 QCOMPARE(m_model->indexForKeyboardSearch("text1", 0), 5);
762 QCOMPARE(m_model->indexForKeyboardSearch("text2", 0), 6);
763 QCOMPARE(m_model->indexForKeyboardSearch("text11", 0), 7);
764
765 // Start a search somewhere in the middle
766 QCOMPARE(m_model->indexForKeyboardSearch("a", 1), 1);
767 QCOMPARE(m_model->indexForKeyboardSearch("i", 3), 3);
768 QCOMPARE(m_model->indexForKeyboardSearch("t", 5), 5);
769 QCOMPARE(m_model->indexForKeyboardSearch("text1", 6), 7);
770
771 // Test searches that go past the last item back to index 0
772 QCOMPARE(m_model->indexForKeyboardSearch("a", 2), 0);
773 QCOMPARE(m_model->indexForKeyboardSearch("i", 7), 2);
774 QCOMPARE(m_model->indexForKeyboardSearch("image.jpg", 3), 2);
775 QCOMPARE(m_model->indexForKeyboardSearch("text2", 7), 6);
776
777 // Test searches that yield no result
778 QCOMPARE(m_model->indexForKeyboardSearch("aaa", 0), -1);
779 QCOMPARE(m_model->indexForKeyboardSearch("b", 0), -1);
780 QCOMPARE(m_model->indexForKeyboardSearch("image.svg", 0), -1);
781 QCOMPARE(m_model->indexForKeyboardSearch("text3", 0), -1);
782 QCOMPARE(m_model->indexForKeyboardSearch("text3", 5), -1);
783
784 // Test upper case searches (note that search is case insensitive)
785 QCOMPARE(m_model->indexForKeyboardSearch("A", 0), 0);
786 QCOMPARE(m_model->indexForKeyboardSearch("aA", 0), 1);
787 QCOMPARE(m_model->indexForKeyboardSearch("TexT", 5), 5);
788 QCOMPARE(m_model->indexForKeyboardSearch("IMAGE", 4), 2);
789
790 // TODO: Maybe we should also test keyboard searches in directories which are not sorted by Name?
791 }
792
793 void KFileItemModelTest::testNameFilter()
794 {
795 QStringList files;
796 files << "A1" << "A2" << "Abc" << "Bcd" << "Cde";
797 m_testDir->createFiles(files);
798
799 m_model->loadDirectory(m_testDir->url());
800 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
801
802 m_model->setNameFilter("A"); // Shows A1, A2 and Abc
803 QCOMPARE(m_model->count(), 3);
804
805 m_model->setNameFilter("A2"); // Shows only A2
806 QCOMPARE(m_model->count(), 1);
807
808 m_model->setNameFilter("A2"); // Shows only A1
809 QCOMPARE(m_model->count(), 1);
810
811 m_model->setNameFilter("Bc"); // Shows "Abc" and "Bcd"
812 QCOMPARE(m_model->count(), 2);
813
814 m_model->setNameFilter("bC"); // Shows "Abc" and "Bcd"
815 QCOMPARE(m_model->count(), 2);
816
817 m_model->setNameFilter(QString()); // Shows again all items
818 QCOMPARE(m_model->count(), 5);
819 }
820
821 /**
822 * Verifies that we do not crash when adding a KFileItem with an empty path.
823 * Before this issue was fixed, KFileItemModel::expandedParentsCountCompare()
824 * tried to always read the first character of the path, even if the path is empty.
825 */
826 void KFileItemModelTest::testEmptyPath()
827 {
828 QSet<QByteArray> roles;
829 roles.insert("text");
830 roles.insert("isExpanded");
831 roles.insert("isExpandable");
832 roles.insert("expandedParentsCount");
833 m_model->setRoles(roles);
834
835 const KUrl emptyUrl;
836 QVERIFY(emptyUrl.path().isEmpty());
837
838 const KUrl url("file:///test/");
839
840 KFileItemList items;
841 items << KFileItem(emptyUrl, QString(), KFileItem::Unknown) << KFileItem(url, QString(), KFileItem::Unknown);
842 m_model->slotNewItems(items);
843 m_model->slotCompleted();
844 }
845
846 /**
847 * Verifies that the 'isExpanded' state of folders does not change when the
848 * 'refreshItems' signal is received, see https://bugs.kde.org/show_bug.cgi?id=299675.
849 */
850 void KFileItemModelTest::testRefreshExpandedItem()
851 {
852 QSet<QByteArray> modelRoles = m_model->roles();
853 modelRoles << "isExpanded" << "isExpandable" << "expandedParentsCount";
854 m_model->setRoles(modelRoles);
855
856 QStringList files;
857 files << "a/1" << "a/2" << "3" << "4";
858 m_testDir->createFiles(files);
859
860 m_model->loadDirectory(m_testDir->url());
861 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
862 QCOMPARE(m_model->count(), 3); // "a/", "3", "4"
863
864 m_model->setExpanded(0, true);
865 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
866 QCOMPARE(m_model->count(), 5); // "a/", "a/1", "a/2", "3", "4"
867 QVERIFY(m_model->isExpanded(0));
868
869 QSignalSpy spyItemsChanged(m_model, SIGNAL(itemsChanged(KItemRangeList,QSet<QByteArray>)));
870
871 const KFileItem item = m_model->fileItem(0);
872 m_model->slotRefreshItems(QList<QPair<KFileItem, KFileItem> >() << qMakePair(item, item));
873 QVERIFY(!spyItemsChanged.isEmpty());
874
875 QCOMPARE(m_model->count(), 5); // "a/", "a/1", "a/2", "3", "4"
876 QVERIFY(m_model->isExpanded(0));
877 }
878
879 /**
880 * Verify that removing hidden files and folders from the model does not
881 * result in a crash, see https://bugs.kde.org/show_bug.cgi?id=314046
882 */
883 void KFileItemModelTest::testRemoveHiddenItems()
884 {
885 m_testDir->createDir(".a");
886 m_testDir->createDir(".b");
887 m_testDir->createDir("c");
888 m_testDir->createDir("d");
889 m_testDir->createFiles(QStringList() << ".f" << ".g" << "h" << "i");
890
891 QSignalSpy spyItemsInserted(m_model, SIGNAL(itemsInserted(KItemRangeList)));
892 QSignalSpy spyItemsRemoved(m_model, SIGNAL(itemsRemoved(KItemRangeList)));
893
894 m_model->setShowHiddenFiles(true);
895 m_model->loadDirectory(m_testDir->url());
896 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
897 QCOMPARE(itemsInModel(), QStringList() << ".a" << ".b" << "c" << "d" <<".f" << ".g" << "h" << "i");
898 QCOMPARE(spyItemsInserted.count(), 1);
899 QCOMPARE(spyItemsRemoved.count(), 0);
900 KItemRangeList itemRangeList = spyItemsInserted.takeFirst().at(0).value<KItemRangeList>();
901 QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(0, 8));
902
903 m_model->setShowHiddenFiles(false);
904 QCOMPARE(itemsInModel(), QStringList() << "c" << "d" << "h" << "i");
905 QCOMPARE(spyItemsInserted.count(), 0);
906 QCOMPARE(spyItemsRemoved.count(), 1);
907 itemRangeList = spyItemsRemoved.takeFirst().at(0).value<KItemRangeList>();
908 QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(0, 2) << KItemRange(4, 2));
909
910 m_model->setShowHiddenFiles(true);
911 QCOMPARE(itemsInModel(), QStringList() << ".a" << ".b" << "c" << "d" <<".f" << ".g" << "h" << "i");
912 QCOMPARE(spyItemsInserted.count(), 1);
913 QCOMPARE(spyItemsRemoved.count(), 0);
914 itemRangeList = spyItemsInserted.takeFirst().at(0).value<KItemRangeList>();
915 QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(0, 2) << KItemRange(2, 2));
916
917 m_model->clear();
918 QCOMPARE(itemsInModel(), QStringList());
919 QCOMPARE(spyItemsInserted.count(), 0);
920 QCOMPARE(spyItemsRemoved.count(), 1);
921 itemRangeList = spyItemsRemoved.takeFirst().at(0).value<KItemRangeList>();
922 QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(0, 8));
923
924 // Hiding hidden files makes the dir lister emit its itemsDeleted signal.
925 // Verify that this does not make the model crash.
926 m_model->setShowHiddenFiles(false);
927 }
928
929 /**
930 * Verify that filtered items are removed when their parent is collapsed.
931 */
932 void KFileItemModelTest::collapseParentOfHiddenItems()
933 {
934 QSet<QByteArray> modelRoles = m_model->roles();
935 modelRoles << "isExpanded" << "isExpandable" << "expandedParentsCount";
936 m_model->setRoles(modelRoles);
937
938 QStringList files;
939 files << "a/1" << "a/b/1" << "a/b/c/1" << "a/b/c/d/1";
940 m_testDir->createFiles(files);
941
942 m_model->loadDirectory(m_testDir->url());
943 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
944 QCOMPARE(m_model->count(), 1); // Only "a/"
945
946 // Expand "a/".
947 m_model->setExpanded(0, true);
948 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
949 QCOMPARE(m_model->count(), 3); // 3 items: "a/", "a/b/", "a/1"
950
951 // Expand "a/b/".
952 m_model->setExpanded(1, true);
953 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
954 QCOMPARE(m_model->count(), 5); // 5 items: "a/", "a/b/", "a/b/c", "a/b/1", "a/1"
955
956 // Expand "a/b/c/".
957 m_model->setExpanded(2, true);
958 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
959 QCOMPARE(m_model->count(), 7); // 7 items: "a/", "a/b/", "a/b/c", "a/b/c/d/", "a/b/c/1", "a/b/1", "a/1"
960
961 // Set a name filter that matches nothing -> only the expanded folders remain.
962 m_model->setNameFilter("xyz");
963 QCOMPARE(m_model->count(), 3);
964 QCOMPARE(itemsInModel(), QStringList() << "a" << "b" << "c");
965
966 // Collapse the folder "a/".
967 QSignalSpy spyItemsRemoved(m_model, SIGNAL(itemsRemoved(KItemRangeList)));
968 m_model->setExpanded(0, false);
969 QCOMPARE(spyItemsRemoved.count(), 1);
970 QCOMPARE(m_model->count(), 1);
971 QCOMPARE(itemsInModel(), QStringList() << "a");
972
973 // Remove the filter -> no files should appear (and we should not get a crash).
974 m_model->setNameFilter(QString());
975 QCOMPARE(m_model->count(), 1);
976 }
977
978 /**
979 * Verify that filtered items are removed when their parent is deleted.
980 */
981 void KFileItemModelTest::removeParentOfHiddenItems()
982 {
983 QSet<QByteArray> modelRoles = m_model->roles();
984 modelRoles << "isExpanded" << "isExpandable" << "expandedParentsCount";
985 m_model->setRoles(modelRoles);
986
987 QStringList files;
988 files << "a/1" << "a/b/1" << "a/b/c/1" << "a/b/c/d/1";
989 m_testDir->createFiles(files);
990
991 m_model->loadDirectory(m_testDir->url());
992 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
993 QCOMPARE(m_model->count(), 1); // Only "a/"
994
995 // Expand "a/".
996 m_model->setExpanded(0, true);
997 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
998 QCOMPARE(m_model->count(), 3); // 3 items: "a/", "a/b/", "a/1"
999
1000 // Expand "a/b/".
1001 m_model->setExpanded(1, true);
1002 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
1003 QCOMPARE(m_model->count(), 5); // 5 items: "a/", "a/b/", "a/b/c", "a/b/1", "a/1"
1004
1005 // Expand "a/b/c/".
1006 m_model->setExpanded(2, true);
1007 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
1008 QCOMPARE(m_model->count(), 7); // 7 items: "a/", "a/b/", "a/b/c", "a/b/c/d/", "a/b/c/1", "a/b/1", "a/1"
1009
1010 // Set a name filter that matches nothing -> only the expanded folders remain.
1011 m_model->setNameFilter("xyz");
1012 QCOMPARE(m_model->count(), 3);
1013 QCOMPARE(itemsInModel(), QStringList() << "a" << "b" << "c");
1014
1015 // Simulate the deletion of the directory "a/b/".
1016 QSignalSpy spyItemsRemoved(m_model, SIGNAL(itemsRemoved(KItemRangeList)));
1017 m_model->slotItemsDeleted(KFileItemList() << m_model->fileItem(1));
1018 QCOMPARE(spyItemsRemoved.count(), 1);
1019 QCOMPARE(m_model->count(), 1);
1020 QCOMPARE(itemsInModel(), QStringList() << "a");
1021
1022 // Remove the filter -> only the file "a/1" should appear.
1023 m_model->setNameFilter(QString());
1024 QCOMPARE(m_model->count(), 2);
1025 QCOMPARE(itemsInModel(), QStringList() << "a" << "1");
1026 }
1027
1028 bool KFileItemModelTest::isModelConsistent() const
1029 {
1030 if (m_model->m_items.count() != m_model->m_itemData.count()) {
1031 return false;
1032 }
1033
1034 for (int i = 0; i < m_model->count(); ++i) {
1035 const KFileItem item = m_model->fileItem(i);
1036 if (item.isNull()) {
1037 qWarning() << "Item" << i << "is null";
1038 return false;
1039 }
1040
1041 const int itemIndex = m_model->index(item);
1042 if (itemIndex != i) {
1043 qWarning() << "Item" << i << "has a wrong index:" << itemIndex;
1044 return false;
1045 }
1046 }
1047
1048 return true;
1049 }
1050
1051 QStringList KFileItemModelTest::itemsInModel() const
1052 {
1053 QStringList items;
1054 for (int i = 0; i < m_model->count(); i++) {
1055 items << m_model->data(i).value("text").toString();
1056 }
1057 return items;
1058 }
1059
1060 QTEST_KDEMAIN(KFileItemModelTest, NoGUI)
1061
1062 #include "kfileitemmodeltest.moc"