]> cloud.milkyroute.net Git - dolphin.git/blob - src/tests/kfileitemmodeltest.cpp
Implement restoring expanded folders in Details View
[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 "testdir.h"
26
27 namespace {
28 const int DefaultTimeout = 5000;
29 };
30
31 Q_DECLARE_METATYPE(KItemRangeList)
32
33 class KFileItemModelTest : public QObject
34 {
35 Q_OBJECT
36
37 private slots:
38 void init();
39 void cleanup();
40
41 void testDefaultRoles();
42 void testDefaultSortRole();
43 void testDefaultGroupRole();
44 void testNewItems();
45 void testRemoveItems();
46 void testModelConsistencyWhenInsertingItems();
47 void testItemRangeConsistencyWhenInsertingItems();
48 void testExpandItems();
49
50 void testExpansionLevelsCompare_data();
51 void testExpansionLevelsCompare();
52
53 void testIndexForKeyboardSearch();
54
55 private:
56 bool isModelConsistent() const;
57
58 private:
59 KFileItemModel* m_model;
60 KDirLister* m_dirLister;
61 TestDir* m_testDir;
62 };
63
64 void KFileItemModelTest::init()
65 {
66 qRegisterMetaType<KItemRangeList>("KItemRangeList");
67 qRegisterMetaType<KFileItemList>("KFileItemList");
68
69 m_testDir = new TestDir();
70 m_dirLister = new KDirLister();
71 m_model = new KFileItemModel(m_dirLister);
72 }
73
74 void KFileItemModelTest::cleanup()
75 {
76 delete m_model;
77 m_model = 0;
78
79 delete m_dirLister;
80 m_dirLister = 0;
81
82 delete m_testDir;
83 m_testDir = 0;
84 }
85
86 void KFileItemModelTest::testDefaultRoles()
87 {
88 const QSet<QByteArray> roles = m_model->roles();
89 QCOMPARE(roles.count(), 2);
90 QVERIFY(roles.contains("name"));
91 QVERIFY(roles.contains("isDir"));
92 }
93
94 void KFileItemModelTest::testDefaultSortRole()
95 {
96 QCOMPARE(m_model->sortRole(), QByteArray("name"));
97
98 QStringList files;
99 files << "c.txt" << "a.txt" << "b.txt";
100
101 m_testDir->createFiles(files);
102
103 m_dirLister->openUrl(m_testDir->url());
104 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
105
106 QCOMPARE(m_model->count(), 3);
107 QCOMPARE(m_model->data(0)["name"].toString(), QString("a.txt"));
108 QCOMPARE(m_model->data(1)["name"].toString(), QString("b.txt"));
109 QCOMPARE(m_model->data(2)["name"].toString(), QString("c.txt"));
110 }
111
112 void KFileItemModelTest::testDefaultGroupRole()
113 {
114 QVERIFY(m_model->groupRole().isEmpty());
115 }
116
117 void KFileItemModelTest::testNewItems()
118 {
119 QStringList files;
120 files << "a.txt" << "b.txt" << "c.txt";
121 m_testDir->createFiles(files);
122
123 m_dirLister->openUrl(m_testDir->url());
124 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
125
126 QCOMPARE(m_model->count(), 3);
127
128 QVERIFY(isModelConsistent());
129 }
130
131 void KFileItemModelTest::testRemoveItems()
132 {
133 m_testDir->createFile("a.txt");
134 m_dirLister->openUrl(m_testDir->url());
135 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
136 QCOMPARE(m_model->count(), 1);
137
138 m_testDir->removeFile("a.txt");
139 m_dirLister->updateDirectory(m_testDir->url());
140 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsRemoved(KItemRangeList)), DefaultTimeout));
141 QCOMPARE(m_model->count(), 0);
142 }
143
144 void KFileItemModelTest::testModelConsistencyWhenInsertingItems()
145 {
146 QSKIP("Temporary disabled", SkipSingle);
147
148 // KFileItemModel prevents that inserting a punch of items sequentially
149 // results in an itemsInserted()-signal for each item. Instead internally
150 // a timeout is given that collects such operations and results in only
151 // one itemsInserted()-signal. However in this test we want to stress
152 // KFileItemModel to do a lot of insert operation and hence decrease
153 // the timeout to 1 millisecond.
154 m_model->m_minimumUpdateIntervalTimer->setInterval(1);
155
156 m_testDir->createFile("1");
157 m_dirLister->openUrl(m_testDir->url());
158 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
159 QCOMPARE(m_model->count(), 1);
160
161 // Insert 10 items for 20 times. After each insert operation the model consistency
162 // is checked.
163 QSet<int> insertedItems;
164 for (int i = 0; i < 20; ++i) {
165 QSignalSpy spy(m_model, SIGNAL(itemsInserted(KItemRangeList)));
166
167 for (int j = 0; j < 10; ++j) {
168 int itemName = qrand();
169 while (insertedItems.contains(itemName)) {
170 itemName = qrand();
171 }
172 insertedItems.insert(itemName);
173
174 m_testDir->createFile(QString::number(itemName));
175 }
176
177 m_dirLister->updateDirectory(m_testDir->url());
178 if (spy.count() == 0) {
179 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
180 }
181
182 QVERIFY(isModelConsistent());
183 }
184
185 QCOMPARE(m_model->count(), 201);
186 }
187
188 void KFileItemModelTest::testItemRangeConsistencyWhenInsertingItems()
189 {
190 QStringList files;
191 files << "B" << "E" << "G";
192 m_testDir->createFiles(files);
193
194 // Due to inserting the 3 items one item-range with index == 0 and
195 // count == 3 must be given
196 QSignalSpy spy1(m_model, SIGNAL(itemsInserted(KItemRangeList)));
197 m_dirLister->openUrl(m_testDir->url());
198 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
199
200 QCOMPARE(spy1.count(), 1);
201 QList<QVariant> arguments = spy1.takeFirst();
202 KItemRangeList itemRangeList = arguments.at(0).value<KItemRangeList>();
203 QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(0, 3));
204
205 // The indexes of the item-ranges must always be related to the model before
206 // the items have been inserted. Having:
207 // 0 1 2
208 // B E G
209 // and inserting A, C, D, F the resulting model will be:
210 // 0 1 2 3 4 5 6
211 // A B C D E F G
212 // and the item-ranges must be:
213 // index: 0, count: 1 for A
214 // index: 1, count: 2 for B, C
215 // index: 2, count: 1 for G
216
217 files.clear();
218 files << "A" << "C" << "D" << "F";
219 m_testDir->createFiles(files);
220
221 QSignalSpy spy2(m_model, SIGNAL(itemsInserted(KItemRangeList)));
222 m_dirLister->updateDirectory(m_testDir->url());
223 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
224
225 QCOMPARE(spy2.count(), 1);
226 arguments = spy2.takeFirst();
227 itemRangeList = arguments.at(0).value<KItemRangeList>();
228 QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(0, 1) << KItemRange(1, 2) << KItemRange(2, 1));
229 }
230
231 void KFileItemModelTest::testExpandItems()
232 {
233 // Test expanding subfolders in a folder with the items "a/", "a/a/", "a/a/1", "a/a-1/", "a/a-1/1".
234 // Besides testing the basic item expansion functionality, the test makes sure that
235 // KFileItemModel::expansionLevelsCompare(const KFileItem& a, const KFileItem& b)
236 // yields the correct result for "a/a/1" and "a/a-1/", whis is non-trivial because they share the
237 // first three characters.
238 QSet<QByteArray> modelRoles = m_model->roles();
239 modelRoles << "isExpanded" << "expansionLevel";
240 m_model->setRoles(modelRoles);
241
242 QStringList files;
243 files << "a/a/1" << "a/a-1/1"; // missing folders are created automatically
244 m_testDir->createFiles(files);
245
246 // Store the URLs of all folders in a set.
247 QSet<KUrl> allFolders;
248 allFolders << KUrl(m_testDir->name() + "a") << KUrl(m_testDir->name() + "a/a") << KUrl(m_testDir->name() + "a/a-1");
249
250 m_dirLister->openUrl(m_testDir->url());
251 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
252
253 // So far, the model contains only "a/"
254 QCOMPARE(m_model->count(), 1);
255 QVERIFY(m_model->isExpandable(0));
256 QVERIFY(!m_model->isExpanded(0));
257 QVERIFY(m_model->expandedUrls().empty());
258
259 QSignalSpy spyInserted(m_model, SIGNAL(itemsInserted(KItemRangeList)));
260
261 // Expand the folder "a/" -> "a/a/" and "a/a-1/" become visible
262 m_model->setExpanded(0, true);
263 QVERIFY(m_model->isExpanded(0));
264 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
265 QCOMPARE(m_model->count(), 3); // 3 items: "a/", "a/a/", "a/a-1/"
266 QCOMPARE(m_model->expandedUrls(), QSet<KUrl>() << KUrl(m_testDir->name() + "a"));
267
268 QCOMPARE(spyInserted.count(), 1);
269 KItemRangeList itemRangeList = spyInserted.takeFirst().at(0).value<KItemRangeList>();
270 QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(1, 2)); // 2 new items "a/a/" and "a/a-1/" with indices 1 and 2
271
272 QVERIFY(m_model->isExpandable(1));
273 QVERIFY(!m_model->isExpanded(1));
274 QVERIFY(m_model->isExpandable(2));
275 QVERIFY(!m_model->isExpanded(2));
276
277 // Expand the folder "a/a/" -> "a/a/1" becomes visible
278 m_model->setExpanded(1, true);
279 QVERIFY(m_model->isExpanded(1));
280 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
281 QCOMPARE(m_model->count(), 4); // 4 items: "a/", "a/a/", "a/a/1", "a/a-1/"
282 QCOMPARE(m_model->expandedUrls(), QSet<KUrl>() << KUrl(m_testDir->name() + "a") << KUrl(m_testDir->name() + "a/a"));
283
284 QCOMPARE(spyInserted.count(), 1);
285 itemRangeList = spyInserted.takeFirst().at(0).value<KItemRangeList>();
286 QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(2, 1)); // 1 new item "a/a/1" with index 2
287
288 QVERIFY(!m_model->isExpandable(2));
289 QVERIFY(!m_model->isExpanded(2));
290
291 // Expand the folder "a/a-1/" -> "a/a-1/1" becomes visible
292 m_model->setExpanded(3, true);
293 QVERIFY(m_model->isExpanded(3));
294 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
295 QCOMPARE(m_model->count(), 5); // 5 items: "a/", "a/a/", "a/a/1", "a/a-1/", "a/a-1/1"
296 QCOMPARE(m_model->expandedUrls(), allFolders);
297
298 QCOMPARE(spyInserted.count(), 1);
299 itemRangeList = spyInserted.takeFirst().at(0).value<KItemRangeList>();
300 QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(4, 1)); // 1 new item "a/a-1/1" with index 4
301
302 QVERIFY(!m_model->isExpandable(4));
303 QVERIFY(!m_model->isExpanded(4));
304
305 QSignalSpy spyRemoved(m_model, SIGNAL(itemsRemoved(KItemRangeList)));
306
307 // Collapse the top-level folder -> all other items should disappear
308 m_model->setExpanded(0, false);
309 QVERIFY(!m_model->isExpanded(0));
310 QCOMPARE(m_model->count(), 1);
311 QVERIFY(!m_model->expandedUrls().contains(KUrl(m_testDir->name() + "a"))); // TODO: Make sure that child URLs are also removed
312
313 QCOMPARE(spyRemoved.count(), 1);
314 itemRangeList = spyRemoved.takeFirst().at(0).value<KItemRangeList>();
315 QCOMPARE(itemRangeList, KItemRangeList() << KItemRange(1, 4)); // 4 items removed
316
317 // Clear the model, reload the folder and try to restore the expanded folders.
318 m_model->clear();
319 QCOMPARE(m_model->count(), 0);
320 QVERIFY(m_model->expandedUrls().empty());
321
322 m_dirLister->openUrl(m_testDir->url());
323 m_model->restoreExpandedUrls(allFolders);
324 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(loadingCompleted()), DefaultTimeout));
325 QCOMPARE(m_model->count(), 5); // 5 items: "a/", "a/a/", "a/a/1", "a/a-1/", "a/a-1/1"
326 QVERIFY(m_model->isExpanded(0));
327 QVERIFY(m_model->isExpanded(1));
328 QVERIFY(!m_model->isExpanded(2));
329 QVERIFY(m_model->isExpanded(3));
330 QVERIFY(!m_model->isExpanded(4));
331 QCOMPARE(m_model->expandedUrls(), allFolders);
332 }
333
334 void KFileItemModelTest::testExpansionLevelsCompare_data()
335 {
336 QTest::addColumn<QString>("urlA");
337 QTest::addColumn<QString>("urlB");
338 QTest::addColumn<int>("result");
339
340 QTest::newRow("Equal") << "/a/b" << "/a/b" << 0;
341 QTest::newRow("Sub path: A < B") << "/a/b" << "/a/b/c" << -1;
342 QTest::newRow("Sub path: A > B") << "/a/b/c" << "/a/b" << +1;
343 QTest::newRow("Same level: /a/1 < /a-1/1") << "/a/1" << "/a-1/1" << -1;
344 QTest::newRow("Same level: /a-/1 > /a/1") << "/a-1/1" << "/a/1" << +1;
345 QTest::newRow("Different levels: /a/a/1 < /a/a-1") << "/a/a/1" << "/a/a-1" << -1;
346 QTest::newRow("Different levels: /a/a-1 > /a/a/1") << "/a/a-1" << "/a/a/1" << +1;
347 }
348
349 void KFileItemModelTest::testExpansionLevelsCompare()
350 {
351 QFETCH(QString, urlA);
352 QFETCH(QString, urlB);
353 QFETCH(int, result);
354
355 const KFileItem a(KUrl(urlA), QString(), mode_t(-1));
356 const KFileItem b(KUrl(urlB), QString(), mode_t(-1));
357 QCOMPARE(m_model->expansionLevelsCompare(a, b), result);
358 }
359
360 void KFileItemModelTest::testIndexForKeyboardSearch()
361 {
362 QStringList files;
363 files << "a" << "aa" << "Image.jpg" << "Image.png" << "Text" << "Text1" << "Text2" << "Text11";
364 m_testDir->createFiles(files);
365
366 m_dirLister->openUrl(m_testDir->url());
367 QVERIFY(QTest::kWaitForSignal(m_model, SIGNAL(itemsInserted(KItemRangeList)), DefaultTimeout));
368
369 // Search from index 0
370 QCOMPARE(m_model->indexForKeyboardSearch("a", 0), 0);
371 QCOMPARE(m_model->indexForKeyboardSearch("aa", 0), 1);
372 QCOMPARE(m_model->indexForKeyboardSearch("i", 0), 2);
373 QCOMPARE(m_model->indexForKeyboardSearch("image", 0), 2);
374 QCOMPARE(m_model->indexForKeyboardSearch("image.jpg", 0), 2);
375 QCOMPARE(m_model->indexForKeyboardSearch("image.png", 0), 3);
376 QCOMPARE(m_model->indexForKeyboardSearch("t", 0), 4);
377 QCOMPARE(m_model->indexForKeyboardSearch("text", 0), 4);
378 QCOMPARE(m_model->indexForKeyboardSearch("text1", 0), 5);
379 QCOMPARE(m_model->indexForKeyboardSearch("text2", 0), 6);
380 QCOMPARE(m_model->indexForKeyboardSearch("text11", 0), 7);
381
382 // Start a search somewhere in the middle
383 QCOMPARE(m_model->indexForKeyboardSearch("a", 1), 1);
384 QCOMPARE(m_model->indexForKeyboardSearch("i", 3), 3);
385 QCOMPARE(m_model->indexForKeyboardSearch("t", 5), 5);
386 QCOMPARE(m_model->indexForKeyboardSearch("text1", 6), 7);
387
388 // Test searches that go past the last item back to index 0
389 QCOMPARE(m_model->indexForKeyboardSearch("a", 2), 0);
390 QCOMPARE(m_model->indexForKeyboardSearch("i", 7), 2);
391 QCOMPARE(m_model->indexForKeyboardSearch("image.jpg", 3), 2);
392 QCOMPARE(m_model->indexForKeyboardSearch("text2", 7), 6);
393
394 // Test searches that yield no result
395 QCOMPARE(m_model->indexForKeyboardSearch("aaa", 0), -1);
396 QCOMPARE(m_model->indexForKeyboardSearch("b", 0), -1);
397 QCOMPARE(m_model->indexForKeyboardSearch("image.svg", 0), -1);
398 QCOMPARE(m_model->indexForKeyboardSearch("text3", 0), -1);
399 QCOMPARE(m_model->indexForKeyboardSearch("text3", 5), -1);
400
401 // Test upper case searches (note that search is case insensitive)
402 QCOMPARE(m_model->indexForKeyboardSearch("A", 0), 0);
403 QCOMPARE(m_model->indexForKeyboardSearch("aA", 0), 1);
404 QCOMPARE(m_model->indexForKeyboardSearch("TexT", 5), 5);
405 QCOMPARE(m_model->indexForKeyboardSearch("IMAGE", 4), 2);
406
407 // TODO: Maybe we should also test keyboard searches in directories which are not sorted by Name?
408 }
409
410 bool KFileItemModelTest::isModelConsistent() const
411 {
412 for (int i = 0; i < m_model->count(); ++i) {
413 const KFileItem item = m_model->fileItem(i);
414 if (item.isNull()) {
415 qWarning() << "Item" << i << "is null";
416 return false;
417 }
418
419 const int itemIndex = m_model->index(item);
420 if (itemIndex != i) {
421 qWarning() << "Item" << i << "has a wrong index:" << itemIndex;
422 return false;
423 }
424 }
425
426 return true;
427 }
428
429 QTEST_KDEMAIN(KFileItemModelTest, NoGUI)
430
431 #include "kfileitemmodeltest.moc"