MailJDTourDownloadDocsQualityTestsCoverage

Which source lines and branches the tests execute. Lines 91.5 %, branches 75.0 % (minimum 85 % and 72 %). main @ 399ffe3d · 21 October 2026 · pipeline 7238.

GCC Code Coverage Report


Directory: src/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 94.4% 101 / 0 / 107
Functions: 100.0% 14 / 0 / 14
Branches: 86.2% 156 / 0 / 181

data/MailSort.cpp
Line Branch Exec Source
1 #include "MailSort.h"
2
3 #include "ContactIndex.h"
4 #include <QRegularExpression>
5
6 828 bool MailSortOrder::columnFromId(const QString &id, Column *column) {
7 static const QHash<QString, Column> columns = {
8 66 {QStringLiteral("date"), Column::Date}, {QStringLiteral("from"), Column::Sender},
9 66 {QStringLiteral("to"), Column::Recipient}, {QStringLiteral("subject"), Column::Subject},
10
5/10
✓ Branch 2 → 3 taken 22 times.
✓ Branch 2 → 48 taken 806 times.
✓ Branch 4 → 5 taken 22 times.
✗ Branch 4 → 48 not taken.
✓ Branch 33 → 34 taken 132 times.
✓ Branch 33 → 35 taken 22 times.
✗ Branch 60 → 61 not taken.
✗ Branch 60 → 62 not taken.
✗ Branch 111 → 112 not taken.
✗ Branch 111 → 113 not taken.
1048 {QStringLiteral("size"), Column::Size}, {QStringLiteral("star"), Column::Star}};
11 828 const auto found = columns.constFind(id);
12
2/2
✓ Branch 51 → 52 taken 1 time.
✓ Branch 51 → 53 taken 827 times.
828 if (found == columns.constEnd()) return false;
13
1/2
✓ Branch 53 → 54 taken 827 times.
✗ Branch 53 → 56 not taken.
827 if (column) *column = found.value();
14 827 return true;
15 154 }
16
17 1776 QString MailSortOrder::idOf(Column column) {
18
6/7
✓ Branch 2 → 3 taken 1646 times.
✓ Branch 2 → 8 taken 52 times.
✓ Branch 2 → 13 taken 2 times.
✓ Branch 2 → 18 taken 72 times.
✓ Branch 2 → 23 taken 2 times.
✓ Branch 2 → 28 taken 2 times.
✗ Branch 2 → 33 not taken.
1776 switch (column) {
19 3292 case Column::Date: return QStringLiteral("date");
20 104 case Column::Sender: return QStringLiteral("from");
21 4 case Column::Recipient: return QStringLiteral("to");
22 144 case Column::Subject: return QStringLiteral("subject");
23 4 case Column::Size: return QStringLiteral("size");
24 4 case Column::Star: return QStringLiteral("star");
25 }
26 ✗ return QStringLiteral("date");
27 }
28
29 6 bool MailSortOrder::defaultAscending(Column column) {
30
5/6
✓ Branch 2 → 3 taken 5 times.
✓ Branch 2 → 5 taken 1 time.
✓ Branch 3 → 4 taken 5 times.
✗ Branch 3 → 5 not taken.
✓ Branch 4 → 5 taken 3 times.
✓ Branch 4 → 6 taken 2 times.
6 return column == Column::Sender || column == Column::Recipient || column == Column::Subject;
31 }
32
33 977 QString MailSortOrder::toString() const {
34
8/12
✓ Branch 2 → 3 taken 92 times.
✓ Branch 2 → 6 taken 885 times.
✓ Branch 9 → 10 taken 977 times.
✓ Branch 10 → 11 taken 977 times.
✓ Branch 14 → 15 taken 885 times.
✓ Branch 14 → 16 taken 92 times.
✓ Branch 16 → 17 taken 92 times.
✓ Branch 16 → 18 taken 885 times.
✗ Branch 25 → 26 not taken.
✗ Branch 25 → 27 not taken.
✗ Branch 29 → 30 not taken.
✗ Branch 29 → 31 not taken.
1954 return idOf(column) + (ascending ? QStringLiteral(":asc") : QStringLiteral(":desc"));
35 }
36
37 1615 MailSortOrder MailSortOrder::fromString(const QString &text) {
38 1615 MailSortOrder order;
39
1/1
✓ Branch 5 → 6 taken 1615 times.
1615 const auto parts = text.split(QLatin1Char(':'));
40
7/8
✓ Branch 7 → 8 taken 817 times.
✓ Branch 7 → 19 taken 798 times.
✓ Branch 11 → 12 taken 805 times.
✓ Branch 11 → 16 taken 12 times.
✓ Branch 15 → 16 taken 805 times.
✗ Branch 15 → 19 not taken.
✓ Branch 21 → 22 taken 799 times.
✓ Branch 21 → 23 taken 816 times.
2432 if (parts.size() != 2 || (parts.at(1) != QLatin1String("asc") && parts.at(1) != QLatin1String("desc")) ||
41
3/3
✓ Branch 17 → 18 taken 817 times.
✓ Branch 18 → 19 taken 1 time.
✓ Branch 18 → 20 taken 816 times.
817 !columnFromId(parts.at(0), &order.column))
42 799 return {};
43 816 order.ascending = parts.at(1) == QLatin1String("asc");
44 816 return order;
45 1615 }
46
47 namespace MailSort {
48
49 namespace {
50 // The first entry of an address list; commas inside quotes or <> do not split.
51 100384 QString firstAddressEntry(const QString &addresses) {
52 100384 bool quoted = false;
53 100384 int angle = 0;
54
2/2
✓ Branch 41 → 3 taken 2458346 times.
✓ Branch 41 → 42 taken 83005 times.
2541351 for (qsizetype i = 0; i < addresses.size(); ++i) {
55 2458346 const QChar c = addresses.at(i);
56
2/2
✓ Branch 7 → 8 taken 39384 times.
✓ Branch 7 → 9 taken 2418962 times.
2458346 if (c == QLatin1Char('"')) quoted = !quoted;
57
6/6
✓ Branch 9 → 10 taken 2241734 times.
✓ Branch 9 → 15 taken 177228 times.
✓ Branch 13 → 14 taken 77834 times.
✓ Branch 13 → 15 taken 2163900 times.
✓ Branch 16 → 17 taken 77834 times.
✓ Branch 16 → 18 taken 2341128 times.
2418962 else if (!quoted && c == QLatin1Char('<')) ++angle;
58
7/8
✓ Branch 18 → 19 taken 2163900 times.
✓ Branch 18 → 25 taken 177228 times.
✓ Branch 22 → 23 taken 77834 times.
✓ Branch 22 → 25 taken 2086066 times.
✓ Branch 23 → 24 taken 77834 times.
✗ Branch 23 → 25 not taken.
✓ Branch 26 → 27 taken 77834 times.
✓ Branch 26 → 28 taken 2263294 times.
2341128 else if (!quoted && c == QLatin1Char('>') && angle > 0) --angle;
59
9/9
✓ Branch 28 → 29 taken 2086066 times.
✓ Branch 28 → 35 taken 177228 times.
✓ Branch 29 → 30 taken 763993 times.
✓ Branch 29 → 35 taken 1322073 times.
✓ Branch 33 → 34 taken 17379 times.
✓ Branch 33 → 35 taken 746614 times.
✓ Branch 36 → 37 taken 17379 times.
✓ Branch 36 → 39 taken 2245915 times.
✓ Branch 37 → 38 taken 17379 times.
2263294 else if (!quoted && angle == 0 && c == QLatin1Char(',')) return addresses.left(i);
60 }
61 83005 return addresses;
62 }
63 } // namespace
64
65 100384 QString personKey(const QString &addresses) {
66
2/2
✓ Branch 2 → 3 taken 100384 times.
✓ Branch 3 → 4 taken 100384 times.
100384 const QString entry = firstAddressEntry(addresses).trimmed();
67 100384 const qsizetype open = entry.indexOf(QLatin1Char('<'));
68
3/3
✓ Branch 8 → 9 taken 22550 times.
✓ Branch 8 → 11 taken 77834 times.
✓ Branch 9 → 10 taken 22550 times.
100384 if (open < 0) return ContactIndex::foldForSort(entry);
69
2/2
✓ Branch 11 → 12 taken 77834 times.
✓ Branch 12 → 13 taken 77834 times.
77834 const QString name = ContactIndex::foldForSort(entry.left(open));
70
2/2
✓ Branch 15 → 16 taken 60365 times.
✓ Branch 15 → 17 taken 17469 times.
77834 if (!name.isEmpty()) return name;
71 17469 const qsizetype close = entry.indexOf(QLatin1Char('>'), open);
72
3/4
✓ Branch 20 → 21 taken 17469 times.
✗ Branch 20 → 22 not taken.
✓ Branch 23 → 24 taken 17469 times.
✓ Branch 24 → 25 taken 17469 times.
17469 return ContactIndex::foldForSort(entry.mid(open + 1, close < 0 ? -1 : close - open - 1));
73 100384 }
74
75 56904 QString subjectKey(const QString &subject) {
76 // Reply and forward markers of common clients and languages, optionally
77 // numbered ("Re[2]:", "AW(3):").
78 static const QRegularExpression prefix(
79 6 QStringLiteral("^\\s*(re|fw|fwd|aw|wg|sv|vs|antw|tr|rif|enc|r)\\s*(\\[\\d+\\]|\\(\\d+\\))?\\s*:\\s*"),
80
4/7
✓ Branch 2 → 3 taken 3 times.
✓ Branch 2 → 15 taken 56901 times.
✓ Branch 4 → 5 taken 3 times.
✗ Branch 4 → 15 not taken.
✓ Branch 9 → 10 taken 3 times.
✗ Branch 50 → 51 not taken.
✗ Branch 50 → 52 not taken.
56910 QRegularExpression::CaseInsensitiveOption);
81 56904 QString text = subject;
82
1/2
✓ Branch 37 → 17 taken 94220 times.
✗ Branch 37 → 38 not taken.
94220 for (int guard = 0; guard < 16; ++guard) {
83
1/1
✓ Branch 18 → 19 taken 94220 times.
94220 const auto match = prefix.match(text);
84
7/8
✓ Branch 19 → 20 taken 94220 times.
✓ Branch 20 → 21 taken 37316 times.
✓ Branch 20 → 23 taken 56904 times.
✓ Branch 21 → 22 taken 37316 times.
✗ Branch 22 → 23 not taken.
✓ Branch 22 → 24 taken 37316 times.
✓ Branch 25 → 26 taken 56904 times.
✓ Branch 25 → 27 taken 37316 times.
94220 if (!match.hasMatch() || match.capturedLength() == 0) break;
85
2/2
✓ Branch 27 → 28 taken 37316 times.
✓ Branch 28 → 29 taken 37316 times.
37316 text = text.mid(match.capturedLength());
86 94220 }
87
1/1
✓ Branch 38 → 39 taken 56904 times.
113808 return ContactIndex::foldForSort(text);
88 56904 }
89
90 36511 QVariant key(const MailHeader &header, MailSortOrder::Column column) {
91
5/7
✓ Branch 2 → 3 taken 8603 times.
✓ Branch 2 → 7 taken 6842 times.
✓ Branch 2 → 11 taken 8060 times.
✓ Branch 2 → 15 taken 6694 times.
✓ Branch 2 → 16 taken 6312 times.
✗ Branch 2 → 17 not taken.
✗ Branch 2 → 18 not taken.
36511 switch (column) {
92
1/1
✓ Branch 3 → 4 taken 8603 times.
8603 case MailSortOrder::Column::Sender: return personKey(header.from);
93
1/1
✓ Branch 7 → 8 taken 6842 times.
6842 case MailSortOrder::Column::Recipient: return personKey(header.to);
94
1/1
✓ Branch 11 → 12 taken 8060 times.
8060 case MailSortOrder::Column::Subject: return subjectKey(header.subject);
95 6694 case MailSortOrder::Column::Size: return qint64(header.size);
96 6312 case MailSortOrder::Column::Star: return qint64(header.flags & MailFlag::Flagged);
97 ✗ case MailSortOrder::Column::Date: break;
98 }
99 ✗ return {};
100 }
101
102 namespace {
103 // Canonical (descending) order; see MailSortOrder.
104 20723 bool canonicalBefore(const MailHeader &left, const MailHeader &right, MailSortOrder::Column column) {
105
2/2
✓ Branch 2 → 3 taken 3714 times.
✓ Branch 2 → 16 taken 17009 times.
20723 if (column == MailSortOrder::Column::Date) {
106 // MailCache: ORDER BY date DESC, uid DESC; SQLite lists NULL dates last.
107
2/2
✓ Branch 3 → 4 taken 3714 times.
✓ Branch 4 → 5 taken 3714 times.
3714 const bool leftDated = left.date.isValid(), rightDated = right.date.isValid();
108
2/2
✓ Branch 5 → 6 taken 45 times.
✓ Branch 5 → 7 taken 3669 times.
3714 if (leftDated != rightDated) return leftDated;
109
8/8
✓ Branch 7 → 8 taken 3667 times.
✓ Branch 7 → 11 taken 2 times.
✓ Branch 8 → 9 taken 3667 times.
✓ Branch 9 → 10 taken 3627 times.
✓ Branch 9 → 11 taken 40 times.
✓ Branch 12 → 13 taken 3627 times.
✓ Branch 12 → 15 taken 42 times.
✓ Branch 13 → 14 taken 3627 times.
3669 if (leftDated && left.date != right.date) return left.date > right.date;
110 42 return left.uid > right.uid;
111 }
112
2/2
✓ Branch 16 → 17 taken 17009 times.
✓ Branch 17 → 18 taken 17009 times.
17009 const QVariant leftKey = key(left, column), rightKey = key(right, column);
113
3/3
✓ Branch 18 → 19 taken 17009 times.
✓ Branch 19 → 20 taken 10576 times.
✓ Branch 19 → 32 taken 6433 times.
17009 if (leftKey.typeId() == QMetaType::QString) {
114
4/4
✓ Branch 20 → 21 taken 10576 times.
✓ Branch 21 → 22 taken 10576 times.
✓ Branch 22 → 23 taken 10576 times.
✓ Branch 23 → 24 taken 10576 times.
10576 const int compared = leftKey.toString().toUtf8().compare(rightKey.toString().toUtf8());
115
2/2
✓ Branch 30 → 31 taken 6218 times.
✓ Branch 30 → 38 taken 4358 times.
10576 if (compared != 0) return compared > 0;
116
4/4
✓ Branch 32 → 33 taken 6433 times.
✓ Branch 33 → 34 taken 6433 times.
✓ Branch 34 → 35 taken 3221 times.
✓ Branch 34 → 38 taken 3212 times.
6433 } else if (leftKey.toLongLong() != rightKey.toLongLong()) {
117
2/2
✓ Branch 35 → 36 taken 3221 times.
✓ Branch 36 → 37 taken 3221 times.
3221 return leftKey.toLongLong() > rightKey.toLongLong();
118 }
119 7570 return left.uid > right.uid;
120 17009 }
121 } // namespace
122
123 20723 bool listedBefore(const MailHeader &left, const MailHeader &right, const MailSortOrder &order) {
124
2/2
✓ Branch 2 → 3 taken 10711 times.
✓ Branch 2 → 5 taken 10012 times.
20723 return order.ascending ? canonicalBefore(right, left, order.column)
125 20723 : canonicalBefore(left, right, order.column);
126 }
127
128 808 MailSortOrder threadOrder(const MailSortOrder &order) {
129 using Column = MailSortOrder::Column;
130
6/6
✓ Branch 2 → 3 taken 9 times.
✓ Branch 2 → 5 taken 799 times.
✓ Branch 3 → 4 taken 7 times.
✓ Branch 3 → 5 taken 2 times.
✓ Branch 4 → 5 taken 6 times.
✓ Branch 4 → 6 taken 1 time.
808 if (order.column == Column::Date || order.column == Column::Sender || order.column == Column::Subject)
131 807 return order;
132 1 return {};
133 }
134
135 1212 QString sqlKey(MailSortOrder::Column column, const QString &alias) {
136
3/3
✓ Branch 3 → 4 taken 762 times.
✓ Branch 3 → 5 taken 450 times.
✓ Branch 7 → 8 taken 450 times.
1212 const QString prefix = alias.isEmpty() ? QString() : alias + QLatin1Char('.');
137
5/7
✓ Branch 8 → 9 taken 298 times.
✓ Branch 8 → 17 taken 212 times.
✓ Branch 8 → 25 taken 278 times.
✓ Branch 8 → 33 taken 212 times.
✓ Branch 8 → 41 taken 212 times.
✗ Branch 8 → 52 not taken.
✗ Branch 8 → 53 not taken.
1212 switch (column) {
138
1/1
✓ Branch 13 → 14 taken 298 times.
894 case MailSortOrder::Column::Sender: return QStringLiteral("mailjd_sort_key(1,%1from_addr)").arg(prefix);
139
1/1
✓ Branch 21 → 22 taken 212 times.
636 case MailSortOrder::Column::Recipient: return QStringLiteral("mailjd_sort_key(1,%1to_addr)").arg(prefix);
140
1/1
✓ Branch 29 → 30 taken 278 times.
834 case MailSortOrder::Column::Subject: return QStringLiteral("mailjd_sort_key(2,%1subject)").arg(prefix);
141
1/1
✓ Branch 37 → 38 taken 212 times.
636 case MailSortOrder::Column::Size: return QStringLiteral("coalesce(%1size,0)").arg(prefix);
142
2/2
✓ Branch 45 → 46 taken 212 times.
✓ Branch 47 → 48 taken 212 times.
848 case MailSortOrder::Column::Star: return QStringLiteral("(%1flags & %2)").arg(prefix).arg(MailFlag::Flagged);
143 ✗ case MailSortOrder::Column::Date: break;
144 }
145 ✗ return prefix + QStringLiteral("date");
146 1212 }
147
148 6671 QByteArray descendingKey(const QByteArray &utf8) {
149 6671 QByteArray reversed(utf8.size() + 1, '\xff');
150
3/3
✓ Branch 6 → 7 taken 73039 times.
✓ Branch 9 → 5 taken 73039 times.
✓ Branch 9 → 10 taken 6671 times.
79710 for (qsizetype i = 0; i < utf8.size(); ++i) reversed[i] = char(~uchar(utf8.at(i)));
151 6671 return reversed;
152 ✗ }
153
154 } // namespace MailSort
155