File indexing completed on 2024-04-21 11:36:01

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 }