// SPDX-FileCopyrightText: 2023 James Graham // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL #include #include #include "texthandler.h" #include #include #include #include "enums/messagecomponenttype.h" #include "models/customemojimodel.h" #include "neochatconnection.h" #include "testutils.h" using namespace Quotient; class TextHandlerTest : public QObject { Q_OBJECT private: Connection *connection = nullptr; TestUtils::TestRoom *room = nullptr; private Q_SLOTS: void initTestCase(); void allowedAttributes(); void stripDisallowedTags(); void stripDisallowedAttributes(); void emptyCodeTags(); void addStyle_data(); void addStyle(); void dontAddStyle_data(); void dontAddStyle(); void sendSimpleStringCase(); void sendSingleParaMarkup(); void sendMultipleSectionMarkup(); void sendBadLinks(); void sendEscapeCode(); void sendCodeClass(); void sendCustomEmoji(); void sendCustomEmojiCode_data(); void sendCustomEmojiCode(); void sendCustomTags_data(); void sendCustomTags(); void receiveSpacelessSelfClosingTag(); void receiveStripReply(); void receivePlainTextIn(); void receiveRichInPlainOut_data(); void receiveRichInPlainOut(); void receivePlainStripHtml(); void receivePlainStripMarkup(); void receiveStripNewlines(); void receiveRichUserPill(); void receiveRichStrikethrough(); void receiveRichtextIn(); void receiveRichMxcUrl(); void receiveRichPlainUrl_data(); void receiveRichPlainUrl(); void receiveRichEdited_data(); void receiveRichEdited(); void receiveLineSeparator(); void receiveRichCodeUrl(); void receiveRichColor(); void componentOutput_data(); void componentOutput(); void updateSpoiler_data(); void updateSpoiler(); }; void TextHandlerTest::initTestCase() { connection = Connection::makeMockConnection(u"@bob:kde.org"_s); connection->setAccountData(u"im.ponies.user_emotes"_s, QJsonObject{{"images"_L1, QJsonObject{{"test"_L1, QJsonObject{{"body"_L1, "Test custom emoji"_L1}, {"url"_L1, "mxc://example.org/test"_L1}, {"usage"_L1, QJsonArray{"emoticon"_L1}}}}}}}); CustomEmojiModel::instance().setConnection(static_cast(connection)); room = new TestUtils::TestRoom(connection, u"#myroom:kde.org"_s, u"test-texthandler-sync.json"_s); } void TextHandlerTest::allowedAttributes() { auto theme = static_cast(qmlAttachedPropertiesObject(this, true)); const QString testInputString1 = u"Test"_s; const QString testOutputString1S = u"Test"_s; const QString testOutputString1R = u"Test"_s.arg( theme->alternateBackgroundColor().name()); // Handle urls where the href has either single (') or double (") quotes. const QString testInputString2 = u"linklink"_s; const QString testOutputString2S = u"linklink"_s; const QString testOutputString2R = u"linklink"_s; TextHandler testTextHandler; testTextHandler.setData(testInputString1); QCOMPARE(testTextHandler.handleSendText(), testOutputString1S); QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString1R); testTextHandler.setData(testInputString2); QCOMPARE(testTextHandler.handleSendText(), testOutputString2S); QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString2R); } void TextHandlerTest::stripDisallowedTags() { const QString testInputString = u"

Allowed

Allowed Disallowed"_s; const QString testOutputString = u"

Allowed

Allowed Disallowed"_s; TextHandler testTextHandler; testTextHandler.setData(testInputString); QCOMPARE(testTextHandler.handleSendText(), testOutputString); QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString); } void TextHandlerTest::stripDisallowedAttributes() { const QString testInputString = u"

Test

