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 | #include <qtest.h> |
30 | #include "../shared/util.h" |
31 | #include "qquickabstractdialog_p.h" |
32 | #include <QtQml/QQmlComponent> |
33 | #include <QtQml/QQmlContext> |
34 | #include <QtQml/QQmlEngine> |
35 | #include <QtQuick/QQuickItem> |
36 | #include <QtQuick/QQuickView> |
37 | #include <QSignalSpy> |
38 | |
39 | class tst_dialogs : public QQmlDataTest |
40 | { |
41 | Q_OBJECT |
42 | public: |
43 | |
44 | private slots: |
45 | void initTestCase() |
46 | { |
47 | QQmlDataTest::initTestCase(); |
48 | } |
49 | |
50 | //Dialog |
51 | void dialogImplicitWidth_data(); |
52 | void dialogImplicitWidth(); |
53 | void dialogContentResize(); |
54 | void dialogButtonHandler_data(); |
55 | void dialogButtonHandler(); |
56 | void dialogKeyHandler_data(); |
57 | void dialogKeyHandler(); |
58 | void dialogWithDynamicTitle(); |
59 | |
60 | // FileDialog |
61 | void fileDialogDefaultModality(); |
62 | void fileDialogNonModal(); |
63 | void fileDialogNameFilters(); |
64 | void fileDialogDefaultSuffix(); |
65 | |
66 | // ColorDialog |
67 | void colorDialogCreated(); |
68 | |
69 | private: |
70 | }; |
71 | |
72 | void tst_dialogs::dialogImplicitWidth_data() |
73 | { |
74 | QTest::addColumn<int>(name: "standardButtons" ); |
75 | QTest::addColumn<int>(name: "minimumHeight" ); |
76 | |
77 | QTest::newRow(dataTag: "No buttons" ) << |
78 | int(QQuickAbstractDialog::NoButton) << |
79 | 150; |
80 | QTest::newRow(dataTag: "OK button" ) << |
81 | int(QQuickAbstractDialog::Ok) << |
82 | 160; |
83 | } |
84 | |
85 | void tst_dialogs::dialogImplicitWidth() |
86 | { |
87 | QFETCH(int, standardButtons); |
88 | QFETCH(int, minimumHeight); |
89 | |
90 | /* This is the outerSpacing from DefaultDialogWrapper.qml, |
91 | * which is always present */ |
92 | int heightMargins = 12 * 2; |
93 | QQmlEngine engine; |
94 | engine.rootContext()->setContextProperty("buttonsFromTest" , standardButtons); |
95 | QQmlComponent component(&engine); |
96 | component.loadUrl(url: testFileUrl(fileName: "DialogImplicitSize.qml" )); |
97 | QObject *created = component.create(); |
98 | QScopedPointer<QObject> cleanup(created); |
99 | QVERIFY2(created, qPrintable(component.errorString())); |
100 | |
101 | QTRY_VERIFY(created->property("width" ).toInt() >= 400); |
102 | QTRY_VERIFY(created->property("height" ).toInt() >= minimumHeight + heightMargins); |
103 | } |
104 | |
105 | void tst_dialogs::dialogContentResize() |
106 | { |
107 | QQmlEngine engine; |
108 | QQmlComponent component(&engine); |
109 | component.loadUrl(url: testFileUrl(fileName: "DialogMinimumSize.qml" )); |
110 | QObject *created = component.create(); |
111 | QScopedPointer<QObject> cleanup(created); |
112 | QVERIFY2(created, qPrintable(component.errorString())); |
113 | |
114 | QTRY_COMPARE(created->property("width" ).toInt(), 400); |
115 | QTRY_COMPARE(created->property("height" ).toInt(), 300); |
116 | |
117 | // Check that the content item has been sized up from its implicit size |
118 | QQuickItem *userContent = created->findChild<QQuickItem*>(aName: "userContent" ); |
119 | QVERIFY(userContent); |
120 | QVERIFY(userContent->width() > 350); |
121 | QVERIFY(userContent->height() > 200); |
122 | } |
123 | |
124 | void tst_dialogs::dialogButtonHandler_data() |
125 | { |
126 | QTest::addColumn<int>(name: "standardButtons" ); |
127 | QTest::addColumn<bool>(name: "mustBlock" ); |
128 | QTest::addColumn<QString>(name: "expectedAction" ); |
129 | |
130 | QTest::newRow(dataTag: "Cancel, ignored" ) << |
131 | int(QQuickAbstractDialog::Cancel) << |
132 | false << |
133 | "rejected" ; |
134 | QTest::newRow(dataTag: "Cancel, blocked" ) << |
135 | int(QQuickAbstractDialog::Cancel) << |
136 | true << |
137 | "" ; |
138 | QTest::newRow(dataTag: "OK, ignored" ) << |
139 | int(QQuickAbstractDialog::Ok) << |
140 | false << |
141 | "accepted" ; |
142 | QTest::newRow(dataTag: "OK, blocked" ) << |
143 | int(QQuickAbstractDialog::Ok) << |
144 | true << |
145 | "" ; |
146 | } |
147 | |
148 | void tst_dialogs::dialogButtonHandler() |
149 | { |
150 | QFETCH(int, standardButtons); |
151 | QFETCH(bool, mustBlock); |
152 | QFETCH(QString, expectedAction); |
153 | |
154 | QQmlEngine engine; |
155 | engine.rootContext()->setContextProperty("buttonsFromTest" , standardButtons); |
156 | QQmlComponent component(&engine); |
157 | component.loadUrl(url: testFileUrl(fileName: "DialogButtonHandler.qml" )); |
158 | QObject *root = component.create(); |
159 | QScopedPointer<QObject> cleanup(root); |
160 | QVERIFY2(root, qPrintable(component.errorString())); |
161 | |
162 | root->setProperty(name: "visible" , value: true); |
163 | root->setProperty(name: "mustBlock" , value: mustBlock); |
164 | |
165 | QQuickWindow *window = root->findChild<QQuickWindow*>(); |
166 | QVERIFY(QTest::qWaitForWindowExposed(window)); |
167 | |
168 | /* Hack to find the created buttons: since they are created by a |
169 | * QQuickRepeater, they don't appear on the hierarchy tree; therefore, we |
170 | * first need to find the repeater, and then to get its child. */ |
171 | const QList<QQuickItem*> children = root->findChildren<QQuickItem*>(); |
172 | QQuickItem *buttonWidget = nullptr; |
173 | for (QQuickItem *child: children) { |
174 | if (qstrcmp(str1: child->metaObject()->className(), str2: "QQuickRepeater" ) == 0 && |
175 | child->property(name: "count" ).toInt() > 0) { |
176 | int index = 0; |
177 | QMetaObject::invokeMethod(obj: child, |
178 | member: "itemAt" , |
179 | Q_RETURN_ARG(QQuickItem *, buttonWidget), |
180 | Q_ARG(int, index)); |
181 | break; |
182 | } |
183 | } |
184 | QVERIFY(buttonWidget); |
185 | |
186 | const QPointF buttonCenterF(buttonWidget->width() / 2, |
187 | buttonWidget->height() / 2); |
188 | const QPoint buttonCenter = buttonWidget->mapToScene(point: buttonCenterF).toPoint(); |
189 | |
190 | QTest::mouseClick(window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: buttonCenter); |
191 | QTRY_VERIFY(root->property("handlerWasCalled" ).toBool()); |
192 | |
193 | QCOMPARE(root->property("buttonCode" ).toInt(), standardButtons); |
194 | QCOMPARE(root->property("keyCode" ).toInt(), 0); |
195 | |
196 | QCOMPARE(root->property("actionCalled" ).toString(), expectedAction); |
197 | } |
198 | |
199 | void tst_dialogs::dialogKeyHandler_data() |
200 | { |
201 | QTest::addColumn<int>(name: "key" ); |
202 | QTest::addColumn<bool>(name: "mustBlock" ); |
203 | QTest::addColumn<int>(name: "expectedButton" ); |
204 | QTest::addColumn<QString>(name: "expectedAction" ); |
205 | |
206 | QTest::newRow(dataTag: "Escape, ignored" ) << |
207 | int(Qt::Key_Escape) << |
208 | false << |
209 | int(QQuickAbstractDialog::Cancel) << |
210 | "rejected" ; |
211 | QTest::newRow(dataTag: "Cancel, blocked" ) << |
212 | int(Qt::Key_Escape) << |
213 | true << |
214 | int(QQuickAbstractDialog::Cancel) << |
215 | "" ; |
216 | QTest::newRow(dataTag: "Enter, ignored" ) << |
217 | int(Qt::Key_Enter) << |
218 | false << |
219 | int(QQuickAbstractDialog::Ok) << |
220 | "accepted" ; |
221 | QTest::newRow(dataTag: "Enter, blocked" ) << |
222 | int(Qt::Key_Enter) << |
223 | true << |
224 | int(QQuickAbstractDialog::Ok) << |
225 | "" ; |
226 | } |
227 | |
228 | void tst_dialogs::dialogKeyHandler() |
229 | { |
230 | QFETCH(int, key); |
231 | QFETCH(bool, mustBlock); |
232 | QFETCH(int, expectedButton); |
233 | QFETCH(QString, expectedAction); |
234 | |
235 | QQmlEngine engine; |
236 | QQuickAbstractDialog::StandardButtons buttons = |
237 | QQuickAbstractDialog::Ok | QQuickAbstractDialog::Cancel; |
238 | engine.rootContext()->setContextProperty("buttonsFromTest" , int(buttons)); |
239 | QQmlComponent component(&engine); |
240 | component.loadUrl(url: testFileUrl(fileName: "DialogButtonHandler.qml" )); |
241 | QObject *root = component.create(); |
242 | QScopedPointer<QObject> cleanup(root); |
243 | QVERIFY2(root, qPrintable(component.errorString())); |
244 | |
245 | root->setProperty(name: "visible" , value: true); |
246 | root->setProperty(name: "mustBlock" , value: mustBlock); |
247 | |
248 | QQuickWindow *window = root->findChild<QQuickWindow*>(); |
249 | QVERIFY(QTest::qWaitForWindowExposed(window)); |
250 | |
251 | QTest::keyClick(window, key: Qt::Key(key)); |
252 | QTRY_VERIFY(root->property("handlerWasCalled" ).toBool()); |
253 | |
254 | QCOMPARE(root->property("buttonCode" ).toInt(), expectedButton); |
255 | QCOMPARE(root->property("keyCode" ).toInt(), key); |
256 | |
257 | QCOMPARE(root->property("actionCalled" ).toString(), expectedAction); |
258 | } |
259 | |
260 | void tst_dialogs::fileDialogDefaultModality() |
261 | { |
262 | QQuickView *window = new QQuickView; |
263 | QScopedPointer<QQuickWindow> cleanup(window); |
264 | |
265 | window->setSource(testFileUrl(fileName: "RectWithFileDialog.qml" )); |
266 | window->setGeometry(posx: 240,posy: 240,w: 1024,h: 320); |
267 | window->show(); |
268 | QVERIFY(QTest::qWaitForWindowExposed(window)); |
269 | QVERIFY2(window->rootObject(), qPrintable(window->errors().value(0).toString())); |
270 | |
271 | // Click to show |
272 | QObject *dlg = qvariant_cast<QObject *>(v: window->rootObject()->property(name: "fileDialog" )); |
273 | QSignalSpy spyVisibilityChanged(dlg, SIGNAL(visibilityChanged())); |
274 | QTest::mouseClick(window, button: Qt::LeftButton, stateKey: {}, pos: QPoint(1000, 100)); // show |
275 | QTRY_VERIFY(spyVisibilityChanged.count() > 0); |
276 | int visibilityChangedCount = spyVisibilityChanged.count(); |
277 | // Can't hide by clicking the main window, because dialog is modal. |
278 | QTest::mouseClick(window, button: Qt::LeftButton, stateKey: {}, pos: QPoint(1000, 100)); |
279 | /* |
280 | On OS X, if you send an event directly to a window, the modal dialog |
281 | doesn't block the event, so the window will process it normally. This |
282 | is a different code path compared to having a user click the mouse and |
283 | generate a native event; in that case the OS does the filtering itself, |
284 | and Qt will not even see the event. But simulating real events in the |
285 | test framework is generally unstable. So there isn't a good way to test |
286 | modality on OS X. |
287 | This test sometimes fails on other platforms too. Maybe it's not reliable |
288 | to try to click the main window in a location which is outside the |
289 | dialog, without checking or guaranteeing it somehow. |
290 | */ |
291 | QSKIP("Modality test is flaky in general and doesn't work at all on OS X" ); |
292 | // So we expect no change in visibility. |
293 | QCOMPARE(spyVisibilityChanged.count(), visibilityChangedCount); |
294 | |
295 | QCOMPARE(dlg->property("visible" ).toBool(), true); |
296 | QMetaObject::invokeMethod(obj: dlg, member: "close" ); |
297 | QTRY_VERIFY(spyVisibilityChanged.count() > visibilityChangedCount); |
298 | visibilityChangedCount = spyVisibilityChanged.count(); |
299 | QCOMPARE(dlg->property("visible" ).toBool(), false); |
300 | QMetaObject::invokeMethod(obj: dlg, member: "open" ); |
301 | QTRY_VERIFY(spyVisibilityChanged.count() > visibilityChangedCount); |
302 | QCOMPARE(dlg->property("visible" ).toBool(), true); |
303 | QCOMPARE(dlg->property("modality" ).toInt(), (int)Qt::WindowModal); |
304 | } |
305 | |
306 | void tst_dialogs::fileDialogNonModal() |
307 | { |
308 | QQuickView *window = new QQuickView; |
309 | QScopedPointer<QQuickWindow> cleanup(window); |
310 | |
311 | window->setSource(testFileUrl(fileName: "RectWithFileDialog.qml" )); |
312 | window->setGeometry(posx: 240,posy: 240,w: 1024,h: 320); |
313 | window->show(); |
314 | QVERIFY(QTest::qWaitForWindowExposed(window)); |
315 | QVERIFY2(window->rootObject(), qPrintable(window->errors().value(0).toString())); |
316 | |
317 | // Click to toggle visibility |
318 | QObject *dlg = qvariant_cast<QObject *>(v: window->rootObject()->property(name: "fileDialog" )); |
319 | dlg->setProperty(name: "modality" , value: QVariant((int)Qt::NonModal)); |
320 | QSignalSpy spyVisibilityChanged(dlg, SIGNAL(visibilityChanged())); |
321 | QTest::mouseClick(window, button: Qt::LeftButton, stateKey: {}, pos: QPoint(1000, 100)); // show |
322 | QTRY_VERIFY(spyVisibilityChanged.count() > 0); |
323 | int visibilityChangedCount = spyVisibilityChanged.count(); |
324 | QCOMPARE(dlg->property("visible" ).toBool(), true); |
325 | QTest::mouseClick(window, button: Qt::LeftButton, stateKey: {}, pos: QPoint(1000, 100)); // hide |
326 | QTRY_VERIFY(spyVisibilityChanged.count() > visibilityChangedCount); |
327 | QCOMPARE(dlg->property("visible" ).toBool(), false); |
328 | #ifdef Q_OS_WIN |
329 | QCOMPARE(dlg->property("modality" ).toInt(), (int)Qt::ApplicationModal); |
330 | #else |
331 | QCOMPARE(dlg->property("modality" ).toInt(), (int)Qt::NonModal); |
332 | #endif |
333 | } |
334 | |
335 | void tst_dialogs::fileDialogNameFilters() |
336 | { |
337 | QQuickView *window = new QQuickView; |
338 | QScopedPointer<QQuickWindow> cleanup(window); |
339 | |
340 | window->setSource(testFileUrl(fileName: "RectWithFileDialog.qml" )); |
341 | window->setGeometry(posx: 240,posy: 240,w: 1024,h: 320); |
342 | window->show(); |
343 | QVERIFY(QTest::qWaitForWindowExposed(window)); |
344 | QVERIFY2(window->rootObject(), qPrintable(window->errors().value(0).toString())); |
345 | |
346 | QObject *dlg = qvariant_cast<QObject *>(v: window->rootObject()->property(name: "fileDialog" )); |
347 | QStringList filters; |
348 | filters << "QML files (*.qml)" ; |
349 | filters << "Image files (*.jpg, *.png, *.gif)" ; |
350 | filters << "All files (*)" ; |
351 | dlg->setProperty(name: "nameFilters" , value: QVariant(filters)); |
352 | QCOMPARE(dlg->property("selectedNameFilter" ).toString(), filters.first()); |
353 | } |
354 | |
355 | void tst_dialogs::fileDialogDefaultSuffix() |
356 | { |
357 | QQuickView *window = new QQuickView; |
358 | QScopedPointer<QQuickWindow> cleanup(window); |
359 | |
360 | const QUrl sourceUrl = testFileUrl(fileName: "RectWithFileDialog.qml" ); |
361 | window->setSource(sourceUrl); |
362 | window->setGeometry(posx: 240, posy: 240, w: 1024, h: 320); |
363 | window->show(); |
364 | QTRY_VERIFY(QTest::qWaitForWindowExposed(window)); |
365 | QVERIFY(window->rootObject()); |
366 | |
367 | QObject *dlg = qvariant_cast<QObject *>(v: window->rootObject()->property(name: "fileDialog" )); |
368 | QCOMPARE(dlg->property("defaultSuffix" ).toString(), QString()); |
369 | dlg->setProperty(name: "defaultSuffix" , value: "txt" ); |
370 | QCOMPARE(dlg->property("defaultSuffix" ).toString(), QString("txt" )); |
371 | dlg->setProperty(name: "defaultSuffix" , value: ".txt" ); |
372 | QCOMPARE(dlg->property("defaultSuffix" ).toString(), QString("txt" )); |
373 | dlg->setProperty(name: "defaultSuffix" , value: QString()); |
374 | QCOMPARE(dlg->property("defaultSuffix" ).toString(), QString()); |
375 | } |
376 | |
377 | void tst_dialogs::dialogWithDynamicTitle() |
378 | { |
379 | QQmlEngine engine; |
380 | QQmlComponent component(&engine); |
381 | component.loadUrl(url: testFileUrl(fileName: "DialogWithDynamicTitle.qml" )); |
382 | QObject *dlg = component.create(); |
383 | QScopedPointer<QObject> cleanup(dlg); |
384 | QVERIFY2(dlg, qPrintable(component.errorString())); |
385 | QWindow *window = dlg->findChild<QWindow *>(); |
386 | QVERIFY(window); |
387 | QTRY_COMPARE(window->title(), QLatin1String("Title" )); |
388 | dlg->setProperty(name: "newTitle" , value: true); |
389 | QTRY_COMPARE(window->title(), QLatin1String("New Title" )); |
390 | } |
391 | |
392 | void tst_dialogs::colorDialogCreated() |
393 | { |
394 | // Test to ensure it is not crashing when color is assigned |
395 | // on startup |
396 | QQmlEngine engine; |
397 | QQmlComponent component(&engine); |
398 | component.loadUrl(url: testFileUrl(fileName: "ColorDialogCreated.qml" )); |
399 | QScopedPointer<QObject> cleanup(component.create()); |
400 | QObject *colorDlg = cleanup->findChild<QObject *>(aName: "colorDialog" ); |
401 | QVERIFY(colorDlg); |
402 | } |
403 | |
404 | QTEST_MAIN(tst_dialogs) |
405 | |
406 | #include "tst_dialogs.moc" |
407 | |