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"