File indexing completed on 2024-04-21 03:57:12

0001 /*
0002     This file is part of the Kate project.
0003     SPDX-FileCopyrightText: 2010 Christoph Cullmann <cullmann@kde.org>
0004     SPDX-FileCopyrightText: 2010-2018 Dominik Haumann <dhaumann@kde.org>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 #include "katetextbuffertest.h"
0010 #include "katebuffer.h"
0011 #include "katedocument.h"
0012 #include "katetextfolding.h"
0013 #include <kateglobal.h>
0014 #include <ktexteditor/movingcursor.h>
0015 
0016 QTEST_MAIN(KateTextBufferTest)
0017 
0018 KateTextBufferTest::KateTextBufferTest()
0019     : QObject()
0020 {
0021     KTextEditor::EditorPrivate::enableUnitTestMode();
0022 }
0023 
0024 KateTextBufferTest::~KateTextBufferTest()
0025 {
0026 }
0027 
0028 void KateTextBufferTest::basicBufferTest()
0029 {
0030     // construct an empty text buffer
0031     KTextEditor::DocumentPrivate doc;
0032     Kate::TextBuffer &buffer = doc.buffer();
0033 
0034     // one line per default
0035     QVERIFY(buffer.lines() == 1);
0036     QVERIFY(buffer.text() == QString());
0037 
0038     // FIXME: use QTestLib macros for checking the correct state
0039     // start editing
0040     buffer.startEditing();
0041 
0042     // end editing
0043     buffer.finishEditing();
0044 }
0045 
0046 void KateTextBufferTest::wrapLineTest()
0047 {
0048     // construct an empty text buffer
0049     KTextEditor::DocumentPrivate doc;
0050     Kate::TextBuffer &buffer = doc.buffer();
0051 
0052     // wrap first empty line -> we should have two empty lines
0053     buffer.startEditing();
0054     buffer.wrapLine(KTextEditor::Cursor(0, 0));
0055     buffer.finishEditing();
0056     buffer.debugPrint(QStringLiteral("Two empty lines"));
0057     QVERIFY(buffer.text() == QLatin1String("\n"));
0058 
0059     // unwrap again -> only one empty line
0060     buffer.startEditing();
0061     buffer.unwrapLine(1);
0062     buffer.finishEditing();
0063 
0064     // print debug
0065     buffer.debugPrint(QStringLiteral("Empty Buffer"));
0066     QVERIFY(buffer.text() == QString());
0067 }
0068 
0069 void KateTextBufferTest::insertRemoveTextTest()
0070 {
0071     // construct an empty text buffer
0072     KTextEditor::DocumentPrivate doc;
0073     Kate::TextBuffer &buffer = doc.buffer();
0074 
0075     // wrap first line
0076     buffer.startEditing();
0077     buffer.wrapLine(KTextEditor::Cursor(0, 0));
0078     buffer.finishEditing();
0079     buffer.debugPrint(QStringLiteral("Two empty lines"));
0080     QVERIFY(buffer.text() == QLatin1String("\n"));
0081 
0082     // remember second line
0083     Kate::TextLine second = buffer.line(1);
0084 
0085     // unwrap second line
0086     buffer.startEditing();
0087     buffer.unwrapLine(1);
0088     buffer.finishEditing();
0089     buffer.debugPrint(QStringLiteral("One empty line"));
0090     QVERIFY(buffer.text() == QString());
0091 
0092     // second text line should be still there
0093     // const QString &secondText = second->text ();
0094     // QVERIFY (secondText == "")
0095 
0096     // insert text
0097     buffer.startEditing();
0098     buffer.insertText(KTextEditor::Cursor(0, 0), QStringLiteral("testremovetext"));
0099     buffer.finishEditing();
0100     buffer.debugPrint(QStringLiteral("One line"));
0101     QVERIFY(buffer.text() == QLatin1String("testremovetext"));
0102 
0103     // remove text
0104     buffer.startEditing();
0105     buffer.removeText(KTextEditor::Range(KTextEditor::Cursor(0, 4), KTextEditor::Cursor(0, 10)));
0106     buffer.finishEditing();
0107     buffer.debugPrint(QStringLiteral("One line"));
0108     QVERIFY(buffer.text() == QLatin1String("testtext"));
0109 
0110     // wrap text
0111     buffer.startEditing();
0112     buffer.wrapLine(KTextEditor::Cursor(0, 2));
0113     buffer.finishEditing();
0114     buffer.debugPrint(QStringLiteral("Two line"));
0115     QVERIFY(buffer.text() == QLatin1String("te\nsttext"));
0116 
0117     // unwrap text
0118     buffer.startEditing();
0119     buffer.unwrapLine(1);
0120     buffer.finishEditing();
0121     buffer.debugPrint(QStringLiteral("One line"));
0122     QVERIFY(buffer.text() == QLatin1String("testtext"));
0123 }
0124 
0125 void KateTextBufferTest::cursorTest()
0126 {
0127     // last buffer content, for consistence checks
0128     QString lastBufferContent;
0129 
0130     // construct an empty text buffer
0131     KTextEditor::DocumentPrivate doc;
0132     Kate::TextBuffer &buffer = doc.buffer();
0133 
0134     // wrap first line
0135     buffer.startEditing();
0136     buffer.insertText(KTextEditor::Cursor(0, 0), QStringLiteral("sfdfjdsklfjlsdfjlsdkfjskldfjklsdfjklsdjkfl"));
0137     buffer.wrapLine(KTextEditor::Cursor(0, 8));
0138     buffer.wrapLine(KTextEditor::Cursor(1, 8));
0139     buffer.wrapLine(KTextEditor::Cursor(2, 8));
0140     buffer.finishEditing();
0141     buffer.debugPrint(QStringLiteral("Cursor buffer"));
0142 
0143     // construct cursor
0144     KTextEditor::MovingCursor *cursor1 = doc.newMovingCursor(KTextEditor::Cursor(0, 0), KTextEditor::MovingCursor::MoveOnInsert);
0145     QVERIFY(cursor1->toCursor() == KTextEditor::Cursor(0, 0));
0146     // printf ("cursor %d, %d\n", cursor1->line(), cursor1->column());
0147 
0148     // insert text
0149     buffer.startEditing();
0150     buffer.insertText(KTextEditor::Cursor(0, 0), QStringLiteral("hallo"));
0151     buffer.finishEditing();
0152     buffer.debugPrint(QStringLiteral("Cursor buffer"));
0153 
0154     // printf ("cursor %d, %d\n", cursor1->line(), cursor1->column());
0155     QVERIFY(cursor1->toCursor() == KTextEditor::Cursor(0, 5));
0156 
0157     // remove text
0158     buffer.startEditing();
0159     buffer.removeText(KTextEditor::Range(KTextEditor::Cursor(0, 4), KTextEditor::Cursor(0, 10)));
0160     buffer.finishEditing();
0161     buffer.debugPrint(QStringLiteral("Cursor buffer"));
0162 
0163     // printf ("cursor %d, %d\n", cursor1->line(), cursor1->column());
0164     QVERIFY(cursor1->toCursor() == KTextEditor::Cursor(0, 4));
0165 
0166     // wrap line
0167     buffer.startEditing();
0168     buffer.wrapLine(KTextEditor::Cursor(0, 3));
0169     buffer.finishEditing();
0170     buffer.debugPrint(QStringLiteral("Cursor buffer"));
0171 
0172     // printf ("cursor %d, %d\n", cursor1->line(), cursor1->column());
0173     QVERIFY(cursor1->toCursor() == KTextEditor::Cursor(1, 1));
0174 
0175     // unwrap line
0176     buffer.startEditing();
0177     buffer.unwrapLine(1);
0178     buffer.finishEditing();
0179     buffer.debugPrint(QStringLiteral("Cursor buffer"));
0180 
0181     // printf ("cursor %d, %d\n", cursor1->line(), cursor1->column());
0182     QVERIFY(cursor1->toCursor() == KTextEditor::Cursor(0, 4));
0183 
0184     // remember content
0185     lastBufferContent = buffer.text();
0186     // verify content
0187     QVERIFY(lastBufferContent == buffer.text());
0188 }
0189 
0190 void KateTextBufferTest::foldingTest()
0191 {
0192     // construct an empty text buffer & folding info
0193     KTextEditor::DocumentPrivate doc;
0194     KateBuffer &buffer = doc.buffer();
0195     Kate::TextFolding folding(buffer);
0196 
0197     // insert some text
0198     buffer.startEditing();
0199     for (int i = 0; i < 100; ++i) {
0200         buffer.insertText(KTextEditor::Cursor(i, 0), QStringLiteral("1234567890"));
0201         if (i < 99) {
0202             buffer.wrapLine(KTextEditor::Cursor(i, 10));
0203         }
0204     }
0205     buffer.finishEditing();
0206     QVERIFY(buffer.lines() == 100);
0207 
0208     // starting with empty folding!
0209     folding.debugPrint(QStringLiteral("Empty Folding"));
0210     QVERIFY(folding.debugDump() == QLatin1String("tree  - folded "));
0211 
0212     // check visibility
0213     QVERIFY(folding.isLineVisible(0));
0214     QVERIFY(folding.isLineVisible(99));
0215 
0216     // all visible
0217     QVERIFY(folding.visibleLines() == 100);
0218 
0219     // we shall be able to insert new range
0220     QVERIFY(folding.newFoldingRange(KTextEditor::Range(KTextEditor::Cursor(5, 0), KTextEditor::Cursor(10, 0))) == 0);
0221 
0222     // we shall have now exactly one range toplevel, that is not folded!
0223     folding.debugPrint(QStringLiteral("One Toplevel Fold"));
0224     QVERIFY(folding.debugDump() == QLatin1String("tree [5:0  10:0] - folded "));
0225 
0226     // fold the range!
0227     QVERIFY(folding.foldRange(0));
0228 
0229     folding.debugPrint(QStringLiteral("One Toplevel Fold - Folded"));
0230     QVERIFY(folding.debugDump() == QLatin1String("tree [5:0 f 10:0] - folded [5:0 f 10:0]"));
0231 
0232     // check visibility
0233     QVERIFY(folding.isLineVisible(5));
0234     for (int i = 6; i <= 10; ++i) {
0235         QVERIFY(!folding.isLineVisible(i));
0236     }
0237     QVERIFY(folding.isLineVisible(11));
0238 
0239     // 5 lines are hidden
0240     QVERIFY(folding.visibleLines() == (100 - 5));
0241 
0242     // check line mapping
0243     QVERIFY(folding.visibleLineToLine(5) == 5);
0244     for (int i = 6; i <= 50; ++i) {
0245         QVERIFY(folding.visibleLineToLine(i) == (i + 5));
0246     }
0247 
0248     // there shall be one range starting at 5
0249     QList<QPair<qint64, Kate::TextFolding::FoldingRangeFlags>> forLine = folding.foldingRangesStartingOnLine(5);
0250     QVERIFY(forLine.size() == 1);
0251     QVERIFY(forLine[0].first == 0);
0252     QVERIFY(forLine[0].second & Kate::TextFolding::Folded);
0253 
0254     // we shall be able to insert new range
0255     QVERIFY(folding.newFoldingRange(KTextEditor::Range(KTextEditor::Cursor(20, 0), KTextEditor::Cursor(30, 0)), Kate::TextFolding::Folded) == 1);
0256 
0257     // we shall have now exactly two range toplevel
0258     folding.debugPrint(QStringLiteral("Two Toplevel Folds"));
0259     QVERIFY(folding.debugDump() == QLatin1String("tree [5:0 f 10:0] [20:0 f 30:0] - folded [5:0 f 10:0] [20:0 f 30:0]"));
0260 
0261     // check visibility
0262     QVERIFY(folding.isLineVisible(5));
0263     for (int i = 6; i <= 10; ++i) {
0264         QVERIFY(!folding.isLineVisible(i));
0265     }
0266     QVERIFY(folding.isLineVisible(11));
0267 
0268     QVERIFY(folding.isLineVisible(20));
0269     for (int i = 21; i <= 30; ++i) {
0270         QVERIFY(!folding.isLineVisible(i));
0271     }
0272     QVERIFY(folding.isLineVisible(31));
0273 
0274     // 15 lines are hidden
0275     QVERIFY(folding.visibleLines() == (100 - 5 - 10));
0276 
0277     // check line mapping
0278     QVERIFY(folding.visibleLineToLine(5) == 5);
0279     for (int i = 6; i <= 15; ++i) {
0280         QVERIFY(folding.visibleLineToLine(i) == (i + 5));
0281     }
0282     for (int i = 16; i <= 50; ++i) {
0283         QVERIFY(folding.visibleLineToLine(i) == (i + 15));
0284     }
0285 
0286     // check line mapping
0287     QVERIFY(folding.lineToVisibleLine(5) == 5);
0288     for (int i = 11; i <= 20; ++i) {
0289         QVERIFY(folding.lineToVisibleLine(i) == (i - 5));
0290     }
0291     for (int i = 31; i <= 40; ++i) {
0292         QVERIFY(folding.lineToVisibleLine(i) == (i - 15));
0293     }
0294 
0295     // there shall be one range starting at 20
0296     forLine = folding.foldingRangesStartingOnLine(20);
0297     QVERIFY(forLine.size() == 1);
0298     QVERIFY(forLine[0].first == 1);
0299     QVERIFY(forLine[0].second & Kate::TextFolding::Folded);
0300 
0301     // this shall fail to be inserted, as it badly overlaps with the first range!
0302     QVERIFY(folding.newFoldingRange(KTextEditor::Range(KTextEditor::Cursor(6, 0), KTextEditor::Cursor(15, 0)), Kate::TextFolding::Folded) == -1);
0303 
0304     // this shall fail to be inserted, as it badly overlaps with the second range!
0305     QVERIFY(folding.newFoldingRange(KTextEditor::Range(KTextEditor::Cursor(15, 0), KTextEditor::Cursor(25, 0)), Kate::TextFolding::Folded) == -1);
0306 
0307     // we shall still have now exactly two range toplevel
0308     folding.debugPrint(QStringLiteral("Still Two Toplevel Folds"));
0309     QVERIFY(folding.debugDump() == QLatin1String("tree [5:0 f 10:0] [20:0 f 30:0] - folded [5:0 f 10:0] [20:0 f 30:0]"));
0310 
0311     // still 15 lines are hidden
0312     QVERIFY(folding.visibleLines() == (100 - 5 - 10));
0313 
0314     // we shall be able to insert new range, should lead to nested folds!
0315     QVERIFY(folding.newFoldingRange(KTextEditor::Range(KTextEditor::Cursor(15, 0), KTextEditor::Cursor(35, 0)), Kate::TextFolding::Folded) == 2);
0316 
0317     // we shall have now exactly two range toplevel and one embedded fold
0318     folding.debugPrint(QStringLiteral("Two Toplevel Folds, One Nested Fold"));
0319     QVERIFY(folding.debugDump() == QLatin1String("tree [5:0 f 10:0] [15:0 f [20:0 f 30:0] 35:0] - folded [5:0 f 10:0] [15:0 f 35:0]"));
0320 
0321     // 25 lines are hidden
0322     QVERIFY(folding.visibleLines() == (100 - 5 - 20));
0323 
0324     // check line mapping
0325     QVERIFY(folding.lineToVisibleLine(5) == 5);
0326     for (int i = 11; i <= 15; ++i) {
0327         QVERIFY(folding.lineToVisibleLine(i) == (i - 5));
0328     }
0329 
0330     // special case: hidden lines, should fall ack to last visible one!
0331     for (int i = 16; i <= 35; ++i) {
0332         QVERIFY(folding.lineToVisibleLine(i) == 10);
0333     }
0334 
0335     for (int i = 36; i <= 40; ++i) {
0336         QVERIFY(folding.lineToVisibleLine(i) == (i - 25));
0337     }
0338 
0339     // we shall be able to insert new range, should lead to nested folds!
0340     QVERIFY(folding.newFoldingRange(KTextEditor::Range(KTextEditor::Cursor(0, 0), KTextEditor::Cursor(50, 0)), Kate::TextFolding::Folded) == 3);
0341 
0342     // we shall have now exactly one range toplevel and many embedded fold
0343     folding.debugPrint(QStringLiteral("One Toplevel + Embedded Folds"));
0344     QVERIFY(folding.debugDump() == QLatin1String("tree [0:0 f [5:0 f 10:0] [15:0 f [20:0 f 30:0] 35:0] 50:0] - folded [0:0 f 50:0]"));
0345 
0346     // there shall still be one range starting at 20
0347     forLine = folding.foldingRangesStartingOnLine(20);
0348     QVERIFY(forLine.size() == 1);
0349     QVERIFY(forLine[0].first == 1);
0350     QVERIFY(forLine[0].second & Kate::TextFolding::Folded);
0351 
0352     // add more regions starting at 20
0353     QVERIFY(folding.newFoldingRange(KTextEditor::Range(KTextEditor::Cursor(20, 5), KTextEditor::Cursor(24, 0)), Kate::TextFolding::Folded) == 4);
0354     QVERIFY(folding.newFoldingRange(KTextEditor::Range(KTextEditor::Cursor(20, 3), KTextEditor::Cursor(25, 0)), Kate::TextFolding::Folded) == 5);
0355     folding.debugPrint(QStringLiteral("More ranges at 20"));
0356 
0357     // there shall still be three ranges starting at 20
0358     forLine = folding.foldingRangesStartingOnLine(20);
0359     QVERIFY(forLine.size() == 3);
0360     QVERIFY(forLine[0].first == 1);
0361     QVERIFY(forLine[0].second & Kate::TextFolding::Folded);
0362     QVERIFY(forLine[1].first == 5);
0363     QVERIFY(forLine[1].second & Kate::TextFolding::Folded);
0364     QVERIFY(forLine[2].first == 4);
0365     QVERIFY(forLine[2].second & Kate::TextFolding::Folded);
0366 
0367     // 50 lines are hidden
0368     QVERIFY(folding.visibleLines() == (100 - 50));
0369 
0370     // save state
0371     QJsonDocument folds = folding.exportFoldingRanges();
0372     QString textDump = folding.debugDump();
0373 
0374     // clear folds
0375     folding.clear();
0376     QVERIFY(folding.debugDump() == QLatin1String("tree  - folded "));
0377 
0378     // restore state
0379     folding.importFoldingRanges(folds);
0380     QCOMPARE(folding.debugDump(), textDump);
0381 }
0382 
0383 void KateTextBufferTest::nestedFoldingTest()
0384 {
0385     // construct an empty text buffer & folding info
0386     KTextEditor::DocumentPrivate doc;
0387     Kate::TextBuffer &buffer = doc.buffer();
0388     Kate::TextFolding folding(buffer);
0389 
0390     // insert two nested folds in 5 lines
0391     buffer.startEditing();
0392     for (int i = 0; i < 4; ++i) {
0393         buffer.wrapLine(KTextEditor::Cursor(0, 0));
0394     }
0395     buffer.finishEditing();
0396 
0397     QVERIFY(buffer.lines() == 5);
0398 
0399     // folding for line 1
0400     QVERIFY(folding.newFoldingRange(KTextEditor::Range(KTextEditor::Cursor(0, 0), KTextEditor::Cursor(3, 0)), Kate::TextFolding::Folded) == 0);
0401     QVERIFY(folding.newFoldingRange(KTextEditor::Range(KTextEditor::Cursor(1, 0), KTextEditor::Cursor(2, 0)), Kate::TextFolding::Folded) == 1);
0402 
0403     QVERIFY(folding.foldRange(1));
0404     QVERIFY(folding.foldRange(0));
0405 
0406     QVERIFY(folding.unfoldRange(0));
0407     QVERIFY(folding.unfoldRange(1));
0408 }
0409 
0410 void KateTextBufferTest::saveFileInUnwritableFolder()
0411 {
0412     // create temp dir and get file name inside
0413     QTemporaryDir dir;
0414     QVERIFY(dir.isValid());
0415     const QString folder_name = dir.path();
0416     const QString file_path = folder_name + QLatin1String("/foo");
0417 
0418     QFile f(file_path);
0419     QVERIFY(f.open(QIODevice::WriteOnly | QIODevice::Truncate));
0420     f.write("1234567890");
0421     QVERIFY(f.flush());
0422     f.close();
0423 
0424     QFile::setPermissions(folder_name, QFile::ExeOwner);
0425 
0426     KTextEditor::DocumentPrivate doc;
0427     Kate::TextBuffer &buffer = doc.buffer();
0428     buffer.setTextCodec(QStringLiteral("UTF-8"));
0429     buffer.setFallbackTextCodec(QStringLiteral("UTF-8"));
0430     bool a;
0431     bool b;
0432     int c;
0433     buffer.load(file_path, a, b, c, true);
0434     buffer.clear();
0435     buffer.startEditing();
0436     buffer.insertText(KTextEditor::Cursor(0, 0), QStringLiteral("ABC"));
0437     buffer.finishEditing();
0438     buffer.save(file_path);
0439 
0440     f.open(QIODevice::ReadOnly);
0441     QCOMPARE(f.readAll(), QByteArray("ABC"));
0442     f.close();
0443 
0444     QFile::setPermissions(folder_name, QFile::WriteOwner | QFile::ExeOwner);
0445     QVERIFY(f.remove());
0446     QVERIFY(dir.remove());
0447 }
0448 
0449 void KateTextBufferTest::lineLengthLimit()
0450 {
0451     // create temp dir and get file name inside
0452     QTemporaryDir dir;
0453     QVERIFY(dir.isValid());
0454     const QString file_path = dir.path() + QLatin1String("/foo");
0455 
0456     // create some 'long' lines
0457     QFile f(file_path);
0458     QVERIFY(f.open(QIODevice::WriteOnly | QIODevice::Truncate));
0459     f.write("0123456789\n0123456 789\n01234 56789");
0460     QVERIFY(f.flush());
0461     f.close();
0462 
0463     // load with long line limit 8 and inspect results
0464     KTextEditor::DocumentPrivate doc;
0465     Kate::TextBuffer buffer(&doc, true);
0466     buffer.setTextCodec(QStringLiteral("UTF-8"));
0467     buffer.setFallbackTextCodec(QStringLiteral("UTF-8"));
0468     buffer.setLineLengthLimit(8);
0469     bool encodingErrors = false;
0470     bool tooLongLinesWrapped = false;
0471     int longestLineLoaded = 0;
0472     buffer.load(file_path, encodingErrors, tooLongLinesWrapped, longestLineLoaded, true);
0473     QVERIFY(!encodingErrors);
0474     QVERIFY(tooLongLinesWrapped);
0475     QCOMPARE(longestLineLoaded, 11);
0476     QCOMPARE(buffer.lines(), 6);
0477     QCOMPARE(buffer.line(0).text(), QLatin1String("01234567"));
0478     QCOMPARE(buffer.line(1).text(), QLatin1String("89"));
0479     QCOMPARE(buffer.line(2).text(), QLatin1String("0123456 "));
0480     QCOMPARE(buffer.line(3).text(), QLatin1String("789"));
0481     QCOMPARE(buffer.line(4).text(), QLatin1String("01234 56"));
0482     QCOMPARE(buffer.line(5).text(), QLatin1String("789"));
0483 }
0484 
0485 #if HAVE_KAUTH
0486 void KateTextBufferTest::saveFileWithElevatedPrivileges()
0487 {
0488     // create temp dir and get file name inside
0489     QTemporaryDir dir;
0490     QVERIFY(dir.isValid());
0491     const QString file_path = dir.path() + QLatin1String("/foo");
0492 
0493     QFile f(file_path);
0494     QVERIFY(f.open(QIODevice::WriteOnly | QIODevice::Truncate));
0495     f.write("1234567890");
0496     QVERIFY(f.flush());
0497     f.close();
0498 
0499     KTextEditor::DocumentPrivate doc;
0500     Kate::TextBuffer buffer(&doc, true);
0501     buffer.setTextCodec(QStringLiteral("UTF-8"));
0502     buffer.setFallbackTextCodec(QStringLiteral("UTF-8"));
0503     bool a;
0504     bool b;
0505     int c;
0506     buffer.load(file_path, a, b, c, true);
0507     buffer.clear();
0508     buffer.startEditing();
0509     buffer.insertText(KTextEditor::Cursor(0, 0), QStringLiteral("ABC"));
0510     buffer.finishEditing();
0511     qDebug() << buffer.text();
0512     buffer.save(file_path);
0513 
0514     f.open(QIODevice::ReadOnly);
0515     QCOMPARE(f.readAll(), QByteArray("ABC"));
0516     f.close();
0517 
0518     QVERIFY(f.remove());
0519     QVERIFY(dir.remove());
0520 }
0521 #endif
0522 
0523 #include "moc_katetextbuffertest.cpp"