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
29
30#include <QtTest/QtTest>
31
32#include <qcoreapplication.h>
33#include <qfile.h>
34#include <qdebug.h>
35#include <qsharedpointer.h>
36#include <qfiledialog.h>
37#include <qabstractitemdelegate.h>
38#include <qdirmodel.h>
39#include <qitemdelegate.h>
40#include <qlistview.h>
41#include <qcombobox.h>
42#include <qpushbutton.h>
43#include <qtoolbutton.h>
44#include <qtreeview.h>
45#include <qheaderview.h>
46#include <qcompleter.h>
47#include <qaction.h>
48#include <qdialogbuttonbox.h>
49#include <qsortfilterproxymodel.h>
50#include <qlineedit.h>
51#include <qlayout.h>
52#include <qtemporarydir.h>
53#include <private/qfiledialog_p.h>
54#if defined QT_BUILD_INTERNAL
55#include <private/qsidebar_p.h>
56#include <private/qfilesystemmodel_p.h>
57#endif
58#include <private/qguiapplication_p.h>
59#include <qpa/qplatformtheme.h>
60#include <qpa/qplatformintegration.h>
61#include <QFileDialog>
62#include <QFileSystemModel>
63
64#if defined(Q_OS_UNIX)
65#include <unistd.h> // for pathconf() on OS X
66#ifdef QT_BUILD_INTERNAL
67QT_BEGIN_NAMESPACE
68extern Q_GUI_EXPORT QString qt_tildeExpansion(const QString &path);
69QT_END_NAMESPACE
70#endif
71#endif
72
73static inline bool isCaseSensitiveFileSystem(const QString &path)
74{
75 Q_UNUSED(path)
76#if defined(Q_OS_MAC)
77 return pathconf(QFile::encodeName(path).constData(), _PC_CASE_SENSITIVE);
78#elif defined(Q_OS_WIN)
79 return false;
80#else
81 return true;
82#endif
83}
84
85class tst_QFiledialog : public QObject
86{
87Q_OBJECT
88
89private slots:
90 void initTestCase();
91 void init();
92 void cleanup();
93 void currentChangedSignal();
94#ifdef QT_BUILD_INTERNAL
95 void directoryEnteredSignal();
96#endif
97 void filesSelectedSignal_data();
98 void filesSelectedSignal();
99 void filterSelectedSignal();
100
101 void args();
102 void directory();
103 void completer_data();
104 void completer();
105 void completer_up();
106 void acceptMode();
107 void confirmOverwrite();
108 void defaultSuffix();
109 void fileMode();
110 void filters();
111 void history();
112 void iconProvider();
113 void isReadOnly();
114 void itemDelegate();
115 void labelText();
116 void resolveSymlinks();
117 void selectFile_data();
118 void selectFile();
119 void selectFiles();
120 void selectFileWrongCaseSaveAs();
121 void selectFilter();
122 void viewMode();
123 void proxymodel();
124 void setMimeTypeFilters_data();
125 void setMimeTypeFilters();
126 void setNameFilter_data();
127 void setNameFilter();
128 void setEmptyNameFilter();
129 void focus();
130 void caption();
131 void historyBack();
132 void historyForward();
133 void disableSaveButton_data();
134 void disableSaveButton();
135 void saveButtonText_data();
136 void saveButtonText();
137 void clearLineEdit();
138 void enableChooseButton();
139 void selectedFilesWithoutWidgets();
140 void trailingDotsAndSpaces();
141#ifdef Q_OS_UNIX
142#ifdef QT_BUILD_INTERNAL
143 void tildeExpansion_data();
144 void tildeExpansion();
145#endif // QT_BUILD_INTERNAL
146#endif
147 void rejectModalDialogs();
148 void QTBUG49600_nativeIconProviderCrash();
149 void focusObjectDuringDestruction();
150
151 // NOTE: Please keep widgetlessNativeDialog() as the LAST test!
152 //
153 // widgetlessNativeDialog() is the only test function that creates
154 // a native file dialog instance. GTK+ versions prior 3.15.5 have
155 // a nasty bug (https://bugzilla.gnome.org/show_bug.cgi?id=725164)
156 // in GtkFileChooserWidget, which makes it leak its folder change
157 // callback, causing a crash "at some point later". Running the
158 // native test last is enough to avoid spinning the event loop after
159 // the test, and that way circumvent the crash.
160 //
161 // The crash has been fixed in GTK+ 3.15.5, but the RHEL 7.2 CI has
162 // GTK+ 3.14.13 installed (QTBUG-55276).
163 void widgetlessNativeDialog();
164
165private:
166 void cleanupSettingsFile();
167};
168
169void tst_QFiledialog::cleanupSettingsFile()
170{
171 // clean up the sidebar between each test
172 QSettings settings(QSettings::UserScope, QLatin1String("QtProject"));
173 settings.beginGroup(prefix: QLatin1String("FileDialog"));
174 settings.remove(key: QString());
175 settings.endGroup();
176 settings.beginGroup(prefix: QLatin1String("Qt")); // Compatibility settings
177 settings.remove(key: QLatin1String("filedialog"));
178 settings.endGroup();
179}
180
181void tst_QFiledialog::initTestCase()
182{
183 QStandardPaths::setTestModeEnabled(true);
184 cleanupSettingsFile();
185}
186
187void tst_QFiledialog::init()
188{
189 // all tests, except widgetlessNativeDialog, use non-native dialogs
190 QCoreApplication::setAttribute(attribute: Qt::AA_DontUseNativeDialogs);
191 QFileDialogPrivate::setLastVisitedDirectory(QUrl());
192 // populate the sidebar with some default settings
193 QFileDialog fd;
194}
195
196void tst_QFiledialog::cleanup()
197{
198 cleanupSettingsFile();
199}
200
201class MyAbstractItemDelegate : public QAbstractItemDelegate
202{
203public:
204 MyAbstractItemDelegate() : QAbstractItemDelegate() {};
205 void paint(QPainter *, const QStyleOptionViewItem &, const QModelIndex &) const {}
206 QSize sizeHint(const QStyleOptionViewItem &, const QModelIndex &) const { return QSize(); }
207};
208
209// emitted any time the selection model emits current changed
210void tst_QFiledialog::currentChangedSignal()
211{
212 QFileDialog fd;
213 fd.setViewMode(QFileDialog::List);
214 QSignalSpy spyCurrentChanged(&fd, SIGNAL(currentChanged(QString)));
215
216 QListView* listView = fd.findChild<QListView*>(aName: "listView");
217 QVERIFY(listView);
218 fd.setDirectory(QDir::root());
219 QModelIndex root = listView->rootIndex();
220 QTRY_COMPARE(listView->model()->rowCount(root) > 0, true);
221
222 QModelIndex folder;
223 for (int i = 0; i < listView->model()->rowCount(parent: root); ++i) {
224 folder = listView->model()->index(row: i, column: 0, parent: root);
225 if (listView->model()->hasChildren(parent: folder))
226 break;
227 }
228 QVERIFY(listView->model()->hasChildren(folder));
229 listView->setCurrentIndex(folder);
230
231 QCOMPARE(spyCurrentChanged.count(), 1);
232}
233
234// only emitted from the views, sidebar, or lookin combo
235#if defined QT_BUILD_INTERNAL
236void tst_QFiledialog::directoryEnteredSignal()
237{
238 QFileDialog fd(0, "", QDir::root().path());
239 QSidebar *sidebar = fd.findChild<QSidebar*>(aName: "sidebar");
240 QVERIFY(sidebar);
241 if (sidebar->model()->rowCount() < 2)
242 QSKIP("This test requires at least 2 side bar entries.");
243
244 fd.show();
245 QTRY_COMPARE(fd.isVisible(), true);
246 QSignalSpy spyDirectoryEntered(&fd, SIGNAL(directoryEntered(QString)));
247
248 // sidebar
249 QModelIndex secondItem = sidebar->model()->index(row: 1, column: 0);
250 QVERIFY(secondItem.isValid());
251 sidebar->setCurrentIndex(secondItem);
252 QTest::keyPress(widget: sidebar->viewport(), key: Qt::Key_Return);
253 QCOMPARE(spyDirectoryEntered.count(), 1);
254 spyDirectoryEntered.clear();
255
256 // lookInCombo
257 QComboBox *comboBox = fd.findChild<QComboBox*>(aName: "lookInCombo");
258 comboBox->showPopup();
259 QVERIFY(comboBox->view()->model()->index(1, 0).isValid());
260 comboBox->view()->setCurrentIndex(comboBox->view()->model()->index(row: 1, column: 0));
261 QTest::keyPress(widget: comboBox->view()->viewport(), key: Qt::Key_Return);
262 QCOMPARE(spyDirectoryEntered.count(), 1);
263 spyDirectoryEntered.clear();
264
265 // view
266 /*
267 // platform specific
268 fd.setViewMode(QFileDialog::ViewMode(QFileDialog::List));
269 QListView* listView = fd.findChild<QListView*>("listView");
270 QVERIFY(listView);
271 QModelIndex root = listView->rootIndex();
272 QTRY_COMPARE(listView->model()->rowCount(root) > 0, true);
273
274 QModelIndex folder;
275 for (int i = 0; i < listView->model()->rowCount(root); ++i) {
276 folder = listView->model()->index(i, 0, root);
277 if (listView->model()->hasChildren(folder))
278 break;
279 }
280 QVERIFY(listView->model()->hasChildren(folder));
281 listView->setCurrentIndex(folder);
282 QTRY_COMPARE((listView->indexAt(listView->visualRect(folder).center())), folder);
283 QTest::mouseDClick(listView->viewport(), Qt::LeftButton, 0, listView->visualRect(folder).center());
284 QTRY_COMPARE(spyDirectoryEntered.count(), 1);
285 */
286}
287#endif
288
289Q_DECLARE_METATYPE(QFileDialog::FileMode)
290void tst_QFiledialog::filesSelectedSignal_data()
291{
292 QTest::addColumn<QFileDialog::FileMode>(name: "fileMode");
293 QTest::newRow(dataTag: "any") << QFileDialog::AnyFile;
294 QTest::newRow(dataTag: "existing") << QFileDialog::ExistingFile;
295 QTest::newRow(dataTag: "directory") << QFileDialog::Directory;
296 QTest::newRow(dataTag: "directoryOnly") << QFileDialog::DirectoryOnly;
297 QTest::newRow(dataTag: "existingFiles") << QFileDialog::ExistingFiles;
298}
299
300// emitted when the dialog closes with the selected files
301void tst_QFiledialog::filesSelectedSignal()
302{
303 QFileDialog fd;
304 fd.setViewMode(QFileDialog::List);
305 QDir testDir(SRCDIR);
306 fd.setDirectory(testDir);
307 QFETCH(QFileDialog::FileMode, fileMode);
308 fd.setFileMode(fileMode);
309 QSignalSpy spyFilesSelected(&fd, SIGNAL(filesSelected(QStringList)));
310
311 fd.show();
312 QVERIFY(QTest::qWaitForWindowExposed(&fd));
313 QListView *listView = fd.findChild<QListView*>(aName: "listView");
314 QVERIFY(listView);
315
316 QModelIndex root = listView->rootIndex();
317 QTRY_COMPARE(listView->model()->rowCount(root) > 0, true);
318 QModelIndex file;
319 for (int i = 0; i < listView->model()->rowCount(parent: root); ++i) {
320 file = listView->model()->index(row: i, column: 0, parent: root);
321 if (fileMode == QFileDialog::Directory || fileMode == QFileDialog::DirectoryOnly) {
322 if (listView->model()->hasChildren(parent: file))
323 break;
324 } else {
325 if (!listView->model()->hasChildren(parent: file))
326 break;
327 }
328 file = QModelIndex();
329 }
330 QVERIFY(file.isValid());
331 listView->selectionModel()->select(index: file, command: QItemSelectionModel::Select | QItemSelectionModel::Rows);
332 listView->setCurrentIndex(file);
333
334 QDialogButtonBox *buttonBox = fd.findChild<QDialogButtonBox*>(aName: "buttonBox");
335 QPushButton *button = buttonBox->button(which: QDialogButtonBox::Open);
336 QVERIFY(button);
337 QVERIFY(button->isEnabled());
338 button->animateClick();
339 QTRY_COMPARE(fd.isVisible(), false);
340 QCOMPARE(spyFilesSelected.count(), 1);
341}
342
343// only emitted when the combo box is activated
344void tst_QFiledialog::filterSelectedSignal()
345{
346 QFileDialog fd;
347 fd.setAcceptMode(QFileDialog::AcceptSave);
348 fd.show();
349 QSignalSpy spyFilterSelected(&fd, SIGNAL(filterSelected(QString)));
350
351 QStringList filterChoices;
352 filterChoices << "Image files (*.png *.xpm *.jpg)"
353 << "Text files (*.txt)"
354 << "Any files (*.*)";
355 fd.setNameFilters(filterChoices);
356 QCOMPARE(fd.nameFilters(), filterChoices);
357
358 QComboBox *filters = fd.findChild<QComboBox*>(aName: "fileTypeCombo");
359 QVERIFY(filters);
360 QVERIFY(filters->view());
361 QCOMPARE(filters->isVisible(), true);
362
363 QTest::keyPress(widget: filters, key: Qt::Key_Down);
364
365 QCOMPARE(spyFilterSelected.count(), 1);
366}
367
368void tst_QFiledialog::args()
369{
370 QWidget *parent = 0;
371 QString caption = "caption";
372 QString directory = QDir::tempPath();
373 QString filter = "*.mp3";
374 QFileDialog fd(parent, caption, directory, filter);
375 QCOMPARE(fd.parent(), (QObject *)parent);
376 QCOMPARE(fd.windowTitle(), caption);
377#ifndef Q_OS_WIN
378 QCOMPARE(fd.directory(), QDir(directory));
379#endif
380 QCOMPARE(fd.nameFilters(), QStringList(filter));
381}
382
383void tst_QFiledialog::directory()
384{
385 QFileDialog fd;
386 fd.setViewMode(QFileDialog::List);
387 QFileSystemModel *model = fd.findChild<QFileSystemModel*>(aName: "qt_filesystem_model");
388 QVERIFY(model);
389 fd.setDirectory(QDir::currentPath());
390 QSignalSpy spyCurrentChanged(&fd, SIGNAL(currentChanged(QString)));
391 QSignalSpy spyDirectoryEntered(&fd, SIGNAL(directoryEntered(QString)));
392 QSignalSpy spyFilesSelected(&fd, SIGNAL(filesSelected(QStringList)));
393 QSignalSpy spyFilterSelected(&fd, SIGNAL(filterSelected(QString)));
394
395 QCOMPARE(QDir::current().absolutePath(), fd.directory().absolutePath());
396 QDir temp = QDir::temp();
397 QString tempPath = temp.absolutePath();
398#ifdef Q_OS_WIN
399 // since the user can have lowercase temp dir, check that we are actually case-insensitive.
400 tempPath = tempPath.toLower();
401#endif
402 fd.setDirectory(tempPath);
403#ifndef Q_OS_WIN
404 QCOMPARE(tempPath, fd.directory().absolutePath());
405#endif
406 QCOMPARE(spyCurrentChanged.count(), 0);
407 QCOMPARE(spyDirectoryEntered.count(), 0);
408 QCOMPARE(spyFilesSelected.count(), 0);
409 QCOMPARE(spyFilterSelected.count(), 0);
410
411 // Check my way
412 QList<QListView*> list = fd.findChildren<QListView*>(aName: "listView");
413 QVERIFY(list.count() > 0);
414#ifdef Q_OS_WIN
415 QCOMPARE(list.at(0)->rootIndex().data().toString().toLower(), temp.dirName().toLower());
416#else
417 QCOMPARE(list.at(0)->rootIndex().data().toString(), temp.dirName());
418#endif
419 QFileDialog *dlg = new QFileDialog(0, "", tempPath);
420 QCOMPARE(model->index(tempPath), model->index(dlg->directory().absolutePath()));
421 QCOMPARE(model->index(tempPath).data(QFileSystemModel::FileNameRole).toString(),
422 model->index(dlg->directory().absolutePath()).data(QFileSystemModel::FileNameRole).toString());
423 delete dlg;
424 dlg = new QFileDialog();
425 QCOMPARE(model->index(tempPath), model->index(dlg->directory().absolutePath()));
426 delete dlg;
427}
428
429void tst_QFiledialog::completer_data()
430{
431 QTest::addColumn<QString>(name: "startPath");
432 QTest::addColumn<QString>(name: "input");
433 QTest::addColumn<int>(name: "expected");
434
435 const QString rootPath = QDir::rootPath();
436
437 QTest::newRow(dataTag: "r, 10") << QString() << "r" << 10;
438 QTest::newRow(dataTag: "x, 0") << QString() << "x" << 0;
439 QTest::newRow(dataTag: "../, -1") << QString() << "../" << -1;
440
441 QTest::newRow(dataTag: "goto root") << QString() << rootPath << -1;
442 QTest::newRow(dataTag: "start at root") << rootPath << QString() << -1;
443
444 QFileInfoList list = QDir::root().entryInfoList(filters: QDir::Dirs | QDir::NoDotAndDotDot);
445 QVERIFY(!list.isEmpty());
446 const QString folder = list.first().absoluteFilePath();
447 QTest::newRow(dataTag: "start at one below root r") << folder << "r" << -1;
448 QTest::newRow(dataTag: "start at one below root ../") << folder << "../" << -1;
449}
450
451void tst_QFiledialog::completer()
452{
453 typedef QSharedPointer<QTemporaryFile> TemporaryFilePtr;
454
455#ifdef Q_OS_WIN
456 static const Qt::CaseSensitivity caseSensitivity = Qt::CaseInsensitive;
457#else
458 static const Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive;
459#endif
460
461 QFETCH(QString, input);
462 QFETCH(QString, startPath);
463 QFETCH(int, expected);
464
465 // make temp dir and files
466 QScopedPointer<QTemporaryDir> tempDir;
467 QList<TemporaryFilePtr> files;
468
469 if (startPath.isEmpty()) {
470 tempDir.reset(other: new QTemporaryDir);
471 QVERIFY2(tempDir->isValid(), qPrintable(tempDir->errorString()));
472 startPath = tempDir->path();
473 for (int i = 0; i < 10; ++i) {
474 TemporaryFilePtr file(new QTemporaryFile(startPath + QStringLiteral("/rXXXXXX")));
475 QVERIFY2(file->open(), qPrintable(file->errorString()));
476 // Force the temporary file to materialize with the requested name
477 (void) file->fileName();
478 QVERIFY(file->exists());
479 files.append(t: file);
480 }
481 }
482
483 // ### flesh this out more
484 QFileDialog fd(0, QLatin1String(QTest::currentTestFunction())
485 + QStringLiteral(" \"") + QLatin1String(QTest::currentDataTag())
486 + QLatin1Char('"'), startPath);
487 fd.show();
488 QVERIFY(QTest::qWaitForWindowExposed(&fd));
489 QVERIFY(fd.isVisible());
490 QFileSystemModel *model = fd.findChild<QFileSystemModel*>(aName: "qt_filesystem_model");
491 QVERIFY(model);
492 QLineEdit *lineEdit = fd.findChild<QLineEdit*>(aName: "fileNameEdit");
493 QVERIFY(lineEdit);
494 QCompleter *completer = lineEdit->completer();
495 QVERIFY(completer);
496 QAbstractItemModel *cModel = completer->completionModel();
497 QVERIFY(cModel);
498
499 // path C:\depot\qt\examples\dialogs\standarddialogs
500 // files
501 // [debug] [release] [tmp] dialog dialog main makefile makefile.debug makefile.release standarddialgos
502 //
503 // d -> D:\ debug dialog.cpp dialog.h
504 // ..\ -> ..\classwizard ..\configdialog ..\dialogs.pro
505 // c -> C:\ control panel
506 // c: -> C:\ (nothing more)
507 // C:\ -> C:\, C:\_viminfo, ...
508 // \ -> \_viminfo
509 // c:\depot -> 'nothing'
510 // c:\depot\ -> C:\depot\devtools, C:\depot\dteske
511 QTRY_COMPARE(model->index(fd.directory().path()), model->index(startPath));
512
513 if (input.isEmpty()) {
514 // Try to find a suitable directory under root that does not
515 // start with 'C' to avoid issues with completing to "C:" drives on Windows.
516 const QString rootPath = model->rootPath();
517 const QChar rootPathFirstChar = rootPath.at(i: 0).toLower();
518 QModelIndex rootIndex = model->index(path: rootPath);
519 const int rowCount = model->rowCount(parent: rootIndex);
520 QVERIFY(rowCount > 0);
521 for (int row = 0; row < rowCount && input.isEmpty(); ++row) {
522 const QString name = model->index(row, column: 0, parent: rootIndex).data().toString();
523 const QChar firstChar = name.at(i: 0);
524 if (firstChar.isLetter() && firstChar.toLower() != rootPathFirstChar)
525 input = firstChar;
526 }
527 QVERIFY2(!input.isEmpty(), "Unable to find a suitable input directory");
528 }
529
530 // press 'keys' for the input
531 for (int i = 0; i < input.count(); ++i)
532 QTest::keyPress(widget: lineEdit, key: input[i].toLatin1());
533
534 QStringList expectedFiles;
535 if (expected == -1) {
536 QString fullPath = startPath;
537 if (!fullPath.endsWith(c: QLatin1Char('/')))
538 fullPath.append(c: QLatin1Char('/'));
539 fullPath.append(s: input);
540 if (input.startsWith(s: QDir::rootPath(), cs: caseSensitivity)) {
541 fullPath = input;
542 input.clear();
543 }
544
545 QFileInfo fi(fullPath);
546 QDir x(fi.absolutePath());
547 expectedFiles = x.entryList(filters: model->filter());
548 expected = 0;
549 if (input.startsWith(s: ".."))
550 input.clear();
551 foreach (const QString &expectedFile, expectedFiles) {
552 if (expectedFile.startsWith(s: input, cs: caseSensitivity))
553 ++expected;
554 }
555 // The temporary dir may create a node in QFileSystemModel
556 // which will bypass filters. If the path to the temporary
557 // dir contains an element which should be a subdirectory
558 // of x dir, but which is not listed, then take it into
559 // accont.
560 if (!tempDir.isNull()) {
561 QString xPath = x.absolutePath();
562 if (!xPath.endsWith(c: QLatin1Char('/')))
563 xPath.append(c: QLatin1Char('/'));
564 QString tmpPath = tempDir->path();
565 if (tmpPath.startsWith(s: xPath)) {
566 QString bypassedDirName = tmpPath.mid(position: xPath.size()).section(asep: QLatin1Char('/'), astart: 0, aend: 0);
567 if (!expectedFiles.contains(str: bypassedDirName))
568 ++expected;
569 }
570 }
571 }
572
573 QTRY_COMPARE(cModel->rowCount(), expected);
574}
575
576void tst_QFiledialog::completer_up()
577{
578 QFileDialog fd;
579 QSignalSpy spyCurrentChanged(&fd, SIGNAL(currentChanged(QString)));
580 QSignalSpy spyDirectoryEntered(&fd, SIGNAL(directoryEntered(QString)));
581 QSignalSpy spyFilesSelected(&fd, SIGNAL(filesSelected(QStringList)));
582 QSignalSpy spyFilterSelected(&fd, SIGNAL(filterSelected(QString)));
583
584 fd.show();
585 QLineEdit *lineEdit = fd.findChild<QLineEdit*>(aName: "fileNameEdit");
586 QVERIFY(lineEdit);
587 QCOMPARE(spyFilesSelected.count(), 0);
588 int depth = QDir::currentPath().split(sep: '/').count();
589 for (int i = 0; i <= depth * 3 + 1; ++i) {
590 lineEdit->insert("../");
591 qApp->processEvents();
592 }
593 QCOMPARE(spyCurrentChanged.count(), 0);
594 QCOMPARE(spyDirectoryEntered.count(), 0);
595 QCOMPARE(spyFilesSelected.count(), 0);
596 QCOMPARE(spyFilterSelected.count(), 0);
597}
598
599void tst_QFiledialog::acceptMode()
600{
601 QFileDialog fd;
602 fd.show();
603
604 QToolButton* newButton = fd.findChild<QToolButton*>(aName: "newFolderButton");
605 QVERIFY(newButton);
606
607 // default
608 QCOMPARE(fd.acceptMode(), QFileDialog::AcceptOpen);
609 QCOMPARE(newButton && newButton->isVisible(), true);
610
611 //fd.setDetailsExpanded(true);
612 fd.setAcceptMode(QFileDialog::AcceptSave);
613 QCOMPARE(fd.acceptMode(), QFileDialog::AcceptSave);
614 QCOMPARE(newButton->isVisible(), true);
615
616 fd.setAcceptMode(QFileDialog::AcceptOpen);
617 QCOMPARE(fd.acceptMode(), QFileDialog::AcceptOpen);
618 QCOMPARE(newButton->isVisible(), true);
619}
620
621void tst_QFiledialog::confirmOverwrite()
622{
623 QFileDialog fd;
624 QCOMPARE(fd.testOption(QFileDialog::DontConfirmOverwrite), false);
625 fd.setOption(option: QFileDialog::DontConfirmOverwrite, on: false);
626 QCOMPARE(fd.testOption(QFileDialog::DontConfirmOverwrite), false);
627 fd.setOption(option: QFileDialog::DontConfirmOverwrite, on: true);
628 QCOMPARE(fd.testOption(QFileDialog::DontConfirmOverwrite), true);
629 fd.setOption(option: QFileDialog::DontConfirmOverwrite, on: false);
630 QCOMPARE(fd.testOption(QFileDialog::DontConfirmOverwrite), false);
631}
632
633void tst_QFiledialog::defaultSuffix()
634{
635 QFileDialog fd;
636 QCOMPARE(fd.defaultSuffix(), QString());
637 fd.setDefaultSuffix("txt");
638 QCOMPARE(fd.defaultSuffix(), QString("txt"));
639 fd.setDefaultSuffix(".txt");
640 QCOMPARE(fd.defaultSuffix(), QString("txt"));
641 fd.setDefaultSuffix(QString());
642 QCOMPARE(fd.defaultSuffix(), QString());
643}
644
645void tst_QFiledialog::fileMode()
646{
647 QFileDialog fd;
648 QCOMPARE(fd.fileMode(), QFileDialog::AnyFile);
649 fd.setFileMode(QFileDialog::ExistingFile);
650 QCOMPARE(fd.fileMode(), QFileDialog::ExistingFile);
651 fd.setFileMode(QFileDialog::Directory);
652 QCOMPARE(fd.fileMode(), QFileDialog::Directory);
653 fd.setFileMode(QFileDialog::DirectoryOnly);
654 QCOMPARE(fd.fileMode(), QFileDialog::DirectoryOnly);
655 fd.setFileMode(QFileDialog::ExistingFiles);
656 QCOMPARE(fd.fileMode(), QFileDialog::ExistingFiles);
657}
658
659void tst_QFiledialog::caption()
660{
661 QFileDialog fd;
662 fd.setWindowTitle("testing");
663 fd.setFileMode(QFileDialog::Directory);
664 QCOMPARE(fd.windowTitle(), QString("testing"));
665}
666
667void tst_QFiledialog::filters()
668{
669 QFileDialog fd;
670 QSignalSpy spyCurrentChanged(&fd, SIGNAL(currentChanged(QString)));
671 QSignalSpy spyDirectoryEntered(&fd, SIGNAL(directoryEntered(QString)));
672 QSignalSpy spyFilesSelected(&fd, SIGNAL(filesSelected(QStringList)));
673 QSignalSpy spyFilterSelected(&fd, SIGNAL(filterSelected(QString)));
674 QCOMPARE(fd.nameFilters(), QStringList("All Files (*)"));
675
676 // effects
677 QList<QComboBox*> views = fd.findChildren<QComboBox*>(aName: "fileTypeCombo");
678 QCOMPARE(views.count(), 1);
679 QCOMPARE(views.at(0)->isVisible(), false);
680
681 QStringList filters;
682 filters << "Image files (*.png *.xpm *.jpg)"
683 << "Text files (*.txt)"
684 << "Any files (*.*)";
685 fd.setNameFilters(filters);
686 QCOMPARE(views.at(0)->isVisible(), false);
687 fd.show();
688 fd.setAcceptMode(QFileDialog::AcceptSave);
689 QCOMPARE(views.at(0)->isVisible(), true);
690 QCOMPARE(fd.nameFilters(), filters);
691 fd.setNameFilter("Image files (*.png *.xpm *.jpg);;Text files (*.txt);;Any files (*.*)");
692 QCOMPARE(fd.nameFilters(), filters);
693 QCOMPARE(spyCurrentChanged.count(), 0);
694 QCOMPARE(spyDirectoryEntered.count(), 0);
695 QCOMPARE(spyFilesSelected.count(), 0);
696 QCOMPARE(spyFilterSelected.count(), 0);
697
698 // setting shouldn't emit any signals
699 for (int i = views.at(i: 0)->currentIndex(); i < views.at(i: 0)->count(); ++i)
700 views.at(i: 0)->setCurrentIndex(i);
701 QCOMPARE(spyFilterSelected.count(), 0);
702
703 //Let check if filters with whitespaces
704 QFileDialog fd2;
705 QStringList expected;
706 expected << "C++ Source Files(*.cpp)";
707 expected << "Any(*.*)";
708 fd2.setNameFilter("C++ Source Files(*.cpp);;Any(*.*)");
709 QCOMPARE(expected, fd2.nameFilters());
710 fd2.setNameFilter("C++ Source Files(*.cpp) ;;Any(*.*)");
711 QCOMPARE(expected, fd2.nameFilters());
712 fd2.setNameFilter("C++ Source Files(*.cpp);; Any(*.*)");
713 QCOMPARE(expected, fd2.nameFilters());
714 fd2.setNameFilter(" C++ Source Files(*.cpp);; Any(*.*)");
715 QCOMPARE(expected, fd2.nameFilters());
716 fd2.setNameFilter("C++ Source Files(*.cpp) ;; Any(*.*)");
717 QCOMPARE(expected, fd2.nameFilters());
718}
719
720void tst_QFiledialog::selectFilter()
721{
722 QFileDialog fd;
723 QSignalSpy spyFilterSelected(&fd, SIGNAL(filterSelected(QString)));
724 QCOMPARE(fd.selectedNameFilter(), QString("All Files (*)"));
725 QStringList filters;
726 filters << "Image files (*.png *.xpm *.jpg)"
727 << "Text files (*.txt)"
728 << "Any files (*.*)";
729 fd.setNameFilters(filters);
730 QCOMPARE(fd.selectedNameFilter(), filters.at(0));
731 fd.selectNameFilter(filter: filters.at(i: 1));
732 QCOMPARE(fd.selectedNameFilter(), filters.at(1));
733 fd.selectNameFilter(filter: filters.at(i: 2));
734 QCOMPARE(fd.selectedNameFilter(), filters.at(2));
735
736 fd.selectNameFilter(filter: "bob");
737 QCOMPARE(fd.selectedNameFilter(), filters.at(2));
738 fd.selectNameFilter(filter: "");
739 QCOMPARE(fd.selectedNameFilter(), filters.at(2));
740 QCOMPARE(spyFilterSelected.count(), 0);
741}
742
743void tst_QFiledialog::history()
744{
745 QFileDialog fd;
746 fd.setViewMode(QFileDialog::List);
747 QFileSystemModel *model = fd.findChild<QFileSystemModel*>(aName: "qt_filesystem_model");
748 QVERIFY(model);
749 QSignalSpy spyCurrentChanged(&fd, SIGNAL(currentChanged(QString)));
750 QSignalSpy spyDirectoryEntered(&fd, SIGNAL(directoryEntered(QString)));
751 QSignalSpy spyFilesSelected(&fd, SIGNAL(filesSelected(QStringList)));
752 QSignalSpy spyFilterSelected(&fd, SIGNAL(filterSelected(QString)));
753 QCOMPARE(model->index(fd.history().first()), model->index(QDir::toNativeSeparators(fd.directory().absolutePath())));
754 fd.setDirectory(QDir::current().absolutePath());
755 QStringList history;
756 history << QDir::toNativeSeparators(pathName: QDir::current().absolutePath())
757 << QDir::toNativeSeparators(pathName: QDir::home().absolutePath())
758 << QDir::toNativeSeparators(pathName: QDir::temp().absolutePath());
759 fd.setHistory(history);
760 if (fd.history() != history) {
761 qDebug() << fd.history() << history;
762 // quick and dirty output for windows failure.
763 QListView* list = fd.findChild<QListView*>(aName: "listView");
764 QVERIFY(list);
765 QModelIndex root = list->rootIndex();
766 while (root.isValid()) {
767 qDebug() << root.data();
768 root = root.parent();
769 }
770 }
771 QCOMPARE(fd.history(), history);
772
773 QStringList badHistory;
774 badHistory << "junk";
775 fd.setHistory(badHistory);
776 badHistory << QDir::toNativeSeparators(pathName: QDir::current().absolutePath());
777 QCOMPARE(fd.history(), badHistory);
778
779 QCOMPARE(spyCurrentChanged.count(), 0);
780 QCOMPARE(spyDirectoryEntered.count(), 0);
781 QCOMPARE(spyFilesSelected.count(), 0);
782 QCOMPARE(spyFilterSelected.count(), 0);
783}
784
785void tst_QFiledialog::iconProvider()
786{
787 QFileDialog *fd = new QFileDialog();
788 QVERIFY(fd->iconProvider() != 0);
789 QFileIconProvider *ip = new QFileIconProvider();
790 fd->setIconProvider(ip);
791 QCOMPARE(fd->iconProvider(), ip);
792 delete fd;
793 delete ip;
794}
795
796void tst_QFiledialog::isReadOnly()
797{
798 QFileDialog fd;
799
800 QPushButton* newButton = fd.findChild<QPushButton*>(aName: "newFolderButton");
801 QAction* renameAction = fd.findChild<QAction*>(aName: "qt_rename_action");
802 QAction* deleteAction = fd.findChild<QAction*>(aName: "qt_delete_action");
803
804#if QT_DEPRECATED_SINCE(5, 13)
805 QCOMPARE(fd.isReadOnly(), false);
806#endif
807 QCOMPARE(fd.testOption(QFileDialog::ReadOnly), false);
808
809 // This is dependent upon the file/dir, find cross platform way to test
810 //fd.setDirectory(QDir::home());
811 //QCOMPARE(newButton && newButton->isEnabled(), true);
812 //QCOMPARE(renameAction && renameAction->isEnabled(), true);
813 //QCOMPARE(deleteAction && deleteAction->isEnabled(), true);
814
815 fd.setOption(option: QFileDialog::ReadOnly, on: true);
816 QCOMPARE(fd.testOption(QFileDialog::ReadOnly), true);
817
818 QCOMPARE(newButton && newButton->isEnabled(), false);
819 QCOMPARE(renameAction && renameAction->isEnabled(), false);
820 QCOMPARE(deleteAction && deleteAction->isEnabled(), false);
821}
822
823void tst_QFiledialog::itemDelegate()
824{
825 QFileDialog fd;
826 QVERIFY(fd.itemDelegate() != 0);
827 QItemDelegate *id = new QItemDelegate(&fd);
828 fd.setItemDelegate(id);
829 QCOMPARE(fd.itemDelegate(), (QAbstractItemDelegate *)id);
830}
831
832void tst_QFiledialog::labelText()
833{
834 QFileDialog fd;
835 QDialogButtonBox buttonBox;
836 QPushButton *cancelButton = buttonBox.addButton(button: QDialogButtonBox::Cancel);
837 QCOMPARE(fd.labelText(QFileDialog::LookIn), QString("Look in:"));
838 QCOMPARE(fd.labelText(QFileDialog::FileName), QString("File &name:"));
839 QCOMPARE(fd.labelText(QFileDialog::FileType), QString("Files of type:"));
840 QCOMPARE(fd.labelText(QFileDialog::Accept), QString("&Open")); ///### see task 241462
841 QCOMPARE(fd.labelText(QFileDialog::Reject), cancelButton->text());
842
843 fd.setLabelText(label: QFileDialog::LookIn, text: "1");
844 QCOMPARE(fd.labelText(QFileDialog::LookIn), QString("1"));
845 fd.setLabelText(label: QFileDialog::FileName, text: "2");
846 QCOMPARE(fd.labelText(QFileDialog::FileName), QString("2"));
847 fd.setLabelText(label: QFileDialog::FileType, text: "3");
848 QCOMPARE(fd.labelText(QFileDialog::FileType), QString("3"));
849 fd.setLabelText(label: QFileDialog::Accept, text: "4");
850 QCOMPARE(fd.labelText(QFileDialog::Accept), QString("4"));
851 fd.setLabelText(label: QFileDialog::Reject, text: "5");
852 QCOMPARE(fd.labelText(QFileDialog::Reject), QString("5"));
853}
854
855void tst_QFiledialog::resolveSymlinks()
856{
857 QFileDialog fd;
858
859 // default
860 QCOMPARE(fd.testOption(QFileDialog::DontResolveSymlinks), false);
861 fd.setOption(option: QFileDialog::DontResolveSymlinks, on: true);
862 QCOMPARE(fd.testOption(QFileDialog::DontResolveSymlinks), true);
863 fd.setOption(option: QFileDialog::DontResolveSymlinks, on: false);
864 QCOMPARE(fd.testOption(QFileDialog::DontResolveSymlinks), false);
865
866 // the file dialog doesn't do anything based upon this, just passes it to the model
867 // the model should fully test it, don't test it here
868}
869
870void tst_QFiledialog::selectFile_data()
871{
872 QTest::addColumn<QString>(name: "file");
873 QTest::addColumn<int>(name: "count");
874 QTest::newRow(dataTag: "null") << QString() << 1;
875 QTest::newRow(dataTag: "file") << "foo" << 1;
876 QTest::newRow(dataTag: "tmp") << "temp" << 1;
877}
878
879void tst_QFiledialog::selectFile()
880{
881 QFETCH(QString, file);
882 QFETCH(int, count);
883 QScopedPointer<QFileDialog> fd(new QFileDialog);
884 QFileSystemModel *model = fd->findChild<QFileSystemModel*>(aName: "qt_filesystem_model");
885 QVERIFY(model);
886 fd->setDirectory(QDir::currentPath());
887 // default value
888 QCOMPARE(fd->selectedFiles().count(), 1);
889
890 QScopedPointer<QTemporaryFile> tempFile;
891 if (file == QLatin1String("temp")) {
892 tempFile.reset(other: new QTemporaryFile(QDir::tempPath() + QStringLiteral("/aXXXXXX")));
893 QVERIFY2(tempFile->open(), qPrintable(tempFile->errorString()));
894 file = tempFile->fileName();
895 }
896
897 fd->selectFile(filename: file);
898 QCOMPARE(fd->selectedFiles().count(), count);
899 if (tempFile.isNull()) {
900 QCOMPARE(model->index(fd->directory().path()), model->index(QDir::currentPath()));
901 } else {
902 QCOMPARE(model->index(fd->directory().path()), model->index(QDir::tempPath()));
903 }
904 fd.reset(); // Ensure the file dialog let's go of the temporary file for "temp".
905}
906
907void tst_QFiledialog::selectFileWrongCaseSaveAs()
908{
909 const QString home = QDir::homePath();
910 if (isCaseSensitiveFileSystem(path: home))
911 QSKIP("This test is intended for case-insensitive file systems only.");
912 // QTBUG-38162: when passing a wrongly capitalized path to selectFile()
913 // on a case-insensitive file system, the line edit should only
914 // contain the file name ("c:\PRogram files\foo.txt" -> "foo.txt").
915 const QString fileName = QStringLiteral("foo.txt");
916 const QString path = home + QLatin1Char('/') + fileName;
917 QString wrongCasePath = path;
918 for (int c = 0; c < wrongCasePath.size(); c += 2)
919 wrongCasePath[c] = wrongCasePath.at(i: c).isLower() ? wrongCasePath.at(i: c).toUpper() : wrongCasePath.at(i: c).toLower();
920 QFileDialog fd(0, "QTBUG-38162", wrongCasePath);
921 fd.setAcceptMode(QFileDialog::AcceptSave);
922 fd.selectFile(filename: wrongCasePath);
923 const QLineEdit *lineEdit = fd.findChild<QLineEdit*>(aName: "fileNameEdit");
924 QVERIFY(lineEdit);
925 QCOMPARE(lineEdit->text().compare(fileName, Qt::CaseInsensitive), 0);
926}
927
928void tst_QFiledialog::selectFiles()
929{
930 QTemporaryDir tempDir;
931 QVERIFY2(tempDir.isValid(), qPrintable(tempDir.errorString()));
932 const QString tempPath = tempDir.path();
933 {
934 QFileDialog fd;
935 fd.setViewMode(QFileDialog::List);
936 fd.setDirectory(tempPath);
937 QSignalSpy spyCurrentChanged(&fd, SIGNAL(currentChanged(QString)));
938 QSignalSpy spyDirectoryEntered(&fd, SIGNAL(directoryEntered(QString)));
939 QSignalSpy spyFilesSelected(&fd, SIGNAL(filesSelected(QStringList)));
940 QSignalSpy spyFilterSelected(&fd, SIGNAL(filterSelected(QString)));
941 fd.setFileMode(QFileDialog::ExistingFiles);
942
943 QString filesPath = fd.directory().absolutePath();
944 for (int i=0; i < 5; ++i) {
945 QFile file(filesPath + QLatin1String("/qfiledialog_auto_test_not_pres_") + QString::number(i));
946 file.open(flags: QIODevice::WriteOnly);
947 file.resize(sz: 1024);
948 file.flush();
949 file.close();
950 }
951
952 // Get a list of files in the view and then get the corresponding index's
953 QStringList list = fd.directory().entryList(filters: QDir::Files);
954 QModelIndexList toSelect;
955 QVERIFY(list.count() > 1);
956 QListView* listView = fd.findChild<QListView*>(aName: "listView");
957 QVERIFY(listView);
958 for (int i = 0; i < list.count(); ++i) {
959 fd.selectFile(filename: fd.directory().path() + QLatin1Char('/') + list.at(i));
960 QTRY_VERIFY(!listView->selectionModel()->selectedRows().isEmpty());
961 toSelect.append(t: listView->selectionModel()->selectedRows().last());
962 }
963 QCOMPARE(spyFilesSelected.count(), 0);
964
965 listView->selectionModel()->clear();
966 QCOMPARE(spyFilesSelected.count(), 0);
967
968 // select the indexes
969 for (int i = 0; i < toSelect.count(); ++i) {
970 listView->selectionModel()->select(index: toSelect.at(i),
971 command: QItemSelectionModel::Select | QItemSelectionModel::Rows);
972 }
973 QCOMPARE(fd.selectedFiles().count(), toSelect.count());
974 QCOMPARE(spyCurrentChanged.count(), 0);
975 QCOMPARE(spyDirectoryEntered.count(), 0);
976 QCOMPARE(spyFilesSelected.count(), 0);
977 QCOMPARE(spyFilterSelected.count(), 0);
978
979 }
980
981 {
982 //If the selection is invalid then we fill the line edit but without the /
983 QFileDialog dialog( 0, "Save" );
984 dialog.setFileMode( QFileDialog::AnyFile );
985 dialog.setAcceptMode( QFileDialog::AcceptSave );
986 dialog.selectFile(filename: tempPath + QStringLiteral("/blah"));
987 dialog.show();
988 QVERIFY(QTest::qWaitForWindowExposed(&dialog));
989 QLineEdit *lineEdit = dialog.findChild<QLineEdit*>(aName: "fileNameEdit");
990 QVERIFY(lineEdit);
991 QCOMPARE(lineEdit->text(),QLatin1String("blah"));
992 }
993}
994
995void tst_QFiledialog::viewMode()
996{
997 QFileDialog fd;
998 fd.setViewMode(QFileDialog::List);
999 fd.show();
1000
1001 // find widgets
1002 QList<QTreeView*> treeView = fd.findChildren<QTreeView*>(aName: "treeView");
1003 QCOMPARE(treeView.count(), 1);
1004 QList<QListView*> listView = fd.findChildren<QListView*>(aName: "listView");
1005 QCOMPARE(listView.count(), 1);
1006 QList<QToolButton*> listButton = fd.findChildren<QToolButton*>(aName: "listModeButton");
1007 QCOMPARE(listButton.count(), 1);
1008 QList<QToolButton*> treeButton = fd.findChildren<QToolButton*>(aName: "detailModeButton");
1009 QCOMPARE(treeButton.count(), 1);
1010
1011 // default value
1012 QCOMPARE(fd.viewMode(), QFileDialog::List);
1013
1014 // detail
1015 fd.setViewMode(QFileDialog::ViewMode(QFileDialog::Detail));
1016
1017 QCOMPARE(QFileDialog::ViewMode(QFileDialog::Detail), fd.viewMode());
1018 QCOMPARE(listView.at(0)->isVisible(), false);
1019 QCOMPARE(listButton.at(0)->isDown(), false);
1020 QCOMPARE(treeView.at(0)->isVisible(), true);
1021 QCOMPARE(treeButton.at(0)->isDown(), true);
1022
1023 // list
1024 fd.setViewMode(QFileDialog::ViewMode(QFileDialog::List));
1025
1026 QCOMPARE(QFileDialog::ViewMode(QFileDialog::List), fd.viewMode());
1027 QCOMPARE(treeView.at(0)->isVisible(), false);
1028 QCOMPARE(treeButton.at(0)->isDown(), false);
1029 QCOMPARE(listView.at(0)->isVisible(), true);
1030 QCOMPARE(listButton.at(0)->isDown(), true);
1031}
1032
1033void tst_QFiledialog::proxymodel()
1034{
1035 QFileDialog fd;
1036 QCOMPARE(fd.proxyModel(), nullptr);
1037
1038 fd.setProxyModel(0);
1039 QCOMPARE(fd.proxyModel(), nullptr);
1040
1041 QSortFilterProxyModel *proxyModel = new QSortFilterProxyModel(&fd);
1042 fd.setProxyModel(proxyModel);
1043 QCOMPARE(fd.proxyModel(), (QAbstractProxyModel *)proxyModel);
1044
1045 fd.setProxyModel(0);
1046 QCOMPARE(fd.proxyModel(), nullptr);
1047}
1048
1049void tst_QFiledialog::setMimeTypeFilters_data()
1050{
1051 QTest::addColumn<QStringList>(name: "mimeTypeFilters");
1052 QTest::addColumn<QString>(name: "targetMimeTypeFilter");
1053 QTest::addColumn<QString>(name: "expectedSelectedMimeTypeFilter");
1054
1055 const auto headerMime = QStringLiteral("text/x-chdr");
1056 const auto pdfMime = QStringLiteral("application/pdf");
1057 const auto zipMime = QStringLiteral("application/zip");
1058
1059 QTest::newRow(dataTag: "single mime filter (C header file)") << QStringList {headerMime} << headerMime << headerMime;
1060 QTest::newRow(dataTag: "single mime filter (JSON file)") << QStringList {pdfMime} << pdfMime << pdfMime;
1061 QTest::newRow(dataTag: "multiple mime filters") << QStringList {pdfMime, zipMime} << pdfMime << pdfMime;
1062}
1063
1064void tst_QFiledialog::setMimeTypeFilters()
1065{
1066 QFETCH(QStringList, mimeTypeFilters);
1067 QFETCH(QString, targetMimeTypeFilter);
1068 QFETCH(QString, expectedSelectedMimeTypeFilter);
1069
1070 QFileDialog fd;
1071 fd.setMimeTypeFilters(mimeTypeFilters);
1072 fd.selectMimeTypeFilter(filter: targetMimeTypeFilter);
1073
1074 QCOMPARE(fd.selectedMimeTypeFilter(), expectedSelectedMimeTypeFilter);
1075
1076 auto *filters = fd.findChild<QComboBox*>(aName: "fileTypeCombo");
1077 filters->setCurrentIndex(filters->count() - 1);
1078 QCOMPARE(fd.selectedMimeTypeFilter(), mimeTypeFilters.last());
1079}
1080
1081void tst_QFiledialog::setEmptyNameFilter()
1082{
1083 QFileDialog fd;
1084 fd.setNameFilter(QString());
1085 fd.setNameFilters(QStringList());
1086}
1087
1088void tst_QFiledialog::setNameFilter_data()
1089{
1090 QTest::addColumn<bool>(name: "nameFilterDetailsVisible");
1091 QTest::addColumn<QStringList>(name: "filters");
1092 QTest::addColumn<QString>(name: "selectFilter");
1093 QTest::addColumn<QString>(name: "expectedSelectedFilter");
1094
1095 QTest::newRow(dataTag: "namedetailsvisible-empty") << true << QStringList() << QString() << QString();
1096 QTest::newRow(dataTag: "namedetailsinvisible-empty") << false << QStringList() << QString() << QString();
1097
1098 const QString anyFileNoDetails = QLatin1String("Any files");
1099 const QString anyFile = anyFileNoDetails + QLatin1String(" (*)");
1100 const QString imageFilesNoDetails = QLatin1String("Image files");
1101 const QString imageFiles = imageFilesNoDetails + QLatin1String(" (*.png *.xpm *.jpg)");
1102 const QString textFileNoDetails = QLatin1String("Text files");
1103 const QString textFile = textFileNoDetails + QLatin1String(" (*.txt)");
1104
1105 QStringList filters;
1106 filters << anyFile << imageFiles << textFile;
1107
1108 QTest::newRow(dataTag: "namedetailsvisible-images") << true << filters << imageFiles << imageFiles;
1109 QTest::newRow(dataTag: "namedetailsinvisible-images") << false << filters << imageFiles << imageFilesNoDetails;
1110
1111 const QString invalid = "foo";
1112 QTest::newRow(dataTag: "namedetailsvisible-invalid") << true << filters << invalid << anyFile;
1113 // Potential crash when trying to convert the invalid filter into a list and stripping it, resulting in an empty list.
1114 QTest::newRow(dataTag: "namedetailsinvisible-invalid") << false << filters << invalid << anyFileNoDetails;
1115}
1116
1117void tst_QFiledialog::setNameFilter()
1118{
1119 QFETCH(bool, nameFilterDetailsVisible);
1120 QFETCH(QStringList, filters);
1121 QFETCH(QString, selectFilter);
1122 QFETCH(QString, expectedSelectedFilter);
1123
1124 QFileDialog fd;
1125 fd.setNameFilters(filters);
1126 fd.setOption(option: QFileDialog::HideNameFilterDetails, on: !nameFilterDetailsVisible);
1127 fd.selectNameFilter(filter: selectFilter);
1128 QCOMPARE(fd.selectedNameFilter(), expectedSelectedFilter);
1129}
1130
1131void tst_QFiledialog::focus()
1132{
1133 if (!QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::WindowActivation))
1134 QSKIP("Window activation is not supported");
1135 QFileDialog fd;
1136 fd.setDirectory(QDir::currentPath());
1137 fd.show();
1138 QApplication::setActiveWindow(&fd);
1139 QVERIFY(QTest::qWaitForWindowActive(&fd));
1140 QCOMPARE(fd.isVisible(), true);
1141 QCOMPARE(QApplication::activeWindow(), static_cast<QWidget*>(&fd));
1142 qApp->processEvents();
1143
1144 // make sure the tests work with focus follows mouse
1145 QCursor::setPos(fd.geometry().center());
1146
1147 QList<QWidget*> treeView = fd.findChildren<QWidget*>(aName: "fileNameEdit");
1148 QCOMPARE(treeView.count(), 1);
1149 QVERIFY(treeView.at(0));
1150 QTRY_COMPARE(treeView.at(0)->hasFocus(), true);
1151 QCOMPARE(treeView.at(0)->hasFocus(), true);
1152}
1153
1154
1155void tst_QFiledialog::historyBack()
1156{
1157 QFileDialog fd;
1158 QFileSystemModel *model = fd.findChild<QFileSystemModel*>(aName: "qt_filesystem_model");
1159 QVERIFY(model);
1160 QToolButton *backButton = fd.findChild<QToolButton*>(aName: "backButton");
1161 QVERIFY(backButton);
1162 QToolButton *forwardButton = fd.findChild<QToolButton*>(aName: "forwardButton");
1163 QVERIFY(forwardButton);
1164
1165 QSignalSpy spy(model, SIGNAL(rootPathChanged(QString)));
1166
1167 QString home = fd.directory().absolutePath();
1168 QString desktop = QDir::homePath();
1169 QString temp = QDir::tempPath();
1170
1171 QCOMPARE(backButton->isEnabled(), false);
1172 QCOMPARE(forwardButton->isEnabled(), false);
1173 fd.setDirectory(temp);
1174 qApp->processEvents();
1175 QCOMPARE(backButton->isEnabled(), true);
1176 QCOMPARE(forwardButton->isEnabled(), false);
1177 fd.setDirectory(desktop);
1178 QCOMPARE(spy.count(), 2);
1179
1180 backButton->click();
1181 qApp->processEvents();
1182 QCOMPARE(backButton->isEnabled(), true);
1183 QCOMPARE(forwardButton->isEnabled(), true);
1184 QCOMPARE(spy.count(), 3);
1185 QString currentPath = qvariant_cast<QString>(v: spy.last().first());
1186 QCOMPARE(model->index(currentPath), model->index(temp));
1187
1188 backButton->click();
1189 currentPath = qvariant_cast<QString>(v: spy.last().first());
1190 QCOMPARE(currentPath, home);
1191 QCOMPARE(backButton->isEnabled(), false);
1192 QCOMPARE(forwardButton->isEnabled(), true);
1193 QCOMPARE(spy.count(), 4);
1194
1195 // nothing should change at this point
1196 backButton->click();
1197 QCOMPARE(spy.count(), 4);
1198 QCOMPARE(backButton->isEnabled(), false);
1199 QCOMPARE(forwardButton->isEnabled(), true);
1200}
1201
1202void tst_QFiledialog::historyForward()
1203{
1204 QFileDialog fd;
1205 fd.setDirectory(QDir::currentPath());
1206 QToolButton *backButton = fd.findChild<QToolButton*>(aName: "backButton");
1207 QVERIFY(backButton);
1208 QToolButton *forwardButton = fd.findChild<QToolButton*>(aName: "forwardButton");
1209 QVERIFY(forwardButton);
1210
1211 QFileSystemModel *model = fd.findChild<QFileSystemModel*>(aName: "qt_filesystem_model");
1212 QVERIFY(model);
1213 QSignalSpy spy(model, SIGNAL(rootPathChanged(QString)));
1214
1215 QString home = fd.directory().absolutePath();
1216 QString desktop = QDir::homePath();
1217 QString temp = QDir::tempPath();
1218
1219 fd.setDirectory(home);
1220 fd.setDirectory(temp);
1221 fd.setDirectory(desktop);
1222
1223 backButton->click();
1224 QCOMPARE(forwardButton->isEnabled(), true);
1225 QCOMPARE(model->index(qvariant_cast<QString>(spy.last().first())), model->index(temp));
1226
1227 forwardButton->click();
1228 QCOMPARE(model->index(qvariant_cast<QString>(spy.last().first())), model->index(desktop));
1229 QCOMPARE(backButton->isEnabled(), true);
1230 QCOMPARE(forwardButton->isEnabled(), false);
1231 QCOMPARE(spy.count(), 4);
1232
1233 backButton->click();
1234 QCOMPARE(model->index(qvariant_cast<QString>(spy.last().first())), model->index(temp));
1235 QCOMPARE(backButton->isEnabled(), true);
1236
1237 backButton->click();
1238 QCOMPARE(model->index(qvariant_cast<QString>(spy.last().first())), model->index(home));
1239 QCOMPARE(backButton->isEnabled(), false);
1240 QCOMPARE(forwardButton->isEnabled(), true);
1241 QCOMPARE(spy.count(), 6);
1242
1243 forwardButton->click();
1244 QCOMPARE(model->index(qvariant_cast<QString>(spy.last().first())), model->index(temp));
1245 backButton->click();
1246 QCOMPARE(model->index(qvariant_cast<QString>(spy.last().first())), model->index(home));
1247 QCOMPARE(spy.count(), 8);
1248
1249 forwardButton->click();
1250 QCOMPARE(model->index(qvariant_cast<QString>(spy.last().first())), model->index(temp));
1251 forwardButton->click();
1252 QCOMPARE(model->index(qvariant_cast<QString>(spy.last().first())), model->index(desktop));
1253
1254 backButton->click();
1255 QCOMPARE(model->index(qvariant_cast<QString>(spy.last().first())), model->index(temp));
1256 backButton->click();
1257 QCOMPARE(model->index(qvariant_cast<QString>(spy.last().first())), model->index(home));
1258 fd.setDirectory(desktop);
1259 QCOMPARE(forwardButton->isEnabled(), false);
1260}
1261
1262void tst_QFiledialog::disableSaveButton_data()
1263{
1264 QTest::addColumn<QString>(name: "path");
1265 QTest::addColumn<bool>(name: "isEnabled");
1266
1267 QTest::newRow(dataTag: "valid path") << QDir::temp().absolutePath() + QDir::separator() + "qfiledialog.new_file" << true;
1268 QTest::newRow(dataTag: "no path") << "" << false;
1269#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) && !defined(Q_OS_OPENBSD)
1270 QTest::newRow(dataTag: "too long path") << "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii" << false;
1271#endif
1272 QTest::newRow(dataTag: "file") << "foo.html" << true;
1273}
1274
1275void tst_QFiledialog::disableSaveButton()
1276{
1277 QFETCH(QString, path);
1278 QFETCH(bool, isEnabled);
1279
1280 QFileDialog fd(0, "caption", path);
1281 fd.setAcceptMode(QFileDialog::AcceptSave);
1282 QDialogButtonBox *buttonBox = fd.findChild<QDialogButtonBox*>(aName: "buttonBox");
1283 QPushButton *button = buttonBox->button(which: QDialogButtonBox::Save);
1284 QVERIFY(button);
1285 QCOMPARE(button->isEnabled(), isEnabled);
1286}
1287
1288void tst_QFiledialog::saveButtonText_data()
1289{
1290 QTest::addColumn<QString>(name: "path");
1291 QTest::addColumn<QString>(name: "label");
1292 QTest::addColumn<QString>(name: "caption");
1293
1294 QTest::newRow(dataTag: "empty path") << "" << QString() << QFileDialog::tr(s: "&Save");
1295 QTest::newRow(dataTag: "file path") << "qfiledialog.new_file" << QString() << QFileDialog::tr(s: "&Save");
1296 QTest::newRow(dataTag: "dir") << QDir::temp().absolutePath() << QString() << QFileDialog::tr(s: "&Open");
1297 QTest::newRow(dataTag: "setTextLabel") << "qfiledialog.new_file" << "Mooo" << "Mooo";
1298 QTest::newRow(dataTag: "dir & label") << QDir::temp().absolutePath() << "Poo" << QFileDialog::tr(s: "&Open");
1299}
1300
1301void tst_QFiledialog::saveButtonText()
1302{
1303 QFETCH(QString, path);
1304 QFETCH(QString, label);
1305 QFETCH(QString, caption);
1306
1307 QFileDialog fd(0, "auto test", QDir::temp().absolutePath());
1308 fd.setAcceptMode(QFileDialog::AcceptSave);
1309 if (!label.isNull())
1310 fd.setLabelText(label: QFileDialog::Accept, text: label);
1311 fd.setDirectory(QDir::temp());
1312 fd.selectFile(filename: path);
1313 QDialogButtonBox *buttonBox = fd.findChild<QDialogButtonBox*>(aName: "buttonBox");
1314 QVERIFY(buttonBox);
1315 QPushButton *button = buttonBox->button(which: QDialogButtonBox::Save);
1316 QVERIFY(button);
1317 QCOMPARE(button->text(), caption);
1318}
1319
1320// Predicate for use with QTRY_VERIFY() that checks whether the file dialog list
1321// has been populated (contains an entry).
1322class DirPopulatedPredicate
1323{
1324public:
1325 explicit DirPopulatedPredicate(QListView *list, const QString &needle) :
1326 m_list(list), m_needle(needle) {}
1327
1328 operator bool() const
1329 {
1330 const auto model = m_list->model();
1331 const auto root = m_list->rootIndex();
1332 for (int r = 0, count = model->rowCount(parent: root); r < count; ++r) {
1333 if (m_needle == model->index(row: r, column: 0, parent: root).data(arole: Qt::DisplayRole).toString())
1334 return true;
1335 }
1336 return false;
1337 }
1338
1339private:
1340 QListView *m_list;
1341 QString m_needle;
1342};
1343
1344// A predicate for use with QTRY_VERIFY() that ensures an entry of the file dialog
1345// list is selected by pressing cursor down.
1346class SelectDirTestPredicate
1347{
1348public:
1349 explicit SelectDirTestPredicate(QListView *list, const QString &needle) :
1350 m_list(list), m_needle(needle) {}
1351
1352 operator bool() const
1353 {
1354 if (m_needle == m_list->currentIndex().data(arole: Qt::DisplayRole).toString())
1355 return true;
1356 QCoreApplication::processEvents();
1357 QTest::keyClick(widget: m_list, key: Qt::Key_Down);
1358 return false;
1359 }
1360
1361private:
1362 QListView *m_list;
1363 QString m_needle;
1364};
1365
1366void tst_QFiledialog::clearLineEdit()
1367{
1368 // Play it really safe by creating a directory which should show first in
1369 // a temporary dir
1370 QTemporaryDir workDir(QDir::tempPath() + QLatin1String("/tst_qfd_clearXXXXXX"));
1371 QVERIFY2(workDir.isValid(), qPrintable(workDir.errorString()));
1372 const QString workDirPath = workDir.path();
1373 const QString dirName = QLatin1String("aaaaa");
1374 QVERIFY(QDir(workDirPath).mkdir(dirName));
1375
1376 QFileDialog fd(nullptr,
1377 QLatin1String(QTest::currentTestFunction()) + QLatin1String(" AnyFile"),
1378 "foo");
1379 fd.setViewMode(QFileDialog::List);
1380 fd.setFileMode(QFileDialog::AnyFile);
1381 fd.show();
1382
1383 QVERIFY(QTest::qWaitForWindowExposed(&fd));
1384 QLineEdit *lineEdit = fd.findChild<QLineEdit*>(aName: "fileNameEdit");
1385 QVERIFY(lineEdit);
1386 QCOMPARE(lineEdit->text(), QLatin1String("foo"));
1387
1388 QListView* list = fd.findChild<QListView*>(aName: "listView");
1389 QVERIFY(list);
1390
1391 // When in AnyFile mode, lineEdit's text shouldn't be cleared when entering
1392 // a directory by activating one in the list
1393 fd.setDirectory(workDirPath);
1394 DirPopulatedPredicate dirPopulated(list, dirName);
1395 QTRY_VERIFY(dirPopulated);
1396
1397#ifdef QT_KEYPAD_NAVIGATION
1398 list->setEditFocus(true);
1399#endif
1400
1401 SelectDirTestPredicate selectTestDir(list, dirName);
1402 QTRY_VERIFY(selectTestDir);
1403
1404#ifndef Q_OS_MAC
1405 QTest::keyClick(widget: list, key: Qt::Key_Return);
1406#else
1407 QTest::keyClick(list, Qt::Key_O, Qt::ControlModifier);
1408#endif
1409
1410 QTRY_VERIFY(fd.directory().absolutePath() != workDirPath);
1411 QVERIFY(!lineEdit->text().isEmpty());
1412
1413 // When in Directory mode, lineEdit's text should be cleared when entering
1414 // a directory by activating one in the list so one can just hit ok
1415 // and it selects that directory
1416 fd.setFileMode(QFileDialog::Directory);
1417 fd.setWindowTitle(QLatin1String(QTest::currentTestFunction()) + QLatin1String(" Directory"));
1418 fd.setDirectory(workDirPath);
1419 QTRY_VERIFY(dirPopulated);
1420
1421 QTRY_VERIFY(selectTestDir);
1422
1423#ifndef Q_OS_MAC
1424 QTest::keyClick(widget: list, key: Qt::Key_Return);
1425#else
1426 QTest::keyClick(list, Qt::Key_O, Qt::ControlModifier);
1427#endif
1428
1429 QTRY_VERIFY(fd.directory().absolutePath() != workDirPath);
1430 QVERIFY(lineEdit->text().isEmpty());
1431
1432 // QTBUG-71415: When pressing back, the selection (activated
1433 // directory) should be restored.
1434 QToolButton *backButton = fd.findChild<QToolButton*>(aName: "backButton");
1435 QVERIFY(backButton);
1436 QTreeView *treeView = fd.findChildren<QTreeView*>(aName: "treeView").value(i: 0);
1437 QVERIFY(treeView);
1438 backButton->click();
1439 QTRY_COMPARE(treeView->selectionModel()->selectedIndexes().value(0).data().toString(),
1440 dirName);
1441}
1442
1443void tst_QFiledialog::enableChooseButton()
1444{
1445 QFileDialog fd;
1446 fd.setFileMode(QFileDialog::Directory);
1447 fd.show();
1448 QDialogButtonBox *buttonBox = fd.findChild<QDialogButtonBox*>(aName: "buttonBox");
1449 QPushButton *button = buttonBox->button(which: QDialogButtonBox::Open);
1450 QVERIFY(button);
1451 QCOMPARE(button->isEnabled(), true);
1452}
1453
1454void tst_QFiledialog::widgetlessNativeDialog()
1455{
1456 if (!QGuiApplicationPrivate::platformTheme()->usePlatformNativeDialog(type: QPlatformTheme::FileDialog))
1457 QSKIP("This platform always uses widgets to realize its QFileDialog, instead of the native file dialog.");
1458 QApplication::setAttribute(attribute: Qt::AA_DontUseNativeDialogs, on: false);
1459 QFileDialog fd;
1460 fd.setWindowModality(Qt::ApplicationModal);
1461 fd.show();
1462 QTRY_VERIFY(fd.isVisible());
1463 QFileSystemModel *model = fd.findChild<QFileSystemModel*>(aName: "qt_filesystem_model");
1464 QVERIFY(!model);
1465 QPushButton *button = fd.findChild<QPushButton*>();
1466 QVERIFY(!button);
1467 QApplication::setAttribute(attribute: Qt::AA_DontUseNativeDialogs, on: true);
1468}
1469
1470void tst_QFiledialog::selectedFilesWithoutWidgets()
1471{
1472 // Test for a crash when widgets are not instantiated yet.
1473 QFileDialog fd;
1474 fd.setAcceptMode(QFileDialog::AcceptOpen);
1475 QVERIFY(fd.selectedFiles().size() >= 0);
1476}
1477
1478void tst_QFiledialog::trailingDotsAndSpaces()
1479{
1480#ifndef Q_OS_WIN
1481 QSKIP("This is only tested on Windows");
1482#endif
1483 QFileDialog fd;
1484 fd.setViewMode(QFileDialog::List);
1485 fd.setFileMode(QFileDialog::ExistingFile);
1486 fd.show();
1487 QLineEdit *lineEdit = fd.findChild<QLineEdit *>(aName: "fileNameEdit");
1488 QVERIFY(lineEdit);
1489 QListView *list = fd.findChild<QListView *>(aName: "listView");
1490 QVERIFY(list);
1491 QTest::qWait(ms: 1000);
1492 int currentChildrenCount = list->model()->rowCount(parent: list->rootIndex());
1493 QTest::keyClick(widget: lineEdit, key: Qt::Key_Space);
1494 QTest::keyClick(widget: lineEdit, key: Qt::Key_Period);
1495 QTest::qWait(ms: 1000);
1496 QCOMPARE(currentChildrenCount, list->model()->rowCount(list->rootIndex()));
1497 lineEdit->clear();
1498 QTest::keyClick(widget: lineEdit, key: Qt::Key_Period);
1499 QTest::keyClick(widget: lineEdit, key: Qt::Key_Space);
1500 QTest::qWait(ms: 1000);
1501 QCOMPARE(currentChildrenCount, list->model()->rowCount(list->rootIndex()));
1502}
1503
1504#ifdef Q_OS_UNIX
1505#ifdef QT_BUILD_INTERNAL
1506void tst_QFiledialog::tildeExpansion_data()
1507{
1508 QTest::addColumn<QString>(name: "tildePath");
1509 QTest::addColumn<QString>(name: "expandedPath");
1510
1511 const QString tilde = QStringLiteral("~");
1512 const QString tildeUser = tilde + QString(qgetenv(varName: "USER"));
1513 const QLatin1String someSubDir("/some/sub/dir");
1514 const QString homePath = QDir::homePath();
1515 const QString invalid = QStringLiteral("~thisIsNotAValidUserName");
1516
1517 QTest::newRow(dataTag: "empty path") << QString() << QString();
1518 QTest::newRow(dataTag: "~") << tilde << homePath;
1519 QTest::newRow(dataTag: "~/some/sub/dir/") << tilde + someSubDir << homePath + someSubDir;
1520 QTest::newRow(dataTag: "~<user>") << tildeUser << homePath;
1521 QTest::newRow(dataTag: "~<user>/some/sub/dir") << tildeUser + someSubDir << homePath + someSubDir;
1522 QTest::newRow(dataTag: "invalid user name") << invalid << invalid;
1523}
1524#endif // QT_BUILD_INTERNAL
1525
1526#ifdef QT_BUILD_INTERNAL
1527void tst_QFiledialog::tildeExpansion()
1528{
1529 QFETCH(QString, tildePath);
1530 QFETCH(QString, expandedPath);
1531
1532 QCOMPARE(qt_tildeExpansion(tildePath), expandedPath);
1533}
1534#endif // QT_BUILD_INTERNAL
1535#endif
1536
1537class DialogRejecter : public QObject
1538{
1539 Q_OBJECT
1540public:
1541 DialogRejecter()
1542 {
1543 connect(qApp, signal: &QApplication::focusChanged, receiver: this, slot: &DialogRejecter::rejectFileDialog);
1544 }
1545
1546public slots:
1547 virtual void rejectFileDialog()
1548 {
1549 if (QWidget *w = QApplication::activeModalWidget())
1550 if (QDialog *d = qobject_cast<QDialog *>(object: w))
1551 QTest::keyClick(widget: d, key: Qt::Key_Escape);
1552 }
1553};
1554
1555void tst_QFiledialog::rejectModalDialogs()
1556{
1557 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
1558 QSKIP("Wayland: This freezes. Figure out why.");
1559
1560 // QTBUG-38672 , static functions should return empty Urls
1561 DialogRejecter dr;
1562
1563 QUrl url = QFileDialog::getOpenFileUrl(parent: 0, QStringLiteral("getOpenFileUrl"));
1564 QVERIFY(url.isEmpty());
1565 QVERIFY(!url.isValid());
1566
1567 url = QFileDialog::getExistingDirectoryUrl(parent: 0, QStringLiteral("getExistingDirectoryUrl"));
1568 QVERIFY(url.isEmpty());
1569 QVERIFY(!url.isValid());
1570
1571 url = QFileDialog::getSaveFileUrl(parent: 0, QStringLiteral("getSaveFileUrl"));
1572 QVERIFY(url.isEmpty());
1573 QVERIFY(!url.isValid());
1574
1575 // Same test with local files
1576 QString file = QFileDialog::getOpenFileName(parent: 0, QStringLiteral("getOpenFileName"));
1577 QVERIFY(file.isEmpty());
1578
1579 file = QFileDialog::getExistingDirectory(parent: 0, QStringLiteral("getExistingDirectory"));
1580 QVERIFY(file.isEmpty());
1581
1582 file = QFileDialog::getSaveFileName(parent: 0, QStringLiteral("getSaveFileName"));
1583 QVERIFY(file.isEmpty());
1584}
1585
1586void tst_QFiledialog::QTBUG49600_nativeIconProviderCrash()
1587{
1588 if (!QGuiApplicationPrivate::platformTheme()->usePlatformNativeDialog(type: QPlatformTheme::FileDialog))
1589 QSKIP("This platform always uses widgets to realize its QFileDialog, instead of the native file dialog.");
1590 QFileDialog fd;
1591 fd.iconProvider();
1592}
1593
1594class qtbug57193DialogRejecter : public DialogRejecter
1595{
1596public:
1597 void rejectFileDialog() override
1598 {
1599 QCOMPARE(QGuiApplication::topLevelWindows().size(), 1);
1600 const QWindow *window = QGuiApplication::topLevelWindows().constFirst();
1601
1602 const QFileDialog *fileDialog = qobject_cast<QFileDialog*>(object: QApplication::activeModalWidget());
1603 if (!fileDialog)
1604 return;
1605
1606 // The problem in QTBUG-57193 was from a platform input context plugin that was
1607 // connected to QWindow::focusObjectChanged(), and consequently accessed the focus
1608 // object (the QFileDialog) that was in the process of being destroyed. This test
1609 // checks that the QFileDialog is never set as the focus object after its destruction process begins.
1610 connect(sender: window, signal: &QWindow::focusObjectChanged, slot: [=](QObject *focusObject) {
1611 QVERIFY(focusObject != fileDialog);
1612 });
1613 DialogRejecter::rejectFileDialog();
1614 }
1615};
1616
1617void tst_QFiledialog::focusObjectDuringDestruction()
1618{
1619 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
1620 QSKIP("Wayland: This freezes. Figure out why.");
1621
1622 QTRY_VERIFY(QGuiApplication::topLevelWindows().isEmpty());
1623
1624 qtbug57193DialogRejecter dialogRejecter;
1625
1626 QFileDialog::getOpenFileName(parent: nullptr, caption: QString(), dir: QString(), filter: QString(), selectedFilter: nullptr);
1627}
1628
1629QTEST_MAIN(tst_QFiledialog)
1630#include "tst_qfiledialog.moc"
1631

source code of qtbase/tests/auto/widgets/dialogs/qfiledialog/tst_qfiledialog.cpp