"_s; const QString testOutputString = u"Test"_s; TextHandler testTextHandler; testTextHandler.setData(testInputString); QCOMPARE(testTextHandler.handleSendText(), testOutputString); QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString); } /** * Make sure that empty code tags are handled. * (this was a bug during development hence the test) */ void TextHandlerTest::emptyCodeTags() { const QString testInputString = u"
"_s; const QString testOutputString = u"
"_s; TextHandler testTextHandler; testTextHandler.setData(testInputString); QCOMPARE(testTextHandler.handleSendText(), testOutputString); QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString); } void TextHandlerTest::addStyle_data() { QTest::addColumn("testInputString"); QTest::addColumn("testOutputString"); QTest::newRow("link") << u"link"_s << u"link"_s; QTest::newRow("table") << u"
CompanyContactCountry
Alfreds FutterkisteMaria AndersGermany
Centro comercial MoctezumaFrancisco ChangMexico
"_s << u"
CompanyContactCountry
Alfreds FutterkisteMaria AndersGermany
Centro comercial MoctezumaFrancisco ChangMexico
"_s; auto theme = static_cast(qmlAttachedPropertiesObject(this, true)); QTest::newRow("spoiler") << u"Test"_s << u"Test"_s.arg( theme->alternateBackgroundColor().name()); } void TextHandlerTest::addStyle() { QFETCH(QString, testInputString); QFETCH(QString, testOutputString); TextHandler testTextHandler; testTextHandler.setData(testInputString); QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString); } void TextHandlerTest::dontAddStyle_data() { QTest::addColumn("testInputString"); QTest::addColumn("testOutputString"); QTest::newRow("link") << u"link"_s << u"link"_s; QTest::newRow("table") << u"
CompanyContactCountry
Alfreds FutterkisteMaria AndersGermany
Centro comercial MoctezumaFrancisco ChangMexico
"_s << u"
CompanyContactCountry
Alfreds FutterkisteMaria AndersGermany
Centro comercial MoctezumaFrancisco ChangMexico
"_s; QTest::newRow("spoiler") << u"Test"_s << u"Test"_s; } void TextHandlerTest::dontAddStyle() { QFETCH(QString, testInputString); QFETCH(QString, testOutputString); TextHandler testTextHandler; testTextHandler.setData(testInputString); QCOMPARE(testTextHandler.handleSendText(), testOutputString); } void TextHandlerTest::sendSimpleStringCase() { const QString testInputString = u"This data should just be left alone."_s; const QString testOutputString = u"This data should just be left alone."_s; TextHandler testTextHandler; testTextHandler.setData(testInputString); QCOMPARE(testTextHandler.handleSendText(), testOutputString); } void TextHandlerTest::sendSingleParaMarkup() { const QString testInputString = u"Text para with **bold**, *italic*, [link](https://kde.org), ![image](mxc://kde.org/aebd3ffd40503e1ef0525bf8f0d60282fec6183e), `inline code`."_s; const QString testOutputString = u"Text para with bold, italic, link, \"image\", inline code."_s; TextHandler testTextHandler; testTextHandler.setData(testInputString); QCOMPARE(testTextHandler.handleSendText(), testOutputString); } void TextHandlerTest::sendMultipleSectionMarkup() { const QString testInputString = u"Text para\n> blockquote\n* List 1\n* List 2\n1. one\n2. two\n# Heading 1\n## Heading 2\nhorizontal rule\n\n---\n```\ncodeblock\n```"_s; const QString testOutputString = u"

Text para

\n
\n

blockquote

\n
\n
    \n
  • List 1
  • \n
  • List 2
  • \n
\n
    \n
  1. one
  2. \n
  3. two
  4. \n
\n

Heading 1

\n

Heading 2

\n

horizontal rule

\n
\n
codeblock\n
"_s; TextHandler testTextHandler; testTextHandler.setData(testInputString); QCOMPARE(testTextHandler.handleSendText(), testOutputString); } void TextHandlerTest::sendBadLinks() { const QString testInputString = u"[link](kde.org), ![image](https://kde.org/aebd3ffd40503e1ef0525bf8f0d60282fec6183e)"_s; const QString testOutputString = u"link, \"image\""_s; TextHandler testTextHandler; testTextHandler.setData(testInputString); QCOMPARE(testTextHandler.handleSendText(), testOutputString); } /** * All text between code tags is treated as plain so it should get escaped. */ void TextHandlerTest::sendEscapeCode() { const QString testInputString = u"```\n

Test some code

\n```"_s; const QString testOutputString = u"
<p>Test <span style="font-size:50px;">some</span> code</p>\n
"_s; TextHandler testTextHandler; testTextHandler.setData(testInputString); QCOMPARE(testTextHandler.handleSendText(), testOutputString); } void TextHandlerTest::sendCodeClass() { const QString testInputString = u"```html\nsome code\n```\n
some more code
"_s; const QString testOutputString = u"
some code\n
\n
some more code
"_s; TextHandler testTextHandler; testTextHandler.setData(testInputString); QCOMPARE(testTextHandler.handleSendText(), testOutputString); } void TextHandlerTest::sendCustomEmoji() { const QString testInputString = u":test:"_s; const QString testOutputString = u"\":test:\""_s; TextHandler testTextHandler; testTextHandler.setData(testInputString); QCOMPARE(testTextHandler.handleSendText(), testOutputString); } void TextHandlerTest::sendCustomEmojiCode_data() { QTest::addColumn("testInputString"); QTest::addColumn("testOutputString"); QTest::newRow("inline") << u"`:test:`"_s << u":test:"_s; QTest::newRow("block") << u"```\n:test:\n```"_s << u"
:test:\n
"_s; } // Custom emojis in code blocks should be left alone. void TextHandlerTest::sendCustomEmojiCode() { QFETCH(QString, testInputString); QFETCH(QString, testOutputString); TextHandler testTextHandler; testTextHandler.setData(testInputString); QCOMPARE(testTextHandler.handleSendText(), testOutputString); } void TextHandlerTest::sendCustomTags_data() { QTest::addColumn("testInputString"); QTest::addColumn("testOutputString"); // spoiler QTest::newRow("incomplete spoiler") << u"||test"_s << u"||test"_s; QTest::newRow("complete spoiler") << u"||test||"_s << u"test"_s; QTest::newRow("multiple spoiler") << u"||apple||banana||pear||"_s << u"applebananapear"_s; QTest::newRow("inside code block spoiler") << u"```||apple||```"_s << u"||apple||"_s; QTest::newRow("outside code block spoiler") << u"||apple|| ```||banana||``` ||pear||"_s << u"apple ||banana|| pear"_s; QTest::newRow("complex spoiler") << u"Between `formFactor == Horizontal||Vertical` and `location == top||left||bottom||right`"_s << u"Between formFactor == Horizontal||Vertical and location == top||left||bottom||right"_s; // strikethrough QTest::newRow("incomplete strikethrough") << u"~~test"_s << u"~~test"_s; QTest::newRow("complete strikethrough") << u"~~test~~"_s << u"test"_s; QTest::newRow("inside code block strikethrough") << u"```~~apple~~```"_s << u"~~apple~~"_s; QTest::newRow("outside code block strikethrough") << u"~~apple~~ ```~~banana~~``` ~~pear~~"_s << u"apple ~~banana~~ pear"_s; } void TextHandlerTest::sendCustomTags() { QFETCH(QString, testInputString); QFETCH(QString, testOutputString); TextHandler testTextHandler; testTextHandler.setData(testInputString); QCOMPARE(testTextHandler.handleSendText(), testOutputString); } void TextHandlerTest::receiveSpacelessSelfClosingTag() { const QString testInputString = u"Test...
...ing"_s; const QString testRichOutputString = u"Test...
...ing"_s; const QString testPlainOutputString = u"Test...\n...ing"_s; TextHandler testTextHandler; testTextHandler.setData(testInputString); QCOMPARE(testTextHandler.handleRecieveRichText(), testRichOutputString); QCOMPARE(testTextHandler.handleRecievePlainText(Qt::RichText), testPlainOutputString); } void TextHandlerTest::receiveStripReply() { const QString testInputString = u"
In reply to@alice:example.org
Message replied to.
Reply message."_s; const QString testOutputString = u"Reply message."_s; TextHandler testTextHandler; testTextHandler.setData(testInputString); QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString); QCOMPARE(testTextHandler.handleRecievePlainText(), testOutputString); } void TextHandlerTest::receiveRichInPlainOut_data() { QTest::addColumn("testInputString"); QTest::addColumn("testOutputString"); QTest::newRow("ampersand") << u"a & b"_s << u"a & b"_s; QTest::newRow("quote") << u""a and b""_s << u"\"a and b\""_s; QTest::newRow("new line") << u"new
line"_s << u"new\nline"_s; QTest::newRow("unescape") << u"can't"_s << u"can't"_s; } void TextHandlerTest::receiveRichInPlainOut() { QFETCH(QString, testInputString); QFETCH(QString, testOutputString); TextHandler testTextHandler; testTextHandler.setData(testInputString); QCOMPARE(testTextHandler.handleRecievePlainText(Qt::RichText), testOutputString); } void TextHandlerTest::receivePlainTextIn() { const QString testInputString = u"\nTest link https://kde.org."_s; const QString testOutputStringRich = u"<plain text in tag bracket>
Test link https://kde.org."_s; QString testOutputStringPlain = u"\nTest link https://kde.org."_s; // Make sure quotes are maintained in a plain string. const QString testInputString2 = u"last line is \"Time to switch to a new topic.\""_s; const QString testOutputString2 = u"last line is \"Time to switch to a new topic.\""_s; TextHandler testTextHandler; testTextHandler.setData(testInputString); QCOMPARE(testTextHandler.handleRecieveRichText(Qt::PlainText), testOutputStringRich); QCOMPARE(testTextHandler.handleRecievePlainText(), testOutputStringPlain); testTextHandler.setData(testInputString2); QCOMPARE(testTextHandler.handleRecieveRichText(Qt::PlainText), testOutputString2); QCOMPARE(testTextHandler.handleRecievePlainText(), testOutputString2); } void TextHandlerTest::receiveStripNewlines() { const QString testInputStringPlain = u"Test\nmany\nnew\nlines."_s; const QString testInputStringRich = u"Test
many
new
lines."_s; const QString testOutputString = u"Test many new lines."_s; const QString testInputStringPlain2 = u"* List\n* Items"_s; const QString testOutputString2 = u"List Items"_s; TextHandler testTextHandler; testTextHandler.setData(testInputStringPlain); QCOMPARE(testTextHandler.handleRecievePlainText(Qt::PlainText, true), testOutputString); QCOMPARE(testTextHandler.handleRecieveRichText(Qt::PlainText, nullptr, nullptr, true), testOutputString); testTextHandler.setData(testInputStringRich); QCOMPARE(testTextHandler.handleRecievePlainText(Qt::RichText, true), testOutputString); QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText, nullptr, nullptr, true), testOutputString); testTextHandler.setData(testInputStringPlain2); QCOMPARE(testTextHandler.handleRecievePlainText(Qt::RichText, true), testOutputString2); } /** * For a plain text output of a received string all html is stripped except for * code which is unescaped if it's html. */ void TextHandlerTest::receivePlainStripHtml() { const QString testInputString = u"

Test

Some code with tags
"_s; const QString testOutputString = u"Test Some code with tags"_s; TextHandler testTextHandler; testTextHandler.setData(testInputString); QCOMPARE(testTextHandler.handleRecievePlainText(Qt::RichText), testOutputString); } void TextHandlerTest::receivePlainStripMarkup() { const QString testInputString = u"**bold** `

inline code

` *italic*"_s; const QString testOutputString = u"bold

inline code

italic"_s; TextHandler testTextHandler; testTextHandler.setData(testInputString); QCOMPARE(testTextHandler.handleRecievePlainText(), testOutputString); } void TextHandlerTest::receiveRichUserPill() { const QString testInputString = u"

@alice:example.org

"_s; const QString testOutputString = u"@alice:example.org"_s; TextHandler testTextHandler; testTextHandler.setData(testInputString); QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString); } void TextHandlerTest::receiveRichStrikethrough() { const QString testInputString = u"

Test

"_s; const QString testOutputString = u"Test"_s; TextHandler testTextHandler; testTextHandler.setData(testInputString); QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString); } void TextHandlerTest::receiveRichtextIn() { const QString testInputString = u"

Test

Some code with tags
"_s; const QString testOutputString = u"

Test

Some code <strong>with tags</strong>
"_s; TextHandler testTextHandler; testTextHandler.setData(testInputString); QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString); } void TextHandlerTest::receiveRichMxcUrl() { const QString testInputString = u"\"image\"\"image\""_s; const QString testOutputString = u"\"image\"\"image\""_s; TextHandler testTextHandler; testTextHandler.setData(testInputString); QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText, room, room->messageEvents().at(0).get()), testOutputString); } void TextHandlerTest::receiveRichPlainUrl_data() { QTest::addColumn("input"); QTest::addColumn("output"); // This is an actual link that caused trouble which is why it's so long. Keeping // so we can confirm consistent behaviour for complex urls. QTest::addRow("link 1") << u"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&via=matrix.org&via=fedora.im Link already rich"_s << u"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&via=matrix.org&via=fedora.im Link already rich"_s; // Another real case. The linkification wasn't handling it when a single link // contains what looks like and email. It was broken into 3 but needs to // be just single link. QTest::addRow("link 2") << u"https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/"_s << u"https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/"_s; QTest::addRow("email") << uR"(email@example.com Link already rich)"_s << uR"(email@example.com Link already rich)"_s; QTest::addRow("mxid") << u"@user:kde.org Link already rich"_s << u"@user:kde.org Link already rich"_s; QTest::addRow("mxid with prefix") << u"a @user:kde.org b"_s << u"a @user:kde.org b"_s; } /** * For when your rich input string has a plain text url left in. * * This test is to show that a url that is already rich will be left alone but a * plain one will be linkified. */ void TextHandlerTest::receiveRichPlainUrl() { QFETCH(QString, input); QFETCH(QString, output); TextHandler testTextHandler; testTextHandler.setData(input); QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), output); } void TextHandlerTest::receiveRichEdited_data() { QTest::addColumn("testInputString"); QTest::addColumn("testOutputString"); auto theme = static_cast(qmlAttachedPropertiesObject(this, true)); QTest::newRow("basic") << u"Edited"_s << u"Edited (edited)"_s.arg(theme ? theme->disabledTextColor().name() : u"#000000"_s); QTest::newRow("multiple paragraphs") << u"

Edited

\n

Edited

"_s << u"

Edited

\n

Edited (edited)

"_s.arg( theme ? theme->disabledTextColor().name() : u"#000000"_s); } void TextHandlerTest::receiveRichEdited() { QFETCH(QString, testInputString); QFETCH(QString, testOutputString); TextHandler testTextHandler; testTextHandler.setData(testInputString); const auto event = eventCast(room->messageEvents().at(2).get()); QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText, room, event, false, event->isReplaced()), testOutputString); } void TextHandlerTest::receiveLineSeparator() { auto text = u"foo\u2028bar"_s; TextHandler textHandler; textHandler.setData(text); QCOMPARE(textHandler.handleRecievePlainText(Qt::PlainText, true), u"foo bar"_s); } void TextHandlerTest::receiveRichCodeUrl() { auto input = u"https://kde.org"_s; TextHandler testTextHandler; testTextHandler.setData(input); QCOMPARE(testTextHandler.handleRecieveRichText(), input); } void TextHandlerTest::receiveRichColor() { const QString testInputString = u"¯\\_()_/¯"_s; const QString testOutputString = u"¯\\_()_/¯"_s; TextHandler testTextHandler; testTextHandler.setData(testInputString); QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString); } void TextHandlerTest::componentOutput_data() { QTest::addColumn("testInputString"); QTest::addColumn>("testOutputComponents"); QTest::newRow("multiple paragraphs") << u"

Text

\n

Text

"_s << QList{MessageComponent{MessageComponentType::Text, u"Text"_s, {}}, MessageComponent{MessageComponentType::Text, u"Text"_s, {}}}; QTest::newRow("code") << u"

Text

\n
Some code\n
"_s << QList{MessageComponent{MessageComponentType::Text, u"Text"_s, {}}, MessageComponent{MessageComponentType::Code, u"Some code"_s, QVariantMap{{u"class"_s, u"html"_s}}}}; QTest::newRow("quote") << u"

Text

\n
\n

blockquote

\n
"_s << QList{MessageComponent{MessageComponentType::Text, u"Text"_s, {}}, MessageComponent{MessageComponentType::Quote, u"“blockquote”"_s, {}}}; QTest::newRow("multiple paragraph quote") << u"
\n

blockquote

\n

next paragraph

\n
"_s << QList{ MessageComponent{MessageComponentType::Quote, u"

“blockquote

\n

next paragraph”

"_s, {}}}; QTest::newRow("no tag first paragraph") << u"Text\n

Text

"_s << QList{MessageComponent{MessageComponentType::Text, u"Text"_s, {}}, MessageComponent{MessageComponentType::Text, u"Text"_s, {}}}; QTest::newRow("no tag last paragraph") << u"

Text

\nText"_s << QList{MessageComponent{MessageComponentType::Text, u"Text"_s, {}}, MessageComponent{MessageComponentType::Text, u"Text"_s, {}}}; QTest::newRow("inline code") << u"

https://kde.org

\n

Text

"_s << QList{MessageComponent{MessageComponentType::Text, u"https://kde.org"_s, {}}, MessageComponent{MessageComponentType::Text, u"Text"_s, {}}}; QTest::newRow("inline code single block") << u"https://kde.org"_s << QList{MessageComponent{MessageComponentType::Text, u"https://kde.org"_s, {}}}; QTest::newRow("long start tag") << u"Ah, you mean something like
# main.qml\nimport CustomQml\n...\nControls.TextField { id: someField }\nCustomQml {\n    someTextProperty: someField.text\n}\n
Sure you can, it's still local to the same file where you defined the id"_s << QList{ MessageComponent{MessageComponentType::Text, u"Ah, you mean something like
"_s, {}}, MessageComponent{ MessageComponentType::Code, u"# main.qml\nimport CustomQml\n...\nControls.TextField { id: someField }\nCustomQml {\n someTextProperty: someField.text\n}"_s, QVariantMap{{u"class"_s, u"qml"_s}}}, MessageComponent{MessageComponentType::Text, u"Sure you can, it's still local to the same file where you defined the id"_s, {}}}; } void TextHandlerTest::componentOutput() { QFETCH(QString, testInputString); QFETCH(QList, testOutputComponents); TextHandler testTextHandler; QCOMPARE(testTextHandler.textComponents(testInputString), testOutputComponents); } void TextHandlerTest::updateSpoiler_data() { QTest::addColumn("testInputString"); QTest::addColumn("testOutputString"); QTest::addColumn("spoilerRevealed"); auto theme = static_cast(qmlAttachedPropertiesObject(this, true)); QTest::newRow("same length") << u"Test"_s << u"Test"_s.arg( theme->alternateBackgroundColor().name()) << false; QTest::newRow("different length") << u"Test"_s << u"Test"_s.arg( theme->alternateBackgroundColor().name()) << false; QTest::newRow("spoiler revealed") << u"Test"_s.arg(theme->alternateBackgroundColor().name()) << u"Test"_s.arg(theme->textColor().name(), theme->alternateBackgroundColor().name()) << true; } void TextHandlerTest::updateSpoiler() { QFETCH(QString, testInputString); QFETCH(QString, testOutputString); QFETCH(bool, spoilerRevealed); QCOMPARE(TextHandler::updateSpoilerText(this, testInputString, spoilerRevealed), testOutputString); } QTEST_MAIN(TextHandlerTest) #include "texthandlertest.moc"