File indexing completed on 2024-04-28 07:46:07

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 }