File indexing completed on 2024-05-26 05:28:45

0001 /* Copyright (C) 2006 - 2014 Jan Kundrát <jkt@flaska.net>
0002 
0003    This file is part of the Trojita Qt IMAP e-mail client,
0004    http://trojita.flaska.net/
0005 
0006    This program is free software; you can redistribute it and/or
0007    modify it under the terms of the GNU General Public License as
0008    published by the Free Software Foundation; either version 2 of
0009    the License or (at your option) version 3 or any later version
0010    accepted by the membership of KDE e.V. (or its successor approved
0011    by the membership of KDE e.V.), which shall act as a proxy
0012    defined in Section 14 of version 3 of the license.
0013 
0014    This program is distributed in the hope that it will be useful,
0015    but WITHOUT ANY WARRANTY; without even the implied warranty of
0016    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0017    GNU General Public License for more details.
0018 
0019    You should have received a copy of the GNU General Public License
0020    along with this program.  If not, see <http://www.gnu.org/licenses/>.
0021 */
0022 
0023 #include <QtTest>
0024 #include <QAction>
0025 #include <QTextDocument>
0026 #include <QWebFrame>
0027 #include <QWebView>
0028 #include "test_Html_formatting.h"
0029 #include "Composer/Recipients.h"
0030 #include "Composer/ReplaceSignature.h"
0031 #include "Composer/SenderIdentitiesModel.h"
0032 #include "Composer/SubjectMangling.h"
0033 
0034 #if defined(__has_feature)
0035 #  if  __has_feature(address_sanitizer)
0036 #    define SKIP_WEBKIT_TESTS
0037 #  endif
0038 #endif
0039 
0040 
0041 Q_DECLARE_METATYPE(QList<QUrl>)
0042 
0043 static QString visualizeWhitespace(QString s)
0044 {
0045     return s.replace(QLatin1Char(' '),
0046                      // U+00B7, a middle dot
0047                      QChar(0x00b7));
0048 }
0049 
0050 /** @short Test that conversion of plaintext mail to HTML works reasonably well */
0051 void HtmlFormattingTest::testPlainTextFormattingFlowed()
0052 {
0053     QFETCH(QString, plaintext);
0054     QFETCH(QString, htmlFlowed);
0055     QFETCH(QString, htmlNotFlowed);
0056 
0057     QCOMPARE(visualizeWhitespace(UiUtils::plainTextToHtml(plaintext, UiUtils::FlowedFormat::FLOWED)),
0058              visualizeWhitespace(htmlFlowed));
0059     QCOMPARE(visualizeWhitespace(UiUtils::plainTextToHtml(plaintext, UiUtils::FlowedFormat::PLAIN)),
0060              visualizeWhitespace(htmlNotFlowed));
0061 }
0062 
0063 /** @short Data for testPlainTextFormattingFlowed */
0064 void HtmlFormattingTest::testPlainTextFormattingFlowed_data()
0065 {
0066     QTest::addColumn<QString>("plaintext");
0067     QTest::addColumn<QString>("htmlFlowed");
0068     QTest::addColumn<QString>("htmlNotFlowed");
0069 
0070     QTest::newRow("empty-1") << QString() << QString() << QString();
0071     QTest::newRow("empty-2") << QStringLiteral("") << QStringLiteral("") << QStringLiteral("");
0072     QTest::newRow("empty-3") << QStringLiteral("\n") << QStringLiteral("\n") << QStringLiteral("\n");
0073     QTest::newRow("empty-4") << QStringLiteral("\n\n") << QStringLiteral("\n\n") << QStringLiteral("\n\n");
0074 
0075     QTest::newRow("minimal") << QStringLiteral("ahoj") << QStringLiteral("ahoj") << QStringLiteral("ahoj");
0076 
0077     QTest::newRow("multiline-trivial-LF") << QStringLiteral("Sample \ntext") << QStringLiteral("Sample text") << QStringLiteral("Sample \ntext");
0078     QTest::newRow("multiline-trivial-CR") << QStringLiteral("Sample \rtext") << QStringLiteral("Sample \rtext") << QStringLiteral("Sample \rtext");
0079     QTest::newRow("multiline-trivial-CRLF") << QStringLiteral("Sample \r\ntext") << QStringLiteral("Sample text") << QStringLiteral("Sample \ntext");
0080     QTest::newRow("multiline-with-empty-lines")
0081             << QStringLiteral("Sample \ntext.\n\nYay!")
0082             << QStringLiteral("Sample text.\n\nYay!")
0083             << QStringLiteral("Sample \ntext.\n\nYay!");
0084 
0085     QTest::newRow("signature-LF")
0086             << QStringLiteral("Yay.\n-- \nMeh.\n")
0087             << QStringLiteral("Yay.\n<span class=\"signature\">-- \nMeh.\n</span>")
0088             << QStringLiteral("Yay.\n<span class=\"signature\">-- \nMeh.\n</span>");
0089     QTest::newRow("signature-CRLF")
0090             << QStringLiteral("Yay.\r\n-- \r\nMeh.\r\n")
0091             << QStringLiteral("Yay.\n<span class=\"signature\">-- \nMeh.\n</span>")
0092             << QStringLiteral("Yay.\n<span class=\"signature\">-- \nMeh.\n</span>");
0093 
0094     QTest::newRow("gerrit-extra-leading-space")
0095             << QStringLiteral("Patch Set 2: Code-Review+2\r\n"
0096                        "\r\n"
0097                        "> There is no limit on the number of flags which could be present for\r\n"
0098                        " > a message\r\n"
0099                        "\r\n"
0100                        "Ok, checked ::normalizedFlags() - indeed *everything* is implicitly shared =)")
0101             << QStringLiteral("Patch Set 2: Code-Review+2\n"
0102                        "\n"
0103                        "<span class=\"level\"><input type=\"checkbox\" id=\"q1\"/><span class=\"shortquote\"><blockquote><span class=\"quotemarks\">&gt; </span>There is no limit on the number of flags which could be present for\n"
0104                        "<label for=\"q1\"></label></blockquote></span></span>&gt; a message\n"
0105                        "\n"
0106                        "Ok, checked ::normalizedFlags() - indeed <b><span class=\"markup\">*</span>everything<span class=\"markup\">*</span></b> is implicitly shared =)")
0107             << QStringLiteral("Patch Set 2: Code-Review+2\n"
0108                        "\n"
0109                        "<span class=\"level\"><input type=\"checkbox\" id=\"q1\"/><span class=\"shortquote\"><blockquote><span class=\"quotemarks\">&gt; </span>There is no limit on the number of flags which could be present for\n"
0110                        "<span class=\"quotemarks\">&gt; </span>a message\n"
0111                        "<label for=\"q1\"></label></blockquote></span></span>\n"
0112                        "Ok, checked ::normalizedFlags() - indeed <b><span class=\"markup\">*</span>everything<span class=\"markup\">*</span></b> is implicitly shared =)");
0113 }
0114 
0115 /** @short Corner cases of the DelSp formatting */
0116 void HtmlFormattingTest::testPlainTextFormattingFlowedDelSp()
0117 {
0118     QFETCH(QString, plaintext);
0119     QFETCH(QString, htmlFlowedDelSp);
0120 
0121     QCOMPARE(visualizeWhitespace(UiUtils::plainTextToHtml(plaintext, UiUtils::FlowedFormat::FLOWED_DELSP)),
0122              visualizeWhitespace(htmlFlowedDelSp));
0123 }
0124 
0125 /** @short Data for testPlainTextFormattingFlowedDelSp */
0126 void HtmlFormattingTest::testPlainTextFormattingFlowedDelSp_data()
0127 {
0128     QTest::addColumn<QString>("plaintext");
0129     QTest::addColumn<QString>("htmlFlowedDelSp");
0130 
0131     QTest::newRow("delsp-canonical") << QStringLiteral("abc  \r\ndef") << QStringLiteral("abc def");
0132     QTest::newRow("delsp-just-lf") << QStringLiteral("abc  \ndef") << QStringLiteral("abc def");
0133     QTest::newRow("delsp-borked-crlf") << QStringLiteral("abc\r\ndef") << QStringLiteral("abc\ndef");
0134     QTest::newRow("delsp-borked-lf") << QStringLiteral("abc\ndef") << QStringLiteral("abc\ndef");
0135     QTest::newRow("delsp-single-line-no-crlf") << QStringLiteral("abc ") << QStringLiteral("abc");
0136     QTest::newRow("delsp-single-line-crlf") << QStringLiteral("abc \r\n") << QStringLiteral("abc\n");
0137     QTest::newRow("delsp-single-line-lf") << QStringLiteral("abc \n") << QStringLiteral("abc\n");
0138     QTest::newRow("delsp-single-line-cr") << QStringLiteral("abc \r") << QStringLiteral("abc");
0139 }
0140 
0141 void HtmlFormattingTest::testPlainTextFormattingViaHtml()
0142 {
0143     QFETCH(QString, plaintext);
0144     QFETCH(QString, html);
0145 
0146     QCOMPARE(UiUtils::plainTextToHtml(plaintext, UiUtils::FlowedFormat::FLOWED), html);
0147 }
0148 
0149 void HtmlFormattingTest::testPlainTextFormattingViaHtml_data()
0150 {
0151     QTest::addColumn<QString>("plaintext");
0152     QTest::addColumn<QString>("html");
0153 
0154     QTest::newRow("containing-html")
0155             << QStringLiteral("<p>ahoj &amp; blesmrt</p>")
0156             << QStringLiteral("&lt;p&gt;ahoj &amp;amp; blesmrt&lt;/p&gt;");
0157     QTest::newRow("basic-formatting-1") << QStringLiteral("foo bar") << QStringLiteral("foo bar");
0158     QTest::newRow("basic-formatting-2")
0159             << QStringLiteral("ahoj *cau* nazdar")
0160             << QStringLiteral("ahoj <b><span class=\"markup\">*</span>cau<span class=\"markup\">*</span></b> nazdar");
0161     QTest::newRow("basic-formatting-3")
0162             << QStringLiteral("/ahoj/ *cau*")
0163             << QStringLiteral("<i><span class=\"markup\">/</span>ahoj<span class=\"markup\">/</span></i> <b><span class=\"markup\">*</span>cau<span class=\"markup\">*</span></b>");
0164     QTest::newRow("basic-formatting-4")
0165             << QStringLiteral("ahoj *_cau_* nazdar")
0166             << QStringLiteral("ahoj <b><span class=\"markup\">*</span><u><span class=\"markup\">_</span>cau"
0167                        "<span class=\"markup\">_</span></u><span class=\"markup\">*</span></b> nazdar");
0168     QTest::newRow("basic-formatting-666")
0169             << QStringLiteral("foo *bar* _baz_ /pwn/ yay foo@ @bar @ blesmrt")
0170             << QStringLiteral("foo <b><span class=\"markup\">*</span>bar<span class=\"markup\">*</span></b> "
0171                        "<u><span class=\"markup\">_</span>baz<span class=\"markup\">_</span></u> "
0172                        "<i><span class=\"markup\">/</span>pwn<span class=\"markup\">/</span></i> yay foo@ @bar @ blesmrt");
0173     QTest::newRow("formatting-and-newlines")
0174             << QStringLiteral("*blesmrt*\ntrojita")
0175             << QStringLiteral("<b><span class=\"markup\">*</span>blesmrt<span class=\"markup\">*</span></b>\ntrojita");
0176     QTest::newRow("links")
0177             << QStringLiteral("ahoj http://pwn:123/foo?bar&baz#nope")
0178             << QStringLiteral("ahoj <a href=\"http://pwn:123/foo?bar&amp;baz#nope\">http://pwn:123/foo?bar&amp;baz#nope</a>");
0179     // Test our escaping
0180     QTest::newRow("escaping-1")
0181             << QStringLiteral("<>&&gt; § §gt; §para;\n")
0182             << QStringLiteral("&lt;&gt;&amp;&amp;gt; § §gt; §para;\n");
0183     // A plaintext actually containing some HTML code -- bug 323390
0184     QTest::newRow("escaping-html-url-bug-323390")
0185             << QStringLiteral("<a href=\"http://trojita.flaska.net\">Trojita</a>")
0186             << QStringLiteral("&lt;a href=&quot;<a href=\"http://trojita.flaska.net\">http://trojita.flaska.net</a>&quot;&gt;Trojita&lt;/a&gt;");
0187     QTest::newRow("escaping-html-mail-bug-323390")
0188             << QStringLiteral("some-mail&ad'dr@foo")
0189             << QStringLiteral("<a href=\"mailto:some-mail&amp;ad'dr@foo\">some-mail&amp;ad'dr@foo</a>");
0190 
0191     QTest::newRow("mailto-1")
0192             << QStringLiteral("ble.smrt-1_2+3@example.org")
0193             << QStringLiteral("<a href=\"mailto:ble.smrt-1_2+3@example.org\">ble.smrt-1_2+3@example.org</a>");
0194 
0195     QTest::newRow("multiple-links-on-line")
0196             << QStringLiteral("Hi,\n"
0197                               "http://meh/ http://pwn/now foo@bar http://wtf\n"
0198                               "nothing x@y.org\n"
0199                               "foo@example.org else\n"
0200                               "test@domain"
0201                               )
0202             << QStringLiteral("Hi,\n"
0203                               "<a href=\"http://meh/\">http://meh/</a> <a href=\"http://pwn/now\">http://pwn/now</a> "
0204                                  "<a href=\"mailto:foo@bar\">foo@bar</a> <a href=\"http://wtf\">http://wtf</a>\n"
0205                               "nothing <a href=\"mailto:x@y.org\">x@y.org</a>\n"
0206                               "<a href=\"mailto:foo@example.org\">foo@example.org</a> else\n"
0207                               "<a href=\"mailto:test@domain\">test@domain</a>");
0208 
0209     QTest::newRow("http-link-with-nested-mail-and-formatting-chars")
0210             << QStringLiteral("http://example.org/meh/yay/?foo=test@example.org\n"
0211                               "http://example.org/(*checkout*)/pwn\n"
0212                               "*https://domain.org/yay*")
0213             << QStringLiteral("<a href=\"http://example.org/meh/yay/?foo=test@example.org\">http://example.org/meh/yay/?foo=test@example.org</a>\n"
0214                               "<a href=\"http://example.org/(*checkout*)/pwn\">http://example.org/(*checkout*)/pwn</a>\n"
0215                               "<b><span class=\"markup\">*</span><a href=\"https://domain.org/yay\">https://domain.org/yay</a><span class=\"markup\">*</span></b>");
0216 
0217     QTest::newRow("just-underscores")
0218             << QStringLiteral("___________")
0219             << QStringLiteral("___________");
0220 
0221     QTest::newRow("duplicated-formatters")
0222             << QStringLiteral("__meh__ **blah** //boo//")
0223             << QStringLiteral("__meh__ **blah** //boo//");
0224 
0225     QTest::newRow("two-but-different")
0226             << QStringLiteral("_/meh/_ *_blah_* /*boo*/")
0227             << QStringLiteral("<u><span class=\"markup\">_</span><i><span class=\"markup\">/</span>meh"
0228                               "<span class=\"markup\">/</span></i><span class=\"markup\">_</span></u> "
0229                               "<b><span class=\"markup\">*</span><u><span class=\"markup\">_</span>blah"
0230                               "<span class=\"markup\">_</span></u><span class=\"markup\">*</span></b> "
0231                               "<i><span class=\"markup\">/</span><b><span class=\"markup\">*</span>boo"
0232                               "<span class=\"markup\">*</span></b><span class=\"markup\">/</span></i>");
0233 }
0234 
0235 WebRenderingTester::WebRenderingTester()
0236 {
0237     m_web = new QWebView(0);
0238     m_loop = new QEventLoop(this);
0239     connect(m_web, &QWebView::loadFinished, m_loop, &QEventLoop::quit);
0240 }
0241 
0242 WebRenderingTester::~WebRenderingTester()
0243 {
0244     delete m_web;
0245 }
0246 
0247 QString WebRenderingTester::asPlainText(const QString &input, const UiUtils::FlowedFormat format,
0248                                         const CollapsingFlags collapsing)
0249 {
0250     // FIXME: bad pasted thing!
0251     static const QString stylesheet = QStringLiteral(
0252         "pre{word-wrap: break-word; white-space: pre-wrap;}"
0253         ".quotemarks{color:transparent;font-size:0px;}"
0254         "blockquote{font-size:90%; margin: 4pt 0 4pt 0; padding: 0 0 0 1em; border-left: 2px solid blue;}"
0255         "blockquote blockquote blockquote {font-size: 100%}"
0256         ".signature{opacity: 0.6;}"
0257         "input {display: none}"
0258         "input ~ span.full {display: block}"
0259         "input ~ span.short {display: none}"
0260         "input:checked ~ span.full {display: none}"
0261         "input:checked ~ span.short {display: block}"
0262         "label {border: 1px solid #333333; border-radius: 5px; padding: 0px 4px 0px 4px; margin-left: 8px; white-space: nowrap}"
0263         "span.full > blockquote > label:before {content: \"\u25b4\"}"
0264         "span.short > blockquote > label:after {content: \" \u25be\"}"
0265         "span.shortquote > blockquote > label {display: none}"
0266     );
0267     static const QString htmlHeader(QStringLiteral("<html><head><style type=\"text/css\"><!--") + stylesheet + QLatin1String("--></style></head><body><pre>"));
0268     static const QString htmlFooter(QStringLiteral("\n</pre></body></html>"));
0269 
0270     sourceData = htmlHeader + UiUtils::plainTextToHtml(input, format) + htmlFooter;
0271     if (collapsing == RenderExpandEverythingCollapsed)
0272         sourceData = sourceData.replace(QStringLiteral(" checked=\"checked\""), QString());
0273     QTimer::singleShot(0, this, SLOT(doDelayedLoad()));
0274     m_loop->exec();
0275     m_web->page()->action(QWebPage::SelectAll)->trigger();
0276     return m_web->page()->selectedText();
0277 }
0278 
0279 void WebRenderingTester::doDelayedLoad()
0280 {
0281     m_web->page()->mainFrame()->setHtml(sourceData);
0282 }
0283 
0284 // ...because QCOMPARE uses a fixed buffer for 1024 bytes for the debug printing...
0285 #define LONG_STR_QCOMPARE(WHAT, EXPECTED) \
0286 { \
0287     if (EXPECTED.size() < 350) { \
0288         QCOMPARE(WHAT, EXPECTED); \
0289     } else { \
0290         QString actual = WHAT; \
0291         if (actual != EXPECTED) {\
0292             qDebug() << actual; \
0293             qDebug() << EXPECTED; \
0294             qDebug() << #WHAT; \
0295         }; \
0296         QVERIFY(actual == EXPECTED); \
0297     } \
0298 }
0299 
0300 void HtmlFormattingTest::testPlainTextFormattingViaPaste()
0301 {
0302     QFETCH(QString, source);
0303     QFETCH(QString, formattedFlowed);
0304     QFETCH(QString, formattedPlain);
0305     QFETCH(QString, expandedFlowed);
0306 
0307     // Allow specifying "the same" by just passing null QStrings along
0308     if (formattedPlain.isEmpty())
0309         formattedPlain = formattedFlowed;
0310     if (expandedFlowed.isEmpty())
0311         expandedFlowed = formattedFlowed;
0312 
0313 #ifdef SKIP_WEBKIT_TESTS
0314     QSKIP("ASAN build -- QtWebKit is known to be broken, skipping");
0315 #else
0316     {
0317         WebRenderingTester tester;
0318         LONG_STR_QCOMPARE(visualizeWhitespace(tester.asPlainText(source, UiUtils::FlowedFormat::FLOWED)),
0319                           visualizeWhitespace(formattedFlowed));
0320     }
0321 
0322     {
0323         WebRenderingTester tester;
0324         LONG_STR_QCOMPARE(visualizeWhitespace(tester.asPlainText(source, UiUtils::FlowedFormat::PLAIN)),
0325                           visualizeWhitespace(formattedPlain));
0326     }
0327 
0328     {
0329         WebRenderingTester tester;
0330         LONG_STR_QCOMPARE(visualizeWhitespace(tester.asPlainText(source, UiUtils::FlowedFormat::FLOWED, WebRenderingTester::RenderExpandEverythingCollapsed)),
0331                  visualizeWhitespace(expandedFlowed));
0332     }
0333 #endif
0334 }
0335 
0336 void HtmlFormattingTest::testPlainTextFormattingViaPaste_data()
0337 {
0338     QTest::addColumn<QString>("source");
0339     QTest::addColumn<QString>("formattedFlowed");
0340     QTest::addColumn<QString>("formattedPlain");
0341     QTest::addColumn<QString>("expandedFlowed");
0342 
0343     QTest::newRow("no-quotes")
0344             << QStringLiteral("Sample mail message.\n")
0345             << QStringLiteral("Sample mail message.\n")
0346             << QStringLiteral() << QStringLiteral();
0347 
0348     QTest::newRow("no-quotes-flowed")
0349             << QStringLiteral("This is something which is split \namong a few lines \n  like \n   this. ")
0350             << QStringLiteral("This is something which is split among a few lines  like   this.")
0351             << QStringLiteral("This is something which is split \namong a few lines \n  like \n   this. ")
0352             << QStringLiteral();
0353 
0354     QTest::newRow("quote-1")
0355             << QStringLiteral("Foo bar.\n> blesmrt\n>>trojita\nomacka")
0356             << QStringLiteral("Foo bar.\n> blesmrt\n>> trojita\nomacka")
0357             << QStringLiteral() << QStringLiteral();
0358 
0359     QTest::newRow("quote-levels")
0360             << QStringLiteral("Zero.\n>One\n>> Two\n>>>> Four-0\n>>>> Four-1\n>>>> Four-2\n>>>> Four-3\n>>>Three\nZeroB")
0361             << QStringLiteral("Zero.\n> One\n>> Two ...\nZeroB")
0362             << QStringLiteral()
0363             << QStringLiteral("Zero.\n> One\n>> Two\n>>>> Four-0\n>>>> Four-1\n>>>> Four-2\n>>>> Four-3\n>>> Three\nZeroB");
0364 
0365     QTest::newRow("quoted-no-spacing")
0366             << QStringLiteral("> foo\nbar\n> baz")
0367             << QStringLiteral("> foo\nbar\n> baz")
0368             << QStringLiteral() << QStringLiteral();
0369 
0370     QTest::newRow("bottom-quoting")
0371             << QStringLiteral("Foo bar.\n> blesmrt\n>> 333")
0372             << QStringLiteral("Foo bar.\n> blesmrt\n>> 333")
0373             << QStringLiteral()
0374             << QStringLiteral();
0375 
0376     QTest::newRow("bottom-quoting-toobig")
0377             << QStringLiteral("Foo bar.\n> blesmrt\n>> 333\n>> 666\n>> 666-2\n>> 666-3\n>> 666-4")
0378             << QStringLiteral("Foo bar.\n> blesmrt ...\n")
0379             << QStringLiteral()
0380             << QStringLiteral("Foo bar.\n> blesmrt\n>> 333\n>> 666\n>> 666-2\n>> 666-3\n>> 666-4\n");
0381 
0382     QTest::newRow("different-quote-levels-not-flowed-together")
0383             << QStringLiteral("Foo bar. \n> blesmrt \n>> 333")
0384             << QStringLiteral("Foo bar.\n> blesmrt\n>> 333")
0385             << QStringLiteral("Foo bar. \n> blesmrt \n>> 333")
0386             << QStringLiteral("Foo bar.\n> blesmrt\n>> 333");
0387 
0388     QTest::newRow("different-quote-levels-not-flowed-together-toobig")
0389             << QStringLiteral("Foo bar. \n> blesmrt \n>> 333\n>> 666\n>> 666-2\n>> 666-3\n>> 666-4")
0390             // The space right in front of "..." is a separator for copy-paste. The original space from the input
0391             // is interpretted as a line mistakenly marked as flowed, but since the line of a paragraph cannot be
0392             // flowed, it's a bug which we detect and throw it away.
0393             << QStringLiteral("Foo bar.\n> blesmrt ...\n")
0394             // On the other hand, we *do* expect two spaces here -- one for the non-flowed space,
0395             // and the other one for our separator.
0396             << QStringLiteral("Foo bar. \n> blesmrt  ...\n")
0397             << QStringLiteral("Foo bar.\n> blesmrt\n>> 333\n>> 666\n>> 666-2\n>> 666-3\n>> 666-4\n");
0398 
0399     QTest::newRow("nested-quotes-correct-indicator")
0400             << QStringLiteral(">>> Three levels down.\n>> Two levels down.\n> One level down.\nReal mail.")
0401             << QStringLiteral(">>> ...\n>> Two levels down. ...\n> One level down.\nReal mail.")
0402             << QStringLiteral()
0403             << QStringLiteral(">>> Three levels down.\n>> Two levels down.\n> One level down.\nReal mail.");
0404 
0405     QString lipsum = QStringLiteral("Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut "
0406                                     "labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco "
0407                                     "laboris nisi ut aliquid ex ea commodi consequat. Quis aute iure reprehenderit in "
0408                                     "voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint obcaecat "
0409                                     "cupiditat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.");
0410     QString shortLipsum = QString(lipsum + QLatin1Char(' ') + lipsum).left(5*160);
0411 
0412     QTest::newRow("collapsed-last-quote")
0413             << QStringLiteral("Some real text.\n> ") + lipsum + QLatin1Char(' ') + lipsum
0414             << QStringLiteral("Some real text.\n> ") + shortLipsum + " ...\n"
0415             << QString()
0416             << QStringLiteral("Some real text.\n> ") + lipsum + QLatin1Char(' ') + lipsum + "\n";
0417 
0418     QTest::newRow("quoted-common")
0419             << QStringLiteral("On quinta-feira, 4 de outubro de 2012 15.46.57, André Somers wrote:\n"
0420                                  "> If you think that running 21 threads on an 8 core system will run make \n"
0421                                  "> your task go faster, then Thiago is right: you don't understand your \n"
0422                                  "> problem.\n"
0423                                  "If you run 8 threads on an 8-core system and they use the CPU fully, then \n"
0424                                  "you're running as fast as you can.\n"
0425                                  "\n"
0426                                  "If you have more threads than the number of processors and if all threads are \n"
0427                                  "ready to be executed, then the OS will schedule timeslices to each thread. \n"
0428                                  "That means threads get executed and suspended all the time, sometimes \n"
0429                                  "migrating between processors. That adds overhead.\n"
0430                                  // yes, some parts have been removed here.
0431                                  "\n"
0432                                  "-- \n"
0433                                  "Thiago's name goes here.\n")
0434             << QStringLiteral("On quinta-feira, 4 de outubro de 2012 15.46.57, André Somers wrote:\n"
0435                                  "> If you think that running 21 threads on an 8 core system will run make "
0436                                  "your task go faster, then Thiago is right: you don't understand your "
0437                                  "problem.\n"
0438                                  "If you run 8 threads on an 8-core system and they use the CPU fully, then "
0439                                  "you're running as fast as you can.\n"
0440                                  "\n"
0441                                  "If you have more threads than the number of processors and if all threads are "
0442                                  "ready to be executed, then the OS will schedule timeslices to each thread. "
0443                                  "That means threads get executed and suspended all the time, sometimes "
0444                                  "migrating between processors. That adds overhead.\n"
0445                                  "\n"
0446                                  "-- \n"
0447                                  "Thiago's name goes here.\n")
0448             << QStringLiteral("On quinta-feira, 4 de outubro de 2012 15.46.57, André Somers wrote:\n"
0449                                  "> If you think that running 21 threads on an 8 core system will run make \n"
0450                                  "> your task go faster, then Thiago is right: you don't understand your \n"
0451                                  "> problem.\n"
0452                                  "If you run 8 threads on an 8-core system and they use the CPU fully, then \n"
0453                                  "you're running as fast as you can.\n"
0454                                  "\n"
0455                                  "If you have more threads than the number of processors and if all threads are \n"
0456                                  "ready to be executed, then the OS will schedule timeslices to each thread. \n"
0457                                  "That means threads get executed and suspended all the time, sometimes \n"
0458                                  "migrating between processors. That adds overhead.\n"
0459                                  "\n"
0460                                  "-- \n"
0461                                  "Thiago's name goes here.\n")
0462             << QString();
0463 
0464     QTest::newRow("small-quotes-arent-collapsible")
0465             << QStringLiteral("On Wednesday 09 January 2013 08:56:25 Jekyll Wu wrote:\n"
0466                                  "> If you plan to \n"
0467                                  "> use bugs.kde.org as the tracker, then you don't need to call \n"
0468                                  "> setBugAddress() at all. The default value just works.\n"
0469                                  "\n"
0470                                  "Fixed.\n"
0471                                  "\n"
0472                                  "\n"
0473                                  "> And don't forget to ask sysadmins to create a \"mangonel\" product on \n"
0474                                  "> bugs.kde.org :)\n"
0475                                  "\n"
0476                                  "Done.\n"
0477                                  "\n"
0478                                  "Thanks for the review! :D\n"
0479                                  "\n"
0480                                  "-- \n"
0481                                  "Martin Sandsmark\n"
0482                                  "KDE\n")
0483             << QStringLiteral("On Wednesday 09 January 2013 08:56:25 Jekyll Wu wrote:\n"
0484                                  "> If you plan to use bugs.kde.org as the tracker, then you don't need to call setBugAddress() "
0485                                  "at all. The default value just works.\n"
0486                                  "\n"
0487                                  "Fixed.\n"
0488                                  "\n"
0489                                  "\n"
0490                                  "> And don't forget to ask sysadmins to create a \"mangonel\" product on bugs.kde.org :)\n"
0491                                  "\n"
0492                                  "Done.\n"
0493                                  "\n"
0494                                  "Thanks for the review! :D\n"
0495                                  "\n"
0496                                  "-- \n"
0497                                  "Martin Sandsmark\n"
0498                                  "KDE\n")
0499             << QStringLiteral("On Wednesday 09 January 2013 08:56:25 Jekyll Wu wrote:\n"
0500                                  "> If you plan to \n"
0501                                  "> use bugs.kde.org as the tracker, then you don't need to call \n"
0502                                  "> setBugAddress() at all. The default value just works.\n"
0503                                  "\n"
0504                                  "Fixed.\n"
0505                                  "\n"
0506                                  "\n"
0507                                  "> And don't forget to ask sysadmins to create a \"mangonel\" product on \n"
0508                                  "> bugs.kde.org :)\n"
0509                                  "\n"
0510                                  "Done.\n"
0511                                  "\n"
0512                                  "Thanks for the review! :D\n"
0513                                  "\n"
0514                                  "-- \n"
0515                                  "Martin Sandsmark\n"
0516                                  "KDE\n")
0517             << QString();
0518 
0519     // https://bugs.kde.org/show_bug.cgi?id=337919
0520     QTest::newRow("blank-line-separator")
0521             << QStringLiteral("First para. \n\nSecond para.\n\n\nThird para.")
0522             << QStringLiteral("First para. \n\nSecond para.\n\n\nThird para.")
0523             << QStringLiteral("First para. \n\nSecond para.\n\n\nThird para.")
0524             << QStringLiteral("First para. \n\nSecond para.\n\n\nThird para.");
0525 
0526     QTest::newRow("blank-line-separator-of-quoted")
0527             << QStringLiteral("> First para. \n>\n> Second para.\n>\n>\n>Third para.")
0528             << QStringLiteral("> First para. \n> \n> Second para.\n> \n> \n> Third para. ...\n")
0529             << QStringLiteral("> First para. \n> \n> Second para.\n> \n> \n> Third para. ...\n")
0530             << QStringLiteral("> First para. \n> \n> Second para.\n> \n> \n> Third para.\n");
0531 
0532     QTest::newRow("blanks-in-different-levels")
0533             << QStringLiteral("Yesterday: \n>> Test2A. \n>>\n>> Test2B \n>>\nTest0. \n> Test1A. \n> \n>Test 1B. ")
0534             << QStringLiteral("Yesterday:\n>> Test2A. \n>> \n>> Test2B \n>> \nTest0.\n> Test1A. \n> \n> Test 1B.")
0535             << QStringLiteral("Yesterday: \n>> Test2A. \n>> \n>> Test2B \n>> \nTest0. \n> Test1A. \n> \n> Test 1B. ")
0536             << QStringLiteral("Yesterday:\n>> Test2A. \n>> \n>> Test2B \n>> \nTest0.\n> Test1A. \n> \n> Test 1B.");
0537 
0538     QTest::newRow("kmail-spaces-in-quotes")
0539             << QStringLiteral("On Wednesday 12 of November 2014 06:54:53 John Layt wrote:\r\n"
0540                        "> On 8 November 2014 08:45, John Layt <jlayt@kde.org> wrote:\r\n"
0541                        "> > Hi,\r\n"
0542                        "> > \r\n"
0543                        "> > Just checking, has the sponsorship budget been approved (so I can book\r\n"
0544                        "> > flights before they cost silly amounts), and has accommodation been\r\n"
0545                        "> > sorted yet or do we need to do it ourselves?\r\n"
0546                        "> \r\n"
0547                        "> Ping?\r\n"
0548                        "\r\n"
0549                        "CC'd Michael directly in case he missed the emails between all the jenkins \r\n"
0550                        "spam. I'd like to have this sorted out soon too  :)\r\n")
0551             << QStringLiteral("On Wednesday 12 of November 2014 06:54:53 John Layt wrote:\n"
0552                        "> On 8 November 2014 08:45, John Layt <jlayt@kde.org> wrote:\n"
0553                        "> > Hi,\n"
0554                        // This might be surprising at first, but "> > > " is actually correct here.
0555                        // The first ">" is the quote level, second ">" is the second ">" from the first line,
0556                        // and because that line ends with a space, it's a flowed line, and therefore the next
0557                        // line should be joined in there. The next line has a quote depth of one (the matching
0558                        // stops at first space), and then again actually starts with another ">".
0559                        // Therefore ">" for a quote prefix, " " as a spearator, and "> > Just..." as actual content.
0560                        "> > > Just checking, has the sponsorship budget been approved (so I can book\n"
0561                        "> > flights before they cost silly amounts), and has accommodation been\n"
0562                        "> > sorted yet or do we need to do it ourselves?\n"
0563                        "> \n"
0564                        "> Ping?\n"
0565                        "\n"
0566                        "CC'd Michael directly in case he missed the emails between all the jenkins spam. I'd like to have this sorted out soon too  :)\n")
0567             << QStringLiteral("On Wednesday 12 of November 2014 06:54:53 John Layt wrote:\n"
0568                        "> On 8 November 2014 08:45, John Layt <jlayt@kde.org> wrote:\n"
0569                        ">> Hi,\n"
0570                        ">> \n"
0571                        ">> Just checking, has the sponsorship budget been approved (so I can book\n"
0572                        ">> flights before they cost silly amounts), and has accommodation been\n"
0573                        ">> sorted yet or do we need to do it ourselves?\n"
0574                        "> \n"
0575                        "> Ping?\n"
0576                        "\n"
0577                        "CC'd Michael directly in case he missed the emails between all the jenkins \n"
0578                        "spam. I'd like to have this sorted out soon too  :)\n")
0579             << QStringLiteral("On Wednesday 12 of November 2014 06:54:53 John Layt wrote:\n"
0580                        "> On 8 November 2014 08:45, John Layt <jlayt@kde.org> wrote:\n"
0581                        "> > Hi,\n"
0582                        // See above for that "weird" "> > > ".
0583                        "> > > Just checking, has the sponsorship budget been approved (so I can book\n"
0584                        "> > flights before they cost silly amounts), and has accommodation been\n"
0585                        "> > sorted yet or do we need to do it ourselves?\n"
0586                        "> \n"
0587                        "> Ping?\n"
0588                        "\n"
0589                        "CC'd Michael directly in case he missed the emails between all the jenkins spam. I'd like to have this sorted out soon too  :)\n");
0590 }
0591 
0592 void HtmlFormattingTest::testPlainTextFormattingViaPasteDelSp()
0593 {
0594     QFETCH(QString, source);
0595     QFETCH(QString, expandedFlowed);
0596     QFETCH(QString, expandedFlowedDelSp);
0597 
0598 #ifdef SKIP_WEBKIT_TESTS
0599     QSKIP("ASAN build -- QtWebKit is known to be broken, skipping");
0600 #else
0601     {
0602         WebRenderingTester tester;
0603         LONG_STR_QCOMPARE(visualizeWhitespace(tester.asPlainText(source, UiUtils::FlowedFormat::FLOWED, WebRenderingTester::RenderExpandEverythingCollapsed)),
0604                  visualizeWhitespace(expandedFlowed));
0605     }
0606     {
0607         WebRenderingTester tester;
0608         LONG_STR_QCOMPARE(visualizeWhitespace(tester.asPlainText(source, UiUtils::FlowedFormat::FLOWED_DELSP, WebRenderingTester::RenderExpandEverythingCollapsed)),
0609                  visualizeWhitespace(expandedFlowedDelSp));
0610     }
0611 #endif
0612 }
0613 
0614 
0615 void HtmlFormattingTest::testPlainTextFormattingViaPasteDelSp_data()
0616 {
0617     QTest::addColumn<QString>("source");
0618     QTest::addColumn<QString>("expandedFlowed");
0619     QTest::addColumn<QString>("expandedFlowedDelSp");
0620 
0621     QTest::newRow("no-quotes")
0622             << QStringLiteral("Sample mail message.\n")
0623             << QStringLiteral("Sample mail message.\n")
0624             << QStringLiteral("Sample mail message.\n");
0625 
0626     QTest::newRow("no-quotes-flowed")
0627             << QStringLiteral("This is something which is split \namong a \n few lines \n  like \n   this. ")
0628             << QStringLiteral("This is something which is split among a few lines  like   this.")
0629             // yes, "afew" -- that's because of space stuffing.
0630             << QStringLiteral("This is something which is splitamong afew lines like  this.");
0631 
0632     QTest::newRow("quote-1")
0633             << QStringLiteral("Foo bar. \n> blesmrt\n>> trojita \n omacka")
0634             << QStringLiteral("Foo bar.\n> blesmrt\n>> trojita\nomacka")
0635             << QStringLiteral("Foo bar.\n> blesmrt\n>> trojita\nomacka");
0636 
0637     // https://bugs.kde.org/show_bug.cgi?id=337919
0638     QTest::newRow("blank-line-separator")
0639             << QStringLiteral("First para. \n\nSecond para.\n\n\nThird para.")
0640             << QStringLiteral("First para. \n\nSecond para.\n\n\nThird para.")
0641             << QStringLiteral("First para.\n\nSecond para.\n\n\nThird para.");
0642 
0643     QTest::newRow("blank-line-separator-of-quoted")
0644             << QStringLiteral("> First para. \n>\n> Second para.\n>\n>\n>Third para.")
0645             << QStringLiteral("> First para. \n> \n> Second para.\n> \n> \n> Third para.\n")
0646             << QStringLiteral("> First para.\n> \n> Second para.\n> \n> \n> Third para.\n");
0647 
0648     QTest::newRow("blanks-in-different-levels")
0649             << QStringLiteral("Yesterday: \n>> Test2A. \n>>\n>> Test2B \n>>\nTest0. \n> Test1A. \n> \n>Test 1B. ")
0650             << QStringLiteral("Yesterday:\n>> Test2A. \n>> \n>> Test2B \n>> \nTest0.\n> Test1A. \n> \n> Test 1B.")
0651             << QStringLiteral("Yesterday:\n>> Test2A.\n>> \n>> Test2B\n>> \nTest0.\n> Test1A.\n> \n> Test 1B.");
0652 }
0653 
0654 /** @short Test that the link recognition in plaintext -> HTML formatting recognizes the interesting links */
0655 void HtmlFormattingTest::testLinkRecognition()
0656 {
0657     QFETCH(QString, prefix);
0658     QFETCH(QString, link);
0659     QFETCH(QString, suffix);
0660 
0661     QString input = prefix + link + suffix;
0662     QString expected = prefix + QStringLiteral("<a href=\"%1\">%1</a>").arg(link) + suffix;
0663 
0664     QCOMPARE(UiUtils::plainTextToHtml(input, UiUtils::FlowedFormat::PLAIN), expected);
0665 }
0666 
0667 /** @short Test data for testLinkRecognition */
0668 void HtmlFormattingTest::testLinkRecognition_data()
0669 {
0670     QTest::addColumn<QString>("prefix");
0671     QTest::addColumn<QString>("link");
0672     QTest::addColumn<QString>("suffix");
0673 
0674     QString empty;
0675     QString space(QStringLiteral(" "));
0676 
0677     QTest::newRow("basic-http") << empty << QStringLiteral("http://blesmrt") << empty;
0678     QTest::newRow("basic-https") << empty << QStringLiteral("https://blesmrt") << empty;
0679     QTest::newRow("parentheses") << QStringLiteral("(") << QStringLiteral("https://blesmrt") << QStringLiteral(")");
0680     QTest::newRow("url-query") << empty << QStringLiteral("https://blesmrt.trojita/?foo=bar") << empty;
0681     QTest::newRow("url-fragment") << empty << QStringLiteral("https://blesmrt.trojita/#pwn") << empty;
0682 
0683     QTest::newRow("trailing-dot") << empty << QStringLiteral("http://blesmrt") << QStringLiteral(".");
0684     QTest::newRow("trailing-dot-2") << empty << QStringLiteral("http://blesmrt") << QStringLiteral(". Foo");
0685     QTest::newRow("trailing-comma") << empty << QStringLiteral("http://blesmrt") << QStringLiteral(",");
0686     QTest::newRow("trailing-comma-2") << empty << QStringLiteral("http://blesmrt") << QStringLiteral(", foo");
0687     QTest::newRow("trailing-semicolon") << empty << QStringLiteral("http://blesmrt") << QStringLiteral(";");
0688     QTest::newRow("trailing-semicolon-2") << empty << QStringLiteral("http://blesmrt") << QStringLiteral("; foo");
0689     QTest::newRow("trailing-colon") << empty << QStringLiteral("http://blesmrt") << QStringLiteral(":");
0690     QTest::newRow("trailing-colon-2") << empty << QStringLiteral("http://blesmrt") << QStringLiteral(": blah");
0691 
0692     QTest::newRow("trailing-sentence-1") << QStringLiteral("meh ") << QStringLiteral("http://blesmrt") << QStringLiteral("?");
0693     QTest::newRow("trailing-sentence-2") << QStringLiteral("meh ") << QStringLiteral("http://blesmrt") << QStringLiteral("!");
0694     QTest::newRow("trailing-sentence-3") << QStringLiteral("meh ") << QStringLiteral("http://blesmrt") << QStringLiteral(".");
0695 }
0696 
0697 /** @short Test data which should not be recognized as links */
0698 void HtmlFormattingTest::testUnrecognizedLinks()
0699 {
0700     QFETCH(QString, input);
0701 
0702     QCOMPARE(UiUtils::plainTextToHtml(input, UiUtils::FlowedFormat::PLAIN), input);
0703 }
0704 
0705 /** @short Test data for testUnrecognizedLinks */
0706 void HtmlFormattingTest::testUnrecognizedLinks_data()
0707 {
0708     QTest::addColumn<QString>("input");
0709 
0710     QTest::newRow("basic-ftp") << QStringLiteral("ftp://blesmrt");
0711     QTest::newRow("at-sign-start") << QStringLiteral("@foo");
0712     QTest::newRow("at-sign-end") << QStringLiteral("foo@");
0713     QTest::newRow("at-sign-standalone-1") << QStringLiteral("@");
0714     QTest::newRow("at-sign-standalone-2") << QStringLiteral(" @ ");
0715     QTest::newRow("http-standalone") << QStringLiteral("http://");
0716     QTest::newRow("http-standalone-stuff") << QStringLiteral("http:// foo");
0717 }
0718 
0719 void HtmlFormattingTest::testSignatures()
0720 {
0721     QFETCH(QString, original);
0722     QFETCH(QString, signature);
0723     QFETCH(QString, result);
0724 
0725     QTextDocument doc;
0726     doc.setPlainText(original);
0727     Composer::Util::replaceSignature(&doc, signature);
0728     QCOMPARE(doc.toPlainText(), result);
0729 }
0730 
0731 void HtmlFormattingTest::testSignatures_data()
0732 {
0733     QTest::addColumn<QString>("original");
0734     QTest::addColumn<QString>("signature");
0735     QTest::addColumn<QString>("result");
0736 
0737     QTest::newRow("empty-all") << QString() << QString() << QString();
0738     QTest::newRow("empty-signature-1") << QStringLiteral("foo") << QString() << QStringLiteral("foo");
0739     QTest::newRow("empty-signature-2") << QStringLiteral("foo\n") << QString() << QStringLiteral("foo\n");
0740     QTest::newRow("empty-signature-3") << QStringLiteral("foo\n-- ") << QString() << QStringLiteral("foo");
0741     QTest::newRow("empty-signature-4") << QStringLiteral("foo\n-- \n") << QString() << QStringLiteral("foo");
0742     QTest::newRow("empty-signature-5") << QStringLiteral("foo\n\n-- \n") << QString() << QStringLiteral("foo\n");
0743 
0744     QTest::newRow("no-signature-1") << QStringLiteral("foo") << QStringLiteral("meh") << QStringLiteral("foo\n-- \nmeh");
0745     QTest::newRow("no-signature-2") << QStringLiteral("foo\n") << QStringLiteral("meh") << QStringLiteral("foo\n\n-- \nmeh");
0746     QTest::newRow("no-signature-3") << QStringLiteral("foo\n\n") << QStringLiteral("meh") << QStringLiteral("foo\n\n\n-- \nmeh");
0747     QTest::newRow("no-signature-4") << QStringLiteral("foo\nbar\nbaz") << QStringLiteral("meh") << QStringLiteral("foo\nbar\nbaz\n-- \nmeh");
0748     QTest::newRow("no-signature-5") << QStringLiteral("foo\n--") << QStringLiteral("meh") << QStringLiteral("foo\n--\n-- \nmeh");
0749 
0750     QTest::newRow("replacement") << QStringLiteral("foo\n-- \njohoho") << QStringLiteral("sig") << QStringLiteral("foo\n-- \nsig");
0751     QTest::newRow("replacement-of-multiline") << QStringLiteral("foo\n-- \njohoho\nwtf\nbar") << QStringLiteral("sig") << QStringLiteral("foo\n-- \nsig");
0752 }
0753 
0754 QTEST_MAIN(HtmlFormattingTest)