File indexing completed on 2024-05-05 03:53:33

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2007 David Faure <faure@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include <QSignalSpy>
0009 #include <QTest>
0010 #include <khistorycombobox.h>
0011 #include <klineedit.h>
0012 
0013 class KTestComboBox : public KComboBox
0014 {
0015 public:
0016     KTestComboBox(bool rw, QWidget *parent = nullptr)
0017         : KComboBox(rw, parent)
0018     {
0019     }
0020     KCompletionBase *delegate() const
0021     {
0022         return KCompletionBase::delegate();
0023     }
0024 };
0025 
0026 class KComboBox_UnitTest : public QObject
0027 {
0028     Q_OBJECT
0029 
0030 private:
0031     void testComboReturnPressed(bool ctorArg)
0032     {
0033         KComboBox w(ctorArg /*initial value for editable*/);
0034         w.setEditable(true);
0035         w.setCompletionMode(KCompletion::CompletionPopup);
0036         w.addItem(QStringLiteral("Hello world"));
0037         QVERIFY(w.lineEdit());
0038         auto lineEdit = qobject_cast<KLineEdit *>(w.lineEdit());
0039         QVERIFY(lineEdit);
0040 
0041         // set editable again, don't recreate the line edit
0042         w.setEditable(true);
0043         QCOMPARE(w.lineEdit(), lineEdit);
0044         // KLineEdit signals
0045         QSignalSpy qReturnPressedSpy(w.lineEdit(), &QLineEdit::returnPressed);
0046 
0047         QSignalSpy kEditReturnKeyPressedSpy(lineEdit, &KLineEdit::returnKeyPressed);
0048 
0049         // KComboBox signals
0050         QSignalSpy comboReturnPressedStringSpy(&w, qOverload<const QString &>(&KComboBox::returnPressed));
0051 
0052         QSignalSpy comboActivatedSpy(&w, &QComboBox::textActivated);
0053         QTest::keyClick(&w, Qt::Key_Return);
0054         QCOMPARE(qReturnPressedSpy.count(), 1);
0055 
0056         QCOMPARE(kEditReturnKeyPressedSpy.count(), 1);
0057         QCOMPARE(kEditReturnKeyPressedSpy.at(0).at(0).toString(), QStringLiteral("Hello world"));
0058 
0059         QCOMPARE(comboReturnPressedStringSpy.count(), 1);
0060         QCOMPARE(comboReturnPressedStringSpy[0][0].toString(), QString("Hello world"));
0061 
0062         QCOMPARE(comboActivatedSpy.count(), 1);
0063         QCOMPARE(comboActivatedSpy[0][0].toString(), QString("Hello world"));
0064     }
0065 
0066 private Q_SLOTS:
0067     void testComboReturnPressed()
0068     {
0069         testComboReturnPressed(false);
0070         testComboReturnPressed(true);
0071     }
0072 
0073     void testHistoryComboReturnPressed()
0074     {
0075         KHistoryComboBox w;
0076         QVERIFY(qobject_cast<KLineEdit *>(w.lineEdit()));
0077         QSignalSpy comboReturnPressedStringSpy(&w, qOverload<const QString &>(&KComboBox::returnPressed));
0078         connect(&w, &KHistoryComboBox::textActivated, &w, &KHistoryComboBox::addToHistory);
0079         QSignalSpy comboActivatedSpy(&w, &QComboBox::textActivated);
0080         QTest::keyClicks(&w, QStringLiteral("Hello world"));
0081         QTest::keyClick(&w, Qt::Key_Return);
0082         qApp->processEvents(); // QueuedConnection in KHistoryComboBox
0083         QCOMPARE(comboReturnPressedStringSpy.count(), 1);
0084         QCOMPARE(comboReturnPressedStringSpy[0][0].toString(), QString("Hello world"));
0085 
0086         QCOMPARE(comboActivatedSpy.count(), 1);
0087         QCOMPARE(comboActivatedSpy[0][0].toString(), QString("Hello world"));
0088     }
0089 
0090     void testHistoryComboKeyUp()
0091     {
0092         KHistoryComboBox w;
0093         QStringList items;
0094         items << QStringLiteral("One") << QStringLiteral("Two") << QStringLiteral("Three") << QStringLiteral("Four");
0095         w.addItems(items);
0096         QSignalSpy currentIndexChangedSpy(&w, &QComboBox::currentIndexChanged);
0097         w.completionObject()->setItems(items);
0098         QCOMPARE(w.currentIndex(), 0);
0099         QTest::keyClick(&w, Qt::Key_Up);
0100         QCOMPARE(w.currentIndex(), 1);
0101         QCOMPARE(currentIndexChangedSpy.count(), 1);
0102         QCOMPARE(currentIndexChangedSpy[0][0].toInt(), 1);
0103     }
0104 
0105     void testHistoryComboInsertItems()
0106     {
0107         KHistoryComboBox combo;
0108         // uic generates code like this, let's make sure it compiles
0109         combo.insertItems(0, QStringList() << QStringLiteral("foo"));
0110     }
0111 
0112     void testHistoryComboReset()
0113     {
0114         // It only tests that it doesn't crash
0115         // TODO: Finish
0116         KHistoryComboBox combo;
0117         QStringList items;
0118         items << QStringLiteral("One") << QStringLiteral("Two");
0119         combo.addItems(items);
0120         combo.reset();
0121     }
0122 
0123     void testDeleteLineEdit()
0124     {
0125         // Test for KCombo's KLineEdit destruction
0126         KTestComboBox *testCombo = new KTestComboBox(true, nullptr); // rw, with KLineEdit
0127         testCombo->setEditable(false); // destroys our KLineEdit, with deleteLater
0128         qApp->sendPostedEvents(nullptr, QEvent::DeferredDelete);
0129         QVERIFY(testCombo->KTestComboBox::delegate() == nullptr);
0130         delete testCombo; // not needed anymore
0131     }
0132 
0133     void testLineEditCompletion()
0134     {
0135         QFETCH(bool, editable);
0136         QPointer<KCompletion> completion;
0137         QPointer<KLineEdit> lineEdit;
0138         QPointer<KLineEdit> lineEdit2;
0139 
0140         {
0141             // Test for KCombo's KLineEdit inheriting the completion object of the parent
0142             KTestComboBox testCombo(editable, nullptr);
0143 
0144             // we only have a line edit when we are editable already
0145             QCOMPARE(static_cast<bool>(testCombo.lineEdit()), editable);
0146 
0147             // we can always get a completion object
0148             // NOTE: for an editable combo, this refers to the completion object of
0149             // the internal line edit
0150             // NOTE: for a not-yet-editable combo, this refers to the completion object
0151             // that belongs to the combo directly
0152             completion = testCombo.completionObject();
0153             QVERIFY(completion);
0154 
0155             // make editable
0156             testCombo.setEditable(true);
0157             QVERIFY(completion); // completion is still alive
0158 
0159             // verify that the completion object was set on the line edit
0160             lineEdit = qobject_cast<KLineEdit *>(testCombo.lineEdit());
0161             QVERIFY(lineEdit);
0162             QVERIFY(lineEdit->compObj());
0163             QCOMPARE(lineEdit->compObj(), completion.data());
0164             QCOMPARE(testCombo.completionObject(), completion.data());
0165 
0166             // don't lose the completion and don't crash when we set a new line edit
0167             // NOTE: only reuse the completion when it belongs to the combo box
0168             lineEdit2 = new KLineEdit(&testCombo);
0169             QVERIFY(!lineEdit2->compObj());
0170             testCombo.setLineEdit(lineEdit2);
0171             QVERIFY(!lineEdit); // first line edit got deleted now
0172             if (editable) {
0173                 QVERIFY(!completion); // got deleted with the line edit
0174                 // but we get a new one from the second line edit
0175                 completion = testCombo.completionObject();
0176             }
0177             QVERIFY(completion);
0178             QCOMPARE(lineEdit2->compObj(), completion.data());
0179             QCOMPARE(testCombo.completionObject(), completion.data());
0180         }
0181 
0182         // ensure nothing gets leaked
0183         QVERIFY(!completion);
0184         QVERIFY(!lineEdit2);
0185     }
0186 
0187     void testLineEditCompletion_data()
0188     {
0189         QTest::addColumn<bool>("editable");
0190         QTest::newRow("deferred-editable") << false;
0191         QTest::newRow("editable") << true;
0192     }
0193 
0194     void testSelectionResetOnReturn()
0195     {
0196         // void QComboBoxPrivate::_q_returnPressed() calls lineEdit->deselect()
0197         KHistoryComboBox *testCombo = new KHistoryComboBox(true, nullptr);
0198         QCOMPARE(testCombo->insertPolicy(), QComboBox::NoInsert); // not the Qt default; KHistoryComboBox changes that
0199         QTest::keyClicks(testCombo, QStringLiteral("Hello world"));
0200         testCombo->lineEdit()->setSelection(5, 3);
0201         QVERIFY(testCombo->lineEdit()->hasSelectedText());
0202         QTest::keyClick(testCombo, Qt::Key_Return);
0203         // Changed in Qt5: it only does that if insertionPolicy isn't NoInsert.
0204         // Should we add a lineEdit()->deselect() in KHistoryComboBox? Why does this matter?
0205         QEXPECT_FAIL("", "Qt5: QComboBox doesn't deselect text anymore on returnPressed", Continue);
0206         QVERIFY(!testCombo->lineEdit()->hasSelectedText());
0207     }
0208 };
0209 
0210 QTEST_MAIN(KComboBox_UnitTest)
0211 
0212 #include "kcombobox_unittest.moc"