Warning, file /frameworks/ktextwidgets/autotests/krichtextedittest.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 This file is part of the KDE libraries 0003 SPDX-FileCopyrightText: 2009 Thomas McGuire <mcguire@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0006 */ 0007 0008 #include "krichtextedittest.h" 0009 0010 #include <KColorScheme> 0011 #include <krichtextedit.h> 0012 0013 #include <QFont> 0014 #include <QRegularExpression> 0015 #include <QScrollBar> 0016 #include <QTest> 0017 #include <QTextCursor> 0018 #include <QTextList> 0019 0020 QTEST_MAIN(KRichTextEditTest) 0021 0022 void KRichTextEditTest::testLinebreaks() 0023 { 0024 KRichTextEdit edit; 0025 edit.enableRichTextMode(); 0026 0027 // Enter the text with keypresses, for some strange reason a normal setText() or 0028 // setPlainText() call doesn't do the trick 0029 QTest::keyClicks(&edit, QStringLiteral("a\r\r")); 0030 edit.setTextUnderline(true); 0031 QTest::keyClicks(&edit, QStringLiteral("b\r\r\rc")); 0032 QCOMPARE(edit.toPlainText(), QStringLiteral("a\n\nb\n\n\nc")); 0033 0034 QString html = edit.toCleanHtml(); 0035 edit.clear(); 0036 edit.setHtml(html); 0037 QCOMPARE(edit.toPlainText(), QStringLiteral("a\n\nb\n\n\nc")); 0038 } 0039 0040 void KRichTextEditTest::testUpdateLinkAdd() 0041 { 0042 KRichTextEdit edit; 0043 edit.enableRichTextMode(); 0044 0045 // Add text, apply initial formatting, and add a link 0046 QTextCursor cursor = edit.textCursor(); 0047 cursor.insertText(QStringLiteral("Test")); 0048 QTextCharFormat charFormat = cursor.charFormat(); 0049 // Note that QTextEdit doesn't use the palette. Black is black. 0050 QCOMPARE(charFormat.foreground().color().name(), QColor(Qt::black).name()); 0051 0052 cursor.select(QTextCursor::BlockUnderCursor); 0053 edit.setTextCursor(cursor); 0054 edit.setTextBold(true); 0055 edit.setTextItalic(true); 0056 edit.updateLink(QStringLiteral("http://www.kde.org"), QStringLiteral("KDE")); 0057 0058 // Validate text and formatting 0059 cursor.movePosition(QTextCursor::Start); 0060 cursor.select(QTextCursor::WordUnderCursor); 0061 edit.setTextCursor(cursor); 0062 QCOMPARE(edit.toPlainText(), QStringLiteral("KDE ")); 0063 QCOMPARE(edit.fontItalic(), true); 0064 QCOMPARE(edit.fontWeight(), static_cast<int>(QFont::Bold)); 0065 QCOMPARE(edit.fontUnderline(), true); 0066 charFormat = cursor.charFormat(); 0067 QCOMPARE(charFormat.foreground(), QBrush(KColorScheme(QPalette::Active, KColorScheme::View).foreground(KColorScheme::LinkText).color())); 0068 QCOMPARE(charFormat.underlineColor(), KColorScheme(QPalette::Active, KColorScheme::View).foreground(KColorScheme::LinkText).color()); 0069 QCOMPARE(charFormat.underlineStyle(), QTextCharFormat::SingleUnderline); 0070 } 0071 0072 void KRichTextEditTest::testUpdateLinkRemove() 0073 { 0074 KRichTextEdit edit; 0075 edit.enableRichTextMode(); 0076 0077 // Add text, apply initial formatting, and add a link 0078 QTextCursor cursor = edit.textCursor(); 0079 cursor.insertText(QStringLiteral("Test")); 0080 cursor.select(QTextCursor::BlockUnderCursor); 0081 edit.setTextCursor(cursor); 0082 edit.setTextBold(true); 0083 edit.setTextItalic(true); 0084 edit.updateLink(QStringLiteral("http://www.kde.org"), QStringLiteral("KDE")); 0085 0086 // Remove link and validate formatting 0087 cursor.movePosition(QTextCursor::Start); 0088 cursor.select(QTextCursor::WordUnderCursor); 0089 edit.setTextCursor(cursor); 0090 edit.updateLink(QString(), QStringLiteral("KDE")); 0091 cursor.movePosition(QTextCursor::Start); 0092 cursor.select(QTextCursor::WordUnderCursor); 0093 edit.setTextCursor(cursor); 0094 QCOMPARE(edit.toPlainText(), QStringLiteral("KDE ")); 0095 QCOMPARE(edit.fontItalic(), true); 0096 QCOMPARE(edit.fontWeight(), static_cast<int>(QFont::Bold)); 0097 QCOMPARE(edit.fontUnderline(), false); 0098 QTextCharFormat charFormat = cursor.charFormat(); 0099 QCOMPARE(charFormat.foreground().color().name(), QColor(Qt::black).name()); 0100 QCOMPARE(charFormat.underlineColor().name(), QColor(Qt::black).name()); 0101 QCOMPARE(charFormat.underlineStyle(), QTextCharFormat::NoUnderline); 0102 } 0103 0104 void KRichTextEditTest::testHTMLLineBreaks() 0105 { 0106 KRichTextEdit edit; 0107 edit.enableRichTextMode(); 0108 0109 // Create the following text: 0110 // A 0111 // 0112 // B 0113 QTest::keyClicks(&edit, QStringLiteral("a\r")); 0114 0115 edit.setTextUnderline(true); 0116 0117 QTest::keyClicks(&edit, QStringLiteral("\rb")); 0118 0119 QString html = edit.toCleanHtml(); 0120 0121 // The problem we have is that we need to "fake" being a viewer such 0122 // as Thunderbird or MS-Outlook to unit test our html line breaks. 0123 // For now, we'll parse the 6th line (the empty one) and make sure it has the proper format 0124 // The first four (4) HTML code lines are DOCTYPE through <body> declaration 0125 0126 const QStringList lines = html.split(QLatin1Char('\n')); 0127 0128 // for (int idx=0; idx<lines.size(); idx++) { 0129 // qDebug() << ( idx + 1 ) << " : " << lines.at( idx ); 0130 // } 0131 0132 #if QT_VERSION < QT_VERSION_CHECK(6, 3, 0) 0133 QVERIFY2(lines.size() == 7, 0134 "we can't perform this unit test: " 0135 "the html rendering has changed beyond recognition"); 0136 #elif QT_VERSION < QT_VERSION_CHECK(6, 4, 0) 0137 QCOMPARE(lines.size(), 8); 0138 #else 0139 QCOMPARE(lines.size(), 10); 0140 #endif 0141 0142 const QString &line6 = lines.at(lines.size() - 2); 0143 0144 // make sure that this is an empty <p> line 0145 QVERIFY(line6.startsWith(QStringLiteral("<p style=\"-qt-paragraph-type:empty;"))); 0146 0147 // make sure that empty lines have the inserted 0148 QVERIFY2(line6.endsWith(QStringLiteral("> </p>")), 0149 "Empty lines must have or otherwise 3rd party " 0150 "viewers render those as non-existing lines"); 0151 } 0152 0153 void KRichTextEditTest::testHTMLOrderedLists() 0154 { 0155 // The problem we have is that we need to "fake" being a viewer such 0156 // as Thunderbird or MS-Outlook to unit test our html lists. 0157 // For now, we'll parse the 6th line (the <ol> element) and make sure it has the proper format 0158 0159 KRichTextEdit edit; 0160 edit.enableRichTextMode(); 0161 0162 edit.setTextUnderline(true); 0163 0164 // create a numbered (ordered) list 0165 QTextCursor cursor = edit.textCursor(); 0166 cursor.insertList(QTextListFormat::ListDecimal); 0167 0168 QTest::keyClicks(&edit, QStringLiteral("a\rb\rc\r")); 0169 0170 QString html = edit.toCleanHtml(); 0171 0172 const QStringList lines = html.split(QLatin1Char('\n')); 0173 0174 // Uncomment this section in case the first test fails to see if the HTML 0175 // rendering has actually introduced a bug, or merely a problem with the unit test itself 0176 // 0177 // for (int idx=0; idx<lines.size(); idx++) { 0178 // qDebug() << ( idx + 1 ) << " : " << lines.at( idx ); 0179 // } 0180 0181 #if QT_VERSION < QT_VERSION_CHECK(6, 3, 0) 0182 QVERIFY2(lines.size() == 9, 0183 "we can't perform this unit test: " 0184 "the html rendering has changed beyond recognition"); 0185 #elif QT_VERSION < QT_VERSION_CHECK(6, 4, 0) 0186 QCOMPARE(lines.size(), 10); 0187 #else 0188 QCOMPARE(lines.size(), 13); 0189 #endif 0190 0191 // this is the <ol> declaration line 0192 const QString &line6 = lines.at(lines.size() - 4); 0193 0194 // qDebug() << line6; 0195 0196 const QRegularExpression re(QStringLiteral("<ol.*?margin-left: 0px.*?><li")); 0197 QVERIFY2(!re.match(line6, 0).hasMatch(), 0198 "margin-left: 0px specified for ordered lists " 0199 "removes numbers in 3rd party viewers "); 0200 } 0201 0202 void KRichTextEditTest::testHTMLUnorderedLists() 0203 { 0204 // The problem we have is that we need to "fake" being a viewer such 0205 // as Thunderbird or MS-Outlook to unit test our html lists. 0206 // For now, we'll parse the 6th line (the <ul> element) and make sure it has the proper format 0207 // The first four (4) HTML code lines are DOCTYPE through <body> declaration 0208 0209 KRichTextEdit edit; 0210 edit.enableRichTextMode(); 0211 0212 edit.setTextUnderline(true); 0213 0214 // create a numbered (ordered) list 0215 QTextCursor cursor = edit.textCursor(); 0216 cursor.insertList(QTextListFormat::ListDisc); 0217 0218 QTest::keyClicks(&edit, QStringLiteral("a\rb\rc\r")); 0219 0220 QString html = edit.toCleanHtml(); 0221 0222 const QStringList lines = html.split(QLatin1Char('\n')); 0223 0224 // Uncomment this section in case the first test fails to see if the HTML 0225 // rendering has actually introduced a bug, or merely a problem with the unit test itself 0226 // 0227 // for (int idx=0; idx<lines.size(); idx++) { 0228 // qDebug() << ( idx + 1 ) << " : " << lines.at( idx ); 0229 // } 0230 0231 #if QT_VERSION < QT_VERSION_CHECK(6, 3, 0) 0232 QVERIFY2(lines.size() == 9, 0233 "we can't perform this unit test: " 0234 "the html rendering has changed beyond recognition"); 0235 #elif QT_VERSION < QT_VERSION_CHECK(6, 4, 0) 0236 QCOMPARE(lines.size(), 10); 0237 #else 0238 QCOMPARE(lines.size(), 13); 0239 #endif 0240 0241 // this is the <ol> declaration line 0242 const QString &line6 = lines.at(lines.size() - 4); 0243 0244 // qDebug() << line6; 0245 0246 #if KTEXTWIDGETS_BUILD_DEPRECATED_SINCE(5, 70) 0247 // there should not be a margin-left: 0 defined for the <ol> element 0248 QRegExp regex(QStringLiteral("<ul.*margin-left: 0px.*><li")); 0249 regex.setMinimal(true); 0250 QVERIFY2(regex.indexIn(line6, 0) == -1, 0251 "margin-left: 0px specified for unordered lists " 0252 "removes numbers in 3rd party viewers "); 0253 #endif 0254 0255 const QRegularExpression re(QStringLiteral("<ul.*?margin-left: 0px.*?><li")); 0256 QVERIFY2(!re.match(line6, 0).hasMatch(), 0257 "margin-left: 0px specified for unordered lists " 0258 "removes numbers in 3rd party viewers "); 0259 } 0260 0261 void KRichTextEditTest::testHeading() 0262 { 0263 KRichTextEdit edit; 0264 // Create two lines, make second one a heading 0265 QTest::keyClicks(&edit, QStringLiteral("a\rb")); 0266 // Make sure heading actually changes 0267 edit.setHeadingLevel(1); 0268 QCOMPARE(edit.textCursor().blockFormat().headingLevel(), 1); 0269 QCOMPARE(edit.fontWeight(), static_cast<int>(QFont::Bold)); 0270 // Make sure it doesn't clutter undo stack (a single undo is sufficient) 0271 edit.undo(); 0272 QCOMPARE(edit.textCursor().blockFormat().headingLevel(), 0); 0273 QCOMPARE(edit.fontWeight(), static_cast<int>(QFont::Normal)); 0274 0275 // Set heading & keep writing, the text remains a heading 0276 edit.setHeadingLevel(2); 0277 QTest::keyClicks(&edit, QStringLiteral("cd")); 0278 QCOMPARE(edit.textCursor().blockFormat().headingLevel(), 2); 0279 QCOMPARE(edit.fontWeight(), static_cast<int>(QFont::Bold)); 0280 0281 // Now add a new line, make sure it's no longer a heading 0282 QTest::keyClick(&edit, '\r'); 0283 QCOMPARE(edit.textCursor().blockFormat().headingLevel(), 0); 0284 QCOMPARE(edit.fontWeight(), static_cast<int>(QFont::Normal)); 0285 0286 // Make sure creating new line is also undoable 0287 edit.undo(); 0288 QCOMPARE(edit.textCursor().position(), 5); 0289 QCOMPARE(edit.textCursor().blockFormat().headingLevel(), 2); 0290 QCOMPARE(edit.fontWeight(), static_cast<int>(QFont::Bold)); 0291 0292 // Add a new line and some more text, make sure it's still normal 0293 QTest::keyClicks(&edit, QStringLiteral("\ref")); 0294 QCOMPARE(edit.textCursor().blockFormat().headingLevel(), 0); 0295 QCOMPARE(edit.fontWeight(), static_cast<int>(QFont::Normal)); 0296 0297 // Go to beginning of this line, press Backspace -> lines should be merged, 0298 // current line should become a heading 0299 QTest::keyClick(&edit, Qt::Key_Home); 0300 QTest::keyClick(&edit, Qt::Key_Backspace); 0301 QCOMPARE(edit.textCursor().blockFormat().headingLevel(), 2); 0302 QCOMPARE(edit.fontWeight(), static_cast<int>(QFont::Bold)); 0303 // Make sure this is also undoable 0304 edit.undo(); 0305 QCOMPARE(edit.textCursor().blockFormat().headingLevel(), 0); 0306 QCOMPARE(edit.fontWeight(), static_cast<int>(QFont::Normal)); 0307 // Return it back 0308 QTest::keyClick(&edit, Qt::Key_Backspace); 0309 // The line is now "bcd|ef", "|" is cursor. Press Enter, the second line should remain a heading 0310 QTest::keyClick(&edit, Qt::Key_Return); 0311 QCOMPARE(edit.textCursor().blockFormat().headingLevel(), 2); 0312 QCOMPARE(edit.fontWeight(), static_cast<int>(QFont::Bold)); 0313 // Change the heading level back to normal 0314 edit.setHeadingLevel(0); 0315 QCOMPARE(edit.textCursor().blockFormat().headingLevel(), 0); 0316 QCOMPARE(edit.fontWeight(), static_cast<int>(QFont::Normal)); 0317 // Go to end of previous line, press Delete -> lines should be merged again 0318 QTextCursor cursor = edit.textCursor(); 0319 cursor.movePosition(QTextCursor::PreviousBlock); 0320 cursor.movePosition(QTextCursor::EndOfBlock); 0321 edit.setTextCursor(cursor); 0322 QTest::keyClick(&edit, Qt::Key_Delete); 0323 QCOMPARE(edit.textCursor().blockFormat().headingLevel(), 2); 0324 QCOMPARE(edit.fontWeight(), static_cast<int>(QFont::Bold)); 0325 // Make sure this is also undoable 0326 edit.undo(); 0327 QCOMPARE(edit.textCursor().blockFormat().headingLevel(), 0); 0328 QCOMPARE(edit.fontWeight(), static_cast<int>(QFont::Normal)); 0329 0330 // Now playing with selection. The contents are currently: 0331 // --- 0332 // a 0333 // bcd 0334 // ef 0335 // gh 0336 // --- 0337 // Let's add a new line 'gh', select everything between "c" and "g" 0338 // and change heading. It should apply to both lines 0339 QTest::keyClicks(&edit, QStringLiteral("\rgh")); 0340 cursor.setPosition(4, QTextCursor::MoveAnchor); 0341 cursor.setPosition(10, QTextCursor::KeepAnchor); 0342 edit.setTextCursor(cursor); 0343 edit.setHeadingLevel(5); 0344 // In the end, both lines should change the heading (even before the selection, i.e. 'b'!) 0345 cursor.setPosition(3); 0346 edit.setTextCursor(cursor); 0347 QCOMPARE(edit.textCursor().blockFormat().headingLevel(), 5); 0348 QCOMPARE(edit.fontWeight(), static_cast<int>(QFont::Bold)); 0349 // (and after the selection, i.e. 'f'!) 0350 cursor.setPosition(11); 0351 edit.setTextCursor(cursor); 0352 QCOMPARE(edit.textCursor().blockFormat().headingLevel(), 5); 0353 QCOMPARE(edit.fontWeight(), static_cast<int>(QFont::Bold)); 0354 } 0355 0356 void KRichTextEditTest::testRulerScroll() 0357 { 0358 // This is a test for bug 195828 0359 KRichTextEdit edit; 0360 // Add some lines, so that scroll definitely appears 0361 for (int i = 0; i < 100; i++) { 0362 QTest::keyClicks(&edit, QStringLiteral("New line\r")); 0363 } 0364 // Widget has to be shown for the scrollbar to be adjusted 0365 edit.show(); 0366 // Ensure the scrollbar actually appears 0367 QVERIFY(edit.verticalScrollBar()->value() > 0); 0368 0369 edit.insertHorizontalRule(); 0370 // Make sure scrollbar didn't jump to the top 0371 QVERIFY(edit.verticalScrollBar()->value() > 0); 0372 } 0373 0374 void KRichTextEditTest::testNestedLists() 0375 { 0376 KRichTextEdit edit; 0377 // Simplest test: create a list with a single element 0378 QTest::keyClicks(&edit, QStringLiteral("el1")); 0379 edit.setListStyle(-static_cast<int>(QTextListFormat::ListSquare)); 0380 QVERIFY(edit.textCursor().currentList()); 0381 QCOMPARE(edit.textCursor().currentList()->format().style(), QTextListFormat::ListSquare); 0382 // It should not be indentable, as there is nothing above 0383 QVERIFY(!edit.canIndentList()); 0384 // But it should be dedentable 0385 QVERIFY(edit.canDedentList()); 0386 // Press enter, a new element should be added 0387 QTest::keyClicks(&edit, QStringLiteral("\rel2")); 0388 QVERIFY(edit.textCursor().currentList()); 0389 QCOMPARE(edit.textCursor().currentList()->format().style(), QTextListFormat::ListSquare); 0390 // Change indentation 0391 edit.indentListMore(); 0392 edit.setListStyle(-static_cast<int>(QTextListFormat::ListCircle)); 0393 QCOMPARE(edit.textCursor().currentList()->format().indent(), 2); 0394 QCOMPARE(edit.textCursor().currentList()->format().style(), QTextListFormat::ListCircle); 0395 // And another one; let's then change the style of "3" and see if "2" have also changed style 0396 QTest::keyClicks(&edit, QStringLiteral("\rel3")); 0397 edit.setListStyle(-static_cast<int>(QTextListFormat::ListDecimal)); 0398 edit.moveCursor(QTextCursor::PreviousBlock); 0399 QCOMPARE(edit.textCursor().currentList()->format().style(), QTextListFormat::ListDecimal); 0400 // Now add another element, and dedent it, so the list should look like following: 0401 // [] el1 0402 // 1. el2 0403 // 2. el3 0404 // [] el4 0405 edit.moveCursor(QTextCursor::End); 0406 QTest::keyClicks(&edit, QStringLiteral("\rel4")); 0407 edit.indentListLess(); 0408 QCOMPARE(edit.textCursor().currentList()->format().style(), QTextListFormat::ListSquare); 0409 // Let's change the style to disc and see if first element have also changed the style 0410 edit.setListStyle(-static_cast<int>(QTextListFormat::ListDisc)); 0411 edit.moveCursor(QTextCursor::Start); 0412 QCOMPARE(edit.textCursor().currentList()->format().style(), QTextListFormat::ListDisc); 0413 // Now let's play with selection. First we add couple subelements below, so the list is: 0414 // * el1 0415 // 1. el2 0416 // 2. el3 0417 // * el4 0418 // o el5 0419 // o el6 0420 edit.moveCursor(QTextCursor::End); 0421 QTest::keyClicks(&edit, QStringLiteral("\rel5")); 0422 edit.indentListMore(); 0423 edit.setListStyle(-static_cast<int>(QTextListFormat::ListCircle)); 0424 QTest::keyClicks(&edit, QStringLiteral("\rel6")); 0425 0426 // Let's select (el3-el5) and indent them. It should become: 0427 // * el1 0428 // 1. el2 0429 // 1. el3 0430 // 2. el4 0431 // o el5 0432 // 3. el6 0433 QTextCursor cursor(edit.document()); 0434 cursor.setPosition(9); 0435 cursor.setPosition(17, QTextCursor::KeepAnchor); 0436 edit.setTextCursor(cursor); 0437 edit.indentListMore(); 0438 edit.moveCursor(QTextCursor::End); 0439 QCOMPARE(edit.textCursor().currentList()->count(), 3); 0440 QCOMPARE(edit.textCursor().currentList()->format().style(), QTextListFormat::ListDecimal); 0441 // Now select el2-el5 and dedent them. It should become: 0442 // * el1 0443 // * el2 0444 // 1. el3 0445 // * el4 0446 // o el5 0447 // o el6 0448 cursor.setPosition(6); 0449 cursor.setPosition(18, QTextCursor::KeepAnchor); 0450 edit.setTextCursor(cursor); 0451 edit.indentListLess(); 0452 edit.moveCursor(QTextCursor::End); 0453 QCOMPARE(edit.textCursor().currentList()->count(), 2); 0454 QCOMPARE(edit.textCursor().currentList()->format().style(), QTextListFormat::ListCircle); 0455 // point at "el4" 0456 cursor.setPosition(13); 0457 QCOMPARE(cursor.currentList()->count(), 3); 0458 QCOMPARE(cursor.currentList()->format().style(), QTextListFormat::ListDisc); 0459 // Select el4 && el5, dedent it, so el4 becomes a simple text: 0460 // * el1 0461 // * el2 0462 // 1. el3 0463 // el4 0464 // o el5 0465 // o el6 0466 cursor.setPosition(17, QTextCursor::KeepAnchor); 0467 edit.setTextCursor(cursor); 0468 edit.indentListLess(); 0469 // point cursor at "el4" 0470 cursor.setPosition(13); 0471 QVERIFY(!cursor.currentList()); 0472 // point at "el5", make sure it's a separate list now 0473 cursor.setPosition(16); 0474 QCOMPARE(cursor.currentList()->count(), 1); 0475 QCOMPARE(cursor.currentList()->format().style(), QTextListFormat::ListCircle); 0476 // Make sure the selection is not dedentable anymore 0477 QVERIFY(!edit.canDedentList()); 0478 }