aboutsummaryrefslogtreecommitdiffstats
path: root/tests/test_pendingchangesdialog.cpp
blob: 078f6a80c663a1fd98a84b1309a70dc436cea95f (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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
/*
 * 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 "pendingchangesdialog.h"

/// The grouping the user asked for, asserted on the ROWS rather than on a
/// render. A pixel probe cannot tell a correct layout from a plausible one,
/// which is why MessageDetailsDialog exposes its rows too.
class TestPendingChangesDialog : public QObject
{
    Q_OBJECT
private slots:
    void aMessageIsDrawnOnceWithItsActionsBeneath();
    void everyChangeKeepsARowOfItsOwn();
    void aThreadRowCarriesItsMessageCount();
    void anUnresolvedIdStillOpensItsRun();
};

void TestPendingChangesDialog::aMessageIsDrawnOnceWithItsActionsBeneath()
{
    // The layout: subject once, actions under it.
    //
    //   Build fails      Delete
    //                    Mark read
    //   August digest    Delete
    const QVector<PendingChange> changes {
        { QStringLiteral("a@example.org"), false, QStringLiteral("Delete"),
          QStringLiteral("Build fails"), -1 },
        { QStringLiteral("a@example.org"), false, QStringLiteral("Mark read"),
          QStringLiteral("Build fails"), -1 },
        { QStringLiteral("b@example.org"), false, QStringLiteral("Delete"),
          QStringLiteral("August digest"), -1 },
    };

    const QVector<PendingChangeRow> rows =
        PendingChangesDialog::rowsFor(changes);
    QCOMPARE(rows.size(), 3);

    // First row of the run carries the subject.
    QVERIFY(rows.at(0).startsMessage);
    QCOMPARE(rows.at(0).subject, QStringLiteral("Build fails"));
    QCOMPARE(rows.at(0).action, QStringLiteral("Delete"));

    // The second action of the same message carries NO subject, which is what
    // puts it under the message rather than beside a repeated one.
    QVERIFY(!rows.at(1).startsMessage);
    QVERIFY2(rows.at(1).subject.isEmpty(),
             "the subject was repeated instead of grouping the actions");
    QCOMPARE(rows.at(1).action, QStringLiteral("Mark read"));

    // A different message opens a new run.
    QVERIFY(rows.at(2).startsMessage);
    QCOMPARE(rows.at(2).subject, QStringLiteral("August digest"));
}

void TestPendingChangesDialog::everyChangeKeepsARowOfItsOwn()
{
    // Grouping must not COLLAPSE anything: the count the user clicked has to
    // equal the number of rows they are shown, so two actions on one message
    // are two rows however they are drawn.
    const QVector<PendingChange> changes {
        { QStringLiteral("a@example.org"), false, QStringLiteral("Delete"),
          QStringLiteral("One"), -1 },
        { QStringLiteral("a@example.org"), false, QStringLiteral("Mark read"),
          QStringLiteral("One"), -1 },
        { QStringLiteral("a@example.org"), false, QStringLiteral("Mark spam"),
          QStringLiteral("One"), -1 },
    };

    const QVector<PendingChangeRow> rows =
        PendingChangesDialog::rowsFor(changes);
    QCOMPARE(rows.size(), changes.size());

    // Exactly one of them opens the run, and every action survives.
    int starts = 0;
    QStringList actions;
    for (const PendingChangeRow &row : rows) {
        if (row.startsMessage)
            ++starts;
        actions.append(row.action);
    }
    QCOMPARE(starts, 1);
    QCOMPARE(actions, QStringList({ QStringLiteral("Delete"),
                                    QStringLiteral("Mark read"),
                                    QStringLiteral("Mark spam") }));
}

void TestPendingChangesDialog::aThreadRowCarriesItsMessageCount()
{
    // A thread action reports how many messages it covered. The count belongs
    // to the row that opens the run, since that is where the subject is drawn.
    const QVector<PendingChange> changes {
        { QStringLiteral("t1"), true, QStringLiteral("Delete thread"),
          QStringLiteral("A conversation"), 4 },
        { QStringLiteral("m1@example.org"), false, QStringLiteral("Delete"),
          QStringLiteral("A message"), -1 },
    };

    const QVector<PendingChangeRow> rows =
        PendingChangesDialog::rowsFor(changes);
    QCOMPARE(rows.size(), 2);
    QCOMPARE(rows.at(0).messageCount, 4);
    // A message row claims no count: it stands for one message and saying "1"
    // would read as a thread of one.
    QCOMPARE(rows.at(1).messageCount, -1);
}

void TestPendingChangesDialog::anUnresolvedIdStillOpensItsRun()
{
    // A stale id resolves to an empty subject. The row must still OPEN a run,
    // or its actions would be drawn as though they belonged to the message
    // above them, which is worse than saying the subject is unknown.
    //
    // This is why startsMessage is carried rather than inferred from a
    // non-empty subject.
    const QVector<PendingChange> changes {
        { QStringLiteral("a@example.org"), false, QStringLiteral("Delete"),
          QStringLiteral("Known"), -1 },
        { QStringLiteral("gone@example.org"), false, QStringLiteral("Delete"),
          QString(), -1 },
    };

    const QVector<PendingChangeRow> rows =
        PendingChangesDialog::rowsFor(changes);
    QCOMPARE(rows.size(), 2);
    QVERIFY2(rows.at(1).startsMessage,
             "an unresolved id was folded into the message above it");
    QVERIFY(rows.at(1).subject.isEmpty());
}

QTEST_MAIN(TestPendingChangesDialog)
#include "test_pendingchangesdialog.moc"