File indexing completed on 2025-03-23 12:47:15
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 "katedocument_test.h" 0009 #include "moc_katedocument_test.cpp" 0010 0011 #include <kateconfig.h> 0012 #include <katedocument.h> 0013 #include <kateglobal.h> 0014 #include <kateview.h> 0015 #include <ktexteditor/movingcursor.h> 0016 0017 #include <QRegularExpression> 0018 #include <QSignalSpy> 0019 #include <QTemporaryFile> 0020 #include <QtTestWidgets> 0021 0022 #include <stdio.h> 0023 0024 /// TODO: is there a FindValgrind cmake command we could use to 0025 /// define this automatically? 0026 // comment this out and run the test case with: 0027 // valgrind --tool=callgrind --instr-atstart=no ./katedocument_test testSetTextPerformance 0028 // or similar 0029 // 0030 // #define USE_VALGRIND 0031 0032 #ifdef USE_VALGRIND 0033 #include <valgrind/callgrind.h> 0034 #endif 0035 0036 using namespace KTextEditor; 0037 0038 QTEST_MAIN(KateDocumentTest) 0039 0040 class MovingRangeInvalidator : public QObject 0041 { 0042 Q_OBJECT 0043 public: 0044 explicit MovingRangeInvalidator(QObject *parent = nullptr) 0045 : QObject(parent) 0046 { 0047 } 0048 0049 void addRange(MovingRange *range) 0050 { 0051 m_ranges << range; 0052 } 0053 QList<MovingRange *> ranges() const 0054 { 0055 return m_ranges; 0056 } 0057 0058 public Q_SLOTS: 0059 void aboutToInvalidateMovingInterfaceContent() 0060 { 0061 qDeleteAll(m_ranges); 0062 m_ranges.clear(); 0063 } 0064 0065 private: 0066 QList<MovingRange *> m_ranges; 0067 }; 0068 0069 KateDocumentTest::KateDocumentTest() 0070 : QObject() 0071 { 0072 } 0073 0074 KateDocumentTest::~KateDocumentTest() 0075 { 0076 } 0077 0078 void KateDocumentTest::initTestCase() 0079 { 0080 KTextEditor::EditorPrivate::enableUnitTestMode(); 0081 } 0082 0083 // tests: 0084 // KTextEditor::DocumentPrivate::insertText with word wrap enabled. It is checked whether the 0085 // text is correctly wrapped and whether the moving cursors maintain the correct 0086 // position. 0087 // see also: https://bugs.kde.org/show_bug.cgi?id=168534 0088 void KateDocumentTest::testWordWrap() 0089 { 0090 KTextEditor::DocumentPrivate doc(false, false); 0091 doc.setWordWrap(true); 0092 doc.setWordWrapAt(80); 0093 0094 const QString content = QLatin1String(".........1.........2.........3.........4.........5.........6 ........7 ........8"); 0095 0096 // space after 7 is now kept 0097 // else we kill indentation... 0098 const QString firstWrap = QLatin1String(".........1.........2.........3.........4.........5.........6 ........7 \n....x....8"); 0099 0100 // space after 6 is now kept 0101 // else we kill indentation... 0102 const QString secondWrap = QLatin1String(".........1.........2.........3.........4.........5.........6 \n....ooooooooooo....7 ....x....8"); 0103 0104 doc.setText(content); 0105 MovingCursor *c = doc.newMovingCursor(Cursor(0, 75), MovingCursor::MoveOnInsert); 0106 0107 QCOMPARE(doc.text(), content); 0108 QCOMPARE(c->toCursor(), Cursor(0, 75)); 0109 0110 // type a character at (0, 75) 0111 doc.insertText(c->toCursor(), QLatin1String("x")); 0112 QCOMPARE(doc.text(), firstWrap); 0113 QCOMPARE(c->toCursor(), Cursor(1, 5)); 0114 0115 // set cursor to (0, 65) and type "ooooooooooo" 0116 c->setPosition(Cursor(0, 65)); 0117 doc.insertText(c->toCursor(), QLatin1String("ooooooooooo")); 0118 QCOMPARE(doc.text(), secondWrap); 0119 QCOMPARE(c->toCursor(), Cursor(1, 15)); 0120 } 0121 0122 void KateDocumentTest::testWrapParagraph() 0123 { 0124 // Each paragraph must be kept as an own but re-wrapped nicely 0125 KTextEditor::DocumentPrivate doc(false, false); 0126 doc.setWordWrapAt(30); // Keep needed test data small 0127 0128 const QString before = 0129 QLatin1String("aaaaa a aaaa\naaaaa aaa aa aaaa aaaa \naaaa a aaa aaaaaaa a aaaa\n\nxxxxx x\nxxxx xxxxx\nxxx xx xxxx \nxxxx xxxx x xxx xxxxxxx x xxxx"); 0130 const QString after = 0131 QLatin1String("aaaaa a aaaa aaaaa aaa aa aaaa \naaaa aaaa a aaa aaaaaaa a aaaa\n\nxxxxx x xxxx xxxxx xxx xx xxxx \nxxxx xxxx x xxx xxxxxxx x xxxx"); 0132 0133 doc.setWordWrap(false); // First we try with disabled hard wrap 0134 doc.setText(before); 0135 doc.wrapParagraph(0, doc.lines() - 1); 0136 QCOMPARE(doc.text(), after); 0137 0138 doc.setWordWrap(true); // Test again with enabled hard wrap, that had cause trouble due to twice wrapping 0139 doc.setText(before); 0140 doc.wrapParagraph(0, doc.lines() - 1); 0141 QCOMPARE(doc.text(), after); 0142 } 0143 0144 void KateDocumentTest::testReplaceQStringList() 0145 { 0146 KTextEditor::DocumentPrivate doc(false, false); 0147 doc.setWordWrap(false); 0148 doc.setText( 0149 QLatin1String("asdf\n" 0150 "foo\n" 0151 "foo\n" 0152 "bar\n")); 0153 doc.replaceText(Range(1, 0, 3, 0), {"new", "text", ""}, false); 0154 QCOMPARE(doc.text(), 0155 QLatin1String("asdf\n" 0156 "new\n" 0157 "text\n" 0158 "bar\n")); 0159 } 0160 0161 void KateDocumentTest::testMovingInterfaceSignals() 0162 { 0163 KTextEditor::DocumentPrivate *doc = new KTextEditor::DocumentPrivate; 0164 QSignalSpy aboutToDeleteSpy(doc, &KTextEditor::DocumentPrivate::aboutToDeleteMovingInterfaceContent); 0165 QSignalSpy aboutToInvalidateSpy(doc, &KTextEditor::DocumentPrivate::aboutToInvalidateMovingInterfaceContent); 0166 0167 QCOMPARE(doc->revision(), qint64(0)); 0168 0169 QCOMPARE(aboutToInvalidateSpy.count(), 0); 0170 QCOMPARE(aboutToDeleteSpy.count(), 0); 0171 0172 QTemporaryFile f; 0173 f.open(); 0174 doc->openUrl(QUrl::fromLocalFile(f.fileName())); 0175 QCOMPARE(doc->revision(), qint64(0)); 0176 // TODO: gets emitted once in closeFile and once in openFile - is that OK? 0177 QCOMPARE(aboutToInvalidateSpy.count(), 2); 0178 QCOMPARE(aboutToDeleteSpy.count(), 0); 0179 0180 doc->documentReload(); 0181 QCOMPARE(doc->revision(), qint64(0)); 0182 QCOMPARE(aboutToInvalidateSpy.count(), 4); 0183 // TODO: gets emitted once in closeFile and once in openFile - is that OK? 0184 QCOMPARE(aboutToDeleteSpy.count(), 0); 0185 0186 delete doc; 0187 QCOMPARE(aboutToInvalidateSpy.count(), 4); 0188 QCOMPARE(aboutToDeleteSpy.count(), 1); 0189 } 0190 0191 void KateDocumentTest::testSetTextPerformance() 0192 { 0193 const int lines = 150; 0194 const int columns = 80; 0195 const int rangeLength = 4; 0196 const int rangeGap = 1; 0197 0198 Q_ASSERT(columns % (rangeLength + rangeGap) == 0); 0199 0200 KTextEditor::DocumentPrivate doc; 0201 MovingRangeInvalidator invalidator; 0202 connect(&doc, 0203 &KTextEditor::DocumentPrivate::aboutToInvalidateMovingInterfaceContent, 0204 &invalidator, 0205 &MovingRangeInvalidator::aboutToInvalidateMovingInterfaceContent); 0206 0207 QString text; 0208 QVector<Range> ranges; 0209 ranges.reserve(lines * columns / (rangeLength + rangeGap)); 0210 const QString line = QString().fill('a', columns); 0211 for (int l = 0; l < lines; ++l) { 0212 text.append(line); 0213 text.append('\n'); 0214 for (int c = 0; c < columns; c += rangeLength + rangeGap) { 0215 ranges << Range(l, c, l, c + rangeLength); 0216 } 0217 } 0218 0219 // replace 0220 QBENCHMARK { 0221 // init 0222 doc.setText(text); 0223 for (const Range &range : std::as_const(ranges)) { 0224 invalidator.addRange(doc.newMovingRange(range)); 0225 } 0226 QCOMPARE(invalidator.ranges().size(), ranges.size()); 0227 0228 #ifdef USE_VALGRIND 0229 CALLGRIND_START_INSTRUMENTATION 0230 #endif 0231 0232 doc.setText(text); 0233 0234 #ifdef USE_VALGRIND 0235 CALLGRIND_STOP_INSTRUMENTATION 0236 #endif 0237 0238 QCOMPARE(doc.text(), text); 0239 QVERIFY(invalidator.ranges().isEmpty()); 0240 } 0241 } 0242 0243 void KateDocumentTest::testRemoveTextPerformance() 0244 { 0245 const int lines = 5000; 0246 const int columns = 80; 0247 0248 KTextEditor::DocumentPrivate doc; 0249 0250 QString text; 0251 const QString line = QString().fill('a', columns); 0252 for (int l = 0; l < lines; ++l) { 0253 text.append(line); 0254 text.append('\n'); 0255 } 0256 0257 doc.setText(text); 0258 0259 // replace 0260 QBENCHMARK_ONCE { 0261 #ifdef USE_VALGRIND 0262 CALLGRIND_START_INSTRUMENTATION 0263 #endif 0264 0265 doc.editStart(); 0266 0267 doc.removeText(doc.documentRange()); 0268 0269 doc.editEnd(); 0270 0271 #ifdef USE_VALGRIND 0272 CALLGRIND_STOP_INSTRUMENTATION 0273 #endif 0274 } 0275 } 0276 0277 void KateDocumentTest::testForgivingApiUsage() 0278 { 0279 KTextEditor::DocumentPrivate doc; 0280 0281 QVERIFY(doc.isEmpty()); 0282 QVERIFY(doc.replaceText(Range(0, 0, 100, 100), "asdf")); 0283 QCOMPARE(doc.text(), QString("asdf")); 0284 QCOMPARE(doc.lines(), 1); 0285 QVERIFY(doc.replaceText(Range(2, 5, 2, 100), "asdf")); 0286 QCOMPARE(doc.lines(), 3); 0287 QCOMPARE(doc.text(), QLatin1String("asdf\n\n asdf")); 0288 0289 QVERIFY(doc.removeText(Range(0, 0, 1000, 1000))); 0290 QVERIFY(doc.removeText(Range(0, 0, 0, 100))); 0291 QVERIFY(doc.isEmpty()); 0292 doc.insertText(Cursor(100, 0), "foobar"); 0293 QCOMPARE(doc.line(100), QString("foobar")); 0294 0295 doc.setText("nY\nnYY\n"); 0296 QVERIFY(doc.removeText(Range(0, 0, 0, 1000))); 0297 } 0298 0299 void KateDocumentTest::testAutoBrackets() 0300 { 0301 KTextEditor::DocumentPrivate doc; 0302 auto view = static_cast<KTextEditor::ViewPrivate *>(doc.createView(nullptr)); 0303 0304 auto reset = [&]() { 0305 doc.setText(""); 0306 view->setCursorPosition(Cursor(0, 0)); 0307 }; 0308 0309 auto typeText = [&](const QString &text) { 0310 for (int i = 0; i < text.size(); ++i) { 0311 doc.typeChars(view, text.at(i)); 0312 } 0313 }; 0314 0315 doc.setHighlightingMode("Normal"); // Just to be sure 0316 view->config()->setValue(KateViewConfig::AutoBrackets, true); 0317 0318 QString testInput; 0319 0320 testInput = ("("); 0321 typeText(testInput); 0322 QCOMPARE(doc.text(), "()"); 0323 0324 reset(); 0325 testInput = ("\""); 0326 typeText(testInput); 0327 QCOMPARE(doc.text(), "\"\""); 0328 0329 reset(); 0330 testInput = ("'"); 0331 typeText(testInput); 0332 QCOMPARE(doc.text(), "'"); // In Normal mode there is only one quote to expect 0333 0334 // 0335 // Switch over to some other mode 0336 // 0337 doc.setHighlightingMode("C++"); 0338 0339 reset(); 0340 typeText(testInput); 0341 QCOMPARE(doc.text(), "''"); // Now it must be two 0342 0343 reset(); 0344 testInput = "('')"; 0345 typeText(testInput); 0346 // Known bad behaviour in case of nested brackets 0347 QCOMPARE(doc.text(), testInput); 0348 0349 reset(); 0350 testInput = ("foo \"bar\" haz"); 0351 typeText(testInput); 0352 QCOMPARE(doc.text(), testInput); 0353 // Simulate afterwards to add quotes, bug 405089 0354 doc.setText("foo \"bar"); 0355 typeText("\" haz"); 0356 QCOMPARE(doc.text(), testInput); 0357 0358 // Let's check to add brackets to a selection... 0359 view->setBlockSelection(false); 0360 doc.setText("012xxx678"); 0361 view->setSelection(Range(0, 3, 0, 6)); 0362 typeText("["); 0363 QCOMPARE(doc.text(), "012[xxx]678"); 0364 QCOMPARE(view->selectionRange(), Range(0, 4, 0, 7)); 0365 0366 // ...over multiple lines.. 0367 doc.setText("012xxx678\n012xxx678"); 0368 view->setSelection(Range(0, 3, 1, 6)); 0369 typeText("["); 0370 QCOMPARE(doc.text(), "012[xxx678\n012xxx]678"); 0371 QCOMPARE(view->selectionRange(), Range(0, 4, 1, 6)); 0372 0373 // ..once again in in block mode with increased complexity.. 0374 view->setBlockSelection(true); 0375 doc.setText("012xxx678\n012xxx678"); 0376 view->setSelection(Range(0, 3, 1, 6)); 0377 typeText("[("); 0378 QCOMPARE(doc.text(), "012[(xxx)]678\n012[(xxx)]678"); 0379 QCOMPARE(view->selectionRange(), Range(0, 5, 1, 8)); 0380 0381 // ..and the same with right->left selection 0382 view->setBlockSelection(true); 0383 doc.setText("012xxx678\n012xxx678"); 0384 view->setSelection(Range(0, 6, 1, 3)); 0385 typeText("[("); 0386 QCOMPARE(doc.text(), "012[(xxx)]678\n012[(xxx)]678"); 0387 QCOMPARE(view->selectionRange(), Range(0, 8, 1, 5)); 0388 } 0389 0390 void KateDocumentTest::testReplaceTabs() 0391 { 0392 KTextEditor::DocumentPrivate doc; 0393 auto view = static_cast<KTextEditor::ViewPrivate *>(doc.createView(nullptr)); 0394 0395 auto reset = [&]() { 0396 doc.setText(" Hi!"); 0397 view->setCursorPosition(Cursor(0, 0)); 0398 }; 0399 0400 doc.setHighlightingMode("C++"); 0401 doc.config()->setTabWidth(4); 0402 doc.config()->setIndentationMode("cppstyle"); 0403 0404 // no replace tabs, no indent pasted text 0405 doc.setConfigValue("replace-tabs", false); 0406 doc.setConfigValue("indent-pasted-text", false); 0407 0408 reset(); 0409 doc.insertText(Cursor(0, 0), "\t"); 0410 QCOMPARE(doc.text(), QStringLiteral("\t Hi!")); 0411 0412 reset(); 0413 doc.typeChars(view, "\t"); 0414 QCOMPARE(doc.text(), QStringLiteral("\t Hi!")); 0415 0416 reset(); 0417 doc.paste(view, "some\ncode\n 3\nhi\n"); 0418 QCOMPARE(doc.text(), QStringLiteral("some\ncode\n 3\nhi\n Hi!")); 0419 0420 // now, enable replace tabs 0421 doc.setConfigValue("replace-tabs", true); 0422 0423 reset(); 0424 doc.insertText(Cursor(0, 0), "\t"); 0425 // calling insertText does not replace tabs 0426 QCOMPARE(doc.text(), QStringLiteral("\t Hi!")); 0427 0428 reset(); 0429 doc.typeChars(view, "\t"); 0430 // typing text replaces tabs 0431 QCOMPARE(doc.text(), QStringLiteral(" Hi!")); 0432 0433 reset(); 0434 doc.paste(view, "\t"); 0435 // pasting text does not with indent-pasted off 0436 QCOMPARE(doc.text(), QStringLiteral("\t Hi!")); 0437 0438 doc.setConfigValue("indent-pasted-text", true); 0439 doc.setText("int main() {\n \n}"); 0440 view->setCursorPosition(Cursor(1, 4)); 0441 doc.paste(view, "\tHi"); 0442 // ... and it still does not with indent-pasted on. 0443 // This behaviour is up to discussion. 0444 // \t survives as we don't indent in the given case anymore, see 077dfe954699c674d2c34caf380199a4af7d184a 0445 QCOMPARE(doc.text(), QStringLiteral("int main() {\n \tHi\n}")); 0446 0447 reset(); 0448 doc.paste(view, "some\ncode\n 3\nhi"); 0449 QCOMPARE(doc.text(), QStringLiteral("some\ncode\n3\nhi Hi!")); 0450 } 0451 0452 /** 0453 * Provides slots to check data sent in specific signals. Slot names are derived from corresponding test names. 0454 */ 0455 class SignalHandler : public QObject 0456 { 0457 Q_OBJECT 0458 public Q_SLOTS: 0459 void slotMultipleLinesRemoved(KTextEditor::Document *, const KTextEditor::Range &, const QString &oldText) 0460 { 0461 QCOMPARE(oldText, QString("line2\nline3\n")); 0462 } 0463 0464 void slotNewlineInserted(KTextEditor::Document *, const KTextEditor::Range &range) 0465 { 0466 QCOMPARE(range, Range(Cursor(1, 4), Cursor(2, 0))); 0467 } 0468 }; 0469 0470 void KateDocumentTest::testRemoveMultipleLines() 0471 { 0472 KTextEditor::DocumentPrivate doc; 0473 0474 doc.setText( 0475 "line1\n" 0476 "line2\n" 0477 "line3\n" 0478 "line4\n"); 0479 0480 SignalHandler handler; 0481 connect(&doc, &KTextEditor::DocumentPrivate::textRemoved, &handler, &SignalHandler::slotMultipleLinesRemoved); 0482 doc.removeText(Range(1, 0, 3, 0)); 0483 } 0484 0485 void KateDocumentTest::testInsertNewline() 0486 { 0487 KTextEditor::DocumentPrivate doc; 0488 0489 doc.setText( 0490 "this is line\n" 0491 "this is line2\n"); 0492 0493 SignalHandler handler; 0494 connect(&doc, &KTextEditor::DocumentPrivate::textInsertedRange, &handler, &SignalHandler::slotNewlineInserted); 0495 doc.editWrapLine(1, 4); 0496 } 0497 0498 void KateDocumentTest::testInsertAfterEOF() 0499 { 0500 KTextEditor::DocumentPrivate doc; 0501 0502 doc.setText( 0503 "line0\n" 0504 "line1"); 0505 0506 const QString input = QLatin1String( 0507 "line3\n" 0508 "line4"); 0509 0510 const QString expected = QLatin1String( 0511 "line0\n" 0512 "line1\n" 0513 "\n" 0514 "line3\n" 0515 "line4"); 0516 0517 doc.insertText(KTextEditor::Cursor(3, 0), input); 0518 QCOMPARE(doc.text(), expected); 0519 } 0520 0521 // we have two different ways of creating the checksum: 0522 // in KateFileLoader and KTextEditor::DocumentPrivate::createDigest. Make 0523 // sure, these two implementations result in the same checksum. 0524 void KateDocumentTest::testDigest() 0525 { 0526 // we will write the test file here to avoid that any line ending conversion for git will break it 0527 const QByteArray fileDigest = "aa22605da164a4e4e55f4c9738cfe1e53d4467f9"; 0528 QTemporaryFile file("testDigest"); 0529 file.open(); 0530 file.write("974d9ab0860c755a4f5686b3b6b429e1efd48a96\ntest\ntest\n\r\n\r\n\r\n"); 0531 file.flush(); 0532 0533 // make sure, Kate::TextBuffer and KTextEditor::DocumentPrivate::createDigest() equal 0534 KTextEditor::DocumentPrivate doc; 0535 doc.openUrl(QUrl::fromLocalFile(file.fileName())); 0536 const QByteArray bufferDigest(doc.checksum().toHex()); 0537 QVERIFY(doc.createDigest()); 0538 const QByteArray docDigest(doc.checksum().toHex()); 0539 0540 QCOMPARE(bufferDigest, fileDigest); 0541 QCOMPARE(docDigest, fileDigest); 0542 } 0543 0544 void KateDocumentTest::testModelines() 0545 { 0546 // honor document variable indent-width 0547 { 0548 KTextEditor::DocumentPrivate doc; 0549 QCOMPARE(doc.config()->indentationWidth(), 4); 0550 doc.readVariableLine(QStringLiteral("kate: indent-width 3;")); 0551 QCOMPARE(doc.config()->indentationWidth(), 3); 0552 } 0553 0554 // honor document variable indent-width with * wildcard 0555 { 0556 KTextEditor::DocumentPrivate doc; 0557 QCOMPARE(doc.config()->indentationWidth(), 4); 0558 doc.readVariableLine(QStringLiteral("kate-wildcard(*): indent-width 3;")); 0559 QCOMPARE(doc.config()->indentationWidth(), 3); 0560 } 0561 0562 // ignore document variable indent-width, since the wildcard does not match 0563 { 0564 KTextEditor::DocumentPrivate doc; 0565 QCOMPARE(doc.config()->indentationWidth(), 4); 0566 doc.readVariableLine(QStringLiteral("kate-wildcard(*.txt): indent-width 3;")); 0567 QCOMPARE(doc.config()->indentationWidth(), 4); 0568 } 0569 0570 // document variable indent-width, since the wildcard does not match 0571 { 0572 KTextEditor::DocumentPrivate doc; 0573 doc.openUrl(QUrl::fromLocalFile(QLatin1String(TEST_DATA_DIR "modelines.txt"))); 0574 QVERIFY(!doc.isEmpty()); 0575 0576 // ignore wrong wildcard 0577 QCOMPARE(doc.config()->indentationWidth(), 4); 0578 doc.readVariableLine(QStringLiteral("kate-wildcard(*.bar): indent-width 3;")); 0579 QCOMPARE(doc.config()->indentationWidth(), 4); 0580 0581 // read correct wildcard 0582 QCOMPARE(doc.config()->indentationWidth(), 4); 0583 doc.readVariableLine(QStringLiteral("kate-wildcard(*.txt): indent-width 5;")); 0584 QCOMPARE(doc.config()->indentationWidth(), 5); 0585 0586 // honor correct wildcard 0587 QCOMPARE(doc.config()->indentationWidth(), 5); 0588 doc.readVariableLine(QStringLiteral("kate-wildcard(*.foo;*.txt;*.bar): indent-width 6;")); 0589 QCOMPARE(doc.config()->indentationWidth(), 6); 0590 0591 // ignore incorrect mimetype 0592 QCOMPARE(doc.config()->indentationWidth(), 6); 0593 doc.readVariableLine(QStringLiteral("kate-mimetype(text/unknown): indent-width 7;")); 0594 QCOMPARE(doc.config()->indentationWidth(), 6); 0595 0596 // honor correct mimetype 0597 QCOMPARE(doc.config()->indentationWidth(), 6); 0598 doc.readVariableLine(QStringLiteral("kate-mimetype(text/plain): indent-width 8;")); 0599 QCOMPARE(doc.config()->indentationWidth(), 8); 0600 0601 // honor correct mimetype 0602 QCOMPARE(doc.config()->indentationWidth(), 8); 0603 doc.readVariableLine(QStringLiteral("kate-mimetype(text/foo;text/plain;text/bar): indent-width 9;")); 0604 QCOMPARE(doc.config()->indentationWidth(), 9); 0605 } 0606 } 0607 0608 void KateDocumentTest::testDefStyleNum() 0609 { 0610 KTextEditor::DocumentPrivate doc; 0611 doc.setText("foo\nbar\nasdf"); 0612 QCOMPARE(doc.defStyleNum(0, 0), 0); 0613 } 0614 0615 void KateDocumentTest::testTypeCharsWithSurrogateAndNewLine() 0616 { 0617 KTextEditor::DocumentPrivate doc; 0618 auto view = static_cast<KTextEditor::ViewPrivate *>(doc.createView(nullptr)); 0619 const char32_t surrogateUcs4String[] = {0x1f346, '\n', 0x1f346, 0}; 0620 const auto surrogateString = QString::fromUcs4(surrogateUcs4String); 0621 doc.typeChars(view, surrogateString); 0622 0623 QCOMPARE(doc.text(), surrogateString); 0624 } 0625 0626 void KateDocumentTest::testRemoveComposedCharacters() 0627 { 0628 KTextEditor::DocumentPrivate doc; 0629 auto view = static_cast<KTextEditor::ViewPrivate *>(doc.createView(nullptr)); 0630 view->config()->setValue(KateViewConfig::BackspaceRemoveComposedCharacters, true); 0631 doc.setText(QString::fromUtf8("व्यक्तियों")); 0632 doc.del(view, Cursor(0, 0)); 0633 0634 QCOMPARE(doc.text(), QString::fromUtf8(("क्तियों"))); 0635 0636 view->setCursorPosition({0, 7}); 0637 doc.backspace(view); 0638 0639 QCOMPARE(doc.text(), QString::fromUtf8(("क्ति"))); 0640 } 0641 0642 void KateDocumentTest::testAutoReload() 0643 { 0644 // ATM fails on Windows, mark as such to be able to enforce test success in CI 0645 #ifdef Q_OS_WIN 0646 QSKIP("Fails ATM, please fix"); 0647 #endif 0648 0649 QTemporaryFile file("AutoReloadTestFile"); 0650 file.open(); 0651 file.write("Hello"); 0652 file.flush(); 0653 0654 KTextEditor::DocumentPrivate doc; 0655 auto view = static_cast<KTextEditor::ViewPrivate *>(doc.createView(nullptr)); 0656 QVERIFY(doc.openUrl(QUrl::fromLocalFile(file.fileName()))); 0657 QCOMPARE(doc.text(), "Hello"); 0658 // The cursor should be and stay in the last line... 0659 QCOMPARE(view->cursorPosition().line(), doc.documentEnd().line()); 0660 0661 doc.autoReloadToggled(true); 0662 0663 // Some magic value. You can wait as long as you like after write to file, 0664 // without to wait before it doesn't work here. It mostly fails with 500, 0665 // it tend to work with 600, but is not guarantied to work even with 800 0666 QTest::qWait(1000); 0667 0668 file.write("\nTest"); 0669 file.flush(); 0670 0671 // Hardcoded delay m_modOnHdTimer in DocumentPrivate 0672 // + min val with which it looks working reliable here 0673 QTest::qWait(1000); 0674 QCOMPARE(doc.text(), "Hello\nTest"); 0675 // ...stay in the last line after reload! 0676 QCOMPARE(view->cursorPosition().line(), doc.documentEnd().line()); 0677 0678 // Now we have more then one line, set the cursor to some line != last line... 0679 Cursor c(0, 3); 0680 view->setCursorPosition(c); 0681 0682 file.write("\nWorld!"); 0683 file.flush(); 0684 0685 QTest::qWait(1000); 0686 QCOMPARE(doc.text(), "Hello\nTest\nWorld!"); 0687 // ...and ensure we have not move around 0688 QCOMPARE(view->cursorPosition(), c); 0689 } 0690 0691 void KateDocumentTest::testSearch() 0692 { 0693 /** 0694 * this is the start of some new implementation of searchText that can handle multi-line regex stuff 0695 * just naturally and without always concatenating the full document. 0696 */ 0697 0698 KTextEditor::DocumentPrivate doc; 0699 doc.setText("foo\nbar\nzonk"); 0700 0701 const QRegularExpression pattern(QStringLiteral("ar\nzonk$"), QRegularExpression::MultilineOption | QRegularExpression::UseUnicodePropertiesOption); 0702 int startLine = 0; 0703 int endLine = 2; 0704 QString textToMatch; 0705 int partialMatchLine = -1; 0706 for (int currentLine = startLine; currentLine <= endLine; ++currentLine) { 0707 // if we had a partial match before, we need to keep the old text and append our new line 0708 int matchStartLine = currentLine; 0709 if (partialMatchLine >= 0) { 0710 textToMatch += doc.line(currentLine); 0711 textToMatch += QLatin1Char('\n'); 0712 matchStartLine = partialMatchLine; 0713 } else { 0714 textToMatch = doc.line(currentLine); 0715 textToMatch += QLatin1Char('\n'); 0716 } 0717 0718 auto result = pattern.match(textToMatch, 0, QRegularExpression::PartialPreferFirstMatch); 0719 qDebug() << "match" << result.hasMatch() << result.hasPartialMatch() << result.capturedStart() << result.capturedLength(); 0720 0721 if (result.hasMatch()) { 0722 printf("found stuff starting in line %d\n", matchStartLine); 0723 break; 0724 } 0725 0726 // else: remember if we need to keep text! 0727 if (result.hasPartialMatch()) { 0728 // if we had already a partial match before, keep the line! 0729 if (partialMatchLine >= 0) { 0730 } else { 0731 partialMatchLine = currentLine; 0732 } 0733 } else { 0734 // we can forget the old text 0735 partialMatchLine = -1; 0736 } 0737 } 0738 } 0739 0740 void KateDocumentTest::testMatchingBracket_data() 0741 { 0742 QTest::addColumn<QString>("text"); 0743 QTest::addColumn<KTextEditor::Cursor>("cursor"); 0744 QTest::addColumn<KTextEditor::Range>("match"); 0745 QTest::addColumn<int>("maxLines"); 0746 0747 QTest::addRow("invalid") << "asdf\nasdf" << KTextEditor::Cursor(1, 0) << KTextEditor::Range::invalid() << 10; 0748 QTest::addRow("]-before") << "[\n]" << KTextEditor::Cursor(1, 0) << KTextEditor::Range({0, 0}, {1, 0}) << 10; 0749 QTest::addRow("]-after") << "[\n]" << KTextEditor::Cursor(1, 1) << KTextEditor::Range({0, 0}, {1, 0}) << 10; 0750 QTest::addRow("[-before") << "[\n]" << KTextEditor::Cursor(0, 0) << KTextEditor::Range({0, 0}, {1, 0}) << 10; 0751 QTest::addRow("[-after") << "[\n]" << KTextEditor::Cursor(0, 1) << KTextEditor::Range({0, 0}, {1, 0}) << 10; 0752 QTest::addRow("}-before") << "{\n}" << KTextEditor::Cursor(1, 0) << KTextEditor::Range({0, 0}, {1, 0}) << 10; 0753 QTest::addRow("}-after") << "{\n}" << KTextEditor::Cursor(1, 1) << KTextEditor::Range({0, 0}, {1, 0}) << 10; 0754 QTest::addRow("{-before") << "{\n}" << KTextEditor::Cursor(0, 0) << KTextEditor::Range({0, 0}, {1, 0}) << 10; 0755 QTest::addRow("{-after") << "{\n}" << KTextEditor::Cursor(0, 1) << KTextEditor::Range({0, 0}, {1, 0}) << 10; 0756 QTest::addRow(")-before") << "(\n)" << KTextEditor::Cursor(1, 0) << KTextEditor::Range({0, 0}, {1, 0}) << 10; 0757 QTest::addRow(")-after") << "(\n)" << KTextEditor::Cursor(1, 1) << KTextEditor::Range({0, 0}, {1, 0}) << 10; 0758 QTest::addRow("(-before") << "(\n)" << KTextEditor::Cursor(0, 0) << KTextEditor::Range({0, 0}, {1, 0}) << 10; 0759 QTest::addRow("(-after") << "(\n)" << KTextEditor::Cursor(0, 1) << KTextEditor::Range({0, 0}, {1, 0}) << 10; 0760 QTest::addRow("]-maxlines") << "[\n\n]" << KTextEditor::Cursor(1, 0) << KTextEditor::Range::invalid() << 1; 0761 } 0762 0763 void KateDocumentTest::testMatchingBracket() 0764 { 0765 QFETCH(QString, text); 0766 QFETCH(KTextEditor::Cursor, cursor); 0767 QFETCH(KTextEditor::Range, match); 0768 QFETCH(int, maxLines); 0769 0770 KTextEditor::DocumentPrivate doc; 0771 doc.setText(text); 0772 QCOMPARE(doc.findMatchingBracket(cursor, maxLines), match); 0773 } 0774 0775 void KateDocumentTest::testIndentOnPaste() 0776 { 0777 KTextEditor::DocumentPrivate doc; 0778 auto view = static_cast<KTextEditor::ViewPrivate *>(doc.createView(nullptr)); 0779 0780 doc.setHighlightingMode("C++"); 0781 doc.config()->setTabWidth(4); 0782 doc.config()->setIndentationMode("cppstyle"); 0783 0784 doc.setConfigValue("indent-pasted-text", true); 0785 0786 /** 0787 * namespace ns 0788 * { 0789 * class MyClass 0790 */ 0791 doc.setText(QStringLiteral("namespace ns\n{\nclass MyClass")); 0792 view->setCursorPosition({2, 5}); 0793 doc.paste(view, QStringLiteral(" SOME_MACRO")); 0794 // We have text in the line we are pasting in so the existing indentation shouldn't be disturbed 0795 QCOMPARE(doc.text(), QStringLiteral("namespace ns\n{\nclass SOME_MACRO MyClass")); 0796 0797 /** 0798 * namespace ns 0799 * { 0800 */ 0801 doc.setText(QStringLiteral("namespace ns\n{\n")); 0802 view->setCursorPosition({2, 0}); 0803 doc.paste(view, QStringLiteral("class MyClass")); 0804 // We have no text in the line we are pasting in so indentation will be adjusted 0805 QCOMPARE(doc.text(), QStringLiteral("namespace ns\n{\n class MyClass")); 0806 } 0807 0808 void KateDocumentTest::testAboutToSave() 0809 { 0810 KTextEditor::DocumentPrivate doc; 0811 const QString thisFile = QString::fromUtf8(__FILE__); 0812 bool opened = doc.openUrl(QUrl::fromLocalFile(thisFile)); 0813 0814 QVERIFY(opened); 0815 0816 QSignalSpy spy(&doc, &KTextEditor::DocumentPrivate::aboutToSave); 0817 QSignalSpy savedSpy(&doc, &KTextEditor::DocumentPrivate::documentSavedOrUploaded); 0818 0819 doc.documentSave(); 0820 0821 QVERIFY(spy.count() == 1 || spy.wait()); 0822 QVERIFY(savedSpy.count() == 1 || savedSpy.wait()); 0823 } 0824 0825 void KateDocumentTest::testKeepUndoOverReload() 0826 { 0827 // create test document with some simple text 0828 KTextEditor::DocumentPrivate doc; 0829 const QString initialText = QStringLiteral("lala\ntest\nfoobar\n"); 0830 doc.setText(initialText); 0831 QCOMPARE(doc.text(), initialText); 0832 0833 // now: do some editing 0834 const QString insertedText = QStringLiteral("newfirstline\n"); 0835 doc.insertText(KTextEditor::Cursor(0, 0), insertedText); 0836 QCOMPARE(doc.text(), insertedText + initialText); 0837 0838 // test undo/redo 0839 doc.undo(); 0840 QCOMPARE(doc.text(), initialText); 0841 doc.redo(); 0842 QCOMPARE(doc.text(), insertedText + initialText); 0843 0844 // save it to some local temporary file, for later reload 0845 QTemporaryFile tmpFile; 0846 tmpFile.open(); 0847 QVERIFY(doc.saveAs(QUrl::fromLocalFile(tmpFile.fileName()))); 0848 0849 // first: try if normal reload works 0850 QVERIFY(doc.documentReload()); 0851 QCOMPARE(doc.text(), insertedText + initialText); 0852 0853 // test undo/redo AFTER reload 0854 doc.undo(); 0855 QCOMPARE(doc.text(), initialText); 0856 doc.redo(); 0857 QCOMPARE(doc.text(), insertedText + initialText); 0858 } 0859 0860 void KateDocumentTest::testToggleComment() 0861 { 0862 { // BUG: 451471 0863 KTextEditor::DocumentPrivate doc; 0864 QVERIFY(doc.highlightingModes().contains(QStringLiteral("Python"))); 0865 doc.setHighlightingMode(QStringLiteral("Python")); 0866 const QString original = QStringLiteral("import hello;\ndef method():"); 0867 doc.setText(original); 0868 QVERIFY(doc.lines() == 2); 0869 0870 doc.commentSelection(doc.documentRange(), {1, 2}, false, DocumentPrivate::ToggleComment); 0871 QCOMPARE(doc.text(), QStringLiteral("# import hello;\n# def method():")); 0872 0873 doc.commentSelection(doc.documentRange(), {1, 2}, false, DocumentPrivate::ToggleComment); 0874 QCOMPARE(doc.text(), original); 0875 } 0876 0877 { // Comment C++; 0878 KTextEditor::DocumentPrivate doc; 0879 QVERIFY(doc.highlightingModes().contains(QStringLiteral("C++"))); 0880 doc.setHighlightingMode(QStringLiteral("C++")); 0881 QString original = QStringLiteral("#include<iostream>\nint main()\n{\nreturn 0;\n}\n"); 0882 doc.setText(original); 0883 QVERIFY(doc.lines() == 6); 0884 0885 doc.commentSelection(doc.documentRange(), {5, 0}, false, DocumentPrivate::ToggleComment); 0886 QCOMPARE(doc.text(), QStringLiteral("// #include<iostream>\n// int main()\n// {\n// return 0;\n// }\n")); 0887 0888 doc.commentSelection(doc.documentRange(), {5, 0}, false, DocumentPrivate::ToggleComment); 0889 QCOMPARE(doc.text(), original); 0890 0891 // Comment just a portion 0892 doc.commentSelection(Range(1, 0, 1, 3), Cursor(1, 3), false, DocumentPrivate::ToggleComment); 0893 QCOMPARE(doc.text(), QStringLiteral("#include<iostream>\n/*int*/ main()\n{\nreturn 0;\n}\n")); 0894 doc.commentSelection(Range(1, 0, 1, 7), Cursor(1, 3), false, DocumentPrivate::ToggleComment); 0895 QCOMPARE(doc.text(), original); 0896 0897 // mixed, one line commented, one not => both get commented 0898 original = QStringLiteral(" // int main()\n{}"); 0899 doc.setText(original); 0900 doc.commentSelection(doc.documentRange(), {1, 2}, false, DocumentPrivate::ToggleComment); 0901 QCOMPARE(doc.text(), QStringLiteral("// // int main()\n// {}")); 0902 doc.commentSelection(doc.documentRange(), {1, 2}, false, DocumentPrivate::ToggleComment); 0903 // after uncommenting, we get original text back with one line commented 0904 QCOMPARE(doc.text(), original); 0905 } 0906 0907 { // BUG: 458126 0908 KTextEditor::DocumentPrivate doc; 0909 doc.setText("another:\n\nanother2: hello"); 0910 QVERIFY(doc.highlightingModes().contains(QStringLiteral("YAML"))); 0911 doc.setHighlightingMode("YAML"); 0912 const QString original = doc.text(); 0913 0914 doc.commentSelection(doc.documentRange(), {2, 0}, false, DocumentPrivate::ToggleComment); 0915 QCOMPARE(doc.text(), "# another:\n# \n# another2: hello"); 0916 0917 doc.commentSelection(doc.documentRange(), {2, 0}, false, DocumentPrivate::ToggleComment); 0918 QCOMPARE(doc.text(), original); 0919 } 0920 } 0921 0922 void KateDocumentTest::testInsertTextTooLargeColumn() 0923 { 0924 KTextEditor::DocumentPrivate doc; 0925 const QString original = QStringLiteral("01234567\n01234567"); 0926 doc.setText(original); 0927 QVERIFY(doc.lines() == 2); 0928 QCOMPARE(doc.text(), original); 0929 0930 // try to insert text with initial \n at wrong column, did trigger invalid call to editWrapLine 0931 doc.insertText(KTextEditor::Cursor(0, 10), QStringLiteral("\nxxxx")); 0932 QCOMPARE(doc.text(), QStringLiteral("01234567 \nxxxx\n01234567")); 0933 } 0934 0935 void KateDocumentTest::testBug468495() 0936 { 0937 // original 0938 const char o[] = R"( 0123456789abcdefghijkl 0939 0123456789abcdefghijkl 0940 012345678901234567890123456789)"; 0941 // expected 0942 const char e[] = R"( 0123456789abcdefghijkl 0943 0944 0123456789abcdefghijkl 0945 012345678901234567890123456789)"; 0946 0947 QString original = QString::fromLatin1(o); 0948 KTextEditor::DocumentPrivate doc; 0949 doc.setText(original); 0950 doc.config()->setIndentationMode(QStringLiteral("cstyle")); 0951 KTextEditor::ViewPrivate *v = static_cast<KTextEditor::ViewPrivate *>(doc.createView(nullptr)); 0952 v->setCursorPosition({1, 0}); 0953 v->keyReturn(); 0954 0955 QString afterIndent = doc.text(); 0956 QCOMPARE(QString::fromLatin1(e), afterIndent); 0957 } 0958 0959 #include "katedocument_test.moc"