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