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#include <QString>
32#include <QSpinBox>
33#include <QPushButton>
34#include <QLineEdit>
35#include <QComboBox>
36#include <QDialogButtonBox>
37#include <qinputdialog.h>
38#include <QtWidgets/private/qdialog_p.h>
39
40class tst_QInputDialog : public QObject
41{
42 Q_OBJECT
43 QWidget *parent;
44 QDialog::DialogCode doneCode;
45 void (*testFunc)(QInputDialog *);
46 static void testFuncGetInt(QInputDialog *dialog);
47 static void testFuncGetDouble(QInputDialog *dialog);
48 static void testFuncGetText(QInputDialog *dialog);
49 static void testFuncGetItem(QInputDialog *dialog);
50 static void testFuncSingleStepDouble(QInputDialog *dialog);
51 void timerEvent(QTimerEvent *event);
52private slots:
53 void getInt_data();
54 void getInt();
55 void getDouble_data();
56 void getDouble();
57 void taskQTBUG_54693_crashWhenParentIsDeletedWhileDialogIsOpen();
58 void task255502getDouble();
59 void getText_data();
60 void getText();
61 void getItem_data();
62 void getItem();
63 void task256299_getTextReturnNullStringOnRejected();
64 void inputMethodHintsOfChildWidget();
65 void setDoubleStep_data();
66 void setDoubleStep();
67};
68
69QString stripFraction(const QString &s)
70{
71 int period;
72 if (s.contains(c: '.'))
73 period = s.indexOf(c: '.');
74 else if (s.contains(c: ','))
75 period = s.indexOf(c: ',');
76 else
77 return s;
78 int end;
79 for (end = s.size() - 1; end > period && s[end] == '0'; --end) ;
80 return s.left(n: end + (end == period ? 0 : 1));
81}
82
83QString normalizeNumericString(const QString &s)
84{
85 return stripFraction(s); // assumed to be sufficient
86}
87
88void _keyClick(QWidget *widget, char key)
89{
90 QTest::keyClick(widget, key);
91}
92
93void _keyClick(QWidget *widget, Qt::Key key)
94{
95 QTest::keyClick(widget, key);
96}
97
98template <typename SpinBoxType>
99void testTypingValue(
100 SpinBoxType* sbox, QPushButton *okButton, const QString &value)
101{
102 sbox->selectAll();
103 for (int i = 0; i < value.size(); ++i) {
104 const QChar valChar = value[i];
105 _keyClick(widget: static_cast<QWidget *>(sbox), key: valChar.toLatin1()); // ### always guaranteed to work?
106 if (sbox->hasAcceptableInput())
107 QVERIFY(okButton->isEnabled());
108 else
109 QVERIFY(!okButton->isEnabled());
110 }
111}
112
113void testTypingValue(QLineEdit *ledit, QPushButton *okButton, const QString &value)
114{
115 ledit->selectAll();
116 for (int i = 0; i < value.size(); ++i) {
117 const QChar valChar = value[i];
118 _keyClick(widget: ledit, key: valChar.toLatin1()); // ### always guaranteed to work?
119 QVERIFY(ledit->hasAcceptableInput());
120 QVERIFY(okButton->isEnabled());
121 }
122}
123
124template <typename SpinBoxType, typename ValueType>
125void testInvalidateAndRestore(
126 SpinBoxType* sbox, QPushButton *okButton, QLineEdit *ledit, ValueType * = 0)
127{
128 const ValueType lastValidValue = sbox->value();
129
130 sbox->selectAll();
131 _keyClick(widget: ledit, key: Qt::Key_Delete);
132 QVERIFY(!sbox->hasAcceptableInput());
133 QVERIFY(!okButton->isEnabled());
134
135 _keyClick(widget: ledit, key: Qt::Key_Return); // should work with Qt::Key_Enter too
136 QVERIFY(sbox->hasAcceptableInput());
137 QVERIFY(okButton->isEnabled());
138 QCOMPARE(sbox->value(), lastValidValue);
139 QLocale loc;
140 QCOMPARE(
141 normalizeNumericString(ledit->text()),
142 normalizeNumericString(loc.toString(sbox->value())));
143}
144
145template <typename SpinBoxType, typename ValueType>
146void testGetNumeric(QInputDialog *dialog, SpinBoxType * = 0, ValueType * = 0)
147{
148 SpinBoxType *sbox = dialog->findChild<SpinBoxType *>();
149 QVERIFY(sbox != 0);
150
151 QLineEdit *ledit = static_cast<QObject *>(sbox)->findChild<QLineEdit *>();
152 QVERIFY(ledit != 0);
153
154 QDialogButtonBox *bbox = dialog->findChild<QDialogButtonBox *>();
155 QVERIFY(bbox != 0);
156 QPushButton *okButton = bbox->button(which: QDialogButtonBox::Ok);
157 QVERIFY(okButton != 0);
158
159 QVERIFY(sbox->value() >= sbox->minimum());
160 QVERIFY(sbox->value() <= sbox->maximum());
161 QVERIFY(sbox->hasAcceptableInput());
162 QLocale loc;
163 QCOMPARE(
164 normalizeNumericString(ledit->selectedText()),
165 normalizeNumericString(loc.toString(sbox->value())));
166 QVERIFY(okButton->isEnabled());
167
168 const ValueType origValue = sbox->value();
169
170 testInvalidateAndRestore<SpinBoxType, ValueType>(sbox, okButton, ledit);
171 testTypingValue<SpinBoxType>(sbox, okButton, QString::number(sbox->minimum()));
172 testTypingValue<SpinBoxType>(sbox, okButton, QString::number(sbox->maximum()));
173 testTypingValue<SpinBoxType>(sbox, okButton, QString::number(sbox->minimum() - 1));
174 testTypingValue<SpinBoxType>(sbox, okButton, QString::number(sbox->maximum() + 1));
175 testTypingValue<SpinBoxType>(sbox, okButton, "0");
176 testTypingValue<SpinBoxType>(sbox, okButton, "0.0");
177 testTypingValue<SpinBoxType>(sbox, okButton, "foobar");
178
179 testTypingValue<SpinBoxType>(sbox, okButton, loc.toString(origValue));
180}
181
182void testGetText(QInputDialog *dialog)
183{
184 QLineEdit *ledit = dialog->findChild<QLineEdit *>();
185 QVERIFY(ledit);
186
187 QDialogButtonBox *bbox = dialog->findChild<QDialogButtonBox *>();
188 QVERIFY(bbox);
189 QPushButton *okButton = bbox->button(which: QDialogButtonBox::Ok);
190 QVERIFY(okButton);
191
192 QVERIFY(ledit->hasAcceptableInput());
193 QCOMPARE(ledit->selectedText(), ledit->text());
194 QVERIFY(okButton->isEnabled());
195 const QString origValue = ledit->text();
196
197 testTypingValue(ledit, okButton, value: origValue);
198}
199
200void testGetItem(QInputDialog *dialog)
201{
202 QComboBox *cbox = dialog->findChild<QComboBox *>();
203 QVERIFY(cbox);
204
205 QDialogButtonBox *bbox = dialog->findChild<QDialogButtonBox *>();
206 QVERIFY(bbox);
207 QPushButton *okButton = bbox->button(which: QDialogButtonBox::Ok);
208 QVERIFY(okButton);
209
210 QVERIFY(okButton->isEnabled());
211 const int origIndex = cbox->currentIndex();
212 cbox->setCurrentIndex(origIndex - 1);
213 cbox->setCurrentIndex(origIndex);
214 QVERIFY(okButton->isEnabled());
215}
216
217void tst_QInputDialog::testFuncGetInt(QInputDialog *dialog)
218{
219 testGetNumeric<QSpinBox, int>(dialog);
220}
221
222void tst_QInputDialog::testFuncGetDouble(QInputDialog *dialog)
223{
224 testGetNumeric<QDoubleSpinBox, double>(dialog);
225}
226
227void tst_QInputDialog::testFuncGetText(QInputDialog *dialog)
228{
229 ::testGetText(dialog);
230}
231
232void tst_QInputDialog::testFuncGetItem(QInputDialog *dialog)
233{
234 ::testGetItem(dialog);
235}
236
237void tst_QInputDialog::timerEvent(QTimerEvent *event)
238{
239 killTimer(id: event->timerId());
240 QInputDialog *dialog = parent->findChild<QInputDialog *>();
241 QVERIFY(dialog);
242 if (testFunc)
243 testFunc(dialog);
244 dialog->done(result: doneCode); // cause static function call to return
245}
246
247void tst_QInputDialog::getInt_data()
248{
249 QTest::addColumn<int>(name: "min");
250 QTest::addColumn<int>(name: "max");
251 QTest::newRow(dataTag: "getInt() - -") << -20 << -10;
252 QTest::newRow(dataTag: "getInt() - 0") << -20 << 0;
253 QTest::newRow(dataTag: "getInt() - +") << -20 << 20;
254 QTest::newRow(dataTag: "getInt() 0 +") << 0 << 20;
255 QTest::newRow(dataTag: "getInt() + +") << 10 << 20;
256}
257
258void tst_QInputDialog::getInt()
259{
260 QFETCH(int, min);
261 QFETCH(int, max);
262 QVERIFY(min < max);
263
264#if defined(Q_OS_MACOS)
265 if (QSysInfo::productVersion() == QLatin1String("10.12")) {
266 QSKIP("Test hangs on macOS 10.12 -- QTQAINFRA-1356");
267 return;
268 }
269#endif
270
271 parent = new QWidget;
272 doneCode = QDialog::Accepted;
273 testFunc = &tst_QInputDialog::testFuncGetInt;
274 startTimer(interval: 0);
275 bool ok = false;
276 const int value = min + (max - min) / 2;
277 const int result = QInputDialog::getInt(parent, title: "", label: "", value, minValue: min, maxValue: max, step: 1, ok: &ok);
278 QVERIFY(ok);
279 QCOMPARE(result, value);
280 delete parent;
281}
282
283void tst_QInputDialog::getDouble_data()
284{
285 QTest::addColumn<double>(name: "min");
286 QTest::addColumn<double>(name: "max");
287 QTest::addColumn<int>(name: "decimals");
288 QTest::newRow(dataTag: "getDouble() - - d0") << -20.0 << -10.0 << 0;
289 QTest::newRow(dataTag: "getDouble() - 0 d0") << -20.0 << 0.0 << 0;
290 QTest::newRow(dataTag: "getDouble() - + d0") << -20.0 << 20.0 << 0;
291 QTest::newRow(dataTag: "getDouble() 0 + d0") << 0.0 << 20.0 << 0;
292 QTest::newRow(dataTag: "getDouble() + + d0") << 10.0 << 20.0 << 0;
293 QTest::newRow(dataTag: "getDouble() - - d1") << -20.5 << -10.5 << 1;
294 QTest::newRow(dataTag: "getDouble() - 0 d1") << -20.5 << 0.0 << 1;
295 QTest::newRow(dataTag: "getDouble() - + d1") << -20.5 << 20.5 << 1;
296 QTest::newRow(dataTag: "getDouble() 0 + d1") << 0.0 << 20.5 << 1;
297 QTest::newRow(dataTag: "getDouble() + + d1") << 10.5 << 20.5 << 1;
298 QTest::newRow(dataTag: "getDouble() - - d2") << -20.05 << -10.05 << 2;
299 QTest::newRow(dataTag: "getDouble() - 0 d2") << -20.05 << 0.0 << 2;
300 QTest::newRow(dataTag: "getDouble() - + d2") << -20.05 << 20.05 << 2;
301 QTest::newRow(dataTag: "getDouble() 0 + d2") << 0.0 << 20.05 << 2;
302 QTest::newRow(dataTag: "getDouble() + + d2") << 10.05 << 20.05 << 2;
303}
304
305void tst_QInputDialog::getDouble()
306{
307 QFETCH(double, min);
308 QFETCH(double, max);
309 QFETCH(int, decimals);
310 QVERIFY(min < max && decimals >= 0 && decimals <= 13);
311
312#if defined(Q_OS_MACOS)
313 if (QSysInfo::productVersion() == QLatin1String("10.12")) {
314 QSKIP("Test hangs on macOS 10.12 -- QTQAINFRA-1356");
315 return;
316 }
317#endif
318
319 parent = new QWidget;
320 doneCode = QDialog::Accepted;
321 testFunc = &tst_QInputDialog::testFuncGetDouble;
322 startTimer(interval: 0);
323 bool ok = false;
324 // avoid decimals due to inconsistent roundoff behavior in QInputDialog::getDouble()
325 // (at one decimal, 10.25 is rounded off to 10.2, while at two decimals, 10.025 is
326 // rounded off to 10.03)
327 const double value = static_cast<int>(min + (max - min) / 2);
328 const double result =
329 QInputDialog::getDouble(parent, title: "", label: "", value, minValue: min, maxValue: max, decimals, ok: &ok, flags: Qt::WindowFlags(), step: 1);
330 QVERIFY(ok);
331 QCOMPARE(result, value);
332 delete parent;
333}
334
335namespace {
336class SelfDestructParent : public QWidget
337{
338 Q_OBJECT
339public:
340 explicit SelfDestructParent(int delay = 100)
341 : QWidget(nullptr)
342 {
343 QTimer::singleShot(msec: delay, receiver: this, SLOT(deleteLater()));
344 }
345};
346}
347
348void tst_QInputDialog::taskQTBUG_54693_crashWhenParentIsDeletedWhileDialogIsOpen()
349{
350 // getText
351 {
352 QAutoPointer<SelfDestructParent> dialog(new SelfDestructParent);
353 bool ok = true;
354 const QString result = QInputDialog::getText(parent: dialog.get(), title: "Title", label: "Label", echo: QLineEdit::Normal, text: "Text", ok: &ok);
355 QVERIFY(!dialog);
356 QVERIFY(!ok);
357 QVERIFY(result.isNull());
358 }
359
360 // getMultiLineText
361 {
362 QAutoPointer<SelfDestructParent> dialog(new SelfDestructParent);
363 bool ok = true;
364 const QString result = QInputDialog::getMultiLineText(parent: dialog.get(), title: "Title", label: "Label", text: "Text", ok: &ok);
365 QVERIFY(!dialog);
366 QVERIFY(!ok);
367 QVERIFY(result.isNull());
368 }
369
370 // getItem
371 for (int editable = 0; editable < 2; ++editable) {
372 QAutoPointer<SelfDestructParent> dialog(new SelfDestructParent);
373 bool ok = true;
374 const QString result = QInputDialog::getItem(parent: dialog.get(), title: "Title", label: "Label",
375 items: QStringList() << "1" << "2", current: 1,
376 editable: editable != 0, ok: &ok);
377 QVERIFY(!dialog);
378 QVERIFY(!ok);
379 QCOMPARE(result, QLatin1String("2"));
380 }
381
382 // getInt
383 {
384 const int initial = 7;
385 QAutoPointer<SelfDestructParent> dialog(new SelfDestructParent);
386 bool ok = true;
387 const int result = QInputDialog::getInt(parent: dialog.get(), title: "Title", label: "Label", value: initial, minValue: -10, maxValue: +10, step: 1, ok: &ok);
388 QVERIFY(!dialog);
389 QVERIFY(!ok);
390 QCOMPARE(result, initial);
391 }
392
393 // getDouble
394 {
395 const double initial = 7;
396 QAutoPointer<SelfDestructParent> dialog(new SelfDestructParent);
397 bool ok = true;
398 const double result = QInputDialog::getDouble(parent: dialog.get(), title: "Title", label: "Label", value: initial, minValue: -10, maxValue: +10, decimals: 2, ok: &ok,
399 flags: Qt::WindowFlags(), step: 1);
400 QVERIFY(!dialog);
401 QVERIFY(!ok);
402 QCOMPARE(result, initial);
403 }
404}
405
406void tst_QInputDialog::task255502getDouble()
407{
408 parent = new QWidget;
409 doneCode = QDialog::Accepted;
410 testFunc = &tst_QInputDialog::testFuncGetDouble;
411 startTimer(interval: 0);
412 bool ok = false;
413 const double value = 0.001;
414 const double result =
415 QInputDialog::getDouble(parent, title: "", label: "", value, minValue: -1, maxValue: 1, decimals: 4, ok: &ok, flags: Qt::WindowFlags(), step: 1);
416 QVERIFY(ok);
417 QCOMPARE(result, value);
418 delete parent;
419}
420
421void tst_QInputDialog::getText_data()
422{
423 QTest::addColumn<QString>(name: "text");
424 QTest::newRow(dataTag: "getText() 1") << "";
425 QTest::newRow(dataTag: "getText() 2") << "foobar";
426 QTest::newRow(dataTag: "getText() 3") << " foobar";
427 QTest::newRow(dataTag: "getText() 4") << "foobar ";
428 QTest::newRow(dataTag: "getText() 5") << "aAzZ`1234567890-=~!@#$%^&*()_+[]{}\\|;:'\",.<>/?";
429}
430
431void tst_QInputDialog::getText()
432{
433 QFETCH(QString, text);
434 parent = new QWidget;
435 doneCode = QDialog::Accepted;
436 testFunc = &tst_QInputDialog::testFuncGetText;
437 startTimer(interval: 0);
438 bool ok = false;
439 const QString result = QInputDialog::getText(parent, title: "", label: "", echo: QLineEdit::Normal, text, ok: &ok);
440 QVERIFY(ok);
441 QCOMPARE(result, text);
442 delete parent;
443}
444
445void tst_QInputDialog::task256299_getTextReturnNullStringOnRejected()
446{
447 parent = new QWidget;
448 doneCode = QDialog::Rejected;
449 testFunc = 0;
450 startTimer(interval: 0);
451 bool ok = true;
452 const QString result = QInputDialog::getText(parent, title: "", label: "", echo: QLineEdit::Normal, text: "foobar", ok: &ok);
453 QVERIFY(!ok);
454 QVERIFY(result.isNull());
455 delete parent;
456}
457
458void tst_QInputDialog::getItem_data()
459{
460 QTest::addColumn<QStringList>(name: "items");
461 QTest::addColumn<bool>(name: "editable");
462 QTest::newRow(dataTag: "getItem() 1 true") << (QStringList() << "") << true;
463 QTest::newRow(dataTag: "getItem() 2 true") <<
464 (QStringList() << "spring" << "summer" << "fall" << "winter") << true;
465 QTest::newRow(dataTag: "getItem() 1 false") << (QStringList() << "") << false;
466 QTest::newRow(dataTag: "getItem() 2 false") <<
467 (QStringList() << "spring" << "summer" << "fall" << "winter") << false;
468}
469
470void tst_QInputDialog::getItem()
471{
472 QFETCH(QStringList, items);
473 QFETCH(bool, editable);
474 parent = new QWidget;
475 doneCode = QDialog::Accepted;
476 testFunc = &tst_QInputDialog::testFuncGetItem;
477 startTimer(interval: 0);
478 bool ok = false;
479 const int index = items.size() / 2;
480 const QString result = QInputDialog::getItem(parent, title: "", label: "", items, current: index, editable, ok: &ok);
481 QVERIFY(ok);
482 QCOMPARE(result, items[index]);
483 delete parent;
484}
485
486void tst_QInputDialog::inputMethodHintsOfChildWidget()
487{
488 QInputDialog dialog;
489 dialog.setInputMode(QInputDialog::TextInput);
490 QList<QObject *> children = dialog.children();
491 QLineEdit *editWidget = 0;
492 for (int c = 0; c < children.size(); c++) {
493 editWidget = qobject_cast<QLineEdit *>(object: children.at(i: c));
494 if (editWidget)
495 break;
496 }
497 QVERIFY(editWidget);
498 QCOMPARE(editWidget->inputMethodHints(), dialog.inputMethodHints());
499 QCOMPARE(editWidget->inputMethodHints(), Qt::ImhNone);
500 dialog.setInputMethodHints(Qt::ImhDigitsOnly);
501 QCOMPARE(editWidget->inputMethodHints(), dialog.inputMethodHints());
502 QCOMPARE(editWidget->inputMethodHints(), Qt::ImhDigitsOnly);
503}
504
505void tst_QInputDialog::testFuncSingleStepDouble(QInputDialog *dialog)
506{
507 QDoubleSpinBox *sbox = dialog->findChild<QDoubleSpinBox *>();
508 QVERIFY(sbox);
509 QTest::keyClick(widget: sbox, key: Qt::Key_Up);
510}
511
512void tst_QInputDialog::setDoubleStep_data()
513{
514 QTest::addColumn<double>(name: "min");
515 QTest::addColumn<double>(name: "max");
516 QTest::addColumn<int>(name: "decimals");
517 QTest::addColumn<double>(name: "doubleStep");
518 QTest::addColumn<double>(name: "actualResult");
519 QTest::newRow(dataTag: "step 2.0") << 0.0 << 10.0 << 0 << 2.0 << 2.0;
520 QTest::newRow(dataTag: "step 2.5") << 0.5 << 10.5 << 1 << 2.5 << 3.0;
521 QTest::newRow(dataTag: "step 2.25") << 10.05 << 20.05 << 2 << 2.25 << 12.30;
522 QTest::newRow(dataTag: "step 2.25 fewer decimals") << 0.5 << 10.5 << 1 << 2.25 << 2.75;
523}
524
525void tst_QInputDialog::setDoubleStep()
526{
527 QFETCH(double, min);
528 QFETCH(double, max);
529 QFETCH(int, decimals);
530 QFETCH(double, doubleStep);
531 QFETCH(double, actualResult);
532 QWidget p;
533 parent = &p;
534 doneCode = QDialog::Accepted;
535 testFunc = &tst_QInputDialog::testFuncSingleStepDouble;
536 startTimer(interval: 0);
537 bool ok = false;
538 const double result = QInputDialog::getDouble(parent, title: QString(), label: QString(), value: min, minValue: min,
539 maxValue: max, decimals, ok: &ok, flags: QFlags<Qt::WindowType>(),
540 step: doubleStep);
541 QVERIFY(ok);
542 QCOMPARE(result, actualResult);
543}
544
545QTEST_MAIN(tst_QInputDialog)
546#include "tst_qinputdialog.moc"
547

source code of qtbase/tests/auto/widgets/dialogs/qinputdialog/tst_qinputdialog.cpp