aboutsummaryrefslogtreecommitdiffstats
path: root/tests/test_markdownrenderer.cpp
blob: 44f3ccb2a387aafba2cff3012d17bef6681a8788 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
/*
 * qtmaildir - a Qt6 mail client for notmuch-indexed Maildirs
 * Copyright (C) 2026 Danilo M. <danix@danix.xyz>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include <QtTest>

#include "markdownrenderer.h"

/// The extension configuration cmark-gfm renders the composer's body with.
///
/// No QApplication is needed here: MarkdownRenderer is a pure function over
/// strings, so QTEST_APPLESS_MAIN avoids pulling in a platform plugin for a
/// test that has nothing to do with widgets.
class TestMarkdownRenderer : public QObject
{
    Q_OBJECT
private slots:
    void commonMarkBasicsRender();
    void autolinkTurnsABareUrlIntoALink();
    void strikethroughRenders();
    void tasklistRenders();
    void tablesAreNotEnabled();
    void rawHtmlIsSuppressed();
    void accentedTextSurvivesAsUtf8();
    void emptyInputProducesEmptyOutput();
};

void TestMarkdownRenderer::commonMarkBasicsRender()
{
    const QString html = MarkdownRenderer::toHtml(
        QStringLiteral("**bold** *italic* `code`"));
    QVERIFY2(html.contains(QStringLiteral("<strong>")), qPrintable(html));
    QVERIFY2(html.contains(QStringLiteral("<em>")), qPrintable(html));
    QVERIFY2(html.contains(QStringLiteral("<code>")), qPrintable(html));
}

void TestMarkdownRenderer::autolinkTurnsABareUrlIntoALink()
{
    // The whole reason cmark-gfm was chosen over plain cmark. Under
    // CommonMark a bare URL is text, and a bare URL in mail is expected to
    // be clickable.
    const QString html = MarkdownRenderer::toHtml(
        QStringLiteral("see https://example.org for details"));
    QVERIFY2(html.contains(QStringLiteral("<a href=\"https://example.org\"")),
             qPrintable(html));
}

void TestMarkdownRenderer::strikethroughRenders()
{
    const QString html = MarkdownRenderer::toHtml(QStringLiteral("~~gone~~"));
    QVERIFY2(html.contains(QStringLiteral("<del>gone</del>")), qPrintable(html));
}

void TestMarkdownRenderer::tasklistRenders()
{
    // Known ceiling: many mail clients strip the checkbox, so those
    // recipients see the item with no marker. The plain part still carries
    // the literal "- [ ]", so nothing is lost, only the HTML rendering.
    const QString html = MarkdownRenderer::toHtml(
        QStringLiteral("- [ ] todo\n- [x] done"));
    QVERIFY2(html.contains(QStringLiteral("type=\"checkbox\"")), qPrintable(html));
    QVERIFY2(html.contains(QStringLiteral("checked")), qPrintable(html));
}

void TestMarkdownRenderer::tablesAreNotEnabled()
{
    // Deliberately off: tables render badly across mail clients regardless of
    // who generates them. The extension EXISTS in the library, so this
    // asserts a decision rather than a limitation, and would silently start
    // passing the wrong way if someone attached it "for completeness".
    const QString html = MarkdownRenderer::toHtml(
        QStringLiteral("| a | b |\n|---|---|\n| 1 | 2 |"));
    QVERIFY2(!html.contains(QStringLiteral("<table")), qPrintable(html));
    QVERIFY2(html.contains(QStringLiteral("| a | b |")), qPrintable(html));
}

void TestMarkdownRenderer::rawHtmlIsSuppressed()
{
    // CMARK_OPT_SAFE. The body is the user's own text, but a body that can
    // inject markup into its own generated HTML part is a sharp edge with no
    // upside.
    const QString html = MarkdownRenderer::toHtml(
        QStringLiteral("<script>alert(1)</script>\n\nafter"));
    QVERIFY2(!html.contains(QStringLiteral("<script>")), qPrintable(html));
    QVERIFY2(html.contains(QStringLiteral("after")), qPrintable(html));
}

void TestMarkdownRenderer::accentedTextSurvivesAsUtf8()
{
    // This user writes Italian, so accented text is every message rather
    // than an edge case, and a UTF-8 round trip through a C library is
    // exactly where it would be lost.
    const QString source = QString::fromUtf8("perch\xC3\xA9 \xC3\xA8 cos\xC3\xAC");
    const QString html = MarkdownRenderer::toHtml(source);
    QVERIFY2(html.contains(source), qPrintable(html));
}

void TestMarkdownRenderer::emptyInputProducesEmptyOutput()
{
    // reply_no_quote opens a composer with an empty body and it must not
    // produce a stray paragraph or crash the renderer.
    const QString html = MarkdownRenderer::toHtml(QString());
    QVERIFY2(html.trimmed().isEmpty(), qPrintable(html));
}

QTEST_APPLESS_MAIN(TestMarkdownRenderer)
#include "test_markdownrenderer.moc"