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