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
39class tst_dialogs : public QQmlDataTest
40{
41 Q_OBJECT
42public:
43
44private 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
69private:
70};
71
72void 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
85void 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
105void 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
124void 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
148void 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
199void 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
228void 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
260void 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
306void 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
335void 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
355void 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
377void 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
392void 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
404QTEST_MAIN(tst_dialogs)
405
406#include "tst_dialogs.moc"
407

source code of qtquickcontrols/tests/auto/dialogs/tst_dialogs.cpp