File indexing completed on 2024-07-14 03:56:50

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 &nbsp; inserted
0140     QVERIFY2(line6.endsWith(QStringLiteral(">&nbsp;</p>")),
0141              "Empty lines must have &nbsp; 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"