1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28#include <QtTest/QtTest>
29#include <QtTest/QSignalSpy>
30#include <QtQml/qqmlengine.h>
31#include <QtQml/qqmlcomponent.h>
32#include <QtCore/qdir.h>
33#include <QtCore/qfile.h>
34#include <QtCore/qabstractitemmodel.h>
35#include <QDebug>
36#include "../../shared/util.h"
37
38#if defined (Q_OS_WIN)
39#include <qt_windows.h>
40#endif
41
42// From qquickfolderlistmodel.h
43const int FileNameRole = Qt::UserRole+1;
44enum SortField { Unsorted, Name, Time, Size, Type };
45enum Status { Null, Ready, Loading };
46
47class tst_qquickfolderlistmodel : public QQmlDataTest
48{
49 Q_OBJECT
50public:
51 tst_qquickfolderlistmodel() {}
52
53public slots:
54 void removed(const QModelIndex &, int start, int end) {
55 removeStart = start;
56 removeEnd = end;
57 }
58
59private slots:
60 void initTestCase();
61 void basicProperties();
62 void status();
63 void showFiles();
64 void resetFiltering();
65 void nameFilters();
66 void refresh();
67 void cdUp();
68#ifdef Q_OS_WIN32
69 // WinCE/WinRT do not have drive APIs, so let's execute this test only on desktop Windows.
70 void changeDrive();
71#endif
72 void showDotAndDotDot();
73 void showDotAndDotDot_data();
74 void sortReversed();
75 void introspectQrc();
76 void sortCaseSensitive_data();
77 void sortCaseSensitive();
78 void updateProperties();
79 void importBothVersions();
80private:
81 void checkNoErrors(const QQmlComponent& component);
82 QQmlEngine engine;
83
84 int removeStart = 0;
85 int removeEnd = 0;
86};
87
88void tst_qquickfolderlistmodel::checkNoErrors(const QQmlComponent& component)
89{
90 // Wait until the component is ready
91 QTRY_VERIFY(component.isReady() || component.isError());
92
93 if (component.isError()) {
94 QList<QQmlError> errors = component.errors();
95 for (int ii = 0; ii < errors.count(); ++ii) {
96 const QQmlError &error = errors.at(i: ii);
97 QByteArray errorStr = QByteArray::number(error.line()) + ':' +
98 QByteArray::number(error.column()) + ':' +
99 error.description().toUtf8();
100 qWarning() << errorStr;
101 }
102 }
103 QVERIFY(!component.isError());
104}
105
106void tst_qquickfolderlistmodel::initTestCase()
107{
108 // The tests rely on a fixed number of files in the directory with the qml files
109 // (the data dir), so disable the disk cache to avoid creating .qmlc files and
110 // confusing the test.
111 qputenv(varName: "QML_DISABLE_DISK_CACHE", value: "1");
112 QQmlDataTest::initTestCase();
113}
114
115void tst_qquickfolderlistmodel::basicProperties()
116{
117#ifdef Q_OS_ANDROID
118 QSKIP("[QTBUG-77335] Initial folder of FolderListModel on Android does not work properly,"
119 " and from there on it is unreliable to change the folder");
120#endif
121 QQmlComponent component(&engine, testFileUrl(fileName: "basic.qml"));
122 checkNoErrors(component);
123
124 QAbstractListModel *flm = qobject_cast<QAbstractListModel*>(object: component.create());
125 QVERIFY(flm != nullptr);
126 QCOMPARE(flm->property("nameFilters").toStringList(), QStringList() << "*.qml"); // from basic.qml
127 QCOMPARE(flm->property("folder").toUrl(), QUrl::fromLocalFile(QDir::currentPath()));
128
129 // wait for the initial directory listing (it will find at least the "data" dir,
130 // and other dirs on Windows).
131 QTRY_VERIFY(flm->property("count").toInt() > 0);
132
133 QSignalSpy folderChangedSpy(flm, SIGNAL(folderChanged()));
134 flm->setProperty(name: "folder", value: dataDirectoryUrl());
135 QVERIFY(folderChangedSpy.wait());
136 QCOMPARE(flm->property("count").toInt(), 9);
137 QCOMPARE(flm->property("folder").toUrl(), dataDirectoryUrl());
138 QCOMPARE(flm->property("parentFolder").toUrl(), QUrl::fromLocalFile(QDir(directory()).canonicalPath()));
139 QCOMPARE(flm->property("sortField").toInt(), int(Name));
140 QCOMPARE(flm->property("nameFilters").toStringList(), QStringList() << "*.qml");
141 QCOMPARE(flm->property("sortReversed").toBool(), false);
142 QCOMPARE(flm->property("showFiles").toBool(), true);
143 QCOMPARE(flm->property("showDirs").toBool(), true);
144 QCOMPARE(flm->property("showDotAndDotDot").toBool(), false);
145 QCOMPARE(flm->property("showOnlyReadable").toBool(), false);
146 QCOMPARE(flm->data(flm->index(0),FileNameRole).toString(), QLatin1String("basic.qml"));
147 QCOMPARE(flm->data(flm->index(1),FileNameRole).toString(), QLatin1String("dummy.qml"));
148
149 flm->setProperty(name: "folder",value: QUrl::fromLocalFile(localfile: ""));
150 QCOMPARE(flm->property("folder").toUrl(), QUrl::fromLocalFile(""));
151}
152
153void tst_qquickfolderlistmodel::status()
154{
155 QQmlComponent component(&engine, testFileUrl(fileName: "basic.qml"));
156 checkNoErrors(component);
157
158 QAbstractListModel *flm = qobject_cast<QAbstractListModel*>(object: component.create());
159 QVERIFY(flm != nullptr);
160 QTRY_COMPARE(flm->property("status").toInt(), int(Ready));
161 flm->setProperty(name: "folder", value: QUrl::fromLocalFile(localfile: ""));
162 QTRY_COMPARE(flm->property("status").toInt(), int(Null));
163 flm->setProperty(name: "folder", value: QUrl::fromLocalFile(localfile: QDir::currentPath()));
164 QTRY_COMPARE(flm->property("status").toInt(), int(Ready));
165}
166
167void tst_qquickfolderlistmodel::showFiles()
168{
169 QQmlComponent component(&engine, testFileUrl(fileName: "basic.qml"));
170 checkNoErrors(component);
171
172 QAbstractListModel *flm = qobject_cast<QAbstractListModel*>(object: component.create());
173 QVERIFY(flm != nullptr);
174
175 flm->setProperty(name: "folder", value: dataDirectoryUrl());
176 QTRY_COMPARE(flm->property("count").toInt(), 9); // wait for refresh
177 QCOMPARE(flm->property("showFiles").toBool(), true);
178
179 flm->setProperty(name: "showFiles", value: false);
180 QCOMPARE(flm->property("showFiles").toBool(), false);
181 QTRY_COMPARE(flm->property("count").toInt(), 3); // wait for refresh
182}
183
184void tst_qquickfolderlistmodel::resetFiltering()
185{
186 // see QTBUG-17837
187 QQmlComponent component(&engine, testFileUrl(fileName: "resetFiltering.qml"));
188 checkNoErrors(component);
189
190 QAbstractListModel *flm = qobject_cast<QAbstractListModel*>(object: component.create());
191 QVERIFY(flm != nullptr);
192
193 flm->setProperty(name: "folder", value: testFileUrl(fileName: "resetfiltering"));
194 // _q_directoryUpdated may be triggered if model was empty before, but there won't be a rowsRemoved signal
195 QTRY_COMPARE(flm->property("count").toInt(),3); // all files visible
196
197 flm->setProperty(name: "folder", value: testFileUrl(fileName: "resetfiltering/innerdir"));
198 // _q_directoryChanged is triggered so it's a total model refresh
199 QTRY_COMPARE(flm->property("count").toInt(),1); // should just be "test2.txt" visible
200
201 flm->setProperty(name: "folder", value: testFileUrl(fileName: "resetfiltering"));
202 // _q_directoryChanged is triggered so it's a total model refresh
203 QTRY_COMPARE(flm->property("count").toInt(),3); // all files visible
204}
205
206void tst_qquickfolderlistmodel::nameFilters()
207{
208 // see QTBUG-36576
209 QQmlComponent component(&engine, testFileUrl(fileName: "resetFiltering.qml"));
210 checkNoErrors(component);
211
212 QAbstractListModel *flm = qobject_cast<QAbstractListModel*>(object: component.create());
213 QVERIFY(flm != nullptr);
214
215 connect(sender: flm, SIGNAL(rowsRemoved(QModelIndex,int,int)),
216 receiver: this, SLOT(removed(QModelIndex,int,int)));
217
218 QTRY_VERIFY(flm->rowCount() > 0);
219 flm->setProperty(name: "folder", value: testFileUrl(fileName: "resetfiltering"));
220 QTRY_COMPARE(flm->property("count").toInt(),3); // all files visible
221
222 int count = flm->rowCount();
223 flm->setProperty(name: "nameFilters", value: QStringList() << "*.txt");
224 // _q_directoryUpdated triggered with range 0:1
225 QTRY_COMPARE(flm->property("count").toInt(),1);
226 QCOMPARE(flm->data(flm->index(0),FileNameRole), QVariant("test.txt"));
227 QCOMPARE(removeStart, 0);
228 QCOMPARE(removeEnd, count-1);
229
230 flm->setProperty(name: "nameFilters", value: QStringList() << "*.html");
231 QTRY_COMPARE(flm->property("count").toInt(),2);
232 QCOMPARE(flm->data(flm->index(0),FileNameRole), QVariant("test1.html"));
233 QCOMPARE(flm->data(flm->index(1),FileNameRole), QVariant("test2.html"));
234
235 flm->setProperty(name: "nameFilters", value: QStringList());
236 QTRY_COMPARE(flm->property("count").toInt(),3); // all files visible
237}
238
239void tst_qquickfolderlistmodel::refresh()
240{
241 QQmlComponent component(&engine, testFileUrl(fileName: "basic.qml"));
242 checkNoErrors(component);
243
244 QAbstractListModel *flm = qobject_cast<QAbstractListModel*>(object: component.create());
245 QVERIFY(flm != nullptr);
246
247 flm->setProperty(name: "folder", value: dataDirectoryUrl());
248 QTRY_COMPARE(flm->property("count").toInt(), 9); // wait for refresh
249
250 int count = flm->rowCount();
251
252 connect(sender: flm, SIGNAL(rowsRemoved(QModelIndex,int,int)),
253 receiver: this, SLOT(removed(QModelIndex,int,int)));
254
255 flm->setProperty(name: "sortReversed", value: true);
256
257 QTRY_COMPARE(removeStart, 0);
258 QTRY_COMPARE(removeEnd, count-1); // wait for refresh
259}
260
261void tst_qquickfolderlistmodel::cdUp()
262{
263 enum { maxIterations = 50 };
264 QQmlComponent component(&engine, testFileUrl(fileName: "basic.qml"));
265 checkNoErrors(component);
266
267 QAbstractListModel *flm = qobject_cast<QAbstractListModel*>(object: component.create());
268 QVERIFY(flm != nullptr);
269 const QUrl startFolder = flm->property(name: "folder").toUrl();
270 QVERIFY(startFolder.isValid());
271
272 // QTBUG-32139: Ensure navigating upwards terminates cleanly and does not
273 // return invalid Urls like "file:".
274 for (int i = 0; ; ++i) {
275 const QVariant folderV = flm->property(name: "parentFolder");
276 const QUrl folder = folderV.toUrl();
277 if (!folder.isValid())
278 break;
279 QVERIFY(folder.toString() != QLatin1String("file:"));
280 QVERIFY2(i < maxIterations,
281 qPrintable(QString::fromLatin1("Unable to reach root after %1 iterations starting from %2, stuck at %3")
282 .arg(maxIterations).arg(QDir::toNativeSeparators(startFolder.toLocalFile()),
283 QDir::toNativeSeparators(folder.toLocalFile()))));
284 flm->setProperty(name: "folder", value: folderV);
285 }
286}
287
288#ifdef Q_OS_WIN32
289void tst_qquickfolderlistmodel::changeDrive()
290{
291 QSKIP("QTBUG-26728");
292 class DriveMapper
293 {
294 public:
295 DriveMapper(const QString &dataDir)
296 {
297 size_t stringLen = dataDir.length();
298 targetPath = new wchar_t[stringLen+1];
299 dataDir.toWCharArray(targetPath);
300 targetPath[stringLen] = 0;
301
302 DefineDosDevice(DDD_NO_BROADCAST_SYSTEM, L"X:", targetPath);
303 }
304
305 ~DriveMapper()
306 {
307 DefineDosDevice(DDD_EXACT_MATCH_ON_REMOVE | DDD_NO_BROADCAST_SYSTEM | DDD_REMOVE_DEFINITION, L"X:", targetPath);
308 delete [] targetPath;
309 }
310
311 private:
312 wchar_t *targetPath;
313 };
314
315 QString dataDir = testFile(0);
316 DriveMapper dm(dataDir);
317 QQmlComponent component(&engine, testFileUrl("basic.qml"));
318
319 QAbstractListModel *flm = qobject_cast<QAbstractListModel*>(component.create());
320 QVERIFY(flm != 0);
321
322 QSignalSpy folderChangeSpy(flm, SIGNAL(folderChanged()));
323
324 flm->setProperty("folder",QUrl::fromLocalFile(dataDir));
325 QCOMPARE(flm->property("folder").toUrl(), QUrl::fromLocalFile(dataDir));
326 QTRY_COMPARE(folderChangeSpy.count(), 1);
327
328 flm->setProperty("folder",QUrl::fromLocalFile("X:/resetfiltering/"));
329 QCOMPARE(flm->property("folder").toUrl(), QUrl::fromLocalFile("X:/resetfiltering/"));
330 QTRY_COMPARE(folderChangeSpy.count(), 2);
331}
332#endif
333
334void tst_qquickfolderlistmodel::showDotAndDotDot()
335{
336 QFETCH(QUrl, folder);
337 QFETCH(QUrl, rootFolder);
338 QFETCH(bool, showDotAndDotDot);
339 QFETCH(bool, showDot);
340 QFETCH(bool, showDotDot);
341
342 QQmlComponent component(&engine, testFileUrl(fileName: "showDotAndDotDot.qml"));
343 checkNoErrors(component);
344
345 QAbstractListModel *flm = qobject_cast<QAbstractListModel*>(object: component.create());
346 QVERIFY(flm != nullptr);
347
348 flm->setProperty(name: "folder", value: folder);
349 flm->setProperty(name: "rootFolder", value: rootFolder);
350 flm->setProperty(name: "showDotAndDotDot", value: showDotAndDotDot);
351
352 int count = 10;
353 if (showDot) count++;
354 if (showDotDot) count++;
355 QTRY_COMPARE(flm->property("count").toInt(), count); // wait for refresh
356
357 if (showDot)
358 QCOMPARE(flm->data(flm->index(0),FileNameRole).toString(), QLatin1String("."));
359 if (showDotDot)
360 QCOMPARE(flm->data(flm->index(1),FileNameRole).toString(), QLatin1String(".."));
361}
362
363void tst_qquickfolderlistmodel::showDotAndDotDot_data()
364{
365#ifdef Q_OS_ANDROID
366 QSKIP("Resource file system does not list '.' and '..' due to QDir::entryList() behavior");
367#endif
368 QTest::addColumn<QUrl>(name: "folder");
369 QTest::addColumn<QUrl>(name: "rootFolder");
370 QTest::addColumn<bool>(name: "showDotAndDotDot");
371 QTest::addColumn<bool>(name: "showDot");
372 QTest::addColumn<bool>(name: "showDotDot");
373
374 QTest::newRow(dataTag: "false") << dataDirectoryUrl() << QUrl() << false << false << false;
375 QTest::newRow(dataTag: "true") << dataDirectoryUrl() << QUrl() << true << true << true;
376 QTest::newRow(dataTag: "true but root") << dataDirectoryUrl() << dataDirectoryUrl() << true << true << false;
377}
378
379void tst_qquickfolderlistmodel::sortReversed()
380{
381 QQmlComponent component(&engine, testFileUrl(fileName: "sortReversed.qml"));
382 checkNoErrors(component);
383 QAbstractListModel *flm = qobject_cast<QAbstractListModel*>(object: component.create());
384 QVERIFY(flm != nullptr);
385 flm->setProperty(name: "folder", value: dataDirectoryUrl());
386 QTRY_COMPARE(flm->property("count").toInt(), 10); // wait for refresh
387 QCOMPARE(flm->data(flm->index(0),FileNameRole).toString(), QLatin1String("txtdir"));
388}
389
390void tst_qquickfolderlistmodel::introspectQrc()
391{
392 QQmlComponent component(&engine, testFileUrl(fileName: "qrc.qml"));
393 checkNoErrors(component);
394 QAbstractListModel *flm = qobject_cast<QAbstractListModel*>(object: component.create());
395 QVERIFY(flm != nullptr);
396 QTRY_COMPARE(flm->property("count").toInt(), 1); // wait for refresh
397 QCOMPARE(flm->data(flm->index(0),FileNameRole).toString(), QLatin1String("hello.txt"));
398}
399
400void tst_qquickfolderlistmodel::sortCaseSensitive_data()
401{
402 QTest::addColumn<bool>(name: "sortCaseSensitive");
403 QTest::addColumn<QStringList>(name: "expectedOrder");
404
405 const QString upperFile = QLatin1String("Uppercase.txt");
406 const QString lowerFile = QLatin1String("lowercase.txt");
407
408 QTest::newRow(dataTag: "caseSensitive") << true << (QStringList() << upperFile << lowerFile);
409 QTest::newRow(dataTag: "caseInsensitive") << false << (QStringList() << lowerFile << upperFile);
410}
411
412void tst_qquickfolderlistmodel::sortCaseSensitive()
413{
414 QFETCH(bool, sortCaseSensitive);
415 QFETCH(QStringList, expectedOrder);
416 QQmlComponent component(&engine);
417 component.setData("import Qt.labs.folderlistmodel 1.0\n"
418 "FolderListModel { }", baseUrl: QUrl());
419 checkNoErrors(component);
420
421 QAbstractListModel *flm = qobject_cast<QAbstractListModel*>(object: component.create());
422 QVERIFY(flm != 0);
423 flm->setProperty(name: "folder", value: testFileUrl(fileName: "sortdir"));
424 flm->setProperty(name: "sortCaseSensitive", value: sortCaseSensitive);
425 QTRY_COMPARE(flm->property("count").toInt(), 2); // wait for refresh
426 for (int i = 0; i < 2; ++i)
427 QTRY_COMPARE(flm->data(flm->index(i),FileNameRole).toString(), expectedOrder.at(i));
428}
429
430void tst_qquickfolderlistmodel::updateProperties()
431{
432 QQmlComponent component(&engine, testFileUrl(fileName: "basic.qml"));
433 checkNoErrors(component);
434
435 QObject *folderListModel = component.create();
436 QVERIFY(folderListModel);
437
438 QVariant caseSensitive = folderListModel->property(name: "caseSensitive");
439 QVERIFY(caseSensitive.isValid());
440 QCOMPARE(caseSensitive.toBool(), true);
441 folderListModel->setProperty(name: "caseSensitive", value: false);
442 caseSensitive = folderListModel->property(name: "caseSensitive");
443 QVERIFY(caseSensitive.isValid());
444 QCOMPARE(caseSensitive.toBool(), false);
445
446 QVariant showOnlyReadable = folderListModel->property(name: "showOnlyReadable");
447 QVERIFY(showOnlyReadable.isValid());
448 QCOMPARE(showOnlyReadable.toBool(), false);
449 folderListModel->setProperty(name: "showOnlyReadable", value: true);
450 showOnlyReadable = folderListModel->property(name: "showOnlyReadable");
451 QVERIFY(showOnlyReadable.isValid());
452 QCOMPARE(showOnlyReadable.toBool(), true);
453
454 QVariant showDotAndDotDot = folderListModel->property(name: "showDotAndDotDot");
455 QVERIFY(showDotAndDotDot.isValid());
456 QCOMPARE(showDotAndDotDot.toBool(), false);
457 folderListModel->setProperty(name: "showDotAndDotDot", value: true);
458 showDotAndDotDot = folderListModel->property(name: "showDotAndDotDot");
459 QVERIFY(showDotAndDotDot.isValid());
460 QCOMPARE(showDotAndDotDot.toBool(), true);
461
462 QVariant showHidden = folderListModel->property(name: "showHidden");
463 QVERIFY(showHidden.isValid());
464 QCOMPARE(showHidden.toBool(), false);
465 folderListModel->setProperty(name: "showHidden", value: true);
466 showHidden = folderListModel->property(name: "showHidden");
467 QVERIFY(showHidden.isValid());
468 QCOMPARE(showHidden.toBool(), true);
469}
470
471void tst_qquickfolderlistmodel::importBothVersions()
472{
473 {
474 QQmlComponent component(&engine, testFileUrl(fileName: "sortReversed.qml"));
475 checkNoErrors(component);
476 QScopedPointer<QObject> obj(component.create());
477 QVERIFY(obj);
478 }
479 {
480 QQmlComponent component(&engine, testFileUrl(fileName: "qrc.qml"));
481 checkNoErrors(component);
482 QScopedPointer<QObject> obj(component.create());
483 QVERIFY(obj);
484 }
485}
486
487QTEST_MAIN(tst_qquickfolderlistmodel)
488
489#include "tst_qquickfolderlistmodel.moc"
490

source code of qtdeclarative/tests/auto/qml/qquickfolderlistmodel/tst_qquickfolderlistmodel.cpp