File indexing completed on 2025-01-12 04:34:37
0001 /* 0002 SPDX-FileCopyrightText: 2021 David Faure <faure@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "textselectiontest.h" 0008 #include "delegateutils/textselection.h" 0009 #include "model/messagesmodel.h" 0010 0011 #include <QSignalSpy> 0012 #include <QTest> 0013 #include <QTextCursor> 0014 #include <QTextDocument> 0015 #include <QTextDocumentFragment> 0016 0017 QTEST_MAIN(TextSelectionTest) 0018 0019 TextSelectionTest::TextSelectionTest(QObject *parent) 0020 : QObject(parent) 0021 { 0022 } 0023 0024 class TestFactory : public DocumentFactoryInterface 0025 { 0026 public: 0027 explicit TestFactory(int numRows) 0028 { 0029 mTextDocs.resize(numRows); 0030 } 0031 /** 0032 * Creates (or retrieves from a cache) the QTextDocument for a given @p index. 0033 * @param width The width for layouting that QTextDocument. -1 if no layouting is desired (e.g. for converting to text or HTML) 0034 * @param widget The view to update when fetching thread context on demand. nullptr if this isn't needed (e.g. from SelectionManager) 0035 * @return the QTextDocument. Ownership remains with the cache, don't delete it. 0036 */ 0037 QTextDocument *documentForIndex(const QModelIndex &index) const override 0038 { 0039 const int row = index.row(); 0040 if (!mTextDocs[row]) { 0041 const QString text = index.data(MessagesModel::MessageConvertedText).toString(); 0042 auto doc = std::make_unique<QTextDocument>(); 0043 doc->setHtml(text); 0044 mTextDocs[row] = std::move(doc); 0045 } 0046 return mTextDocs[row].get(); 0047 } 0048 0049 private: 0050 mutable std::vector<std::unique_ptr<QTextDocument>> mTextDocs; 0051 }; 0052 0053 static QStandardItem *newItem(const QString &text) 0054 { 0055 auto item = new QStandardItem; 0056 item->setData(text, MessagesModel::MessageConvertedText); 0057 return item; 0058 } 0059 0060 void TextSelectionTest::initTestCase() 0061 { 0062 int row = 0; 0063 model.setItem(row++, 0, newItem(QStringLiteral("Line 0"))); 0064 model.setItem(row++, 0, newItem(QStringLiteral("Line 1 <b>bold</b>"))); 0065 model.setItem(row++, 0, newItem(QStringLiteral("Line 2 <b>bold</b>"))); 0066 model.setItem(row++, 0, newItem(QStringLiteral("Line 3"))); 0067 } 0068 0069 void TextSelectionTest::testChangingSelection() 0070 { 0071 // GIVEN 0072 0073 const QModelIndex index0 = model.index(0, 0); 0074 const QModelIndex index1 = model.index(1, 0); 0075 const QModelIndex index2 = model.index(2, 0); 0076 const QModelIndex index3 = model.index(3, 0); 0077 0078 TestFactory factory(model.rowCount()); 0079 TextSelection selection; 0080 selection.setTextHelperFactory(&factory); 0081 QSignalSpy spy(&selection, &TextSelection::repaintNeeded); 0082 0083 // WHEN/THEN 0084 0085 selection.setStart(index1, 3); 0086 QCOMPARE(selection.selectedText(TextSelection::Text), QString()); 0087 QVERIFY(!selection.hasSelection()); 0088 selection.setEnd(index1, 4); 0089 QCOMPARE(selection.selectedText(TextSelection::Text), QStringLiteral("e")); 0090 QCOMPARE(spy.count(), 0); 0091 QVERIFY(selection.hasSelection()); 0092 0093 selection.setEnd(index1, 9); 0094 QCOMPARE(selection.selectedText(TextSelection::Text), QStringLiteral("e 1 bo")); 0095 QCOMPARE(spy.count(), 0); 0096 0097 spy.clear(); 0098 selection.setEnd(index3, 2); 0099 QCOMPARE(selection.selectedText(TextSelection::Text), QStringLiteral("e 1 bold\nLine 2 bold\nLi")); 0100 QCOMPARE(spy.count(), 2); 0101 QCOMPARE(spy.at(0).at(0).value<QModelIndex>().row(), 1); // line 1 is now fully selected, needs repaint 0102 QCOMPARE(spy.at(1).at(0).value<QModelIndex>().row(), 2); // line 2 was selected too, needs repaint 0103 0104 spy.clear(); 0105 selection.setEnd(index2, 2); 0106 QCOMPARE(selection.selectedText(TextSelection::Text), QStringLiteral("e 1 bold\nLi")); 0107 QCOMPARE(spy.count(), 1); 0108 QCOMPARE(spy.at(0).at(0).value<QModelIndex>().row(), 3); // line 3 is no longer selected 0109 0110 auto selectionForRow = [&](int row) { 0111 const QModelIndex index = model.index(row, 0); 0112 const QTextCursor cursor = selection.selectionForIndex(index, factory.documentForIndex(index)); 0113 return cursor.selection().toPlainText(); 0114 }; 0115 QCOMPARE(selectionForRow(0), QString()); 0116 QCOMPARE(selectionForRow(1), QStringLiteral("e 1 bold")); 0117 QCOMPARE(selectionForRow(2), QStringLiteral("Li")); 0118 QCOMPARE(selectionForRow(3), QString()); 0119 QVERIFY(!selection.contains(index0, 0)); 0120 QVERIFY(!selection.contains(index1, 2)); 0121 QVERIFY(selection.contains(index1, 3)); 0122 QVERIFY(selection.contains(index1, 300)); 0123 QVERIFY(selection.contains(index2, 1)); 0124 QVERIFY(selection.contains(index2, 2)); // (arguable, end of selection) 0125 QVERIFY(!selection.contains(index2, 3)); 0126 QVERIFY(!selection.contains(index3, 0)); 0127 0128 // Now move up and reverse selection 0129 spy.clear(); 0130 selection.setEnd(index0, 1); 0131 QCOMPARE(selection.selectedText(TextSelection::Text), QStringLiteral("ine 0\nLin")); 0132 QCOMPARE(spy.count(), 2); 0133 QCOMPARE(spy.at(0).at(0).value<QModelIndex>().row(), 1); // line 1's selection is different 0134 QCOMPARE(spy.at(1).at(0).value<QModelIndex>().row(), 2); // line 2 is no longer selected 0135 QCOMPARE(selectionForRow(0), QStringLiteral("ine 0")); 0136 QCOMPARE(selectionForRow(1), QStringLiteral("Lin")); 0137 QCOMPARE(selectionForRow(2), QString()); 0138 QCOMPARE(selectionForRow(3), QString()); 0139 QVERIFY(!selection.contains(index0, 0)); 0140 QVERIFY(selection.contains(index0, 1)); 0141 QVERIFY(selection.contains(index0, 100)); 0142 QVERIFY(selection.contains(index1, 0)); 0143 QVERIFY(selection.contains(index1, 2)); 0144 QVERIFY(selection.contains(index1, 3)); // (arguable, end of selection) 0145 QVERIFY(!selection.contains(index2, 0)); 0146 QVERIFY(!selection.contains(index3, 0)); 0147 } 0148 0149 void TextSelectionTest::testSingleLineReverseSelection() 0150 { 0151 // GIVEN 0152 const QModelIndex index1 = model.index(1, 0); 0153 TestFactory factory(model.rowCount()); 0154 TextSelection selection; 0155 selection.setTextHelperFactory(&factory); 0156 0157 // WHEN 0158 selection.setStart(index1, 4); 0159 selection.setEnd(index1, 1); 0160 0161 // THEN 0162 QCOMPARE(selection.selectedText(TextSelection::Text), QStringLiteral("ine")); 0163 } 0164 0165 void TextSelectionTest::testSelectWordUnderCursor() 0166 { 0167 // GIVEN 0168 const QModelIndex index1 = model.index(1, 0); 0169 const QModelIndex index2 = model.index(2, 0); 0170 TestFactory factory(model.rowCount()); 0171 TextSelection selection; 0172 selection.setTextHelperFactory(&factory); 0173 0174 // WHEN 0175 selection.selectWordUnderCursor(index1, 2, &factory); 0176 0177 // THEN 0178 QCOMPARE(selection.selectedText(TextSelection::Text), QStringLiteral("Line")); 0179 QVERIFY(selection.contains(index1, 0)); 0180 QVERIFY(selection.contains(index1, 2)); 0181 QVERIFY(selection.contains(index1, 4)); 0182 QVERIFY(!selection.contains(index1, 5)); 0183 0184 // and again on another row 0185 0186 // WHEN 0187 selection.setStart(index2, 8); 0188 selection.setEnd(index2, 8); // tiny mouse move while double-clicking 0189 QVERIFY(!selection.hasSelection()); 0190 selection.selectWordUnderCursor(index2, 8, &factory); 0191 0192 // THEN 0193 QCOMPARE(selection.selectedText(TextSelection::Text), QStringLiteral("bold")); 0194 QVERIFY(!selection.contains(index2, 0)); 0195 QVERIFY(!selection.contains(index2, 6)); 0196 QVERIFY(selection.contains(index2, 7)); 0197 QVERIFY(selection.contains(index2, 9)); 0198 } 0199 0200 void TextSelectionTest::shouldHaveDefaultValues() 0201 { 0202 TextSelection selection; 0203 QVERIFY(!selection.hasSelection()); 0204 QVERIFY(!selection.textHelperFactory()); 0205 QVERIFY(selection.attachmentFactories().isEmpty()); 0206 } 0207 0208 void TextSelectionTest::testSelectAll() 0209 { 0210 // GIVEN 0211 const QModelIndex index1 = model.index(1, 0); 0212 TestFactory factory(model.rowCount()); 0213 TextSelection selection; 0214 selection.setTextHelperFactory(&factory); 0215 0216 // WHEN 0217 QVERIFY(!selection.hasSelection()); 0218 selection.selectMessage(index1); 0219 QVERIFY(selection.hasSelection()); 0220 QCOMPARE(selection.selectedText(TextSelection::Text), QStringLiteral("Line 1 bold")); 0221 } 0222 0223 void TextSelectionTest::textClear() 0224 { 0225 // GIVEN 0226 const QModelIndex index1 = model.index(1, 0); 0227 TestFactory factory(model.rowCount()); 0228 TextSelection selection; 0229 selection.setTextHelperFactory(&factory); 0230 0231 // WHEN 0232 QVERIFY(!selection.hasSelection()); 0233 selection.selectMessage(index1); 0234 QVERIFY(selection.hasSelection()); 0235 selection.clear(); 0236 QVERIFY(!selection.hasSelection()); 0237 } 0238 0239 #include "moc_textselectiontest.cpp"