1 /***************************************************************************
2 * Copyright (C) 2011 by Peter Penz <peter.penz19@gmail.com> *
3 * Copyright (C) 2011 by Frank Reininghaus <frank78ac@googlemail.com> *
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. *
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. *
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 ***************************************************************************/
21 #include <qtest_kde.h>
24 #include "kitemviews/kfileitemmodel.h"
28 const int DefaultTimeout
= 5000;
31 Q_DECLARE_METATYPE(KItemRangeList
)
33 class KFileItemModelTest
: public QObject
41 void testDefaultRoles();
42 void testDefaultSortRole();
43 void testDefaultGroupRole();
45 void testRemoveItems();
46 void testModelConsistencyWhenInsertingItems();
47 void testItemRangeConsistencyWhenInsertingItems();
48 void testExpandItems();
50 void testExpansionLevelsCompare_data();
51 void testExpansionLevelsCompare();
53 void testIndexForKeyboardSearch();
56 bool isModelConsistent() const;
59 KFileItemModel
* m_model
;
60 KDirLister
* m_dirLister
;
64 void KFileItemModelTest::init()
66 qRegisterMetaType
<KItemRangeList
>("KItemRangeList");
67 qRegisterMetaType
<KFileItemList
>("KFileItemList");
69 m_testDir
= new TestDir();
70 m_dirLister
= new KDirLister();
71 m_model
= new KFileItemModel(m_dirLister
);
74 void KFileItemModelTest::cleanup()
86 void KFileItemModelTest::testDefaultRoles()
88 const QSet
<QByteArray
> roles
= m_model
->roles();
89 QCOMPARE(roles
.count(), 2);
90 QVERIFY(roles
.contains("name"));
91 QVERIFY(roles
.contains("isDir"));
94 void KFileItemModelTest::testDefaultSortRole()
96 QCOMPARE(m_model
->sortRole(), QByteArray("name"));
99 files
<< "c.txt" << "a.txt" << "b.txt";
101 m_testDir
->createFiles(files
);
103 m_dirLister
->openUrl(m_testDir
->url());
104 QVERIFY(QTest::kWaitForSignal(m_model
, SIGNAL(itemsInserted(KItemRangeList
)), DefaultTimeout
));
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"));
112 void KFileItemModelTest::testDefaultGroupRole()
114 QVERIFY(m_model
->groupRole().isEmpty());
117 void KFileItemModelTest::testNewItems()
120 files
<< "a.txt" << "b.txt" << "c.txt";
121 m_testDir
->createFiles(files
);
123 m_dirLister
->openUrl(m_testDir
->url());
124 QVERIFY(QTest::kWaitForSignal(m_model
, SIGNAL(itemsInserted(KItemRangeList
)), DefaultTimeout
));
126 QCOMPARE(m_model
->count(), 3);
128 QVERIFY(isModelConsistent());
131 void KFileItemModelTest::testRemoveItems()
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);
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);
144 void KFileItemModelTest::testModelConsistencyWhenInsertingItems()
146 QSKIP("Temporary disabled", SkipSingle
);
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);
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);
161 // Insert 10 items for 20 times. After each insert operation the model consistency
163 QSet
<int> insertedItems
;
164 for (int i
= 0; i
< 20; ++i
) {
165 QSignalSpy
spy(m_model
, SIGNAL(itemsInserted(KItemRangeList
)));
167 for (int j
= 0; j
< 10; ++j
) {
168 int itemName
= qrand();
169 while (insertedItems
.contains(itemName
)) {
172 insertedItems
.insert(itemName
);
174 m_testDir
->createFile(QString::number(itemName
));
177 m_dirLister
->updateDirectory(m_testDir
->url());
178 if (spy
.count() == 0) {
179 QVERIFY(QTest::kWaitForSignal(m_model
, SIGNAL(itemsInserted(KItemRangeList
)), DefaultTimeout
));
182 QVERIFY(isModelConsistent());
185 QCOMPARE(m_model
->count(), 201);
188 void KFileItemModelTest::testItemRangeConsistencyWhenInsertingItems()
191 files
<< "B" << "E" << "G";
192 m_testDir
->createFiles(files
);
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
));
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));
205 // The indexes of the item-ranges must always be related to the model before
206 // the items have been inserted. Having:
209 // and inserting A, C, D, F the resulting model will be:
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
218 files
<< "A" << "C" << "D" << "F";
219 m_testDir
->createFiles(files
);
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
));
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));
231 void KFileItemModelTest::testExpandItems()
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
);
243 files
<< "a/a/1" << "a/a-1/1"; // missing folders are created automatically
244 m_testDir
->createFiles(files
);
246 m_dirLister
->openUrl(m_testDir
->url());
247 QVERIFY(QTest::kWaitForSignal(m_model
, SIGNAL(itemsInserted(KItemRangeList
)), DefaultTimeout
));
249 // So far, the model contains only "a/"
250 QCOMPARE(m_model
->count(), 1);
251 QVERIFY(m_model
->isExpandable(0));
252 QVERIFY(!m_model
->isExpanded(0));
254 QSignalSpy
spyInserted(m_model
, SIGNAL(itemsInserted(KItemRangeList
)));
256 // Expand the folder "a/" -> "a/a/" and "a/a-1/" become visible
257 m_model
->setExpanded(0, true);
258 QVERIFY(m_model
->isExpanded(0));
259 QVERIFY(QTest::kWaitForSignal(m_model
, SIGNAL(itemsInserted(KItemRangeList
)), DefaultTimeout
));
260 QCOMPARE(m_model
->count(), 3); // 3 items: "a/", "a/a/", "a/a-1/"
262 QCOMPARE(spyInserted
.count(), 1);
263 KItemRangeList itemRangeList
= spyInserted
.takeFirst().at(0).value
<KItemRangeList
>();
264 QCOMPARE(itemRangeList
, KItemRangeList() << KItemRange(1, 2)); // 2 new items "a/a/" and "a/a-1/" with indices 1 and 2
266 QVERIFY(m_model
->isExpandable(1));
267 QVERIFY(!m_model
->isExpanded(1));
268 QVERIFY(m_model
->isExpandable(2));
269 QVERIFY(!m_model
->isExpanded(2));
271 // Expand the folder "a/a/" -> "a/a/1" becomes visible
272 m_model
->setExpanded(1, true);
273 QVERIFY(m_model
->isExpanded(1));
274 QVERIFY(QTest::kWaitForSignal(m_model
, SIGNAL(itemsInserted(KItemRangeList
)), DefaultTimeout
));
275 QCOMPARE(m_model
->count(), 4); // 4 items: "a/", "a/a/", "a/a/1", "a/a-1/"
277 QCOMPARE(spyInserted
.count(), 1);
278 itemRangeList
= spyInserted
.takeFirst().at(0).value
<KItemRangeList
>();
279 QCOMPARE(itemRangeList
, KItemRangeList() << KItemRange(2, 1)); // 1 new item "a/a/1" with index 2
281 QVERIFY(!m_model
->isExpandable(2));
282 QVERIFY(!m_model
->isExpanded(2));
284 // Expand the folder "a/a-1/" -> "a/a-1/1" becomes visible
285 m_model
->setExpanded(3, true);
286 QVERIFY(m_model
->isExpanded(3));
287 QVERIFY(QTest::kWaitForSignal(m_model
, SIGNAL(itemsInserted(KItemRangeList
)), DefaultTimeout
));
288 QCOMPARE(m_model
->count(), 5); // 4 items: "a/", "a/a/", "a/a/1", "a/a-1/", "a/a-1/1"
290 QCOMPARE(spyInserted
.count(), 1);
291 itemRangeList
= spyInserted
.takeFirst().at(0).value
<KItemRangeList
>();
292 QCOMPARE(itemRangeList
, KItemRangeList() << KItemRange(4, 1)); // 1 new item "a/a-1/1" with index 4
294 QVERIFY(!m_model
->isExpandable(4));
295 QVERIFY(!m_model
->isExpanded(4));
297 QSignalSpy
spyRemoved(m_model
, SIGNAL(itemsRemoved(KItemRangeList
)));
299 // Collapse the top-level folder -> all other items should disappear
300 m_model
->setExpanded(0, false);
301 QVERIFY(!m_model
->isExpanded(0));
302 QCOMPARE(spyRemoved
.count(), 1);
303 itemRangeList
= spyRemoved
.takeFirst().at(0).value
<KItemRangeList
>();
304 QCOMPARE(itemRangeList
, KItemRangeList() << KItemRange(1, 4)); // 4 items removed
307 void KFileItemModelTest::testExpansionLevelsCompare_data()
309 QTest::addColumn
<QString
>("urlA");
310 QTest::addColumn
<QString
>("urlB");
311 QTest::addColumn
<int>("result");
313 QTest::newRow("Equal") << "/a/b" << "/a/b" << 0;
314 QTest::newRow("Sub path: A < B") << "/a/b" << "/a/b/c" << -1;
315 QTest::newRow("Sub path: A > B") << "/a/b/c" << "/a/b" << +1;
316 QTest::newRow("Same level: /a/1 < /a-1/1") << "/a/1" << "/a-1/1" << -1;
317 QTest::newRow("Same level: /a-/1 > /a/1") << "/a-1/1" << "/a/1" << +1;
318 QTest::newRow("Different levels: /a/a/1 < /a/a-1") << "/a/a/1" << "/a/a-1" << -1;
319 QTest::newRow("Different levels: /a/a-1 > /a/a/1") << "/a/a-1" << "/a/a/1" << +1;
322 void KFileItemModelTest::testExpansionLevelsCompare()
324 QFETCH(QString
, urlA
);
325 QFETCH(QString
, urlB
);
328 const KFileItem
a(KUrl(urlA
), QString(), mode_t(-1));
329 const KFileItem
b(KUrl(urlB
), QString(), mode_t(-1));
330 QCOMPARE(m_model
->expansionLevelsCompare(a
, b
), result
);
333 void KFileItemModelTest::testIndexForKeyboardSearch()
336 files
<< "a" << "aa" << "Image.jpg" << "Image.png" << "Text" << "Text1" << "Text2" << "Text11";
337 m_testDir
->createFiles(files
);
339 m_dirLister
->openUrl(m_testDir
->url());
340 QVERIFY(QTest::kWaitForSignal(m_model
, SIGNAL(itemsInserted(KItemRangeList
)), DefaultTimeout
));
342 // Search from index 0
343 QCOMPARE(m_model
->indexForKeyboardSearch("a", 0), 0);
344 QCOMPARE(m_model
->indexForKeyboardSearch("aa", 0), 1);
345 QCOMPARE(m_model
->indexForKeyboardSearch("i", 0), 2);
346 QCOMPARE(m_model
->indexForKeyboardSearch("image", 0), 2);
347 QCOMPARE(m_model
->indexForKeyboardSearch("image.jpg", 0), 2);
348 QCOMPARE(m_model
->indexForKeyboardSearch("image.png", 0), 3);
349 QCOMPARE(m_model
->indexForKeyboardSearch("t", 0), 4);
350 QCOMPARE(m_model
->indexForKeyboardSearch("text", 0), 4);
351 QCOMPARE(m_model
->indexForKeyboardSearch("text1", 0), 5);
352 QCOMPARE(m_model
->indexForKeyboardSearch("text2", 0), 6);
353 QCOMPARE(m_model
->indexForKeyboardSearch("text11", 0), 7);
355 // Start a search somewhere in the middle
356 QCOMPARE(m_model
->indexForKeyboardSearch("a", 1), 1);
357 QCOMPARE(m_model
->indexForKeyboardSearch("i", 3), 3);
358 QCOMPARE(m_model
->indexForKeyboardSearch("t", 5), 5);
359 QCOMPARE(m_model
->indexForKeyboardSearch("text1", 6), 7);
361 // Test searches that go past the last item back to index 0
362 QCOMPARE(m_model
->indexForKeyboardSearch("a", 2), 0);
363 QCOMPARE(m_model
->indexForKeyboardSearch("i", 7), 2);
364 QCOMPARE(m_model
->indexForKeyboardSearch("image.jpg", 3), 2);
365 QCOMPARE(m_model
->indexForKeyboardSearch("text2", 7), 6);
367 // Test searches that yield no result
368 QCOMPARE(m_model
->indexForKeyboardSearch("aaa", 0), -1);
369 QCOMPARE(m_model
->indexForKeyboardSearch("b", 0), -1);
370 QCOMPARE(m_model
->indexForKeyboardSearch("image.svg", 0), -1);
371 QCOMPARE(m_model
->indexForKeyboardSearch("text3", 0), -1);
372 QCOMPARE(m_model
->indexForKeyboardSearch("text3", 5), -1);
374 // Test upper case searches (note that search is case insensitive)
375 QCOMPARE(m_model
->indexForKeyboardSearch("A", 0), 0);
376 QCOMPARE(m_model
->indexForKeyboardSearch("aA", 0), 1);
377 QCOMPARE(m_model
->indexForKeyboardSearch("TexT", 5), 5);
378 QCOMPARE(m_model
->indexForKeyboardSearch("IMAGE", 4), 2);
380 // TODO: Maybe we should also test keyboard searches in directories which are not sorted by Name?
383 bool KFileItemModelTest::isModelConsistent() const
385 for (int i
= 0; i
< m_model
->count(); ++i
) {
386 const KFileItem item
= m_model
->fileItem(i
);
388 qWarning() << "Item" << i
<< "is null";
392 const int itemIndex
= m_model
->index(item
);
393 if (itemIndex
!= i
) {
394 qWarning() << "Item" << i
<< "has a wrong index:" << itemIndex
;
402 QTEST_KDEMAIN(KFileItemModelTest
, NoGUI
)
404 #include "kfileitemmodeltest.moc"