1 /*
2 This file is part of the KDE Frameworks
3 SPDX-FileCopyrightText: 2008, 2016 David Faure <faure@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6 */
7
8 #include <KComboBox>
9 #include <kdiroperator.h>
10 #include <kfilewidget.h>
11 #include <kurlrequester.h>
12
13 #include <QLineEdit>
14 #include <QSignalSpy>
15 #include <QTemporaryFile>
16 #include <QTest>
17
18 /*
19 IMPORTANT:
20 Because this unittest interacts with the file dialog,
21 remember to run it both with plugins/platformthemes/KDEPlasmaPlatformTheme.so (to use KFileWidget)
22 and without it (to use the builtin QFileDialog code)
23 */
24
25 class KUrlRequesterTest : public QObject
26 {
27 Q_OBJECT
28 private Q_SLOTS:
29 void initTestCase();
30 void testUrlRequester();
31 void testComboRequester();
32 void testComboRequester_data();
33
34 private:
createTestFile(const QString & fileName)35 bool createTestFile(const QString &fileName)
36 {
37 QFile file(fileName);
38 if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
39 return false;
40 }
41 file.write("Hello world\n");
42 return true;
43 }
44 };
45
46 // Same as in kfiledialog_unittest.cpp
findFileWidget()47 static KFileWidget *findFileWidget()
48 {
49 QList<KFileWidget *> widgets;
50 const QList<QWidget *> widgetsList = QApplication::topLevelWidgets();
51 for (QWidget *widget : widgetsList) {
52 KFileWidget *fw = widget->findChild<KFileWidget *>();
53 if (fw) {
54 widgets.append(fw);
55 }
56 }
57 return (widgets.count() == 1) ? widgets.first() : nullptr;
58 }
59
initTestCase()60 void KUrlRequesterTest::initTestCase()
61 {
62 qputenv("KDE_FORK_SLAVES", "yes");
63 }
64
testUrlRequester()65 void KUrlRequesterTest::testUrlRequester()
66 {
67 KUrlRequester req;
68 req.setFileDialogModality(Qt::NonModal);
69 const QString fileName = QStringLiteral("some_test_file");
70 QVERIFY(createTestFile(fileName));
71 QTemporaryFile tempFile;
72 QVERIFY(tempFile.open());
73 const QString filePath2 = tempFile.fileName();
74 QVERIFY(QFile::exists(filePath2));
75
76 // Set start dir
77 const QUrl dirUrl = QUrl::fromLocalFile(QDir::currentPath());
78 req.setStartDir(dirUrl);
79 QCOMPARE(req.startDir().toString(), dirUrl.toString());
80
81 // Click the button
82 req.button()->click();
83 QFileDialog *fileDialog = req.findChild<QFileDialog *>();
84 QVERIFY(fileDialog);
85
86 // Find out if we're using KFileDialog or QFileDialog
87 KFileWidget *fw = findFileWidget();
88
89 // Wait for directory listing
90 if (fw) {
91 QSignalSpy spy(fw->dirOperator(), &KDirOperator::finishedLoading);
92 QVERIFY(spy.wait());
93 }
94
95 // Select file
96 const QString filePath = dirUrl.toLocalFile() + '/' + fileName;
97 fileDialog->selectFile(fileName);
98
99 // Click OK, check URLRequester shows and returns selected file
100 QKeyEvent keyPressEv(QKeyEvent::KeyPress, Qt::Key_Return, Qt::NoModifier);
101 qApp->sendEvent(fw ? static_cast<QWidget *>(fw) : static_cast<QWidget *>(fileDialog), &keyPressEv);
102 QCOMPARE(fileDialog->result(), static_cast<int>(QDialog::Accepted));
103 QCOMPARE(fileDialog->selectedFiles(), QStringList{filePath});
104 QCOMPARE(req.url().toLocalFile(), filePath);
105
106 // Check there is no longer any file dialog visible
107 QVERIFY(fileDialog->isHidden());
108
109 // Click KUrlRequester button again. This time the filedialog is initialized with a file URL
110 req.button()->click();
111 fileDialog = req.findChild<QFileDialog *>();
112 QVERIFY(fileDialog);
113 fw = findFileWidget();
114 if (fw) { // no need to wait for dir listing again, but we need it to be visible at least (for Key_Return to accept)
115 // QVERIFY(QTest::qWaitForWindowExposed(fw->window())); // doesn't seem to be enough
116 QTRY_VERIFY(fw->isVisible());
117 }
118
119 // Select file 2
120 fileDialog->selectFile(filePath2);
121
122 // Click OK, check URLRequester shows and returns selected file
123 qApp->sendEvent(fw ? static_cast<QWidget *>(fw) : static_cast<QWidget *>(fileDialog), &keyPressEv);
124 QCOMPARE(fileDialog->result(), static_cast<int>(QDialog::Accepted));
125 QCOMPARE(fileDialog->selectedFiles(), QStringList{filePath2});
126 QCOMPARE(req.url().toLocalFile(), filePath2);
127 }
128
testComboRequester()129 void KUrlRequesterTest::testComboRequester()
130 {
131 QFETCH(bool, editable);
132
133 KUrlComboRequester req;
134 req.show();
135
136 QList<QLineEdit *> lineEdits = req.findChildren<QLineEdit *>();
137 QVERIFY(lineEdits.isEmpty()); // no lineedits, only a readonly combo
138
139 QSignalSpy textSpy(&req, &KUrlComboRequester::textChanged);
140 QSignalSpy editSpy(&req, &KUrlComboRequester::textEdited);
141
142 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 80)
143 QSignalSpy returnSpy(&req, qOverload<>(&KUrlComboRequester::returnPressed));
144 #endif
145 QSignalSpy returnWithTextSpy(&req, qOverload<const QString &>(&KUrlComboRequester::returnPressed));
146
147 QVERIFY(!req.comboBox()->isEditable());
148 if (editable) {
149 req.comboBox()->setEditable(true);
150
151 const auto text = QStringLiteral("foobar");
152 QTest::keyClicks(req.comboBox(), text, Qt::NoModifier);
153 QCOMPARE(textSpy.size(), text.size());
154 QCOMPARE(editSpy.size(), text.size());
155 QCOMPARE(textSpy.last().first().toString(), text);
156 QCOMPARE(editSpy.last().first().toString(), text);
157
158 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 80)
159 QCOMPARE(returnSpy.size(), 0);
160 #endif
161 QCOMPARE(returnWithTextSpy.size(), 0);
162
163 QTest::keyEvent(QTest::Click, req.comboBox(), Qt::Key_Return);
164
165 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 80)
166 QCOMPARE(returnSpy.size(), 1);
167 #endif
168 QCOMPARE(returnWithTextSpy.size(), 1);
169 QCOMPARE(returnWithTextSpy.last().first().toString(), text);
170 } else {
171 const auto url1 = QUrl("file:///foo/bar/1");
172 const auto url2 = QUrl("file:///foo/bar/2");
173 req.comboBox()->addUrl(url1);
174 QCOMPARE(textSpy.size(), 1);
175 QCOMPARE(textSpy.last().first().toUrl(), url1);
176
177 req.comboBox()->addUrl(url2);
178 QCOMPARE(textSpy.size(), 1);
179
180 QTest::keyEvent(QTest::Click, req.comboBox(), Qt::Key_Down);
181 QCOMPARE(textSpy.size(), 2);
182 QCOMPARE(textSpy.last().first().toUrl(), url2);
183
184 // only editable combo boxes get the edit and return signals emitted
185 QCOMPARE(editSpy.size(), 0);
186
187 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 80)
188 QCOMPARE(returnSpy.size(), 0);
189 #endif
190 QCOMPARE(returnWithTextSpy.size(), 0);
191 }
192 }
193
testComboRequester_data()194 void KUrlRequesterTest::testComboRequester_data()
195 {
196 QTest::addColumn<bool>("editable");
197
198 QTest::newRow("read-only") << false;
199 QTest::newRow("editable") << true;
200 }
201
202 QTEST_MAIN(KUrlRequesterTest)
203 #include "kurlrequestertest.moc"
204