File indexing completed on 2025-03-09 04:53:55

0001 /*
0002   SPDX-FileCopyrightText: 2009 Constantin Berzan <exit3219@gmail.com>
0003 
0004   SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "maintextjobtest.h"
0008 
0009 #include <QStandardPaths>
0010 // #include <QTextCodec>
0011 #include <QStringDecoder>
0012 
0013 #include <QDebug>
0014 #include <QTest>
0015 
0016 #include <KMime/Content>
0017 using namespace KMime;
0018 
0019 #include <KPIMTextEdit/RichTextComposerControler>
0020 #include <KPIMTextEdit/RichTextComposerImages>
0021 #include <MessageComposer/Composer>
0022 #include <MessageComposer/GlobalPart>
0023 #include <MessageComposer/MainTextJob>
0024 #include <MessageComposer/RichTextComposerNg>
0025 #include <MessageComposer/TextPart>
0026 
0027 #include <KActionCollection>
0028 
0029 using namespace MessageComposer;
0030 
0031 QTEST_MAIN(MainTextJobTest)
0032 
0033 void MainTextJobTest::initTestCase()
0034 {
0035     QStandardPaths::setTestModeEnabled(true);
0036 }
0037 
0038 void MainTextJobTest::testPlainText()
0039 {
0040     auto composer = Composer();
0041     composer.globalPart()->setGuiEnabled(false);
0042     QList<QByteArray> charsets;
0043     charsets << "us-ascii"
0044              << "utf-8";
0045     composer.globalPart()->setCharsets(charsets);
0046     auto textPart = new TextPart;
0047     QString data = QStringLiteral("they said their never they slept their dream");
0048     textPart->setWrappedPlainText(data);
0049     auto mjob = new MainTextJob(textPart, &composer);
0050     QVERIFY(mjob->exec());
0051     Content *result = mjob->content();
0052     result->assemble();
0053     qDebug() << result->encodedContent();
0054     QVERIFY(result->contentType(false));
0055     QCOMPARE(result->contentType()->mimeType(), QByteArray("text/plain"));
0056     QCOMPARE(result->contentType()->charset(), QByteArray("us-ascii"));
0057     QCOMPARE(QString::fromLatin1(result->body()), data);
0058     delete textPart;
0059 }
0060 
0061 void MainTextJobTest::testWrappingErrors()
0062 {
0063     {
0064         auto composer = Composer();
0065         composer.globalPart()->setGuiEnabled(false);
0066         composer.globalPart()->setFallbackCharsetEnabled(true);
0067         auto textPart = new TextPart;
0068         QString data = QStringLiteral("they said their never they slept their dream");
0069         textPart->setWordWrappingEnabled(false);
0070         textPart->setWrappedPlainText(data);
0071         auto mjob = new MainTextJob(textPart, &composer);
0072         QVERIFY(!mjob->exec()); // error: not UseWrapping but given only wrapped text
0073         QCOMPARE(mjob->error(), int(JobBase::BugError));
0074         delete textPart;
0075     }
0076     {
0077         auto composer = Composer();
0078         composer.globalPart()->setGuiEnabled(false);
0079         composer.globalPart()->setFallbackCharsetEnabled(true);
0080         auto textPart = new TextPart;
0081         textPart->setWordWrappingEnabled(true);
0082         QString data = QStringLiteral("they said their never they slept their dream");
0083         textPart->setCleanPlainText(data);
0084         auto mjob = new MainTextJob(textPart, &composer);
0085         QVERIFY(!mjob->exec()); // error: UseWrapping but given only clean text
0086         QCOMPARE(mjob->error(), int(JobBase::BugError));
0087         delete textPart;
0088     }
0089 }
0090 
0091 void MainTextJobTest::testCustomCharset()
0092 {
0093     auto composer = Composer();
0094     composer.globalPart()->setGuiEnabled(false);
0095     QByteArray charset("iso-8859-2");
0096     composer.globalPart()->setCharsets(QList<QByteArray>() << charset);
0097     auto textPart = new TextPart;
0098     QString data = QStringLiteral("şi el o să se-nchidă cu o frunză de pelin");
0099     textPart->setWrappedPlainText(data);
0100     auto mjob = new MainTextJob(textPart, &composer);
0101     QVERIFY(mjob->exec());
0102     Content *result = mjob->content();
0103     result->assemble();
0104     qDebug() << result->encodedContent();
0105     QVERIFY(result->contentType(false));
0106     QCOMPARE(result->contentType()->mimeType(), QByteArray("text/plain"));
0107     QCOMPARE(result->contentType()->charset(), charset);
0108     QByteArray outData = result->body();
0109 #if 0 // Remove when we remove all QTextCodec support
0110     QTextCodec *codec = QTextCodec::codecForName(charset);
0111     QVERIFY(codec);
0112     QCOMPARE(codec->toUnicode(outData), data);
0113 #endif
0114     QStringDecoder dec(charset.constData());
0115     QVERIFY(dec.isValid());
0116     QCOMPARE(dec.decode(outData), data);
0117 
0118     delete textPart;
0119 }
0120 
0121 void MainTextJobTest::testNoCharset()
0122 {
0123     auto composer = Composer();
0124     QVERIFY(!composer.globalPart()->isFallbackCharsetEnabled());
0125     composer.globalPart()->setGuiEnabled(false);
0126     auto textPart = new TextPart;
0127     QString data = QStringLiteral("do you still play the accordion?");
0128     textPart->setWrappedPlainText(data);
0129     auto mjob = new MainTextJob(textPart, &composer);
0130     QSKIP("This tests has been failing for a long time, please someone fix it", SkipSingle);
0131     QVERIFY(!mjob->exec()); // Error.
0132     QCOMPARE(mjob->error(), int(JobBase::BugError));
0133     qDebug() << mjob->errorString();
0134     delete textPart;
0135 }
0136 
0137 void MainTextJobTest::testBadCharset()
0138 {
0139     auto composer = Composer();
0140     composer.globalPart()->setGuiEnabled(false);
0141     QByteArray charset("us-ascii"); // Cannot handle Romanian chars.
0142     composer.globalPart()->setCharsets(QList<QByteArray>() << charset);
0143     auto textPart = new TextPart;
0144     QString data = QStringLiteral("el a plâns peste ţară cu lacrima limbii noastre");
0145     textPart->setWrappedPlainText(data);
0146     auto mjob = new MainTextJob(textPart, &composer);
0147     QSKIP("This tests has been failing for a long time, please someone fix it", SkipSingle);
0148     QVERIFY(!mjob->exec()); // Error.
0149     QCOMPARE(mjob->error(), int(JobBase::UserError));
0150     qDebug() << mjob->errorString();
0151     delete textPart;
0152 }
0153 
0154 void MainTextJobTest::testFallbackCharset()
0155 {
0156     auto composer = Composer();
0157     composer.globalPart()->setGuiEnabled(false);
0158     composer.globalPart()->setFallbackCharsetEnabled(true);
0159     auto textPart = new TextPart;
0160     QString data = QStringLiteral("and when he falleth...");
0161     textPart->setWrappedPlainText(data);
0162     auto mjob = new MainTextJob(textPart, &composer);
0163     QVERIFY(mjob->exec());
0164     Content *result = mjob->content();
0165     result->assemble();
0166     qDebug() << result->encodedContent();
0167     QVERIFY(result->contentType(false));
0168     QCOMPARE(result->contentType()->mimeType(), QByteArray("text/plain"));
0169     QCOMPARE(result->contentType()->charset(), QByteArray("us-ascii")); // Fallback is us-ascii or utf8.
0170     QCOMPARE(QString::fromLatin1(result->body()), data);
0171     delete textPart;
0172 }
0173 
0174 void MainTextJobTest::testHtml()
0175 {
0176     QLatin1StringView originalHtml("<html><head></head><body>Test <em>with</em> formatting...<br>The end.</body></html>");
0177     MessageComposer::RichTextComposerNg editor;
0178     editor.createActions(new KActionCollection(this));
0179     editor.setTextOrHtml(originalHtml);
0180     QVERIFY(editor.composerControler()->isFormattingUsed());
0181 
0182     auto composer = new Composer;
0183     composer->globalPart()->setGuiEnabled(false);
0184     composer->globalPart()->setFallbackCharsetEnabled(true);
0185     auto textPart = new TextPart;
0186     textPart->setWordWrappingEnabled(false);
0187     textPart->setCleanPlainText(editor.composerControler()->toCleanPlainText());
0188     textPart->setCleanHtml(editor.toCleanHtml());
0189     auto mjob = new MainTextJob(textPart, composer);
0190     QVERIFY(mjob->exec());
0191     Content *result = mjob->content();
0192     result->assemble();
0193     qDebug() << result->encodedContent();
0194 
0195     // multipart/alternative
0196     {
0197         QVERIFY(result->contentType(false));
0198         QCOMPARE(result->contentType()->mimeType(), QByteArray("multipart/alternative"));
0199         QCOMPARE(result->contents().count(), 2);
0200         // text/plain
0201         {
0202             Content *plain = result->contents().at(0);
0203             QVERIFY(plain->contentType(false));
0204             QCOMPARE(plain->contentType()->mimeType(), QByteArray("text/plain"));
0205             QCOMPARE(QString::fromLatin1(plain->body()), editor.composerControler()->toCleanPlainText());
0206         }
0207         // text/html
0208         {
0209             Content *html = result->contents().at(1);
0210             QVERIFY(html->contentType(false));
0211             QCOMPARE(html->contentType()->mimeType(), QByteArray("text/html"));
0212             // The editor adds extra Html stuff, so we can't compare to originalHtml.
0213             QCOMPARE(QLatin1StringView(html->body()), editor.toCleanHtml());
0214         }
0215     }
0216     delete composer;
0217 }
0218 
0219 void MainTextJobTest::testHtmlWithImages()
0220 {
0221     KActionCollection ac(this);
0222     MessageComposer::RichTextComposerNg editor;
0223     editor.createActions(new KActionCollection(this));
0224 
0225     QImage image1(16, 16, QImage::Format_ARGB32_Premultiplied);
0226     image1.fill(Qt::red);
0227     const QString image1Path = QCoreApplication::applicationDirPath() + QLatin1StringView("/image1.png");
0228     image1.save(image1Path);
0229 
0230     QImage image2(16, 16, QImage::Format_ARGB32_Premultiplied);
0231     image2.fill(Qt::blue);
0232     const QString image2Path = QCoreApplication::applicationDirPath() + QLatin1StringView("/image2.png");
0233     image2.save(image2Path);
0234 
0235     QString data = QStringLiteral("dust in the wind");
0236     editor.setTextOrHtml(data);
0237     editor.composerControler()->composerImages()->addImage(QUrl::fromLocalFile(image1Path));
0238     editor.composerControler()->composerImages()->addImage(QUrl::fromLocalFile(image1Path));
0239     editor.composerControler()->composerImages()->addImage(QUrl::fromLocalFile(image2Path));
0240     editor.composerControler()->composerImages()->addImage(QUrl::fromLocalFile(image2Path));
0241 
0242     auto composer = new Composer;
0243     composer->globalPart()->setGuiEnabled(false);
0244     composer->globalPart()->setFallbackCharsetEnabled(true);
0245     auto textPart = new TextPart;
0246     textPart->setWordWrappingEnabled(false);
0247     textPart->setCleanPlainText(editor.composerControler()->toCleanPlainText());
0248     textPart->setCleanHtml(editor.composerControler()->toCleanHtml());
0249     // only get once, are regenerated on call with new contentID each time
0250     const KPIMTextEdit::ImageList images = editor.composerControler()->composerImages()->embeddedImages();
0251     QCOMPARE(images.count(), 2);
0252     const QString cid1 = images[0]->contentID;
0253     const QString cid2 = images[1]->contentID;
0254     const QString name1 = images[0]->imageName;
0255     const QString name2 = images[1]->imageName;
0256     textPart->setEmbeddedImages(images);
0257     auto mjob = new MainTextJob(textPart, composer);
0258     QVERIFY(mjob->exec());
0259     Content *result = mjob->content();
0260     result->assemble();
0261     qDebug() << result->encodedContent();
0262 
0263     // multipart/related
0264     {
0265         QVERIFY(result->contentType(false));
0266         QCOMPARE(result->contentType()->mimeType(), QByteArray("multipart/related"));
0267         QCOMPARE(result->contents().count(), 3);
0268         // multipart/alternative
0269         {
0270             Content *alternative = result->contents().at(0);
0271             QVERIFY(alternative->contentType(false));
0272             QCOMPARE(alternative->contentType()->mimeType(), QByteArray("multipart/alternative"));
0273             QCOMPARE(alternative->contents().count(), 2);
0274             // text/plain
0275             {
0276                 Content *plain = alternative->contents().at(0);
0277                 QCOMPARE(plain->contentType()->mimeType(), QByteArray("text/plain"));
0278                 QCOMPARE(QString::fromLatin1(plain->body()), data);
0279             }
0280             // text/html
0281             {
0282                 Content *html = alternative->contents().at(1);
0283                 QCOMPARE(html->contentType()->mimeType(), QByteArray("text/html"));
0284                 QString data = QString::fromLatin1(html->body());
0285                 const int idx1 = data.indexOf(QStringLiteral("cid:%1").arg(cid1));
0286                 const int idx2 = data.indexOf(QStringLiteral("cid:%1").arg(cid2));
0287                 // qDebug() << " cid1 " << cid1 << "cid2 " << cid2 << " data " << data;
0288                 QVERIFY(idx1 > 0);
0289                 QVERIFY(idx2 > 0);
0290                 QVERIFY(idx1 < idx2);
0291             }
0292         }
0293         // First image/png
0294         {
0295             Content *image = result->contents().at(1);
0296             QVERIFY(image->contentType(false));
0297             QCOMPARE(image->contentType()->mimeType(), QByteArray("image/png"));
0298             QCOMPARE(image->contentType()->name(), name1);
0299             const Headers::ContentID *cid = image->header<Headers::ContentID>();
0300             QVERIFY(cid);
0301             QCOMPARE(cid->identifier(), cid1.toLatin1());
0302         }
0303         // Second image/png
0304         {
0305             Content *image = result->contents().at(2);
0306             QVERIFY(image->contentType(false));
0307             QCOMPARE(image->contentType()->mimeType(), QByteArray("image/png"));
0308             QCOMPARE(image->contentType()->name(), name2);
0309             const Headers::ContentID *cid = image->header<Headers::ContentID>();
0310             QVERIFY(cid);
0311             QCOMPARE(cid->identifier(), cid2.toLatin1());
0312         }
0313     }
0314     delete textPart;
0315     delete composer;
0316 }
0317 
0318 #include "moc_maintextjobtest.cpp"