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 <qdebug.h>
32#include <qapplication.h>
33#include <limits.h>
34
35#include <qspinbox.h>
36#include <qlocale.h>
37#include <qlineedit.h>
38#include <qlayout.h>
39#include <QSpinBox>
40#include <QWidget>
41#include <QString>
42#include <QValidator>
43#include <QLineEdit>
44#include <QObject>
45#include <QStringList>
46#include <QList>
47#include <QLocale>
48#include <QDoubleSpinBox>
49#include <QVBoxLayout>
50#include <QKeySequence>
51#include <QStackedWidget>
52#include <QDebug>
53#include <QStyleOptionSpinBox>
54#include <QStyle>
55#include <QProxyStyle>
56#include <QScreen>
57
58
59class SpinBox : public QSpinBox
60{
61public:
62 SpinBox(QWidget *parent = 0)
63 : QSpinBox(parent)
64 {}
65 QString textFromValue(int v) const
66 {
67 return QSpinBox::textFromValue(val: v);
68 }
69 QValidator::State validate(QString &text, int &pos) const
70 {
71 return QSpinBox::validate(input&: text, pos);
72 }
73 int valueFromText(const QString &text) const
74 {
75 return QSpinBox::valueFromText(text);
76 }
77#if QT_CONFIG(wheelevent)
78 void wheelEvent(QWheelEvent *event)
79 {
80 QSpinBox::wheelEvent(event);
81 }
82#endif
83 void initStyleOption(QStyleOptionSpinBox *option) const
84 {
85 QSpinBox::initStyleOption(option);
86 }
87
88 QLineEdit *lineEdit() const { return QSpinBox::lineEdit(); }
89};
90
91class PressAndHoldStyle : public QProxyStyle
92{
93 Q_OBJECT
94public:
95 using QProxyStyle::QProxyStyle;
96
97 int styleHint(QStyle::StyleHint hint, const QStyleOption *option = nullptr,
98 const QWidget *widget = nullptr, QStyleHintReturn *returnData = nullptr) const override
99 {
100 switch (hint) {
101 case QStyle::SH_SpinBox_ClickAutoRepeatRate:
102 return 5;
103 case QStyle::SH_SpinBox_ClickAutoRepeatThreshold:
104 return 10;
105 default:
106 return QProxyStyle::styleHint(hint, option, widget, returnData);
107 }
108 }
109};
110
111class StepModifierStyle : public QProxyStyle
112{
113 Q_OBJECT
114public:
115 using QProxyStyle::QProxyStyle;
116
117 int styleHint(QStyle::StyleHint hint, const QStyleOption *option = nullptr,
118 const QWidget *widget = nullptr, QStyleHintReturn *returnData = nullptr) const override
119 {
120 switch (hint) {
121 case QStyle::SH_SpinBox_StepModifier:
122 return stepModifier;
123 default:
124 return QProxyStyle::styleHint(hint, option, widget, returnData);
125 }
126 }
127
128 Qt::KeyboardModifier stepModifier = Qt::ControlModifier;
129};
130
131class tst_QSpinBox : public QObject
132{
133 Q_OBJECT
134public:
135 tst_QSpinBox();
136public slots:
137 void init();
138private slots:
139 void getSetCheck();
140 void setValue_data();
141 void setValue();
142
143 void setDisplayIntegerBase_data();
144 void setDisplayIntegerBase();
145
146 void setPrefixSuffix_data();
147 void setPrefixSuffix();
148
149 void setReadOnly();
150
151 void setTracking_data();
152 void setTracking();
153
154 void locale_data();
155 void locale();
156
157 void setWrapping_data();
158 void setWrapping();
159
160 void setSpecialValueText_data();
161 void setSpecialValueText();
162
163 void setSingleStep_data();
164 void setSingleStep();
165
166 void setMinMax_data();
167 void setMinMax();
168
169 void editingFinished();
170
171 void valueFromTextAndValidate_data();
172 void valueFromTextAndValidate();
173
174 void removeAll();
175 void startWithDash();
176 void undoRedo();
177
178 void specialValue();
179 void textFromValue();
180
181 void sizeHint();
182
183 void integerOverflow();
184
185 void taskQTBUG_5008_textFromValueAndValidate();
186 void lineEditReturnPressed();
187
188 void positiveSign();
189
190 void interpretOnLosingFocus();
191
192 void setGroupSeparatorShown_data();
193 void setGroupSeparatorShown();
194
195 void wheelEvents_data();
196 void wheelEvents();
197
198 void adaptiveDecimalStep();
199
200 void stepModifierKeys_data();
201 void stepModifierKeys();
202
203 void stepModifierButtons_data();
204 void stepModifierButtons();
205
206 void stepModifierPressAndHold_data();
207 void stepModifierPressAndHold();
208public slots:
209 void textChangedHelper(const QString &);
210 void valueChangedHelper(int);
211private:
212 QStringList actualTexts;
213 QList<int> actualValues;
214};
215
216typedef QList<int> IntList;
217
218Q_DECLARE_METATYPE(QLocale::Language)
219Q_DECLARE_METATYPE(QLocale::Country)
220
221static QLatin1String modifierToName(Qt::KeyboardModifier modifier)
222{
223 switch (modifier) {
224 case Qt::NoModifier:
225 return QLatin1String("No");
226 break;
227 case Qt::ControlModifier:
228 return QLatin1String("Ctrl");
229 break;
230 case Qt::ShiftModifier:
231 return QLatin1String("Shift");
232 break;
233 case Qt::AltModifier:
234 return QLatin1String("Alt");
235 break;
236 case Qt::MetaModifier:
237 return QLatin1String("Meta");
238 break;
239 default:
240 qFatal(msg: "Unexpected keyboard modifier");
241 return QLatin1String();
242 }
243}
244
245// Testing get/set functions
246void tst_QSpinBox::getSetCheck()
247{
248 QSpinBox obj1;
249 QCOMPARE(obj1.inputMethodQuery(Qt::ImHints), QVariant(int(Qt::ImhDigitsOnly)));
250 // int QSpinBox::singleStep()
251 // void QSpinBox::setSingleStep(int)
252 obj1.setSingleStep(0);
253 QCOMPARE(0, obj1.singleStep());
254 obj1.setSingleStep(INT_MIN);
255 QCOMPARE(0, obj1.singleStep()); // Can't have negative steps => keep old value
256 obj1.setSingleStep(INT_MAX);
257 QCOMPARE(INT_MAX, obj1.singleStep());
258
259 // int QSpinBox::minimum()
260 // void QSpinBox::setMinimum(int)
261 obj1.setMinimum(0);
262 QCOMPARE(0, obj1.minimum());
263 obj1.setMinimum(INT_MIN);
264 QCOMPARE(INT_MIN, obj1.minimum());
265 obj1.setMinimum(INT_MAX);
266 QCOMPARE(INT_MAX, obj1.minimum());
267
268 // int QSpinBox::maximum()
269 // void QSpinBox::setMaximum(int)
270 obj1.setMaximum(0);
271 QCOMPARE(0, obj1.maximum());
272 obj1.setMaximum(INT_MIN);
273 QCOMPARE(INT_MIN, obj1.maximum());
274 obj1.setMaximum(INT_MAX);
275 QCOMPARE(INT_MAX, obj1.maximum());
276
277 // int QSpinBox::value()
278 // void QSpinBox::setValue(int)
279 obj1.setValue(0);
280 QCOMPARE(0, obj1.value());
281 obj1.setValue(INT_MIN);
282 QCOMPARE(INT_MIN, obj1.value());
283 obj1.setValue(INT_MAX);
284 QCOMPARE(INT_MAX, obj1.value());
285
286 QDoubleSpinBox obj2;
287 QCOMPARE(obj2.inputMethodQuery(Qt::ImHints), QVariant(int(Qt::ImhFormattedNumbersOnly)));
288 // double QDoubleSpinBox::singleStep()
289 // void QDoubleSpinBox::setSingleStep(double)
290 obj2.setSingleStep(0.0);
291 QCOMPARE(0.0, obj2.singleStep());
292 obj2.setSingleStep(1.0);
293 QCOMPARE(1.0, obj2.singleStep());
294
295 // double QDoubleSpinBox::minimum()
296 // void QDoubleSpinBox::setMinimum(double)
297 obj2.setMinimum(1.0);
298 QCOMPARE(1.0, obj2.minimum());
299 obj2.setMinimum(0.0);
300 QCOMPARE(0.0, obj2.minimum());
301 obj2.setMinimum(-1.0);
302 QCOMPARE(-1.0, obj2.minimum());
303
304 // double QDoubleSpinBox::maximum()
305 // void QDoubleSpinBox::setMaximum(double)
306 obj2.setMaximum(-1.0);
307 QCOMPARE(-1.0, obj2.maximum());
308 obj2.setMaximum(0.0);
309 QCOMPARE(0.0, obj2.maximum());
310 obj2.setMaximum(1.0);
311 QCOMPARE(1.0, obj2.maximum());
312
313 // int QDoubleSpinBox::decimals()
314 // void QDoubleSpinBox::setDecimals(int)
315 obj2.setDecimals(0);
316 QCOMPARE(0, obj2.decimals());
317 obj2.setDecimals(INT_MIN);
318 QCOMPARE(0, obj2.decimals()); // Range<0, 13>
319
320 //obj2.setDecimals(INT_MAX);
321 //QCOMPARE(13, obj2.decimals()); // Range<0, 13>
322 obj2.setDecimals(128);
323 QCOMPARE(obj2.decimals(), 128); // Range<0, 128>
324
325 // double QDoubleSpinBox::value()
326 // void QDoubleSpinBox::setValue(double)
327 obj2.setValue(-1.0);
328 QCOMPARE(-1.0, obj2.value());
329 obj2.setValue(0.0);
330 QCOMPARE(0.0, obj2.value());
331 obj2.setValue(1.0);
332 QCOMPARE(1.0, obj2.value());
333
334 // Make sure we update line edit geometry when updating
335 // buttons - see task 235747
336 QRect oldEditGeometry = obj1.childrenRect();
337 obj1.setButtonSymbols(QAbstractSpinBox::NoButtons);
338 QVERIFY(obj1.childrenRect() != oldEditGeometry);
339}
340
341tst_QSpinBox::tst_QSpinBox()
342{
343}
344
345void tst_QSpinBox::init()
346{
347 QLocale::setDefault(QLocale(QLocale::C));
348
349#if QT_CONFIG(cursor)
350 // Ensure mouse cursor was not left by previous tests where widgets
351 // will appear, as it could cause events and interfere with the tests.
352 const QScreen *screen = QGuiApplication::primaryScreen();
353 const QRect availableGeometry = screen->availableGeometry();
354 QCursor::setPos(availableGeometry.topLeft());
355#endif
356}
357
358void tst_QSpinBox::setValue_data()
359{
360 QTest::addColumn<int>(name: "set");
361 QTest::addColumn<int>(name: "expected");
362
363 QTest::newRow(dataTag: "data0") << 0 << 0;
364 QTest::newRow(dataTag: "data1") << 100 << 100;
365 QTest::newRow(dataTag: "data2") << -100 << -100;
366 QTest::newRow(dataTag: "data3") << INT_MIN << INT_MIN;
367 QTest::newRow(dataTag: "data4") << INT_MAX << INT_MAX;
368}
369
370void tst_QSpinBox::setValue()
371{
372 QFETCH(int, set);
373 QFETCH(int, expected);
374 QSpinBox spin(0);
375 spin.setRange(INT_MIN, INT_MAX);
376 spin.setValue(set);
377 QCOMPARE(spin.value(), expected);
378}
379
380void tst_QSpinBox::setDisplayIntegerBase_data()
381{
382 QTest::addColumn<int>(name: "value");
383 QTest::addColumn<int>(name: "base");
384 QTest::addColumn<QString>(name: "string");
385
386 QTest::newRow(dataTag: "base 10") << 42 << 10 << "42";
387 QTest::newRow(dataTag: "base 2") << 42 << 2 << "101010";
388 QTest::newRow(dataTag: "base 8") << 42 << 8 << "52";
389 QTest::newRow(dataTag: "base 16") << 42 << 16 << "2a";
390 QTest::newRow(dataTag: "base 0") << 42 << 0 << "42";
391 QTest::newRow(dataTag: "base -4") << 42 << -4 << "42";
392 QTest::newRow(dataTag: "base 40") << 42 << 40 << "42";
393
394 QTest::newRow(dataTag: "negative base 10") << -42 << 10 << "-42";
395 QTest::newRow(dataTag: "negative base 2") << -42 << 2 << "-101010";
396 QTest::newRow(dataTag: "negative base 8") << -42 << 8 << "-52";
397 QTest::newRow(dataTag: "negative base 16") << -42 << 16 << "-2a";
398 QTest::newRow(dataTag: "negative base 0") << -42 << 0 << "-42";
399 QTest::newRow(dataTag: "negative base -4") << -42 << -4 << "-42";
400 QTest::newRow(dataTag: "negative base 40") << -42 << 40 << "-42";
401
402 QTest::newRow(dataTag: "0 base 10") << 0 << 10 << "0";
403 QTest::newRow(dataTag: "0 base 2") << 0 << 2 << "0";
404 QTest::newRow(dataTag: "0 base 8") << 0 << 8 << "0";
405 QTest::newRow(dataTag: "0 base 16") << 0 << 16 << "0";
406 QTest::newRow(dataTag: "0 base 0") << 0 << 0 << "0";
407 QTest::newRow(dataTag: "0 base -4") << 0 << -4 << "0";
408 QTest::newRow(dataTag: "0 base 40") << 0 << 40 << "0";
409}
410
411void tst_QSpinBox::setDisplayIntegerBase()
412{
413 QFETCH(int, value);
414 QFETCH(int, base);
415 QFETCH(QString, string);
416
417 SpinBox spin;
418 spin.setRange(INT_MIN, INT_MAX);
419
420 spin.setValue(value);
421 QCOMPARE(spin.lineEdit()->text(), QString::number(value));
422
423 spin.setDisplayIntegerBase(base);
424 QCOMPARE(spin.lineEdit()->text(), string);
425
426 spin.setValue(0);
427 QCOMPARE(spin.value(), 0);
428 QCOMPARE(spin.lineEdit()->text(), QString::number(0, base));
429
430 spin.lineEdit()->clear();
431 QTest::keyClicks(widget: spin.lineEdit(), sequence: string);
432 QCOMPARE(spin.value(), value);
433}
434
435void tst_QSpinBox::setPrefixSuffix_data()
436{
437 QTest::addColumn<QString>(name: "prefix");
438 QTest::addColumn<QString>(name: "suffix");
439 QTest::addColumn<int>(name: "value");
440 QTest::addColumn<QString>(name: "expectedText");
441 QTest::addColumn<QString>(name: "expectedCleanText");
442 QTest::addColumn<bool>(name: "show");
443
444 QTest::newRow(dataTag: "data0") << QString() << QString() << 10 << "10" << "10" << false;
445 QTest::newRow(dataTag: "data1") << QString() << "cm" << 10 << "10cm" << "10" << false;
446 QTest::newRow(dataTag: "data2") << "cm: " << QString() << 10 << "cm: 10" << "10" << false;
447 QTest::newRow(dataTag: "data3") << "length: " << "cm" << 10 << "length: 10cm" << "10" << false;
448
449 QTest::newRow(dataTag: "data4") << QString() << QString() << 10 << "10" << "10" << true;
450 QTest::newRow(dataTag: "data5") << QString() << "cm" << 10 << "10cm" << "10" << true;
451 QTest::newRow(dataTag: "data6") << "cm: " << QString() << 10 << "cm: 10" << "10" << true;
452 QTest::newRow(dataTag: "data7") << "length: " << "cm" << 10 << "length: 10cm" << "10" << true;
453}
454
455void tst_QSpinBox::setPrefixSuffix()
456{
457 QFETCH(QString, prefix);
458 QFETCH(QString, suffix);
459 QFETCH(int, value);
460 QFETCH(QString, expectedText);
461 QFETCH(QString, expectedCleanText);
462 QFETCH(bool, show);
463
464 QSpinBox spin;
465 if (show) {
466 spin.show();
467 spin.setPrefix(QString()); // trigger a recalc of sizeHint
468 }
469 const QSize size1 = spin.sizeHint();
470 spin.setPrefix(prefix);
471 const QSize size2 = spin.sizeHint();
472 spin.setSuffix(suffix);
473 const QSize size3 = spin.sizeHint();
474 spin.setValue(value);
475
476 QCOMPARE(spin.prefix(), prefix);
477 QCOMPARE(spin.suffix(), suffix);
478 QCOMPARE(spin.text(), expectedText);
479 QCOMPARE(spin.cleanText(), expectedCleanText);
480
481 if (!suffix.isEmpty()) {
482 QVERIFY(size2.width() < size3.width());
483 spin.setSuffix(QString());
484 QCOMPARE(spin.sizeHint(), size2);
485 }
486 if (!prefix.isEmpty()) {
487 QVERIFY(size1.width() < size2.width());
488 spin.setPrefix(QString());
489 QCOMPARE(spin.sizeHint(), size1);
490 }
491}
492
493void tst_QSpinBox::textChangedHelper(const QString &text)
494{
495 actualTexts << text;
496}
497
498void tst_QSpinBox::valueChangedHelper(int value)
499{
500 actualValues << value;
501}
502
503class ReadOnlyChangeTracker: public QSpinBox
504{
505public:
506 ReadOnlyChangeTracker(QWidget *parent = 0) : QSpinBox(parent) {}
507
508 void changeEvent(QEvent *ev) {
509 if (ev->type() == QEvent::ReadOnlyChange)
510 ++readOnlyChangeEventCount;
511 }
512 int readOnlyChangeEventCount = 0;
513};
514
515void tst_QSpinBox::setReadOnly()
516{
517 ReadOnlyChangeTracker spin(0);
518 spin.show();
519 QTest::keyClick(widget: &spin, key: Qt::Key_Up);
520 QCOMPARE(spin.value(), 1);
521 spin.setReadOnly(true);
522#ifndef Q_OS_WINRT // QTBUG-68297
523 QCOMPARE(spin.readOnlyChangeEventCount, 1);
524#endif
525 QTest::keyClick(widget: &spin, key: Qt::Key_Up);
526 QCOMPARE(spin.value(), 1);
527 spin.stepBy(steps: 1);
528 QCOMPARE(spin.value(), 2);
529 spin.setReadOnly(false);
530#ifndef Q_OS_WINRT // QTBUG-68297
531 QCOMPARE(spin.readOnlyChangeEventCount, 2);
532#endif
533 QTest::keyClick(widget: &spin, key: Qt::Key_Up);
534 QCOMPARE(spin.value(), 3);
535}
536void tst_QSpinBox::setTracking_data()
537{
538 QTest::addColumn<QTestEventList>(name: "keys");
539 QTest::addColumn<QStringList>(name: "texts");
540 QTest::addColumn<bool>(name: "tracking");
541
542 QTestEventList keys;
543 QStringList texts1;
544 QStringList texts2;
545
546#ifdef Q_OS_MAC
547 keys.addKeyClick(Qt::Key_Right, Qt::ControlModifier);
548#else
549 keys.addKeyClick(qtKey: Qt::Key_End);
550#endif
551 keys.addKeyClick(ascii: '7');
552 keys.addKeyClick(ascii: '9');
553 keys.addKeyClick(qtKey: Qt::Key_Enter);
554 keys.addKeyClick(qtKey: Qt::Key_Enter);
555 keys.addKeyClick(qtKey: Qt::Key_Enter);
556 texts1 << "07" << "079" << "79" << "79" << "79";
557 texts2 << "79";
558 QTest::newRow(dataTag: "data1") << keys << texts1 << true;
559 QTest::newRow(dataTag: "data2") << keys << texts2 << false;
560}
561
562void tst_QSpinBox::setTracking()
563{
564 actualTexts.clear();
565 QFETCH(QTestEventList, keys);
566 QFETCH(QStringList, texts);
567 QFETCH(bool, tracking);
568
569 QSpinBox spin(0);
570 spin.setKeyboardTracking(tracking);
571 spin.show();
572 connect(sender: &spin, signal: &QSpinBox::textChanged, receiver: this, slot: &tst_QSpinBox::textChangedHelper);
573
574 keys.simulate(w: &spin);
575 QCOMPARE(actualTexts, texts);
576}
577
578void tst_QSpinBox::setWrapping_data()
579{
580 QTest::addColumn<bool>(name: "wrapping");
581 QTest::addColumn<int>(name: "minimum");
582 QTest::addColumn<int>(name: "maximum");
583 QTest::addColumn<int>(name: "startValue");
584 QTest::addColumn<QTestEventList>(name: "keys");
585 QTest::addColumn<IntList>(name: "expected");
586
587 QTestEventList keys;
588 IntList values;
589 keys.addKeyClick(qtKey: Qt::Key_Up);
590 values << 10;
591 keys.addKeyClick(qtKey: Qt::Key_Up);
592 QTest::newRow(dataTag: "data0") << false << 0 << 10 << 9 << keys << values;
593
594 keys.clear();
595 values.clear();
596 keys.addKeyClick(qtKey: Qt::Key_Up);
597 values << 10;
598 keys.addKeyClick(qtKey: Qt::Key_Up);
599 values << 0;
600 QTest::newRow(dataTag: "data1") << true << 0 << 10 << 9 << keys << values;
601
602 keys.clear();
603 values.clear();
604 keys.addKeyClick(qtKey: Qt::Key_Delete); // doesn't emit because lineedit is empty so intermediate
605 keys.addKeyClick(ascii: '1');
606 keys.addKeyClick(qtKey: Qt::Key_Down);
607 keys.addKeyClick(qtKey: Qt::Key_Down);
608 values << 1 << 0;
609 QTest::newRow(dataTag: "data2") << false << 0 << 10 << 9 << keys << values;
610
611 keys.clear();
612 values.clear();
613 keys.addKeyClick(qtKey: Qt::Key_Delete);
614 keys.addKeyClick(ascii: '1');
615 keys.addKeyClick(qtKey: Qt::Key_Down);
616 keys.addKeyClick(qtKey: Qt::Key_Down);
617 values << 1 << 0 << 10;
618 QTest::newRow(dataTag: "data3") << true << 0 << 10 << 9 << keys << values;
619
620 keys.clear();
621 values.clear();
622 keys.addKeyClick(qtKey: Qt::Key_PageDown);
623 keys.addKeyClick(qtKey: Qt::Key_Down);
624 values << 0;
625 QTest::newRow(dataTag: "data4") << false << 0 << 10 << 6 << keys << values;
626
627 keys.clear();
628 values.clear();
629 keys.addKeyClick(qtKey: Qt::Key_PageDown);
630 keys.addKeyClick(qtKey: Qt::Key_Down);
631 values << 0 << 10;
632 QTest::newRow(dataTag: "data5") << true << 0 << 10 << 6 << keys << values;
633
634 keys.clear();
635 values.clear();
636 keys.addKeyClick(qtKey: Qt::Key_PageUp);
637 keys.addKeyClick(qtKey: Qt::Key_PageDown);
638 keys.addKeyClick(qtKey: Qt::Key_Down);
639 keys.addKeyClick(qtKey: Qt::Key_Up);
640 keys.addKeyClick(qtKey: Qt::Key_PageDown);
641 keys.addKeyClick(qtKey: Qt::Key_PageDown);
642 values << 10 << 0 << 10 << 0 << 10 << 0;
643 QTest::newRow(dataTag: "data6") << true << 0 << 10 << 6 << keys << values;
644
645}
646
647
648void tst_QSpinBox::setWrapping()
649{
650 QFETCH(bool, wrapping);
651 QFETCH(int, minimum);
652 QFETCH(int, maximum);
653 QFETCH(int, startValue);
654 QFETCH(QTestEventList, keys);
655 QFETCH(IntList, expected);
656
657 QSpinBox spin(0);
658 QVERIFY(!spin.wrapping());
659 spin.setMinimum(minimum);
660 spin.setMaximum(maximum);
661 spin.setValue(startValue);
662 spin.setWrapping(wrapping);
663 spin.show();
664 actualValues.clear();
665 connect(sender: &spin, SIGNAL(valueChanged(int)), receiver: this, SLOT(valueChangedHelper(int)));
666
667 keys.simulate(w: &spin);
668
669 QCOMPARE(actualValues.size(), expected.size());
670 for (int i=0; i<qMin(a: actualValues.size(), b: expected.size()); ++i) {
671 QCOMPARE(actualValues.at(i), expected.at(i));
672 }
673}
674
675void tst_QSpinBox::setSpecialValueText_data()
676{
677 QTest::addColumn<QString>(name: "specialValueText");
678 QTest::addColumn<int>(name: "minimum");
679 QTest::addColumn<int>(name: "maximum");
680 QTest::addColumn<int>(name: "value");
681 QTest::addColumn<QString>(name: "expected");
682 QTest::addColumn<bool>(name: "show");
683
684 QTest::newRow(dataTag: "data0") << QString() << 0 << 10 << 1 << "1" << false;
685 QTest::newRow(dataTag: "data1") << QString() << 0 << 10 << 1 << "1" << true;
686 QTest::newRow(dataTag: "data2") << "foo" << 0 << 10 << 0 << "foo" << false;
687 QTest::newRow(dataTag: "data3") << "foo" << 0 << 10 << 0 << "foo" << true;
688}
689
690void tst_QSpinBox::setSpecialValueText()
691{
692 QFETCH(QString, specialValueText);
693 QFETCH(int, minimum);
694 QFETCH(int, maximum);
695 QFETCH(int, value);
696 QFETCH(QString, expected);
697 QFETCH(bool, show);
698
699 QSpinBox spin(0);
700 spin.setSpecialValueText(specialValueText);
701 QCOMPARE(spin.specialValueText(), specialValueText);
702 spin.setMinimum(minimum);
703 spin.setMaximum(maximum);
704 spin.setValue(value);
705 if (show)
706 spin.show();
707
708 QCOMPARE(spin.text(), expected);
709}
710
711void tst_QSpinBox::setSingleStep_data()
712{
713 QTest::addColumn<int>(name: "singleStep");
714 QTest::addColumn<int>(name: "startValue");
715 QTest::addColumn<QTestEventList>(name: "keys");
716 QTest::addColumn<IntList>(name: "expected");
717 QTest::addColumn<bool>(name: "show");
718
719 QTestEventList keys;
720 IntList values;
721 keys.addKeyClick(qtKey: Qt::Key_Up);
722 keys.addKeyClick(qtKey: Qt::Key_Down);
723 keys.addKeyClick(qtKey: Qt::Key_Up);
724 values << 11 << 10 << 11;
725 QTest::newRow(dataTag: "data0") << 1 << 10 << keys << values << false;
726 QTest::newRow(dataTag: "data1") << 1 << 10 << keys << values << true;
727
728 keys.clear();
729 values.clear();
730 keys.addKeyClick(qtKey: Qt::Key_Up);
731 keys.addKeyClick(qtKey: Qt::Key_Down);
732 keys.addKeyClick(qtKey: Qt::Key_Up);
733 values << 12 << 10 << 12;
734 QTest::newRow(dataTag: "data2") << 2 << 10 << keys << values << false;
735 QTest::newRow(dataTag: "data3") << 2 << 10 << keys << values << true;
736}
737
738void tst_QSpinBox::setSingleStep()
739{
740 QFETCH(int, singleStep);
741 QFETCH(int, startValue);
742 QFETCH(QTestEventList, keys);
743 QFETCH(IntList, expected);
744 QFETCH(bool, show);
745
746 QSpinBox spin(0);
747 actualValues.clear();
748 spin.setSingleStep(singleStep);
749 QCOMPARE(spin.singleStep(), singleStep);
750 spin.setValue(startValue);
751 if (show)
752 spin.show();
753 connect(sender: &spin, SIGNAL(valueChanged(int)), receiver: this, SLOT(valueChangedHelper(int)));
754
755 QCOMPARE(actualValues.size(), 0);
756 keys.simulate(w: &spin);
757 QCOMPARE(actualValues.size(), expected.size());
758 for (int i=0; i<qMin(a: actualValues.size(), b: expected.size()); ++i) {
759 QCOMPARE(actualValues.at(i), expected.at(i));
760 }
761}
762
763void tst_QSpinBox::setMinMax_data()
764{
765 QTest::addColumn<int>(name: "startValue");
766 QTest::addColumn<int>(name: "mini");
767 QTest::addColumn<int>(name: "maxi");
768 QTest::addColumn<QTestEventList>(name: "keys");
769 QTest::addColumn<int>(name: "expected");
770 QTest::addColumn<bool>(name: "show");
771
772 QTestEventList keys;
773 keys.addKeyClick(qtKey: Qt::Key_Up);
774 keys.addKeyClick(qtKey: Qt::Key_Up);
775 keys.addKeyClick(qtKey: Qt::Key_Up);
776 keys.addKeyClick(qtKey: Qt::Key_Up);
777 keys.addKeyClick(qtKey: Qt::Key_Up);
778 QTest::newRow(dataTag: "data0") << 1 << INT_MIN << 2 << keys << 2 << false;
779 QTest::newRow(dataTag: "data1") << 1 << INT_MIN << 2 << keys << 2 << true;
780
781 keys.clear();
782 QTest::newRow(dataTag: "data2") << 2 << INT_MAX - 2 << INT_MAX << keys << INT_MAX - 2 << false;
783 QTest::newRow(dataTag: "data3") << 2 << INT_MAX - 2 << INT_MAX << keys << INT_MAX - 2 << true;
784}
785
786void tst_QSpinBox::setMinMax()
787{
788 QFETCH(int, startValue);
789 QFETCH(int, mini);
790 QFETCH(int, maxi);
791 QFETCH(QTestEventList, keys);
792 QFETCH(int, expected);
793 QFETCH(bool, show);
794
795 QSpinBox spin(0);
796 spin.setValue(startValue);
797 spin.setMinimum(mini);
798 spin.setMaximum(maxi);
799 QCOMPARE(spin.minimum(), mini);
800 QCOMPARE(spin.maximum(), maxi);
801 if (show)
802 spin.show();
803 keys.simulate(w: &spin);
804 QCOMPARE(spin.value(), expected);
805}
806
807void tst_QSpinBox::valueFromTextAndValidate_data()
808{
809 const int Intermediate = QValidator::Intermediate;
810 const int Invalid = QValidator::Invalid;
811 const int Acceptable = QValidator::Acceptable;
812
813 QTest::addColumn<QString>(name: "txt");
814 QTest::addColumn<int>(name: "state");
815 QTest::addColumn<int>(name: "mini");
816 QTest::addColumn<int>(name: "maxi");
817 QTest::addColumn<QString>(name: "expectedText"); // if empty we don't check
818
819 QTest::newRow(dataTag: "data0") << QString("2") << Intermediate << 3 << 5 << QString();
820 QTest::newRow(dataTag: "data1") << QString() << Intermediate << 0 << 100 << QString();
821 QTest::newRow(dataTag: "data2") << QString("asd") << Invalid << 0 << 100 << QString();
822 QTest::newRow(dataTag: "data3") << QString("2") << Acceptable << 0 << 100 << QString();
823 QTest::newRow(dataTag: "data4") << QString() << Intermediate << 0 << 1 << QString();
824 QTest::newRow(dataTag: "data5") << QString() << Invalid << 0 << 0 << QString();
825 QTest::newRow(dataTag: "data6") << QString("5") << Intermediate << 2004 << 2005 << QString();
826 QTest::newRow(dataTag: "data7") << QString("50") << Intermediate << 2004 << 2005 << QString();
827 QTest::newRow(dataTag: "data8") << QString("205") << Intermediate << 2004 << 2005 << QString();
828 QTest::newRow(dataTag: "data9") << QString("2005") << Acceptable << 2004 << 2005 << QString();
829 QTest::newRow(dataTag: "data10") << QString("3") << Intermediate << 2004 << 2005 << QString();
830 QTest::newRow(dataTag: "data11") << QString("-") << Intermediate << -20 << -10 << QString();
831 QTest::newRow(dataTag: "data12") << QString("-1") << Intermediate << -20 << -10 << QString();
832 QTest::newRow(dataTag: "data13") << QString("-5") << Intermediate << -20 << -10 << QString();
833 QTest::newRow(dataTag: "data14") << QString("-5") << Intermediate << -20 << -16 << QString();
834 QTest::newRow(dataTag: "data15") << QString("-2") << Intermediate << -20 << -16 << QString();
835 QTest::newRow(dataTag: "data16") << QString("2") << Invalid << -20 << -16 << QString();
836 QTest::newRow(dataTag: "data17") << QString() << Intermediate << -20 << -16 << QString();
837 QTest::newRow(dataTag: "data18") << QString(" 22") << Acceptable << 0 << 1000 << QString("22");
838 QTest::newRow(dataTag: "data19") << QString("22 ") << Acceptable << 0 << 1000 << QString("22");
839 QTest::newRow(dataTag: "data20") << QString(" 22 ") << Acceptable << 0 << 1000 << QString("22");
840 QTest::newRow(dataTag: "data21") << QString("2 2") << Invalid << 0 << 1000 << QString();
841}
842
843static QString stateName(int state)
844{
845 switch (state) {
846 case QValidator::Acceptable: return QString("Acceptable");
847 case QValidator::Intermediate: return QString("Intermediate");
848 case QValidator::Invalid: return QString("Invalid");
849 default: break;
850 }
851 qWarning(msg: "%s %d: this should never happen", __FILE__, __LINE__);
852 return QString();
853}
854
855void tst_QSpinBox::valueFromTextAndValidate()
856{
857 QFETCH(QString, txt);
858 QFETCH(int, state);
859 QFETCH(int, mini);
860 QFETCH(int, maxi);
861 QFETCH(QString, expectedText);
862
863 SpinBox sb(0);
864 sb.show();
865 sb.setRange(min: mini, max: maxi);
866 int unused = 0;
867 QCOMPARE(stateName(sb.validate(txt, unused)), stateName(state));
868 if (!expectedText.isEmpty())
869 QCOMPARE(txt, expectedText);
870}
871
872void tst_QSpinBox::locale_data()
873{
874 QTest::addColumn<QLocale>(name: "loc");
875 QTest::addColumn<int>(name: "value");
876 QTest::addColumn<QString>(name: "textFromVal");
877 QTest::addColumn<QString>(name: "text");
878 QTest::addColumn<int>(name: "valFromText");
879
880 QTest::newRow(dataTag: "data0") << QLocale(QLocale::NorwegianBokmal, QLocale::Norway) << 1234 << QString("1234") << QString("2345") << 2345;
881 QTest::newRow(dataTag: "data1") << QLocale(QLocale::German, QLocale::Germany) << 1234 << QString("1234") << QString("2345") << 2345;
882}
883
884void tst_QSpinBox::locale()
885{
886 QFETCH(QLocale, loc);
887 QFETCH(int, value);
888 QFETCH(QString, textFromVal);
889 QFETCH(QString, text);
890 QFETCH(int, valFromText);
891
892 QLocale old;
893
894 QLocale::setDefault(loc);
895 SpinBox box;
896 box.setMaximum(100000);
897 box.setValue(value);
898 QCOMPARE(box.cleanText(), textFromVal);
899
900 box.lineEdit()->setText(text);
901 QCOMPARE(box.cleanText(), text);
902 box.interpretText();
903
904 QCOMPARE(box.value(), valFromText);
905}
906
907
908void tst_QSpinBox::editingFinished()
909{
910 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
911 QSKIP("Wayland: This fails. Figure out why.");
912
913 QWidget testFocusWidget;
914 testFocusWidget.setObjectName(QLatin1String("tst_qspinbox"));
915 testFocusWidget.setWindowTitle(objectName());
916 testFocusWidget.resize(w: 200, h: 100);
917
918 QVBoxLayout *layout = new QVBoxLayout(&testFocusWidget);
919 QSpinBox *box = new QSpinBox(&testFocusWidget);
920 layout->addWidget(box);
921 QSpinBox *box2 = new QSpinBox(&testFocusWidget);
922 layout->addWidget(box2);
923
924 testFocusWidget.show();
925 QApplication::setActiveWindow(&testFocusWidget);
926 QVERIFY(QTest::qWaitForWindowActive(&testFocusWidget));
927 box->activateWindow();
928 box->setFocus();
929
930 QTRY_COMPARE(qApp->focusWidget(), (QWidget *)box);
931
932 QSignalSpy editingFinishedSpy1(box, SIGNAL(editingFinished()));
933 QSignalSpy editingFinishedSpy2(box2, SIGNAL(editingFinished()));
934
935 box->setFocus();
936 QTest::keyClick(widget: box, key: Qt::Key_Up);
937 QTest::keyClick(widget: box, key: Qt::Key_Up);
938
939 QCOMPARE(editingFinishedSpy1.count(), 0);
940 QCOMPARE(editingFinishedSpy2.count(), 0);
941
942 QTest::keyClick(widget: box2, key: Qt::Key_Up);
943 QTest::keyClick(widget: box2, key: Qt::Key_Up);
944 box2->setFocus();
945 QCOMPARE(editingFinishedSpy1.count(), 1);
946 box->setFocus();
947 QCOMPARE(editingFinishedSpy1.count(), 1);
948 QCOMPARE(editingFinishedSpy2.count(), 1);
949 QTest::keyClick(widget: box, key: Qt::Key_Up);
950 QCOMPARE(editingFinishedSpy1.count(), 1);
951 QCOMPARE(editingFinishedSpy2.count(), 1);
952 QTest::keyClick(widget: box, key: Qt::Key_Enter);
953 QCOMPARE(editingFinishedSpy1.count(), 2);
954 QCOMPARE(editingFinishedSpy2.count(), 1);
955 QTest::keyClick(widget: box, key: Qt::Key_Return);
956 QCOMPARE(editingFinishedSpy1.count(), 3);
957 QCOMPARE(editingFinishedSpy2.count(), 1);
958 box2->setFocus();
959 QCOMPARE(editingFinishedSpy1.count(), 4);
960 QCOMPARE(editingFinishedSpy2.count(), 1);
961 QTest::keyClick(widget: box2, key: Qt::Key_Enter);
962 QCOMPARE(editingFinishedSpy1.count(), 4);
963 QCOMPARE(editingFinishedSpy2.count(), 2);
964 QTest::keyClick(widget: box2, key: Qt::Key_Return);
965 QCOMPARE(editingFinishedSpy1.count(), 4);
966 QCOMPARE(editingFinishedSpy2.count(), 3);
967
968 testFocusWidget.hide();
969 QCOMPARE(editingFinishedSpy1.count(), 4);
970 QCOMPARE(editingFinishedSpy2.count(), 4);
971
972 //task203285
973 editingFinishedSpy1.clear();
974 testFocusWidget.show();
975 QVERIFY(QTest::qWaitForWindowActive(&testFocusWidget));
976 box->setKeyboardTracking(false);
977 qApp->setActiveWindow(&testFocusWidget);
978 testFocusWidget.activateWindow();
979 box->setFocus();
980 QTRY_VERIFY(box->hasFocus());
981 box->setValue(0);
982 QTest::keyClick(widget: box, key: '2');
983 QCOMPARE(box->text(), QLatin1String("20"));
984 box2->setFocus();
985 QTRY_VERIFY(qApp->focusWidget() != box);
986 QCOMPARE(box->text(), QLatin1String("20"));
987 QCOMPARE(editingFinishedSpy1.count(), 1);
988}
989
990void tst_QSpinBox::removeAll()
991{
992 SpinBox spin(0);
993 spin.setPrefix("foo");
994 spin.setSuffix("bar");
995 spin.setValue(2);
996 spin.show();
997#ifdef Q_OS_MAC
998 QTest::keyClick(&spin, Qt::Key_Left, Qt::ControlModifier);
999#else
1000 QTest::keyClick(widget: &spin, key: Qt::Key_Home);
1001#endif
1002
1003#ifdef Q_OS_MAC
1004 QTest::keyClick(&spin, Qt::Key_Right, Qt::ControlModifier|Qt::ShiftModifier);
1005#else
1006 QTest::keyClick(widget: &spin, key: Qt::Key_End, modifier: Qt::ShiftModifier);
1007#endif
1008
1009 QCOMPARE(spin.lineEdit()->selectedText(), QString("foo2bar"));
1010 QTest::keyClick(widget: &spin, key: Qt::Key_1);
1011 QCOMPARE(spin.text(), QString("foo1bar"));
1012}
1013
1014void tst_QSpinBox::startWithDash()
1015{
1016 SpinBox spin(0);
1017 spin.show();
1018#ifdef Q_OS_MAC
1019 QTest::keyClick(&spin, Qt::Key_Left, Qt::ControlModifier);
1020#else
1021 QTest::keyClick(widget: &spin, key: Qt::Key_Home);
1022#endif
1023 QCOMPARE(spin.text(), QString("0"));
1024 QTest::keyClick(widget: &spin, key: Qt::Key_Minus);
1025 QCOMPARE(spin.text(), QString("0"));
1026}
1027
1028void tst_QSpinBox::undoRedo()
1029{
1030 //test undo/redo feature (in conjunction with the "undoRedoEnabled" property)
1031 SpinBox spin(0);
1032 spin.show();
1033
1034 //the undo/redo is disabled by default
1035
1036 QCOMPARE(spin.value(), 0); //this is the default value
1037 QVERIFY(!spin.lineEdit()->isUndoAvailable());
1038 QVERIFY(!spin.lineEdit()->isRedoAvailable());
1039
1040 spin.lineEdit()->selectAll(); //ensures everything is selected and will be cleared by typing a key
1041 QTest::keyClick(widget: &spin, key: Qt::Key_1); //we put 1 into the spinbox
1042 QCOMPARE(spin.value(), 1);
1043 QVERIFY(spin.lineEdit()->isUndoAvailable());
1044
1045 //testing CTRL+Z (undo)
1046 int val = QKeySequence(QKeySequence::Undo)[0];
1047 Qt::KeyboardModifiers mods = (Qt::KeyboardModifiers)(val & Qt::KeyboardModifierMask);
1048 QTest::keyClick(widget: &spin, key: val & ~mods, modifier: mods);
1049
1050 QCOMPARE(spin.value(), 0);
1051 QVERIFY(!spin.lineEdit()->isUndoAvailable());
1052 QVERIFY(spin.lineEdit()->isRedoAvailable());
1053
1054 //testing CTRL+Y (redo)
1055 val = QKeySequence(QKeySequence::Redo)[0];
1056 mods = (Qt::KeyboardModifiers)(val & Qt::KeyboardModifierMask);
1057 QTest::keyClick(widget: &spin, key: val & ~mods, modifier: mods);
1058 QCOMPARE(spin.value(), 1);
1059 QVERIFY(!spin.lineEdit()->isRedoAvailable());
1060 QVERIFY(spin.lineEdit()->isUndoAvailable());
1061
1062 spin.setValue(55);
1063 QVERIFY(!spin.lineEdit()->isUndoAvailable());
1064 QVERIFY(!spin.lineEdit()->isRedoAvailable());
1065
1066 QTest::keyClick(widget: &spin, key: Qt::Key_Return);
1067 QTest::keyClick(widget: &spin, key: '1');
1068 QVERIFY(spin.lineEdit()->isUndoAvailable());
1069 QVERIFY(!spin.lineEdit()->isRedoAvailable());
1070 spin.lineEdit()->undo();
1071 QCOMPARE(spin.value(), 55);
1072 QVERIFY(!spin.lineEdit()->isUndoAvailable());
1073 QVERIFY(spin.lineEdit()->isRedoAvailable());
1074 spin.lineEdit()->redo();
1075 QCOMPARE(spin.value(), 1);
1076 QVERIFY(spin.lineEdit()->isUndoAvailable());
1077 QVERIFY(!spin.lineEdit()->isRedoAvailable());
1078}
1079
1080void tst_QSpinBox::specialValue()
1081{
1082 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
1083 QSKIP("Wayland: This fails. Figure out why.");
1084
1085 QString specialText="foo";
1086
1087 QWidget topWidget;
1088 QVBoxLayout layout(&topWidget);
1089 SpinBox spin(&topWidget);
1090 layout.addWidget(&spin);
1091 SpinBox box2(&topWidget);
1092 layout.addWidget(&box2);
1093
1094 spin.setSpecialValueText(specialText);
1095 spin.setMinimum(0);
1096 spin.setMaximum(100);
1097 spin.setValue(50);
1098 topWidget.show();
1099 //make sure we have the focus (even if editingFinished fails)
1100 qApp->setActiveWindow(&topWidget);
1101 topWidget.activateWindow();
1102 QVERIFY(QTest::qWaitForWindowActive(&topWidget));
1103 spin.setFocus();
1104
1105 QTest::keyClick(widget: &spin, key: Qt::Key_Return);
1106 QTest::keyClick(widget: &spin, key: '0');
1107 QCOMPARE(spin.text(), QString("0"));
1108 QTest::keyClick(widget: &spin, key: Qt::Key_Return);
1109 QCOMPARE(spin.text(), specialText);
1110
1111 spin.setValue(50);
1112 QTest::keyClick(widget: &spin, key: Qt::Key_Return);
1113 QTest::keyClick(widget: &spin, key: '0');
1114 QCOMPARE(spin.text(), QString("0"));
1115 QTest::keyClick(widget: spin.lineEdit(), key: Qt::Key_Tab);
1116 QCOMPARE(spin.text(), specialText);
1117
1118 spin.setValue(50);
1119 spin.setFocus();
1120 QTest::keyClick(widget: &spin, key: Qt::Key_Return);
1121 QTest::keyClick(widget: &spin, key: '0');
1122 QCOMPARE(spin.text(), QString("0"));
1123 box2.setFocus();
1124 QCOMPARE(spin.text(), specialText);
1125}
1126
1127void tst_QSpinBox::textFromValue()
1128{
1129 SpinBox spinBox;
1130 QCOMPARE(spinBox.textFromValue(INT_MIN), QString::number(INT_MIN));
1131}
1132
1133class sizeHint_SpinBox : public QSpinBox
1134{
1135public:
1136 QSize sizeHint() const
1137 {
1138 ++sizeHintRequests;
1139 return QSpinBox::sizeHint();
1140 }
1141 mutable int sizeHintRequests;
1142};
1143
1144void tst_QSpinBox::sizeHint()
1145{
1146 QWidget *widget = new QWidget;
1147 QHBoxLayout *layout = new QHBoxLayout(widget);
1148 sizeHint_SpinBox *spinBox = new sizeHint_SpinBox;
1149 layout->addWidget(spinBox);
1150 widget->show();
1151 QVERIFY(QTest::qWaitForWindowExposed(widget));
1152
1153 // Prefix
1154 spinBox->sizeHintRequests = 0;
1155 spinBox->setPrefix(QLatin1String("abcdefghij"));
1156 qApp->processEvents();
1157 QTRY_VERIFY(spinBox->sizeHintRequests > 0);
1158
1159 // Suffix
1160 spinBox->sizeHintRequests = 0;
1161 spinBox->setSuffix(QLatin1String("abcdefghij"));
1162 qApp->processEvents();
1163 QTRY_VERIFY(spinBox->sizeHintRequests > 0);
1164
1165 // Range
1166 spinBox->sizeHintRequests = 0;
1167 spinBox->setRange(min: 0, max: 1234567890);
1168 spinBox->setValue(spinBox->maximum());
1169 qApp->processEvents();
1170 QTRY_VERIFY(spinBox->sizeHintRequests > 0);
1171
1172 delete widget;
1173}
1174
1175void tst_QSpinBox::taskQTBUG_5008_textFromValueAndValidate()
1176{
1177 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
1178 QSKIP("Wayland: This fails. Figure out why.");
1179
1180 class DecoratedSpinBox : public QSpinBox
1181 {
1182 public:
1183 DecoratedSpinBox()
1184 {
1185 setLocale(QLocale::French);
1186 setMaximum(100000000);
1187 setValue(1000000);
1188 }
1189
1190 QLineEdit *lineEdit() const
1191 {
1192 return QSpinBox::lineEdit();
1193 }
1194
1195 //we use the French delimiters here
1196 QString textFromValue (int value) const
1197 {
1198 return locale().toString(i: value);
1199 }
1200
1201 } spinbox;
1202 spinbox.show();
1203 spinbox.activateWindow();
1204 spinbox.setFocus();
1205 QApplication::setActiveWindow(&spinbox);
1206 QVERIFY(QTest::qWaitForWindowActive(&spinbox));
1207 QVERIFY(spinbox.hasFocus());
1208 QTRY_COMPARE(static_cast<QWidget *>(&spinbox), QApplication::activeWindow());
1209 QCOMPARE(spinbox.text(), spinbox.locale().toString(spinbox.value()));
1210 spinbox.lineEdit()->setCursorPosition(2); //just after the first thousand separator
1211 QTest::keyClick(widget: static_cast<QWidget *>(0), key: Qt::Key_0); // let's insert a 0
1212 QCOMPARE(spinbox.value(), 10000000); //it's been multiplied by 10
1213 spinbox.clearFocus(); //make sure the value is correctly formatted
1214 QCOMPARE(spinbox.text(), spinbox.locale().toString(spinbox.value()));
1215}
1216
1217void tst_QSpinBox::integerOverflow()
1218{
1219 QSpinBox sb;
1220 sb.setRange(INT_MIN, INT_MAX);
1221
1222 sb.setValue(INT_MAX - 1);
1223 sb.stepUp();
1224 QCOMPARE(sb.value(), INT_MAX);
1225 sb.stepUp();
1226 QCOMPARE(sb.value(), INT_MAX);
1227
1228 sb.setValue(INT_MIN + 1);
1229 sb.stepDown();
1230 QCOMPARE(sb.value(), INT_MIN);
1231 sb.stepDown();
1232 QCOMPARE(sb.value(), INT_MIN);
1233
1234 sb.setValue(0);
1235 QCOMPARE(sb.value(), 0);
1236 sb.setSingleStep(INT_MAX);
1237 sb.stepUp();
1238 QCOMPARE(sb.value(), INT_MAX);
1239 sb.stepDown();
1240 QCOMPARE(sb.value(), 0);
1241 sb.stepDown();
1242 QCOMPARE(sb.value(), INT_MIN + 1);
1243 sb.stepDown();
1244 QCOMPARE(sb.value(), INT_MIN);
1245}
1246
1247void tst_QSpinBox::lineEditReturnPressed()
1248{
1249 SpinBox spinBox;
1250 QSignalSpy spyCurrentChanged(spinBox.lineEdit(), SIGNAL(returnPressed()));
1251 spinBox.show();
1252 QTest::keyClick(widget: &spinBox, key: Qt::Key_Return);
1253 QCOMPARE(spyCurrentChanged.count(), 1);
1254}
1255
1256void tst_QSpinBox::positiveSign()
1257{
1258 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
1259 QSKIP("Wayland: This fails. Figure out why.");
1260
1261 QSpinBox spinBox;
1262 spinBox.setRange(min: -20, max: 20);
1263 spinBox.setValue(-20);
1264 spinBox.show();
1265 QVERIFY(QTest::qWaitForWindowActive(&spinBox));
1266
1267 QTest::keyClick(widget: &spinBox, key: Qt::Key_End, modifier: Qt::ShiftModifier);
1268 QTest::keyClick(widget: &spinBox, key: Qt::Key_Plus, modifier: Qt::ShiftModifier);
1269 QTest::keyClick(widget: &spinBox, key: Qt::Key_2);
1270 QTest::keyClick(widget: &spinBox, key: Qt::Key_0);
1271 QCOMPARE(spinBox.text(), QLatin1String("+20"));
1272}
1273
1274void tst_QSpinBox::interpretOnLosingFocus()
1275{
1276 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
1277 QSKIP("Wayland: This fails. Figure out why.");
1278
1279 // QTBUG-55249: When typing an invalid value after QSpinBox::clear(),
1280 // it should be fixed up on losing focus.
1281
1282 static const int minimumValue = 10;
1283 static const int maximumValue = 20;
1284
1285 QWidget widget;
1286 widget.setWindowTitle(QTest::currentTestFunction());
1287 QVBoxLayout *layout = new QVBoxLayout(&widget);
1288 QLineEdit *focusDummy = new QLineEdit("focusDummy", &widget);
1289 layout->addWidget(focusDummy);
1290 SpinBox *spinBox = new SpinBox(&widget);
1291 spinBox->setRange(min: minimumValue, max: maximumValue);
1292 spinBox->setValue(minimumValue);
1293 layout->addWidget(spinBox);
1294 spinBox->clear();
1295 spinBox->setFocus();
1296 widget.show();
1297 QVERIFY(QTest::qWaitForWindowActive(&widget));
1298 QTest::keyClick(widget: spinBox, key: Qt::Key_1); // Too small
1299 focusDummy->setFocus();
1300 QCOMPARE(spinBox->value(), minimumValue);
1301 QCOMPARE(spinBox->lineEdit()->text().toInt(), minimumValue);
1302}
1303
1304void tst_QSpinBox::setGroupSeparatorShown_data()
1305{
1306 QTest::addColumn<QLocale::Language>(name: "lang");
1307 QTest::addColumn<QLocale::Country>(name: "country");
1308
1309 QTest::newRow(dataTag: "data0") << QLocale::English << QLocale::UnitedStates;
1310 QTest::newRow(dataTag: "data1") << QLocale::Swedish << QLocale::Sweden;
1311 QTest::newRow(dataTag: "data2") << QLocale::German << QLocale::Germany;
1312 QTest::newRow(dataTag: "data3") << QLocale::Georgian << QLocale::Georgia;
1313 QTest::newRow(dataTag: "data3") << QLocale::Macedonian << QLocale::Macedonia;
1314}
1315
1316void tst_QSpinBox::setGroupSeparatorShown()
1317{
1318 QFETCH(QLocale::Language, lang);
1319 QFETCH(QLocale::Country, country);
1320
1321 QLocale loc(lang, country);
1322 QLocale::setDefault(loc);
1323 SpinBox spinBox;
1324 spinBox.setMaximum(99999);
1325 spinBox.setValue(13000);
1326 spinBox.setGroupSeparatorShown(true);
1327 QCOMPARE(spinBox.lineEdit()->text(), spinBox.locale().toString(13000));
1328 QCOMPARE(spinBox.isGroupSeparatorShown(), true);
1329 QCOMPARE(spinBox.textFromValue(23421),spinBox.locale().toString(23421));
1330
1331 spinBox.setGroupSeparatorShown(false);
1332 QCOMPARE(spinBox.lineEdit()->text(), QStringLiteral("13000"));
1333 QCOMPARE(spinBox.isGroupSeparatorShown(), false);
1334
1335 spinBox.setMaximum(72000);
1336 spinBox.lineEdit()->setText(spinBox.locale().toString(i: 32000));
1337 QCOMPARE(spinBox.value()+1000, 33000);
1338
1339 spinBox.lineEdit()->setText(QStringLiteral("32000"));
1340 QCOMPARE(spinBox.value()+1000, 33000);
1341
1342 spinBox.lineEdit()->setText(QStringLiteral("32,000"));
1343 QCOMPARE(spinBox.value()+1000, 33000);
1344}
1345
1346void tst_QSpinBox::wheelEvents_data()
1347{
1348#if QT_CONFIG(wheelevent)
1349 QTest::addColumn<QPoint>(name: "angleDelta");
1350 QTest::addColumn<int>(name: "stepModifier");
1351 QTest::addColumn<Qt::KeyboardModifiers>(name: "modifier");
1352 QTest::addColumn<Qt::MouseEventSource>(name: "source");
1353 QTest::addColumn<int>(name: "start");
1354 QTest::addColumn<IntList>(name: "expectedValues");
1355
1356 const auto fractions = {false, true};
1357
1358 const auto directions = {true, false};
1359
1360 const auto modifierList = {Qt::NoModifier,
1361 Qt::ShiftModifier,
1362 Qt::ControlModifier,
1363 Qt::AltModifier,
1364 Qt::MetaModifier};
1365
1366 const auto validStepModifierList = {Qt::NoModifier,
1367 Qt::ControlModifier,
1368 Qt::ShiftModifier};
1369
1370 const auto sources = {Qt::MouseEventNotSynthesized,
1371 Qt::MouseEventSynthesizedBySystem,
1372 Qt::MouseEventSynthesizedByQt,
1373 Qt::MouseEventSynthesizedByApplication};
1374
1375 const int startValue = 0;
1376
1377 for (auto fraction : fractions) {
1378 for (auto up : directions) {
1379
1380 const int units = (fraction ? 60 : 120) * (up ? 1 : -1);
1381
1382 for (auto modifier : modifierList) {
1383
1384 const Qt::KeyboardModifiers modifiers(modifier);
1385
1386 const auto modifierName = modifierToName(modifier);
1387 if (modifierName.isEmpty())
1388 continue;
1389
1390 for (auto stepModifier : validStepModifierList) {
1391
1392 const auto stepModifierName = modifierToName(modifier: stepModifier);
1393 if (stepModifierName.isEmpty())
1394 continue;
1395
1396 const int steps = (modifier & stepModifier ? 10 : 1)
1397 * (up ? 1 : -1);
1398
1399 for (auto source : sources) {
1400
1401#ifdef Q_OS_MACOS
1402 QPoint angleDelta;
1403 if ((modifier & Qt::ShiftModifier) &&
1404 source == Qt::MouseEventNotSynthesized) {
1405 // On macOS the Shift modifier converts vertical
1406 // mouse wheel events to horizontal.
1407 angleDelta = { units, 0 };
1408 } else {
1409 // However, this is not the case for trackpad scroll
1410 // events.
1411 angleDelta = { 0, units };
1412 }
1413#else
1414 const QPoint angleDelta(0, units);
1415#endif
1416
1417 QLatin1String sourceName;
1418 switch (source) {
1419 case Qt::MouseEventNotSynthesized:
1420 sourceName = QLatin1String("NotSynthesized");
1421 break;
1422 case Qt::MouseEventSynthesizedBySystem:
1423 sourceName = QLatin1String("SynthesizedBySystem");
1424 break;
1425 case Qt::MouseEventSynthesizedByQt:
1426 sourceName = QLatin1String("SynthesizedByQt");
1427 break;
1428 case Qt::MouseEventSynthesizedByApplication:
1429 sourceName = QLatin1String("SynthesizedByApplication");
1430 break;
1431 default:
1432 qFatal(msg: "Unexpected wheel event source");
1433 continue;
1434 }
1435
1436 IntList expectedValues;
1437 if (fraction)
1438 expectedValues << startValue;
1439 expectedValues << startValue + steps;
1440
1441 QTest::addRow(format: "%s%s%sWith%sKeyboardModifier%s",
1442 fraction ? "half" : "full",
1443 up ? "Up" : "Down",
1444 stepModifierName.latin1(),
1445 modifierName.latin1(),
1446 sourceName.latin1())
1447 << angleDelta
1448 << static_cast<int>(stepModifier)
1449 << modifiers
1450 << source
1451 << startValue
1452 << expectedValues;
1453 }
1454 }
1455 }
1456 }
1457 }
1458#else
1459 QSKIP("Built with --no-feature-wheelevent");
1460#endif
1461}
1462
1463void tst_QSpinBox::wheelEvents()
1464{
1465#if QT_CONFIG(wheelevent)
1466 QFETCH(QPoint, angleDelta);
1467 QFETCH(int, stepModifier);
1468 QFETCH(Qt::KeyboardModifiers, modifier);
1469 QFETCH(Qt::MouseEventSource, source);
1470 QFETCH(int, start);
1471 QFETCH(IntList, expectedValues);
1472
1473 SpinBox spinBox;
1474 spinBox.setRange(min: -20, max: 20);
1475 spinBox.setValue(start);
1476
1477 QScopedPointer<StepModifierStyle, QScopedPointerDeleteLater> style(
1478 new StepModifierStyle);
1479 style->stepModifier = static_cast<Qt::KeyboardModifier>(stepModifier);
1480 spinBox.setStyle(style.data());
1481
1482 QWheelEvent event(QPointF(), QPointF(), QPoint(), angleDelta,
1483 Qt::NoButton, modifier, Qt::NoScrollPhase, false, source);
1484 for (int expected : expectedValues) {
1485 qApp->sendEvent(receiver: &spinBox, event: &event);
1486 QCOMPARE(spinBox.value(), expected);
1487 }
1488#else
1489 QSKIP("Built with --no-feature-wheelevent");
1490#endif
1491}
1492
1493void tst_QSpinBox::adaptiveDecimalStep()
1494{
1495 SpinBox spinBox;
1496 spinBox.setRange(min: -100000, max: 100000);
1497 spinBox.setStepType(SpinBox::StepType::AdaptiveDecimalStepType);
1498
1499 // Positive values
1500
1501 spinBox.setValue(0);
1502
1503 // Go from 0 to 100
1504 for (int i = 0; i < 100; i++) {
1505 QCOMPARE(spinBox.value(), i);
1506 spinBox.stepBy(steps: 1);
1507 }
1508
1509 // Go from 100 to 1000
1510 for (int i = 100; i < 1000; i += 10) {
1511 QCOMPARE(spinBox.value(), i);
1512 spinBox.stepBy(steps: 1);
1513 }
1514
1515 // Go from 1000 to 10000
1516 for (int i = 1000; i < 10000; i += 100) {
1517 QCOMPARE(spinBox.value(), i);
1518 spinBox.stepBy(steps: 1);
1519 }
1520
1521 // Test decreasing the values now
1522
1523 // Go from 10000 down to 1000
1524 for (int i = 10000; i > 1000; i -= 100) {
1525 QCOMPARE(spinBox.value(), i);
1526 spinBox.stepBy(steps: -1);
1527 }
1528
1529 // Go from 1000 down to 100
1530 for (int i = 1000; i > 100; i -= 10) {
1531 QCOMPARE(spinBox.value(), i);
1532 spinBox.stepBy(steps: -1);
1533 }
1534
1535 // Negative values
1536
1537 spinBox.setValue(0);
1538
1539 // Go from 0 to -100
1540 for (int i = 0; i > -100; i--) {
1541 QCOMPARE(spinBox.value(), i);
1542 spinBox.stepBy(steps: -1);
1543 }
1544
1545 // Go from -100 to -1000
1546 for (int i = -100; i > -1000; i -= 10) {
1547 QCOMPARE(spinBox.value(), i);
1548 spinBox.stepBy(steps: -1);
1549 }
1550
1551 // Go from 1000 to 10000
1552 for (int i = -1000; i > -10000; i -= 100) {
1553 QCOMPARE(spinBox.value(), i);
1554 spinBox.stepBy(steps: -1);
1555 }
1556
1557 // Test increasing the values now
1558
1559 // Go from -10000 up to -1000
1560 for (int i = -10000; i < -1000; i += 100) {
1561 QCOMPARE(spinBox.value(), i);
1562 spinBox.stepBy(steps: 1);
1563 }
1564
1565 // Go from -1000 up to -100
1566 for (int i = -1000; i < -100; i += 10) {
1567 QCOMPARE(spinBox.value(), i);
1568 spinBox.stepBy(steps: 1);
1569 }
1570}
1571
1572void tst_QSpinBox::stepModifierKeys_data()
1573{
1574 QTest::addColumn<int>(name: "startValue");
1575 QTest::addColumn<int>(name: "stepModifier");
1576 QTest::addColumn<QTestEventList>(name: "keys");
1577 QTest::addColumn<int>(name: "expectedValue");
1578
1579 const auto keyList = {Qt::Key_Up, Qt::Key_Down};
1580
1581 const auto modifierList = {Qt::NoModifier,
1582 Qt::ShiftModifier,
1583 Qt::ControlModifier,
1584 Qt::AltModifier,
1585 Qt::MetaModifier};
1586
1587 const auto validStepModifierList = {Qt::NoModifier,
1588 Qt::ControlModifier,
1589 Qt::ShiftModifier};
1590
1591 for (auto key : keyList) {
1592
1593 const bool up = key == Qt::Key_Up;
1594 Q_ASSERT(up || key == Qt::Key_Down);
1595
1596 const int startValue = up ? 0.0 : 10.0;
1597
1598 for (auto modifier : modifierList) {
1599
1600 QTestEventList keys;
1601 keys.addKeyClick(qtKey: key, modifiers: modifier);
1602
1603 const auto modifierName = modifierToName(modifier);
1604 if (modifierName.isEmpty())
1605 continue;
1606
1607 for (auto stepModifier : validStepModifierList) {
1608
1609 const auto stepModifierName = modifierToName(modifier: stepModifier);
1610 if (stepModifierName.isEmpty())
1611 continue;
1612
1613 const int steps = (modifier & stepModifier ? 10 : 1)
1614 * (up ? 1 : -1);
1615
1616 const int expectedValue = startValue + steps;
1617
1618 QTest::addRow(format: "%s%sWith%sKeyboardModifier",
1619 up ? "up" : "down",
1620 stepModifierName.latin1(),
1621 modifierName.latin1())
1622 << startValue
1623 << static_cast<int>(stepModifier)
1624 << keys
1625 << expectedValue;
1626 }
1627 }
1628 }
1629}
1630
1631void tst_QSpinBox::stepModifierKeys()
1632{
1633 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
1634 QSKIP("Wayland: This fails. Figure out why.");
1635
1636 QFETCH(int, startValue);
1637 QFETCH(int, stepModifier);
1638 QFETCH(QTestEventList, keys);
1639 QFETCH(int, expectedValue);
1640
1641 QSpinBox spin(0);
1642 spin.setValue(startValue);
1643
1644 QScopedPointer<StepModifierStyle, QScopedPointerDeleteLater> style(
1645 new StepModifierStyle);
1646 style->stepModifier = static_cast<Qt::KeyboardModifier>(stepModifier);
1647 spin.setStyle(style.data());
1648
1649 spin.show();
1650 QVERIFY(QTest::qWaitForWindowActive(&spin));
1651
1652 QCOMPARE(spin.value(), startValue);
1653 keys.simulate(w: &spin);
1654 QCOMPARE(spin.value(), expectedValue);
1655}
1656
1657void tst_QSpinBox::stepModifierButtons_data()
1658{
1659 QTest::addColumn<QStyle::SubControl>(name: "subControl");
1660 QTest::addColumn<int>(name: "stepModifier");
1661 QTest::addColumn<Qt::KeyboardModifiers>(name: "modifiers");
1662 QTest::addColumn<int>(name: "startValue");
1663 QTest::addColumn<int>(name: "expectedValue");
1664
1665 const auto subControls = {QStyle::SC_SpinBoxUp, QStyle::SC_SpinBoxDown};
1666
1667 const auto modifierList = {Qt::NoModifier,
1668 Qt::ShiftModifier,
1669 Qt::ControlModifier,
1670 Qt::AltModifier,
1671 Qt::MetaModifier};
1672
1673 const auto validStepModifierList = {Qt::NoModifier,
1674 Qt::ControlModifier,
1675 Qt::ShiftModifier};
1676
1677 for (auto subControl : subControls) {
1678
1679 const bool up = subControl == QStyle::SC_SpinBoxUp;
1680 Q_ASSERT(up || subControl == QStyle::SC_SpinBoxDown);
1681
1682 const int startValue = up ? 0 : 10;
1683
1684 for (auto modifier : modifierList) {
1685
1686 const Qt::KeyboardModifiers modifiers(modifier);
1687
1688 const auto modifierName = modifierToName(modifier);
1689 if (modifierName.isEmpty())
1690 continue;
1691
1692 for (auto stepModifier : validStepModifierList) {
1693
1694 const auto stepModifierName = modifierToName(modifier: stepModifier);
1695 if (stepModifierName.isEmpty())
1696 continue;
1697
1698 const int steps = (modifier & stepModifier ? 10 : 1)
1699 * (up ? 1 : -1);
1700
1701 const int expectedValue = startValue + steps;
1702
1703 QTest::addRow(format: "%s%sWith%sKeyboardModifier",
1704 up ? "up" : "down",
1705 stepModifierName.latin1(),
1706 modifierName.latin1())
1707 << subControl
1708 << static_cast<int>(stepModifier)
1709 << modifiers
1710 << startValue
1711 << expectedValue;
1712 }
1713 }
1714 }
1715}
1716
1717void tst_QSpinBox::stepModifierButtons()
1718{
1719 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
1720 QSKIP("Wayland: This fails. Figure out why.");
1721
1722 QFETCH(QStyle::SubControl, subControl);
1723 QFETCH(int, stepModifier);
1724 QFETCH(Qt::KeyboardModifiers, modifiers);
1725 QFETCH(int, startValue);
1726 QFETCH(int, expectedValue);
1727
1728 SpinBox spin(0);
1729 spin.setRange(min: -20, max: 20);
1730 spin.setValue(startValue);
1731
1732 QScopedPointer<StepModifierStyle, QScopedPointerDeleteLater> style(
1733 new StepModifierStyle);
1734 style->stepModifier = static_cast<Qt::KeyboardModifier>(stepModifier);
1735 spin.setStyle(style.data());
1736
1737 spin.show();
1738 QVERIFY(QTest::qWaitForWindowActive(&spin));
1739
1740 QStyleOptionSpinBox spinBoxStyleOption;
1741 spin.initStyleOption(option: &spinBoxStyleOption);
1742
1743 const QRect buttonRect = spin.style()->subControlRect(
1744 cc: QStyle::CC_SpinBox, opt: &spinBoxStyleOption, sc: subControl, widget: &spin);
1745
1746 QCOMPARE(spin.value(), startValue);
1747 QTest::mouseClick(widget: &spin, button: Qt::LeftButton, stateKey: modifiers, pos: buttonRect.center());
1748 QCOMPARE(spin.value(), expectedValue);
1749}
1750
1751void tst_QSpinBox::stepModifierPressAndHold_data()
1752{
1753 QTest::addColumn<QStyle::SubControl>(name: "subControl");
1754 QTest::addColumn<int>(name: "stepModifier");
1755 QTest::addColumn<Qt::KeyboardModifiers>(name: "modifiers");
1756 QTest::addColumn<int>(name: "expectedStepModifier");
1757
1758 const auto subControls = {QStyle::SC_SpinBoxUp, QStyle::SC_SpinBoxDown};
1759
1760 const auto modifierList = {Qt::NoModifier,
1761 Qt::ShiftModifier,
1762 Qt::ControlModifier,
1763 Qt::AltModifier,
1764 Qt::MetaModifier};
1765
1766 const auto validStepModifierList = {Qt::NoModifier,
1767 Qt::ControlModifier,
1768 Qt::ShiftModifier};
1769
1770 for (auto subControl : subControls) {
1771
1772 const bool up = subControl == QStyle::SC_SpinBoxUp;
1773 Q_ASSERT(up || subControl == QStyle::SC_SpinBoxDown);
1774
1775 for (auto modifier : modifierList) {
1776
1777 const Qt::KeyboardModifiers modifiers(modifier);
1778
1779 const auto modifierName = modifierToName(modifier);
1780 if (modifierName.isEmpty())
1781 continue;
1782
1783 for (auto stepModifier : validStepModifierList) {
1784
1785 const auto stepModifierName = modifierToName(modifier: stepModifier);
1786 if (stepModifierName.isEmpty())
1787 continue;
1788
1789 const int steps = (modifier & stepModifier ? 10 : 1)
1790 * (up ? 1 : -1);
1791
1792 QTest::addRow(format: "%s%sWith%sKeyboardModifier",
1793 up ? "up" : "down",
1794 stepModifierName.latin1(),
1795 modifierName.latin1())
1796 << subControl
1797 << static_cast<int>(stepModifier)
1798 << modifiers
1799 << steps;
1800 }
1801 }
1802 }
1803}
1804
1805void tst_QSpinBox::stepModifierPressAndHold()
1806{
1807 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
1808 QSKIP("Wayland: This fails. Figure out why.");
1809
1810 QFETCH(QStyle::SubControl, subControl);
1811 QFETCH(int, stepModifier);
1812 QFETCH(Qt::KeyboardModifiers, modifiers);
1813 QFETCH(int, expectedStepModifier);
1814
1815 SpinBox spin(0);
1816 spin.setRange(min: -100, max: 100);
1817 spin.setValue(0);
1818
1819 QScopedPointer<StepModifierStyle, QScopedPointerDeleteLater> stepModifierStyle(
1820 new StepModifierStyle(new PressAndHoldStyle));
1821 stepModifierStyle->stepModifier = static_cast<Qt::KeyboardModifier>(stepModifier);
1822 spin.setStyle(stepModifierStyle.data());
1823
1824 QSignalSpy spy(&spin, QOverload<int>::of(ptr: &SpinBox::valueChanged));
1825 // TODO: remove debug output when QTBUG-69492 is fixed
1826 connect(sender: &spin, signal: QOverload<int>::of(ptr: &SpinBox::valueChanged), slot: [=]() {
1827 qDebug() << QTime::currentTime() << "valueChanged emitted";
1828 });
1829
1830 spin.show();
1831 QVERIFY(QTest::qWaitForWindowActive(&spin));
1832
1833 QStyleOptionSpinBox spinBoxStyleOption;
1834 spin.initStyleOption(option: &spinBoxStyleOption);
1835
1836 const QRect buttonRect = spin.style()->subControlRect(
1837 cc: QStyle::CC_SpinBox, opt: &spinBoxStyleOption, sc: subControl, widget: &spin);
1838
1839 // TODO: remove debug output when QTBUG-69492 is fixed
1840 qDebug() << "QGuiApplication::focusWindow():" << QGuiApplication::focusWindow();
1841 qDebug() << "QGuiApplication::topLevelWindows():" << QGuiApplication::topLevelWindows();
1842 QTest::mousePress(widget: &spin, button: Qt::LeftButton, stateKey: modifiers, pos: buttonRect.center());
1843 QTRY_VERIFY2(spy.length() >= 3, qPrintable(QString::fromLatin1(
1844 "Expected valueChanged() to be emitted 3 or more times, but it was only emitted %1 times").arg(spy.length())));
1845 QTest::mouseRelease(widget: &spin, button: Qt::LeftButton, stateKey: modifiers, pos: buttonRect.center());
1846
1847 const auto value = spy.last().at(i: 0);
1848 QVERIFY(value.type() == QVariant::Int);
1849 QCOMPARE(value.toInt(), spy.length() * expectedStepModifier);
1850}
1851
1852QTEST_MAIN(tst_QSpinBox)
1853#include "tst_qspinbox.moc"
1854

source code of qtbase/tests/auto/widgets/widgets/qspinbox/tst_qspinbox.cpp