File indexing completed on 2024-11-10 03:40:41
0001 /* 0002 This file is part of the KDE libraries 0003 SPDX-FileCopyrightText: 2010-2018 Dominik Haumann <dhaumann@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "movingrange_test.h" 0009 #include "moc_movingrange_test.cpp" 0010 0011 #include <katebuffer.h> 0012 #include <katedocument.h> 0013 #include <kateglobal.h> 0014 #include <kateview.h> 0015 #include <ktexteditor/movingrange.h> 0016 #include <ktexteditor/movingrangefeedback.h> 0017 0018 #include <QTest> 0019 0020 using namespace KTextEditor; 0021 0022 QTEST_MAIN(MovingRangeTest) 0023 0024 class RangeFeedback : public MovingRangeFeedback 0025 { 0026 public: 0027 RangeFeedback() 0028 : MovingRangeFeedback() 0029 { 0030 reset(); 0031 } 0032 0033 void rangeEmpty(MovingRange * /*range*/) override 0034 { 0035 m_rangeEmptyCalled = true; 0036 } 0037 0038 void rangeInvalid(MovingRange * /*range*/) override 0039 { 0040 m_rangeInvalidCalled = true; 0041 } 0042 0043 void mouseEnteredRange(MovingRange * /*range*/, View * /*view*/) override 0044 { 0045 m_mouseEnteredRangeCalled = true; 0046 } 0047 0048 void mouseExitedRange(MovingRange * /*range*/, View * /*view*/) override 0049 { 0050 m_mouseExitedRangeCalled = true; 0051 } 0052 0053 void caretEnteredRange(MovingRange * /*range*/, View * /*view*/) override 0054 { 0055 m_caretEnteredRangeCalled = true; 0056 } 0057 0058 void caretExitedRange(MovingRange * /*range*/, View * /*view*/) override 0059 { 0060 m_caretExitedRangeCalled = true; 0061 } 0062 0063 // 0064 // Test functions to reset feedback watcher 0065 // 0066 public: 0067 void reset() 0068 { 0069 m_rangeEmptyCalled = false; 0070 m_rangeInvalidCalled = false; 0071 m_mouseEnteredRangeCalled = false; 0072 m_mouseExitedRangeCalled = false; 0073 m_caretEnteredRangeCalled = false; 0074 m_caretExitedRangeCalled = false; 0075 } 0076 0077 void verifyReset() 0078 { 0079 QVERIFY(!m_rangeEmptyCalled); 0080 QVERIFY(!m_rangeInvalidCalled); 0081 QVERIFY(!m_mouseEnteredRangeCalled); 0082 QVERIFY(!m_mouseExitedRangeCalled); 0083 QVERIFY(!m_caretEnteredRangeCalled); 0084 QVERIFY(!m_caretExitedRangeCalled); 0085 } 0086 0087 bool rangeEmptyCalled() const 0088 { 0089 return m_rangeEmptyCalled; 0090 } 0091 bool rangeInvalidCalled() const 0092 { 0093 return m_rangeInvalidCalled; 0094 } 0095 bool mouseEnteredRangeCalled() const 0096 { 0097 return m_mouseEnteredRangeCalled; 0098 } 0099 bool mouseExitedRangeCalled() const 0100 { 0101 return m_mouseExitedRangeCalled; 0102 } 0103 bool caretEnteredRangeCalled() const 0104 { 0105 return m_caretEnteredRangeCalled; 0106 } 0107 bool caretExitedRangeCalled() const 0108 { 0109 return m_caretExitedRangeCalled; 0110 } 0111 0112 private: 0113 bool m_rangeEmptyCalled; 0114 bool m_rangeInvalidCalled; 0115 bool m_mouseEnteredRangeCalled; 0116 bool m_mouseExitedRangeCalled; 0117 bool m_caretEnteredRangeCalled; 0118 bool m_caretExitedRangeCalled; 0119 }; 0120 0121 MovingRangeTest::MovingRangeTest() 0122 : QObject() 0123 { 0124 KTextEditor::EditorPrivate::enableUnitTestMode(); 0125 } 0126 0127 MovingRangeTest::~MovingRangeTest() 0128 { 0129 } 0130 0131 // tests: 0132 // - RangeFeedback::rangeEmpty 0133 void MovingRangeTest::testFeedbackEmptyRange() 0134 { 0135 KTextEditor::DocumentPrivate doc; 0136 // the range created below will span the 'x' characters 0137 QString text( 0138 QStringLiteral("..xxxx\n" 0139 "xxxx..")); 0140 doc.setText(text); 0141 0142 // create range feedback 0143 RangeFeedback rf; 0144 0145 // allow empty 0146 MovingRange *range = doc.newMovingRange(Range(Cursor(0, 2), Cursor(1, 4)), KTextEditor::MovingRange::DoNotExpand, KTextEditor::MovingRange::AllowEmpty); 0147 range->setFeedback(&rf); 0148 rf.verifyReset(); 0149 0150 // remove exact range 0151 doc.removeText(range->toRange()); 0152 QVERIFY(rf.rangeEmptyCalled()); 0153 QVERIFY(!rf.rangeInvalidCalled()); 0154 QVERIFY(!rf.mouseEnteredRangeCalled()); 0155 QVERIFY(!rf.mouseExitedRangeCalled()); 0156 QVERIFY(!rf.caretEnteredRangeCalled()); 0157 QVERIFY(!rf.caretExitedRangeCalled()); 0158 0159 // clear document: should call rangeInvalid 0160 rf.reset(); 0161 rf.verifyReset(); 0162 doc.clear(); 0163 QVERIFY(rf.rangeInvalidCalled()); 0164 QVERIFY(!rf.rangeEmptyCalled()); 0165 QVERIFY(!rf.mouseEnteredRangeCalled()); 0166 QVERIFY(!rf.mouseExitedRangeCalled()); 0167 QVERIFY(!rf.caretEnteredRangeCalled()); 0168 QVERIFY(!rf.caretExitedRangeCalled()); 0169 0170 // setText: should behave just like clear document: call rangeInvalid again 0171 doc.setText(text); 0172 range->setRange(Range(Cursor(0, 2), Cursor(1, 4))); 0173 rf.reset(); 0174 rf.verifyReset(); 0175 doc.setText(QStringLiteral("--yyyy\nyyyy--")); 0176 QVERIFY(rf.rangeInvalidCalled()); 0177 QVERIFY(!rf.rangeEmptyCalled()); 0178 QVERIFY(!rf.mouseEnteredRangeCalled()); 0179 QVERIFY(!rf.mouseExitedRangeCalled()); 0180 QVERIFY(!rf.caretEnteredRangeCalled()); 0181 QVERIFY(!rf.caretExitedRangeCalled()); 0182 0183 // now remove entire document range. In this case, emptyRange should be called 0184 // instead of rangeInvalid 0185 doc.setText(text); 0186 range->setRange(Range(Cursor(0, 2), Cursor(1, 4))); 0187 rf.reset(); 0188 rf.verifyReset(); 0189 doc.removeText(doc.documentRange()); 0190 QVERIFY(rf.rangeEmptyCalled()); 0191 QVERIFY(!rf.rangeInvalidCalled()); 0192 QVERIFY(!rf.mouseEnteredRangeCalled()); 0193 QVERIFY(!rf.mouseExitedRangeCalled()); 0194 QVERIFY(!rf.caretEnteredRangeCalled()); 0195 QVERIFY(!rf.caretExitedRangeCalled()); 0196 } 0197 0198 // tests: 0199 // - RangeFeedback::rangeInvalid 0200 void MovingRangeTest::testFeedbackInvalidRange() 0201 { 0202 KTextEditor::DocumentPrivate doc; 0203 // the range created below will span the 'x' characters 0204 QString text( 0205 QStringLiteral("..xxxx\n" 0206 "xxxx..")); 0207 doc.setText(text); 0208 0209 // create range feedback 0210 RangeFeedback rf; 0211 0212 // allow empty 0213 MovingRange *range = 0214 doc.newMovingRange(Range(Cursor(0, 2), Cursor(1, 4)), KTextEditor::MovingRange::DoNotExpand, KTextEditor::MovingRange::InvalidateIfEmpty); 0215 range->setFeedback(&rf); 0216 rf.verifyReset(); 0217 0218 // remove exact range 0219 doc.removeText(range->toRange()); 0220 QVERIFY(!rf.rangeEmptyCalled()); 0221 QVERIFY(rf.rangeInvalidCalled()); 0222 QVERIFY(!rf.mouseEnteredRangeCalled()); 0223 QVERIFY(!rf.mouseExitedRangeCalled()); 0224 QVERIFY(!rf.caretEnteredRangeCalled()); 0225 QVERIFY(!rf.caretExitedRangeCalled()); 0226 0227 // clear document: should call rangeInvalid again 0228 doc.setText(text); 0229 range->setRange(Range(Cursor(0, 2), Cursor(1, 4))); 0230 rf.reset(); 0231 rf.verifyReset(); 0232 doc.clear(); 0233 QVERIFY(rf.rangeInvalidCalled()); 0234 QVERIFY(!rf.rangeEmptyCalled()); 0235 QVERIFY(!rf.mouseEnteredRangeCalled()); 0236 QVERIFY(!rf.mouseExitedRangeCalled()); 0237 QVERIFY(!rf.caretEnteredRangeCalled()); 0238 QVERIFY(!rf.caretExitedRangeCalled()); 0239 0240 // setText: should behave just like clear document: call rangeInvalid again 0241 doc.setText(text); 0242 range->setRange(Range(Cursor(0, 2), Cursor(1, 4))); 0243 rf.reset(); 0244 rf.verifyReset(); 0245 doc.setText(QStringLiteral("--yyyy\nyyyy--")); 0246 QVERIFY(rf.rangeInvalidCalled()); 0247 QVERIFY(!rf.rangeEmptyCalled()); 0248 QVERIFY(!rf.mouseEnteredRangeCalled()); 0249 QVERIFY(!rf.mouseExitedRangeCalled()); 0250 QVERIFY(!rf.caretEnteredRangeCalled()); 0251 QVERIFY(!rf.caretExitedRangeCalled()); 0252 0253 // now remove entire document range. Call rangeInvalid again 0254 doc.setText(text); 0255 range->setRange(Range(Cursor(0, 2), Cursor(1, 4))); 0256 rf.reset(); 0257 rf.verifyReset(); 0258 doc.removeText(doc.documentRange()); 0259 QVERIFY(rf.rangeInvalidCalled()); 0260 QVERIFY(!rf.rangeEmptyCalled()); 0261 QVERIFY(!rf.mouseEnteredRangeCalled()); 0262 QVERIFY(!rf.mouseExitedRangeCalled()); 0263 QVERIFY(!rf.caretEnteredRangeCalled()); 0264 QVERIFY(!rf.caretExitedRangeCalled()); 0265 } 0266 0267 // tests: 0268 // - RangeFeedback::caretEnteredRange 0269 // - RangeFeedback::caretExitedRange 0270 void MovingRangeTest::testFeedbackCaret() 0271 { 0272 KTextEditor::DocumentPrivate doc; 0273 // we only use 'x' characters here to have uniform letter sizes for cursorUp/Down movements 0274 QString text( 0275 QStringLiteral("xxxxxx\n" 0276 "xxxxxx")); 0277 doc.setText(text); 0278 0279 KTextEditor::ViewPrivate *view = static_cast<KTextEditor::ViewPrivate *>(doc.createView(nullptr)); 0280 0281 // create range feedback 0282 RangeFeedback rf; 0283 0284 // first test: with ExpandLeft | ExpandRight 0285 { 0286 view->setCursorPosition(Cursor(1, 6)); 0287 0288 MovingRange *range = doc.newMovingRange(Range(Cursor(0, 2), Cursor(1, 4)), 0289 KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight, 0290 KTextEditor::MovingRange::InvalidateIfEmpty); 0291 rf.reset(); 0292 range->setFeedback(&rf); 0293 rf.verifyReset(); 0294 0295 // left 0296 view->cursorLeft(); 0297 QCOMPARE(view->cursorPosition(), Cursor(1, 5)); 0298 QVERIFY(!rf.caretEnteredRangeCalled()); 0299 QVERIFY(!rf.caretExitedRangeCalled()); 0300 0301 view->cursorLeft(); 0302 QCOMPARE(view->cursorPosition(), Cursor(1, 4)); 0303 QVERIFY(rf.caretEnteredRangeCalled()); // ExpandRight: include cursor already now 0304 QVERIFY(!rf.caretExitedRangeCalled()); 0305 0306 rf.reset(); 0307 view->cursorLeft(); 0308 QCOMPARE(view->cursorPosition(), Cursor(1, 3)); 0309 QVERIFY(!rf.caretEnteredRangeCalled()); 0310 QVERIFY(!rf.caretExitedRangeCalled()); 0311 0312 rf.reset(); 0313 view->up(); 0314 QCOMPARE(view->cursorPosition(), Cursor(0, 3)); 0315 QVERIFY(!rf.caretEnteredRangeCalled()); 0316 QVERIFY(!rf.caretExitedRangeCalled()); 0317 0318 rf.reset(); 0319 view->cursorLeft(); 0320 QCOMPARE(view->cursorPosition(), Cursor(0, 2)); 0321 QVERIFY(!rf.caretEnteredRangeCalled()); 0322 QVERIFY(!rf.caretExitedRangeCalled()); 0323 0324 rf.reset(); 0325 view->cursorLeft(); 0326 QCOMPARE(view->cursorPosition(), Cursor(0, 1)); // ExpandLeft: now we left it, not before 0327 QVERIFY(!rf.caretEnteredRangeCalled()); 0328 QVERIFY(rf.caretExitedRangeCalled()); 0329 0330 delete range; 0331 } 0332 0333 // second test: with DoNotExpand 0334 { 0335 view->setCursorPosition(Cursor(1, 6)); 0336 0337 MovingRange *range = 0338 doc.newMovingRange(Range(Cursor(0, 2), Cursor(1, 4)), KTextEditor::MovingRange::DoNotExpand, KTextEditor::MovingRange::InvalidateIfEmpty); 0339 rf.reset(); 0340 range->setFeedback(&rf); 0341 rf.verifyReset(); 0342 0343 // left 0344 view->cursorLeft(); 0345 QCOMPARE(view->cursorPosition(), Cursor(1, 5)); 0346 QVERIFY(!rf.caretEnteredRangeCalled()); 0347 QVERIFY(!rf.caretExitedRangeCalled()); 0348 0349 view->cursorLeft(); 0350 QCOMPARE(view->cursorPosition(), Cursor(1, 4)); 0351 QVERIFY(!rf.caretEnteredRangeCalled()); // DoNotExpand: does not include cursor 0352 QVERIFY(!rf.caretExitedRangeCalled()); 0353 0354 rf.reset(); 0355 view->cursorLeft(); 0356 QCOMPARE(view->cursorPosition(), Cursor(1, 3)); 0357 QVERIFY(rf.caretEnteredRangeCalled()); 0358 QVERIFY(!rf.caretExitedRangeCalled()); 0359 0360 rf.reset(); 0361 view->up(); 0362 QCOMPARE(view->cursorPosition(), Cursor(0, 3)); 0363 QVERIFY(!rf.caretEnteredRangeCalled()); 0364 QVERIFY(!rf.caretExitedRangeCalled()); 0365 0366 rf.reset(); 0367 view->cursorLeft(); 0368 QCOMPARE(view->cursorPosition(), Cursor(0, 2)); 0369 QVERIFY(!rf.caretEnteredRangeCalled()); 0370 QVERIFY(rf.caretExitedRangeCalled()); // DoNotExpand: that's why we leave already now 0371 0372 rf.reset(); 0373 view->cursorLeft(); 0374 QCOMPARE(view->cursorPosition(), Cursor(0, 1)); 0375 QVERIFY(!rf.caretEnteredRangeCalled()); 0376 QVERIFY(!rf.caretExitedRangeCalled()); 0377 0378 delete range; 0379 } 0380 } 0381 0382 // tests: 0383 // - RangeFeedback::mouseEnteredRange 0384 // - RangeFeedback::mouseExitedRange 0385 void MovingRangeTest::testFeedbackMouse() 0386 { 0387 // mouse move only on X11 0388 if (qApp->platformName() != QLatin1String("xcb")) { 0389 QSKIP("mouse moving only on X11"); 0390 } 0391 0392 KTextEditor::DocumentPrivate doc; 0393 // the range created below will span the 'x' characters 0394 QString text( 0395 QStringLiteral("..xxxx\n" 0396 "xxxx..")); 0397 doc.setText(text); 0398 0399 KTextEditor::ViewPrivate *view = static_cast<KTextEditor::ViewPrivate *>(doc.createView(nullptr)); 0400 view->setCursorPosition(Cursor(1, 6)); 0401 view->show(); 0402 view->resize(200, 100); 0403 0404 // create range feedback 0405 RangeFeedback rf; 0406 QVERIFY(!rf.mouseEnteredRangeCalled()); 0407 QVERIFY(!rf.mouseExitedRangeCalled()); 0408 0409 // allow empty 0410 MovingRange *range = doc.newMovingRange(Range(Cursor(0, 2), Cursor(1, 4)), 0411 KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight, 0412 KTextEditor::MovingRange::InvalidateIfEmpty); 0413 range->setFeedback(&rf); 0414 rf.verifyReset(); 0415 0416 // left (nothing) 0417 QTest::mouseMove(view, view->cursorToCoordinate(Cursor(0, 0)) + QPoint(0, 5)); 0418 QTest::qWait(200); // process mouse events. do not move mouse manually 0419 QVERIFY(!rf.mouseEnteredRangeCalled()); 0420 QVERIFY(!rf.mouseExitedRangeCalled()); 0421 0422 // middle (enter) 0423 rf.reset(); 0424 QTest::mouseMove(view, view->cursorToCoordinate(Cursor(0, 3)) + QPoint(0, 5)); 0425 QTest::qWait(200); // process mouse events. do not move mouse manually 0426 QVERIFY(rf.mouseEnteredRangeCalled()); 0427 QVERIFY(!rf.mouseExitedRangeCalled()); 0428 0429 // right (exit) 0430 rf.reset(); 0431 QTest::mouseMove(view, view->cursorToCoordinate(Cursor(1, 6)) + QPoint(10, 5)); 0432 QTest::qWait(200); // process mouse events. do not move mouse manually 0433 QVERIFY(!rf.mouseEnteredRangeCalled()); 0434 QVERIFY(rf.mouseExitedRangeCalled()); 0435 } 0436 0437 void MovingRangeTest::testLineRemoved() 0438 { 0439 KTextEditor::DocumentPrivate doc; 0440 // the range created below will span the 'x' characters 0441 QString text( 0442 QStringLiteral("abcd\n" 0443 "efgh\n" 0444 "\n" 0445 "hijk")); 0446 doc.setText(text); 0447 0448 KTextEditor::ViewPrivate *view = static_cast<KTextEditor::ViewPrivate *>(doc.createView(nullptr)); 0449 view->setCursorPosition(Cursor(1, 3)); 0450 view->show(); 0451 view->resize(200, 100); 0452 constexpr auto expand = KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight; 0453 MovingRange *range = doc.newMovingRange({1, 1, 1, 2}, expand, KTextEditor::MovingRange::InvalidateIfEmpty); 0454 MovingRange *range2 = doc.newMovingRange({1, 3, 1, 4}, expand, KTextEditor::MovingRange::InvalidateIfEmpty); 0455 KTextEditor::Attribute::Ptr attr(new KTextEditor::Attribute); 0456 attr->setForeground(Qt::red); 0457 range->setAttribute(attr); 0458 doc.removeLine(1); 0459 delete range; 0460 delete range2; 0461 0462 // shouldn't crash 0463 auto r = doc.buffer().rangesForLine(1, view, true); 0464 QVERIFY(r.isEmpty()); 0465 } 0466 0467 void MovingRangeTest::testLineWrapOrUnwrapUpdateRangeForLineCache() 0468 { 0469 KTextEditor::DocumentPrivate doc; 0470 doc.setText( 0471 QStringLiteral("abcd\n" 0472 "efgh\n" 0473 "hijk\n")); 0474 0475 // add range to line 2, it shall be in rangeForLine for the right lines after each update! 0476 // must be single line range to be in the cache! 0477 auto range = doc.newMovingRange({2, 1, 2, 3}, 0478 KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight, 0479 KTextEditor::MovingRange::InvalidateIfEmpty); 0480 0481 // range shall be in the lookup cache for line 2 0482 QVERIFY(doc.buffer().rangesForLine(0, nullptr, false).isEmpty()); 0483 QVERIFY(doc.buffer().rangesForLine(1, nullptr, false).isEmpty()); 0484 QVERIFY(doc.buffer().rangesForLine(2, nullptr, false).contains(range)); 0485 0486 // wrap line 1 => range should move to line 3 0487 doc.editWrapLine(1, 1); 0488 QVERIFY(doc.buffer().rangesForLine(0, nullptr, false).isEmpty()); 0489 QVERIFY(doc.buffer().rangesForLine(1, nullptr, false).isEmpty()); 0490 QVERIFY(doc.buffer().rangesForLine(2, nullptr, false).isEmpty()); 0491 QVERIFY(doc.buffer().rangesForLine(3, nullptr, false).contains(range)); 0492 0493 // unwrap line 1 => range should back move to line 2 0494 doc.editUnWrapLine(1); 0495 QVERIFY(doc.buffer().rangesForLine(0, nullptr, false).isEmpty()); 0496 QVERIFY(doc.buffer().rangesForLine(1, nullptr, false).isEmpty()); 0497 QVERIFY(doc.buffer().rangesForLine(2, nullptr, false).contains(range)); 0498 } 0499 0500 void MovingRangeTest::testMultiline() 0501 { 0502 KTextEditor::DocumentPrivate doc; 0503 doc.setText( 0504 QStringLiteral("abcd\n" 0505 "efgh\n" 0506 "hijk\n")); 0507 0508 // add range to line 1-2 0509 auto range = doc.newMovingRange({1, 0, 2, 3}, 0510 KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight, 0511 KTextEditor::MovingRange::InvalidateIfEmpty); 0512 QVERIFY(doc.buffer().rangesForLine(1, nullptr, false).contains(range)); 0513 QVERIFY(doc.buffer().rangesForLine(2, nullptr, false).contains(range)); 0514 }