File indexing completed on 2023-09-24 11:44:39
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 QCOMPARE(lines.size(), 10); 0133 0134 const QString &line6 = lines.at(lines.size() - 2); 0135 0136 // make sure that this is an empty <p> line 0137 QVERIFY(line6.startsWith(QStringLiteral("<p style=\"-qt-paragraph-type:empty;"))); 0138 0139 // make sure that empty lines have the inserted 0140 QVERIFY2(line6.endsWith(QStringLiteral("> </p>")), 0141 "Empty lines must have or otherwise 3rd party " 0142 "viewers render those as non-existing lines"); 0143 } 0144 0145 void KRichTextEditTest::testHTMLOrderedLists() 0146 { 0147 // The problem we have is that we need to "fake" being a viewer such 0148 // as Thunderbird or MS-Outlook to unit test our html lists. 0149 // For now, we'll parse the 6th line (the <ol> element) and make sure it has the proper format 0150 0151 KRichTextEdit edit; 0152 edit.enableRichTextMode(); 0153 0154 edit.setTextUnderline(true); 0155 0156 // create a numbered (ordered) list 0157 QTextCursor cursor = edit.textCursor(); 0158 cursor.insertList(QTextListFormat::ListDecimal); 0159 0160 QTest::keyClicks(&edit, QStringLiteral("a\rb\rc\r")); 0161 0162 QString html = edit.toCleanHtml(); 0163 0164 const QStringList lines = html.split(QLatin1Char('\n')); 0165 0166 // Uncomment this section in case the first test fails to see if the HTML 0167 // rendering has actually introduced a bug, or merely a problem with the unit test itself 0168 // 0169 // for (int idx=0; idx<lines.size(); idx++) { 0170 // qDebug() << ( idx + 1 ) << " : " << lines.at( idx ); 0171 // } 0172 0173 QCOMPARE(lines.size(), 13); 0174 0175 // this is the <ol> declaration line 0176 const QString &line6 = lines.at(lines.size() - 4); 0177 0178 // qDebug() << line6; 0179 0180 const QRegularExpression re(QStringLiteral("<ol.*?margin-left: 0px.*?><li")); 0181 QVERIFY2(!re.match(line6, 0).hasMatch(), 0182 "margin-left: 0px specified for ordered lists " 0183 "removes numbers in 3rd party viewers "); 0184 } 0185 0186 void KRichTextEditTest::testHTMLUnorderedLists() 0187 { 0188 // The problem we have is that we need to "fake" being a viewer such 0189 // as Thunderbird or MS-Outlook to unit test our html lists. 0190 // For now, we'll parse the 6th line (the <ul> element) and make sure it has the proper format 0191 // The first four (4) HTML code lines are DOCTYPE through <body> declaration 0192 0193 KRichTextEdit edit; 0194 edit.enableRichTextMode(); 0195 0196 edit.setTextUnderline(true); 0197 0198 // create a numbered (ordered) list 0199 QTextCursor cursor = edit.textCursor(); 0200 cursor.insertList(QTextListFormat::ListDisc); 0201 0202 QTest::keyClicks(&edit, QStringLiteral("a\rb\rc\r")); 0203 0204 QString html = edit.toCleanHtml(); 0205 0206 const QStringList lines = html.split(QLatin1Char('\n')); 0207 0208 // Uncomment this section in case the first test fails to see if the HTML 0209 // rendering has actually introduced a bug, or merely a problem with the unit test itself 0210 // 0211 // for (int idx=0; idx<lines.size(); idx++) { 0212 // qDebug() << ( idx + 1 ) << " : " << lines.at( idx ); 0213 // } 0214 0215 QCOMPARE(lines.size(), 13); 0216 0217 // this is the <ol> declaration line 0218 const QString &line6 = lines.at(lines.size() - 4); 0219 0220 // qDebug() << line6; 0221 0222 const QRegularExpression re(QStringLiteral("<ul.*?margin-left: 0px.*?><li")); 0223 QVERIFY2(!re.match(line6, 0).hasMatch(), 0224 "margin-left: 0px specified for unordered lists " 0225 "removes numbers in 3rd party viewers "); 0226 } 0227 0228 void KRichTextEditTest::testHeading() 0229 { 0230 KRichTextEdit edit; 0231 // Create two lines, make second one a heading 0232 QTest::keyClicks(&edit, QStringLiteral("a\rb")); 0233 // Make sure heading actually changes 0234 edit.setHeadingLevel(1); 0235 QCOMPARE(edit.textCursor().blockFormat().headingLevel(), 1); 0236 QCOMPARE(edit.fontWeight(), static_cast<int>(QFont::Bold)); 0237 // Make sure it doesn't clutter undo stack (a single undo is sufficient) 0238 edit.undo(); 0239 QCOMPARE(edit.textCursor().blockFormat().headingLevel(), 0); 0240 QCOMPARE(edit.fontWeight(), static_cast<int>(QFont::Normal)); 0241 0242 // Set heading & keep writing, the text remains a heading 0243 edit.setHeadingLevel(2); 0244 QTest::keyClicks(&edit, QStringLiteral("cd")); 0245 QCOMPARE(edit.textCursor().blockFormat().headingLevel(), 2); 0246 QCOMPARE(edit.fontWeight(), static_cast<int>(QFont::Bold)); 0247 0248 // Now add a new line, make sure it's no longer a heading 0249 QTest::keyClick(&edit, '\r'); 0250 QCOMPARE(edit.textCursor().blockFormat().headingLevel(), 0); 0251 QCOMPARE(edit.fontWeight(), static_cast<int>(QFont::Normal)); 0252 0253 // Make sure creating new line is also undoable 0254 edit.undo(); 0255 QCOMPARE(edit.textCursor().position(), 5); 0256 QCOMPARE(edit.textCursor().blockFormat().headingLevel(), 2); 0257 QCOMPARE(edit.fontWeight(), static_cast<int>(QFont::Bold)); 0258 0259 // Add a new line and some more text, make sure it's still normal 0260 QTest::keyClicks(&edit, QStringLiteral("\ref")); 0261 QCOMPARE(edit.textCursor().blockFormat().headingLevel(), 0); 0262 QCOMPARE(edit.fontWeight(), static_cast<int>(QFont::Normal)); 0263 0264 // Go to beginning of this line, press Backspace -> lines should be merged, 0265 // current line should become a heading 0266 QTest::keyClick(&edit, Qt::Key_Home); 0267 QTest::keyClick(&edit, Qt::Key_Backspace); 0268 QCOMPARE(edit.textCursor().blockFormat().headingLevel(), 2); 0269 QCOMPARE(edit.fontWeight(), static_cast<int>(QFont::Bold)); 0270 // Make sure this is also undoable 0271 edit.undo(); 0272 QCOMPARE(edit.textCursor().blockFormat().headingLevel(), 0); 0273 QCOMPARE(edit.fontWeight(), static_cast<int>(QFont::Normal)); 0274 // Return it back 0275 QTest::keyClick(&edit, Qt::Key_Backspace); 0276 // The line is now "bcd|ef", "|" is cursor. Press Enter, the second line should remain a heading 0277 QTest::keyClick(&edit, Qt::Key_Return); 0278 QCOMPARE(edit.textCursor().blockFormat().headingLevel(), 2); 0279 QCOMPARE(edit.fontWeight(), static_cast<int>(QFont::Bold)); 0280 // Change the heading level back to normal 0281 edit.setHeadingLevel(0); 0282 QCOMPARE(edit.textCursor().blockFormat().headingLevel(), 0); 0283 QCOMPARE(edit.fontWeight(), static_cast<int>(QFont::Normal)); 0284 // Go to end of previous line, press Delete -> lines should be merged again 0285 QTextCursor cursor = edit.textCursor(); 0286 cursor.movePosition(QTextCursor::PreviousBlock); 0287 cursor.movePosition(QTextCursor::EndOfBlock); 0288 edit.setTextCursor(cursor); 0289 QTest::keyClick(&edit, Qt::Key_Delete); 0290 QCOMPARE(edit.textCursor().blockFormat().headingLevel(), 2); 0291 QCOMPARE(edit.fontWeight(), static_cast<int>(QFont::Bold)); 0292 // Make sure this is also undoable 0293 edit.undo(); 0294 QCOMPARE(edit.textCursor().blockFormat().headingLevel(), 0); 0295 QCOMPARE(edit.fontWeight(), static_cast<int>(QFont::Normal)); 0296 0297 // Now playing with selection. The contents are currently: 0298 // --- 0299 // a 0300 // bcd 0301 // ef 0302 // gh 0303 // --- 0304 // Let's add a new line 'gh', select everything between "c" and "g" 0305 // and change heading. It should apply to both lines 0306 QTest::keyClicks(&edit, QStringLiteral("\rgh")); 0307 cursor.setPosition(4, QTextCursor::MoveAnchor); 0308 cursor.setPosition(10, QTextCursor::KeepAnchor); 0309 edit.setTextCursor(cursor); 0310 edit.setHeadingLevel(5); 0311 // In the end, both lines should change the heading (even before the selection, i.e. 'b'!) 0312 cursor.setPosition(3); 0313 edit.setTextCursor(cursor); 0314 QCOMPARE(edit.textCursor().blockFormat().headingLevel(), 5); 0315 QCOMPARE(edit.fontWeight(), static_cast<int>(QFont::Bold)); 0316 // (and after the selection, i.e. 'f'!) 0317 cursor.setPosition(11); 0318 edit.setTextCursor(cursor); 0319 QCOMPARE(edit.textCursor().blockFormat().headingLevel(), 5); 0320 QCOMPARE(edit.fontWeight(), static_cast<int>(QFont::Bold)); 0321 } 0322 0323 void KRichTextEditTest::testRulerScroll() 0324 { 0325 // This is a test for bug 195828 0326 KRichTextEdit edit; 0327 // Add some lines, so that scroll definitely appears 0328 for (int i = 0; i < 100; i++) { 0329 QTest::keyClicks(&edit, QStringLiteral("New line\r")); 0330 } 0331 // Widget has to be shown for the scrollbar to be adjusted 0332 edit.show(); 0333 // Ensure the scrollbar actually appears 0334 QVERIFY(edit.verticalScrollBar()->value() > 0); 0335 0336 edit.insertHorizontalRule(); 0337 // Make sure scrollbar didn't jump to the top 0338 QVERIFY(edit.verticalScrollBar()->value() > 0); 0339 } 0340 0341 void KRichTextEditTest::testNestedLists() 0342 { 0343 KRichTextEdit edit; 0344 // Simplest test: create a list with a single element 0345 QTest::keyClicks(&edit, QStringLiteral("el1")); 0346 edit.setListStyle(-static_cast<int>(QTextListFormat::ListSquare)); 0347 QVERIFY(edit.textCursor().currentList()); 0348 QCOMPARE(edit.textCursor().currentList()->format().style(), QTextListFormat::ListSquare); 0349 // It should not be indentable, as there is nothing above 0350 QVERIFY(!edit.canIndentList()); 0351 // But it should be dedentable 0352 QVERIFY(edit.canDedentList()); 0353 // Press enter, a new element should be added 0354 QTest::keyClicks(&edit, QStringLiteral("\rel2")); 0355 QVERIFY(edit.textCursor().currentList()); 0356 QCOMPARE(edit.textCursor().currentList()->format().style(), QTextListFormat::ListSquare); 0357 // Change indentation 0358 edit.indentListMore(); 0359 edit.setListStyle(-static_cast<int>(QTextListFormat::ListCircle)); 0360 QCOMPARE(edit.textCursor().currentList()->format().indent(), 2); 0361 QCOMPARE(edit.textCursor().currentList()->format().style(), QTextListFormat::ListCircle); 0362 // And another one; let's then change the style of "3" and see if "2" have also changed style 0363 QTest::keyClicks(&edit, QStringLiteral("\rel3")); 0364 edit.setListStyle(-static_cast<int>(QTextListFormat::ListDecimal)); 0365 edit.moveCursor(QTextCursor::PreviousBlock); 0366 QCOMPARE(edit.textCursor().currentList()->format().style(), QTextListFormat::ListDecimal); 0367 // Now add another element, and dedent it, so the list should look like following: 0368 // [] el1 0369 // 1. el2 0370 // 2. el3 0371 // [] el4 0372 edit.moveCursor(QTextCursor::End); 0373 QTest::keyClicks(&edit, QStringLiteral("\rel4")); 0374 edit.indentListLess(); 0375 QCOMPARE(edit.textCursor().currentList()->format().style(), QTextListFormat::ListSquare); 0376 // Let's change the style to disc and see if first element have also changed the style 0377 edit.setListStyle(-static_cast<int>(QTextListFormat::ListDisc)); 0378 edit.moveCursor(QTextCursor::Start); 0379 QCOMPARE(edit.textCursor().currentList()->format().style(), QTextListFormat::ListDisc); 0380 // Now let's play with selection. First we add couple subelements below, so the list is: 0381 // * el1 0382 // 1. el2 0383 // 2. el3 0384 // * el4 0385 // o el5 0386 // o el6 0387 edit.moveCursor(QTextCursor::End); 0388 QTest::keyClicks(&edit, QStringLiteral("\rel5")); 0389 edit.indentListMore(); 0390 edit.setListStyle(-static_cast<int>(QTextListFormat::ListCircle)); 0391 QTest::keyClicks(&edit, QStringLiteral("\rel6")); 0392 0393 // Let's select (el3-el5) and indent them. It should become: 0394 // * el1 0395 // 1. el2 0396 // 1. el3 0397 // 2. el4 0398 // o el5 0399 // 3. el6 0400 QTextCursor cursor(edit.document()); 0401 cursor.setPosition(9); 0402 cursor.setPosition(17, QTextCursor::KeepAnchor); 0403 edit.setTextCursor(cursor); 0404 edit.indentListMore(); 0405 edit.moveCursor(QTextCursor::End); 0406 QCOMPARE(edit.textCursor().currentList()->count(), 3); 0407 QCOMPARE(edit.textCursor().currentList()->format().style(), QTextListFormat::ListDecimal); 0408 // Now select el2-el5 and dedent them. It should become: 0409 // * el1 0410 // * el2 0411 // 1. el3 0412 // * el4 0413 // o el5 0414 // o el6 0415 cursor.setPosition(6); 0416 cursor.setPosition(18, QTextCursor::KeepAnchor); 0417 edit.setTextCursor(cursor); 0418 edit.indentListLess(); 0419 edit.moveCursor(QTextCursor::End); 0420 QCOMPARE(edit.textCursor().currentList()->count(), 2); 0421 QCOMPARE(edit.textCursor().currentList()->format().style(), QTextListFormat::ListCircle); 0422 // point at "el4" 0423 cursor.setPosition(13); 0424 QCOMPARE(cursor.currentList()->count(), 3); 0425 QCOMPARE(cursor.currentList()->format().style(), QTextListFormat::ListDisc); 0426 // Select el4 && el5, dedent it, so el4 becomes a simple text: 0427 // * el1 0428 // * el2 0429 // 1. el3 0430 // el4 0431 // o el5 0432 // o el6 0433 cursor.setPosition(17, QTextCursor::KeepAnchor); 0434 edit.setTextCursor(cursor); 0435 edit.indentListLess(); 0436 // point cursor at "el4" 0437 cursor.setPosition(13); 0438 QVERIFY(!cursor.currentList()); 0439 // point at "el5", make sure it's a separate list now 0440 cursor.setPosition(16); 0441 QCOMPARE(cursor.currentList()->count(), 1); 0442 QCOMPARE(cursor.currentList()->format().style(), QTextListFormat::ListCircle); 0443 // Make sure the selection is not dedentable anymore 0444 QVERIFY(!edit.canDedentList()); 0445 } 0446 0447 #include "moc_krichtextedittest.cpp"