1 /*
2    SPDX-FileCopyrightText: 2015-2021 Laurent Montel <montel@kde.org>
3 
4    SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "richtextcomposertest.h"
8 #include "../richtextcomposer.h"
9 #include <KCodecs>
10 #include <QSignalSpy>
11 #include <QTest>
12 
13 #include <KActionCollection>
14 #include <KIconLoader>
15 #include <QAction>
16 
17 #include <QBuffer>
18 #include <QStandardPaths>
19 #include <QTextBlock>
20 #include <QTextCursor>
21 #include <kpimtextedit/richtextcomposer.h>
22 #include <kpimtextedit/richtextcomposercontroler.h>
23 #include <kpimtextedit/richtextcomposerimages.h>
24 
25 using namespace KPIMTextEdit;
26 
Q_DECLARE_METATYPE(KPIMTextEdit::RichTextComposer::Mode)27 Q_DECLARE_METATYPE(KPIMTextEdit::RichTextComposer::Mode)
28 RichTextComposerTest::RichTextComposerTest(QObject *parent)
29     : QObject(parent)
30 {
31     qRegisterMetaType<KPIMTextEdit::RichTextComposer::Mode>();
32     QIcon::setThemeName(QStringLiteral("breeze"));
33     QStandardPaths::setTestModeEnabled(true);
34 }
35 
~RichTextComposerTest()36 RichTextComposerTest::~RichTextComposerTest()
37 {
38 }
39 
testFormattingUsed()40 void RichTextComposerTest::testFormattingUsed()
41 {
42     // This method tries to test everything that krichtextedit makes available, so
43     // we can sure that in KMail, when the user uses some formatting, the mail is actually
44     // sent as HTML mail
45 
46     KPIMTextEdit::RichTextComposer textEdit;
47     textEdit.createActions(new KActionCollection(this));
48 
49     QVERIFY(!textEdit.composerControler()->isFormattingUsed());
50 
51     // Insert some text.
52     QTextCursor cursor(textEdit.document());
53     cursor.insertText(QStringLiteral("Hello World!!"));
54     QVERIFY(!textEdit.composerControler()->isFormattingUsed());
55     cursor.setPosition(1);
56     textEdit.setTextCursor(cursor);
57 
58     //
59     // Test link
60     //
61     QString someUrl = QStringLiteral("www.test.de");
62     QString altText = QStringLiteral("Hello");
63     textEdit.composerControler()->updateLink(someUrl, altText);
64     QVERIFY(textEdit.composerControler()->isFormattingUsed());
65     QCOMPARE(textEdit.composerControler()->currentLinkUrl(), someUrl);
66     QCOMPARE(textEdit.composerControler()->currentLinkText(), altText);
67 
68     cursor.setPosition(1);
69     textEdit.setTextCursor(cursor);
70     textEdit.composerControler()->updateLink(QString(), QStringLiteral("Hello"));
71     QVERIFY(textEdit.composerControler()->currentLinkUrl().isEmpty());
72     QVERIFY(!textEdit.composerControler()->currentLinkText().isEmpty());
73     QVERIFY(!textEdit.composerControler()->isFormattingUsed());
74 
75     //
76     // Test alignment
77     //
78     cursor.setPosition(1);
79     textEdit.setTextCursor(cursor);
80     textEdit.composerControler()->alignRight();
81     QVERIFY(textEdit.composerControler()->isFormattingUsed());
82     QCOMPARE(textEdit.alignment(), Qt::AlignRight);
83     textEdit.composerControler()->alignLeft();
84     QVERIFY(!textEdit.composerControler()->isFormattingUsed());
85     textEdit.composerControler()->alignCenter();
86     QCOMPARE(textEdit.alignment(), Qt::AlignHCenter);
87     QVERIFY(textEdit.composerControler()->isFormattingUsed());
88     textEdit.composerControler()->alignJustify();
89     QCOMPARE(textEdit.alignment(), Qt::AlignJustify);
90     QVERIFY(textEdit.composerControler()->isFormattingUsed());
91     textEdit.composerControler()->alignLeft();
92     QCOMPARE(textEdit.alignment(), Qt::AlignLeft);
93     QVERIFY(!textEdit.composerControler()->isFormattingUsed());
94 
95     //
96     // Test layout direction
97     //
98     textEdit.selectAll();
99     QTextCharFormat direction;
100     direction.setLayoutDirection(Qt::RightToLeft);
101     textEdit.mergeCurrentCharFormat(direction);
102     QVERIFY(textEdit.composerControler()->isFormattingUsed());
103     direction.setLayoutDirection(Qt::LeftToRight);
104     textEdit.mergeCurrentCharFormat(direction);
105     QVERIFY(textEdit.composerControler()->isFormattingUsed());
106 
107     //
108     // Test lists
109     //
110     textEdit.composerControler()->setListStyle(QTextListFormat::ListCircle);
111     QVERIFY(textEdit.composerControler()->isFormattingUsed());
112     textEdit.composerControler()->setListStyle(0);
113     QVERIFY(textEdit.composerControler()->isFormattingUsed());
114 
115     //
116     // Test font attributes
117     //
118     textEdit.setFontFamily(QStringLiteral("Times"));
119     QVERIFY(textEdit.composerControler()->isFormattingUsed());
120     textEdit.setFontFamily(textEdit.document()->defaultFont().family());
121     QVERIFY(textEdit.composerControler()->isFormattingUsed());
122     textEdit.composerControler()->setFontSize(48);
123     QVERIFY(textEdit.composerControler()->isFormattingUsed());
124     textEdit.composerControler()->setFontSize(textEdit.document()->defaultFont().pointSize());
125     QVERIFY(textEdit.composerControler()->isFormattingUsed());
126     QFont myFont = textEdit.document()->defaultFont();
127     myFont.setStyle(QFont::StyleOblique);
128     textEdit.composerControler()->setFont(myFont);
129     QVERIFY(textEdit.composerControler()->isFormattingUsed());
130     textEdit.composerControler()->setFont(textEdit.document()->defaultFont());
131     QVERIFY(textEdit.composerControler()->isFormattingUsed());
132 
133     //
134     // Test bold, italic, underline and strikeout
135     //
136     textEdit.composerControler()->setTextBold(true);
137     QVERIFY(textEdit.composerControler()->isFormattingUsed());
138     textEdit.composerControler()->setTextBold(false);
139     QVERIFY(textEdit.composerControler()->isFormattingUsed());
140     textEdit.composerControler()->setTextUnderline(true);
141     QVERIFY(textEdit.composerControler()->isFormattingUsed());
142     textEdit.composerControler()->setTextUnderline(false);
143     QVERIFY(textEdit.composerControler()->isFormattingUsed());
144     textEdit.composerControler()->setTextItalic(true);
145     QVERIFY(textEdit.composerControler()->isFormattingUsed());
146     textEdit.composerControler()->setTextItalic(false);
147     QVERIFY(textEdit.composerControler()->isFormattingUsed());
148     textEdit.composerControler()->setTextStrikeOut(true);
149     QVERIFY(textEdit.composerControler()->isFormattingUsed());
150     textEdit.composerControler()->setTextStrikeOut(false);
151     QVERIFY(textEdit.composerControler()->isFormattingUsed());
152 
153     //
154     // Color
155     //
156     QColor oldForeground = textEdit.document()->firstBlock().charFormat().foreground().color();
157     textEdit.composerControler()->setTextForegroundColor(Qt::red);
158     QVERIFY(textEdit.composerControler()->isFormattingUsed());
159     textEdit.composerControler()->setTextForegroundColor(oldForeground);
160     QVERIFY(textEdit.composerControler()->isFormattingUsed());
161     QColor oldBackground = textEdit.document()->firstBlock().charFormat().background().color();
162     textEdit.composerControler()->setTextBackgroundColor(Qt::red);
163     QVERIFY(textEdit.composerControler()->isFormattingUsed());
164     textEdit.composerControler()->setTextBackgroundColor(oldBackground);
165     QVERIFY(textEdit.composerControler()->isFormattingUsed());
166 
167     //
168     // Horizontal rule
169     //
170     textEdit.composerControler()->insertHorizontalRule();
171     QVERIFY(textEdit.composerControler()->isFormattingUsed());
172     // No way to easily remove the horizontal line, so clear the text edit and start over
173     textEdit.clear();
174     cursor.insertText(QStringLiteral("Hello World!!"));
175     QVERIFY(!textEdit.composerControler()->isFormattingUsed());
176     cursor.setPosition(1);
177     textEdit.setTextCursor(cursor);
178 
179     //
180     // Sub and superscript
181     //
182     textEdit.composerControler()->setTextSuperScript(true);
183     QVERIFY(textEdit.composerControler()->isFormattingUsed());
184     textEdit.composerControler()->setTextSuperScript(false);
185     QVERIFY(!textEdit.composerControler()->isFormattingUsed());
186     textEdit.composerControler()->setTextSubScript(true);
187     QVERIFY(textEdit.composerControler()->isFormattingUsed());
188     textEdit.composerControler()->setTextSubScript(false);
189     QVERIFY(!textEdit.composerControler()->isFormattingUsed());
190 
191     //
192     // Image
193     //
194     const QString imagePath = KIconLoader::global()->iconPath(QStringLiteral("folder-new"), KIconLoader::Small, false);
195     textEdit.composerControler()->composerImages()->addImage(QUrl::fromLocalFile(imagePath));
196     QVERIFY(textEdit.composerControler()->isFormattingUsed());
197     cursor = textEdit.textCursor();
198     cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor, 1);
199     cursor.removeSelectedText();
200     QVERIFY(!textEdit.composerControler()->isFormattingUsed());
201 }
202 
testQuoting()203 void RichTextComposerTest::testQuoting()
204 {
205     KPIMTextEdit::RichTextComposer edit;
206     edit.createActions(new KActionCollection(this));
207     QVERIFY(edit.isLineQuoted(QStringLiteral("> Hello")));
208     QVERIFY(edit.isLineQuoted(QStringLiteral(">Hello")));
209     QVERIFY(!edit.isLineQuoted(QStringLiteral("Hello")));
210     QCOMPARE(edit.quoteLength(QStringLiteral("Hello")), 0);
211     QCOMPARE(edit.quoteLength(QStringLiteral(">Hello")), 1);
212     QCOMPARE(edit.quoteLength(QStringLiteral("> Hello")), 2);
213     QCOMPARE(edit.quoteLength(QStringLiteral(">>>Hello")), 3);
214     QCOMPARE(edit.quoteLength(QStringLiteral("> > > Hello")), 6);
215     QCOMPARE(edit.quoteLength(QStringLiteral("|Hello")), 1);
216     QCOMPARE(edit.quoteLength(QStringLiteral("| |Hello")), 3);
217 }
218 
testCleanText()219 void RichTextComposerTest::testCleanText()
220 {
221     KPIMTextEdit::RichTextComposer edit;
222     edit.createActions(new KActionCollection(this));
223     QString html(QStringLiteral("<html><head></head><body>Heelllo&nbsp;World<br>Bye!</body></html>"));
224     QString plain(QStringLiteral("Heelllo World\nBye!"));
225     edit.setTextOrHtml(html);
226     edit.composerControler()->composerImages()->addImage(
227         QUrl::fromLocalFile(KIconLoader::global()->iconPath(QStringLiteral("folder-new"), KIconLoader::Small, false)));
228     QVERIFY(edit.textMode() == KPIMTextEdit::RichTextComposer::Rich);
229     QCOMPARE(edit.composerControler()->toCleanPlainText(), plain);
230 
231     edit.show(); // < otherwise toWrappedPlainText can't work, it needs a layout
232     QCOMPARE(edit.composerControler()->toWrappedPlainText(), plain);
233 }
234 
testEnter_data()235 void RichTextComposerTest::testEnter_data()
236 {
237     QTest::addColumn<QString>("initalText");
238     QTest::addColumn<QString>("expectedText");
239     QTest::addColumn<int>("cursorPos");
240 
241     QTest::newRow("") << QStringLiteral("> Hello World") << QStringLiteral("> Hello \n> World") << 8;
242     QTest::newRow("") << QStringLiteral("Hello World") << QStringLiteral("Hello \nWorld") << 6;
243     QTest::newRow("") << QStringLiteral("> Hello World") << QStringLiteral("> Hello World\n") << 13;
244     QTest::newRow("") << QStringLiteral(">Hello World") << QStringLiteral(">Hello \n>World") << 7;
245     QTest::newRow("") << QStringLiteral("> > Hello World") << QStringLiteral("> > Hello \n> > World") << 10;
246     QTest::newRow("") << QStringLiteral("| | Hello World") << QStringLiteral("| | Hello \n| | World") << 10;
247 }
248 
testEnter()249 void RichTextComposerTest::testEnter()
250 {
251     QFETCH(QString, initalText);
252     QFETCH(QString, expectedText);
253     QFETCH(int, cursorPos);
254 
255     KPIMTextEdit::RichTextComposer edit;
256     edit.createActions(new KActionCollection(this));
257     edit.setPlainText(initalText);
258     QTextCursor textCursor(edit.document());
259     textCursor.setPosition(cursorPos);
260     edit.setTextCursor(textCursor);
261 
262     QTest::keyClick(&edit, Qt::Key_Return);
263     QCOMPARE(edit.toPlainText(), expectedText);
264 }
265 
testImages()266 void RichTextComposerTest::testImages()
267 {
268     KPIMTextEdit::RichTextComposer edit;
269     edit.createActions(new KActionCollection(this));
270     QString image1Path = KIconLoader::global()->iconPath(QStringLiteral("folder-new"), KIconLoader::Small, false);
271     QString image2Path = KIconLoader::global()->iconPath(QStringLiteral("arrow-up"), KIconLoader::Small, false);
272 
273     // Add one image, check that embeddedImages() returns the right stuff
274     edit.composerControler()->composerImages()->addImage(QUrl::fromLocalFile(image1Path));
275     KPIMTextEdit::ImageList images = edit.composerControler()->composerImages()->embeddedImages();
276     KPIMTextEdit::ImageWithNameList imagesWithNames = edit.composerControler()->composerImages()->imagesWithName();
277     QCOMPARE(images.size(), 1);
278     QCOMPARE(imagesWithNames.size(), 1);
279     EmbeddedImage *image = images.first().data();
280     ImageWithName *imageWithName = imagesWithNames.first().data();
281     QCOMPARE(image->imageName, QString::fromLatin1("folder-new.png"));
282     QCOMPARE(imageWithName->name, QString::fromLatin1("folder-new.png"));
283 
284     // Also check that it loads the correct image
285     QImage diskImage(image1Path);
286     QBuffer buffer;
287     buffer.open(QIODevice::WriteOnly);
288     diskImage.save(&buffer, "PNG");
289     QBuffer imageWithNameBuffer;
290     imageWithNameBuffer.open(QIODevice::WriteOnly);
291     imageWithName->image.save(&imageWithNameBuffer, "PNG");
292     QByteArray encodedImage = KCodecs::Codec::codecForName("base64")->encode(buffer.buffer());
293     QCOMPARE(image->image, encodedImage);
294     QCOMPARE(buffer.buffer(), imageWithNameBuffer.buffer());
295 
296     // No image should be there after clearing
297     edit.clear();
298     QVERIFY(edit.composerControler()->composerImages()->embeddedImages().isEmpty());
299     QVERIFY(edit.composerControler()->composerImages()->imagesWithName().isEmpty());
300 
301     // Check that manually removing the image also empties the image list
302     edit.composerControler()->composerImages()->addImage(QUrl::fromLocalFile(image1Path));
303     QCOMPARE(edit.composerControler()->composerImages()->embeddedImages().size(), 1);
304     QCOMPARE(edit.composerControler()->composerImages()->imagesWithName().size(), 1);
305     QTextCursor cursor = edit.textCursor();
306     cursor.setPosition(0, QTextCursor::MoveAnchor);
307     cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, 1);
308     cursor.removeSelectedText();
309     QVERIFY(edit.composerControler()->composerImages()->embeddedImages().isEmpty());
310     QVERIFY(edit.composerControler()->composerImages()->imagesWithName().isEmpty());
311 
312     // Check that adding the identical image two times only adds the image once
313     edit.composerControler()->composerImages()->addImage(QUrl::fromLocalFile(image1Path));
314     edit.composerControler()->composerImages()->addImage(QUrl::fromLocalFile(image1Path));
315     QCOMPARE(edit.composerControler()->composerImages()->embeddedImages().size(), 1);
316     QCOMPARE(edit.composerControler()->composerImages()->imagesWithName().size(), 1);
317 
318     // Another different image added, and we should have two images
319     edit.clear();
320     edit.composerControler()->composerImages()->addImage(QUrl::fromLocalFile(image1Path));
321     edit.composerControler()->composerImages()->addImage(QUrl::fromLocalFile(image2Path));
322     images = edit.composerControler()->composerImages()->embeddedImages();
323     imagesWithNames = edit.composerControler()->composerImages()->imagesWithName();
324     QCOMPARE(images.size(), 2);
325     QCOMPARE(imagesWithNames.size(), 2);
326     KPIMTextEdit::EmbeddedImage *image1 = images.first().data();
327     KPIMTextEdit::EmbeddedImage *image2 = images.last().data();
328     KPIMTextEdit::ImageWithName *imageWithName1 = imagesWithNames.first().data();
329     KPIMTextEdit::ImageWithName *imageWithName2 = imagesWithNames.last().data();
330     QCOMPARE(image1->imageName,
331              QString::fromLatin1("folder-new2.png")); // ### FIXME: should be folder-new.png, but QTextEdit provides no way to remove cached resources!
332     QCOMPARE(imageWithName1->name, QString::fromLatin1("folder-new2.png"));
333     QCOMPARE(image2->imageName, QString::fromLatin1("arrow-up.png"));
334     QCOMPARE(imageWithName2->name, QString::fromLatin1("arrow-up.png"));
335     QVERIFY(image1->contentID != image2->contentID);
336 }
337 
testImageHtmlCode()338 void RichTextComposerTest::testImageHtmlCode()
339 {
340     KPIMTextEdit::RichTextComposer edit;
341     edit.createActions(new KActionCollection(this));
342     QString image1Path = KIconLoader::global()->iconPath(QStringLiteral("folder-new"), KIconLoader::Small, false);
343     QString image2Path = KIconLoader::global()->iconPath(QStringLiteral("arrow-up"), KIconLoader::Small, false);
344     edit.composerControler()->composerImages()->addImage(QUrl::fromLocalFile(image1Path));
345     edit.composerControler()->composerImages()->addImage(QUrl::fromLocalFile(image2Path));
346     KPIMTextEdit::ImageList images = edit.composerControler()->composerImages()->embeddedImages();
347     QCOMPARE(images.size(), 2);
348     KPIMTextEdit::EmbeddedImage *image1 = images.first().data();
349     KPIMTextEdit::EmbeddedImage *image2 = images.last().data();
350     QString startHtml = QStringLiteral("<img src=\"arrow-up.png\"><img src=\"folder-new.png\">Bla<b>Blub</b>");
351     QString endHtml = QStringLiteral("<img src=\"cid:%1\"><img src=\"cid:%2\">Bla<b>Blub</b>").arg(image2->contentID, image1->contentID);
352     QCOMPARE(KPIMTextEdit::RichTextComposerImages::imageNamesToContentIds(startHtml.toLatin1(), images), endHtml.toLatin1());
353 }
354 
testDeleteLine_data()355 void RichTextComposerTest::testDeleteLine_data()
356 {
357     QTest::addColumn<QString>("initalText");
358     QTest::addColumn<QString>("expectedText");
359     QTest::addColumn<int>("cursorPos");
360 
361     QTest::newRow("delete1") << QStringLiteral("line1\nline2\nline3") << QStringLiteral("line1\nline3") << 6;
362     QTest::newRow("delete2") << QStringLiteral("line1\nline2\nline3") << QStringLiteral("line2\nline3") << 5;
363     QTest::newRow("delete3") << QStringLiteral("line1\nline2\nline3") << QStringLiteral("line1\nline3") << 11;
364     QTest::newRow("delete4") << QStringLiteral("line1\nline2\nline3") << QStringLiteral("line2\nline3") << 0;
365     QTest::newRow("delete5") << QStringLiteral("line1\nline2\nline3") << QStringLiteral("line1\nline2") << 17;
366     QTest::newRow("delete6") << QStringLiteral("line1") << QString() << 0;
367     QTest::newRow("delete7") << QStringLiteral("line1") << QString() << 5;
368 
369     // Now, test deletion with word wrapping. The line with the Ms is so long that it will get wrapped
370     QTest::newRow("delete8") << QStringLiteral("line1\nMMMMMMM MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\nline3")
371                              << QStringLiteral("line1\nMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\nline3") << 6;
372     QTest::newRow("delete9") << QStringLiteral("line1\nMMMMMMM MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\nline3")
373                              << QStringLiteral("line1\nMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM\nline3") << 13;
374 }
375 
testDeleteLine()376 void RichTextComposerTest::testDeleteLine()
377 {
378 #if 0
379     QFETCH(QString, initalText);
380     QFETCH(QString, expectedText);
381     QFETCH(int, cursorPos);
382 
383     KPIMTextEdit::RichTextComposer edit;
384     edit.createActions(new KActionCollection(this));
385     edit.setPlainText(initalText);
386     QTextCursor cursor = edit.textCursor();
387     cursor.setPosition(cursorPos);
388     edit.setTextCursor(cursor);
389 
390     edit.show(); // we need a layout for this to work
391 
392     edit.composerControler()->deleteCurrentLine();
393     QCOMPARE(edit.toPlainText(), expectedText);
394 #endif
395 }
396 
testLoadImage()397 void RichTextComposerTest::testLoadImage()
398 {
399     KPIMTextEdit::RichTextComposer edit;
400     edit.createActions(new KActionCollection(this));
401     QString image1Path = KIconLoader::global()->iconPath(QStringLiteral("folder-new"), KIconLoader::Small, false);
402     QString image2Path = KIconLoader::global()->iconPath(QStringLiteral("arrow-up"), KIconLoader::Small, false);
403     QImage image1;
404     QImage image2;
405     QVERIFY(image1.load(image1Path));
406     QVERIFY(image2.load(image2Path));
407 
408     edit.setHtml(QStringLiteral("Bla<img src=\"folder-new.png\">Bla"));
409 
410     // First try to load an image with a name that doesn't match, it should fail
411     edit.composerControler()->composerImages()->loadImage(image1, QStringLiteral("doesntmatch"), QStringLiteral("folder-new"));
412     QVERIFY(!edit.document()->resource(QTextDocument::ImageResource, QUrl(QStringLiteral("folder-new"))).isValid());
413 
414     // Now, load the image for real
415     edit.composerControler()->composerImages()->loadImage(image1, QStringLiteral("folder-new.png"), QStringLiteral("folder-new"));
416     QVERIFY(edit.document()->resource(QTextDocument::ImageResource, QUrl(QStringLiteral("folder-new"))).isValid());
417 
418     // New test with a new textedit (so that we don't use the cached resources
419     // This example has two images in the same text block, make sure that doesn't crash (bug 204214)
420     KPIMTextEdit::RichTextComposer edit2;
421     edit2.createActions(new KActionCollection(this));
422     edit2.setHtml(QStringLiteral("<img src=\"folder-new.png\"><img src=\"folder-new.png\">"));
423     edit2.composerControler()->composerImages()->loadImage(image1, QStringLiteral("folder-new.png"), QStringLiteral("folder-new"));
424     QVERIFY(edit.document()->resource(QTextDocument::ImageResource, QUrl(QStringLiteral("folder-new"))).isValid());
425     QCOMPARE(edit.composerControler()->composerImages()->embeddedImages().size(), 1);
426 }
427 
testWrappedPlainText_data()428 void RichTextComposerTest::testWrappedPlainText_data()
429 {
430     QTest::addColumn<QString>("input");
431     QTest::addColumn<QString>("output");
432 
433     QString defaultStr = QStringLiteral(
434         "http://example.org/test-test-test-test-test-test-test-test-test-test-test-test-test\n  "
435         "https://example.org/test-test-test-test-test-test-test-test-test-test-test-test-test\ntest "
436         "ftp://example.org/test-test-test-test-test-test-test-test-test-test-test-test-test\nftps://example.org/"
437         "test-test-test-test-test-test-test-test-test-test-test-test-test\n  "
438         "ldap://example.org/test-test-test-test-test-test-test-test-test-test-test-test-test");
439     QTest::newRow("default") << defaultStr << defaultStr;
440     QTest::newRow("empty") << QString() << QString();
441     QTest::newRow("wrap") << QStringLiteral("foosdfsdf sdsf sdfsdfsfs fsf sdfs df sfsdf dsf sdfsdf sf sf sfsdf sdsdf")
442                           << QStringLiteral("foosdfsdf sdsf sdfsdfsfs fsf sdfs df sfsdf \ndsf sdfsdf sf sf sfsdf sdsdf");
443     QTest::newRow("wrap-2") << QStringLiteral("test-test-test-test-test-test-test-test-test-test-test-test-test")
444                             << QStringLiteral("test-test-test-test-test-test-test-test-\ntest-test-test-test-test");
445     QTest::newRow("wrap-3") << QStringLiteral("test-test-test-test-test-test-test-test-test-test-test-test-test\n\n")
446                             << QStringLiteral("test-test-test-test-test-test-test-test-\ntest-test-test-test-test\n\n");
447 }
448 
testWrappedPlainText()449 void RichTextComposerTest::testWrappedPlainText()
450 {
451     QFETCH(QString, input);
452     QFETCH(QString, output);
453     KPIMTextEdit::RichTextComposer edit;
454     edit.createActions(new KActionCollection(this));
455     edit.setPlainText(input);
456 
457     edit.show(); // < otherwise toWrappedPlainText can't work, it needs a layout
458 
459     QCOMPARE(edit.composerControler()->toWrappedPlainText(), output);
460 }
461 
testEnableDisableActions()462 void RichTextComposerTest::testEnableDisableActions()
463 {
464     KPIMTextEdit::RichTextComposer composer;
465     KActionCollection *actionCollection = new KActionCollection(&composer);
466     composer.createActions(actionCollection);
467     bool enableAction = true;
468     composer.setEnableActions(enableAction);
469     for (QAction *act : composer.actions()) {
470         QCOMPARE(act->isEnabled(), enableAction);
471     }
472 
473     enableAction = false;
474     composer.setEnableActions(enableAction);
475     for (QAction *act : composer.actions()) {
476         QCOMPARE(act->isEnabled(), enableAction);
477     }
478 }
479 
shouldHaveDefaultValue()480 void RichTextComposerTest::shouldHaveDefaultValue()
481 {
482     KPIMTextEdit::RichTextComposer composer;
483     KActionCollection *actionCollection = new KActionCollection(&composer);
484     composer.createActions(actionCollection);
485     QCOMPARE(composer.linePosition(), 0);
486     QCOMPARE(composer.columnNumber(), 0);
487     QCOMPARE(composer.textMode(), KPIMTextEdit::RichTextComposer::Plain);
488     QVERIFY(!composer.acceptRichText());
489     QVERIFY(!composer.quotePrefixName().isEmpty());
490 }
491 
shouldChangeMode()492 void RichTextComposerTest::shouldChangeMode()
493 {
494     KPIMTextEdit::RichTextComposer composer;
495     KActionCollection *actionCollection = new KActionCollection(&composer);
496     composer.createActions(actionCollection);
497     QSignalSpy spy(&composer, &RichTextComposer::textModeChanged);
498     composer.activateRichText();
499     QCOMPARE(composer.textMode(), KPIMTextEdit::RichTextComposer::Rich);
500     QVERIFY(composer.acceptRichText());
501     QCOMPARE(spy.count(), 1);
502 }
503 
504 QTEST_MAIN(RichTextComposerTest)
505