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: 99.1% 106 / 0 / 107
Functions: 100.0% 11 / 0 / 11
Branches: 78.5% 230 / 0 / 293

presentation/EventQuickInput.cpp
Line Branch Exec Source
1 #include "EventQuickInput.h"
2
3 #include <QLoggingCategory>
4 #include <QRegularExpression>
5 #include <QTime>
6
7
4/7
✓ Branch 2 → 3 taken 2 times.
✓ Branch 2 → 8 taken 7 times.
✓ Branch 4 → 5 taken 2 times.
✗ Branch 4 → 8 not taken.
✓ Branch 5 → 6 taken 2 times.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
9 Q_LOGGING_CATEGORY(lcEventInput, "mailjd.quick.eventinput")
8
9 namespace EventQuickParse {
10
11 namespace {
12
13 const QStringList kRelativeWords = {QStringLiteral("today"), QStringLiteral("heute"), QStringLiteral("tomorrow"),
14 QStringLiteral("morgen"), QStringLiteral("übermorgen")};
15
16 // Weekday spellings per ISO weekday (index 0 = Monday); the two longest
17 // entries of each list are the full English and German names.
18 2342 const QList<QStringList> &weekdayNames() {
19 static const QList<QStringList> names = {
20 {"mo", "mon", "monday", "montag"}, {"tu", "tue", "tuesday", "di", "dienstag"},
21 {"we", "wed", "wednesday", "mi", "mittwoch"}, {"th", "thu", "thursday", "do", "donnerstag"},
22 {"fr", "fri", "friday", "freitag"}, {"sa", "sat", "saturday", "samstag", "sonnabend"},
23
5/10
✓ Branch 2 → 3 taken 7 times.
✓ Branch 2 → 80 taken 2335 times.
✓ Branch 4 → 5 taken 7 times.
✗ Branch 4 → 80 not taken.
✓ Branch 49 → 50 taken 49 times.
✓ Branch 49 → 51 taken 7 times.
✗ Branch 83 → 84 not taken.
✗ Branch 83 → 85 not taken.
✗ Branch 169 → 170 not taken.
✗ Branch 169 → 171 not taken.
2629 {"su", "sun", "sunday", "so", "sonntag"}};
24 2342 return names;
25 245 }
26
27 // ISO weekday (1 = Monday) of a weekday word, 0 when it is none.
28 166 int weekdayOf(const QString &word) {
29
1/1
✓ Branch 2 → 3 taken 166 times.
166 const QString lower = word.toLower();
30
3/3
✓ Branch 10 → 11 taken 1227 times.
✓ Branch 12 → 4 taken 1084 times.
✓ Branch 12 → 13 taken 143 times.
1227 for (int i = 0; i < weekdayNames().size(); ++i)
31
3/3
✓ Branch 4 → 5 taken 1084 times.
✓ Branch 7 → 8 taken 23 times.
✓ Branch 7 → 9 taken 1061 times.
1084 if (weekdayNames().at(i).contains(lower)) return i + 1;
32 143 return 0;
33 166 }
34
35 218 QDate dateOf(const QString &word, const QDate &today) {
36
1/1
✓ Branch 2 → 3 taken 218 times.
218 const QString lower = word.toLower();
37
6/6
✓ Branch 5 → 6 taken 214 times.
✓ Branch 5 → 9 taken 4 times.
✓ Branch 8 → 9 taken 3 times.
✓ Branch 8 → 10 taken 211 times.
✓ Branch 11 → 12 taken 7 times.
✓ Branch 11 → 13 taken 211 times.
218 if (lower == kRelativeWords.at(0) || lower == kRelativeWords.at(1)) return today;
38
7/7
✓ Branch 15 → 16 taken 190 times.
✓ Branch 15 → 19 taken 21 times.
✓ Branch 18 → 19 taken 22 times.
✓ Branch 18 → 20 taken 168 times.
✓ Branch 21 → 22 taken 43 times.
✓ Branch 21 → 24 taken 168 times.
✓ Branch 22 → 23 taken 43 times.
211 if (lower == kRelativeWords.at(2) || lower == kRelativeWords.at(3)) return today.addDays(1);
39
3/3
✓ Branch 26 → 27 taken 2 times.
✓ Branch 26 → 29 taken 166 times.
✓ Branch 27 → 28 taken 2 times.
168 if (lower == kRelativeWords.at(4)) return today.addDays(2);
40 // Two-letter weekdays clash with words ("so", "do", "mi"): only the
41 // longer forms count as dates.
42
7/7
✓ Branch 29 → 30 taken 166 times.
✓ Branch 30 → 31 taken 23 times.
✓ Branch 30 → 34 taken 143 times.
✓ Branch 32 → 33 taken 22 times.
✓ Branch 32 → 34 taken 1 time.
✓ Branch 35 → 36 taken 22 times.
✓ Branch 35 → 39 taken 144 times.
166 if (const int weekday = weekdayOf(lower); weekday > 0 && lower.size() >= 3)
43
2/2
✓ Branch 36 → 37 taken 22 times.
✓ Branch 37 → 38 taken 22 times.
22 return today.addDays((weekday - today.dayOfWeek() + 7) % 7);
44
4/4
✓ Branch 39 → 40 taken 144 times.
✓ Branch 40 → 41 taken 144 times.
✓ Branch 41 → 42 taken 6 times.
✓ Branch 41 → 43 taken 138 times.
144 if (const QDate iso = QDate::fromString(word, Qt::ISODate); iso.isValid()) return iso;
45
4/7
✓ Branch 43 → 45 taken 6 times.
✓ Branch 43 → 57 taken 132 times.
✓ Branch 46 → 47 taken 6 times.
✗ Branch 46 → 57 not taken.
✓ Branch 51 → 52 taken 6 times.
✗ Branch 113 → 114 not taken.
✗ Branch 113 → 115 not taken.
144 static const QRegularExpression dotted(QStringLiteral("^(\\d{1,2})\\.(\\d{1,2})\\.(\\d{4})?$"));
46
4/4
✓ Branch 58 → 59 taken 138 times.
✓ Branch 59 → 60 taken 138 times.
✓ Branch 60 → 61 taken 3 times.
✓ Branch 60 → 94 taken 135 times.
138 if (const auto match = dotted.match(word); match.hasMatch()) {
47
8/10
✓ Branch 61 → 62 taken 3 times.
✓ Branch 63 → 64 taken 2 times.
✓ Branch 63 → 66 taken 1 time.
✓ Branch 64 → 65 taken 2 times.
✓ Branch 66 → 67 taken 1 time.
✓ Branch 67 → 68 taken 1 time.
✓ Branch 69 → 70 taken 1 time.
✓ Branch 69 → 71 taken 2 times.
✗ Branch 117 → 118 not taken.
✗ Branch 117 → 119 not taken.
3 const int year = match.captured(3).isEmpty() ? today.year() : match.captured(3).toInt();
48
5/5
✓ Branch 72 → 73 taken 3 times.
✓ Branch 73 → 74 taken 3 times.
✓ Branch 74 → 75 taken 3 times.
✓ Branch 75 → 76 taken 3 times.
✓ Branch 76 → 77 taken 3 times.
3 QDate date(year, match.captured(2).toInt(), match.captured(1).toInt());
49 // Without a year a date that has passed means next year.
50
11/15
✓ Branch 79 → 80 taken 3 times.
✓ Branch 80 → 81 taken 3 times.
✗ Branch 80 → 88 not taken.
✓ Branch 81 → 82 taken 3 times.
✓ Branch 83 → 84 taken 2 times.
✓ Branch 83 → 88 taken 1 time.
✓ Branch 86 → 87 taken 1 time.
✓ Branch 86 → 88 taken 1 time.
✓ Branch 89 → 90 taken 3 times.
✗ Branch 89 → 91 not taken.
✓ Branch 91 → 92 taken 1 time.
✓ Branch 91 → 93 taken 2 times.
✓ Branch 92 → 93 taken 1 time.
✗ Branch 130 → 131 not taken.
✗ Branch 130 → 132 not taken.
3 if (date.isValid() && match.captured(3).isEmpty() && date < today) date = date.addYears(1);
51 3 return date;
52 138 }
53
1/1
✓ Branch 98 → 100 taken 135 times.
135 return {};
54 218 }
55
56 55 QString timeOf(const QString &text) {
57
4/7
✓ Branch 2 → 3 taken 5 times.
✓ Branch 2 → 15 taken 50 times.
✓ Branch 4 → 5 taken 5 times.
✗ Branch 4 → 15 not taken.
✓ Branch 9 → 10 taken 5 times.
✗ Branch 57 → 58 not taken.
✗ Branch 57 → 59 not taken.
60 static const QRegularExpression pattern(QStringLiteral("^(\\d{1,2})(?:[:.](\\d{2}))?$"));
58
1/1
✓ Branch 16 → 17 taken 55 times.
55 const auto match = pattern.match(text);
59
3/3
✓ Branch 17 → 18 taken 55 times.
✓ Branch 18 → 19 taken 29 times.
✓ Branch 18 → 20 taken 26 times.
55 if (!match.hasMatch()) return {};
60
10/12
✓ Branch 20 → 21 taken 26 times.
✓ Branch 22 → 23 taken 13 times.
✓ Branch 22 → 24 taken 13 times.
✓ Branch 24 → 25 taken 13 times.
✓ Branch 25 → 26 taken 13 times.
✓ Branch 27 → 28 taken 26 times.
✓ Branch 28 → 29 taken 26 times.
✓ Branch 29 → 30 taken 26 times.
✓ Branch 31 → 32 taken 13 times.
✓ Branch 31 → 33 taken 13 times.
✗ Branch 64 → 65 not taken.
✗ Branch 64 → 66 not taken.
26 const QTime time(match.captured(1).toInt(), match.captured(2).isEmpty() ? 0 : match.captured(2).toInt());
61
8/12
✓ Branch 34 → 35 taken 26 times.
✓ Branch 35 → 36 taken 25 times.
✓ Branch 35 → 40 taken 1 time.
✓ Branch 39 → 41 taken 25 times.
✓ Branch 42 → 43 taken 25 times.
✓ Branch 42 → 44 taken 1 time.
✓ Branch 44 → 45 taken 25 times.
✓ Branch 44 → 46 taken 1 time.
✗ Branch 71 → 72 not taken.
✗ Branch 71 → 73 not taken.
✗ Branch 75 → 76 not taken.
✗ Branch 75 → 77 not taken.
51 return time.isValid() ? time.toString(QStringLiteral("HH:mm")) : QString();
62 55 }
63
64 } // namespace
65
66 163 QDate parseDate(const QString &word, const QDate &today) { return dateOf(word, today); }
67
68 31 QStringList dateWords() {
69 31 QStringList words = kRelativeWords;
70
3/3
✓ Branch 3 → 4 taken 31 times.
✓ Branch 20 → 6 taken 217 times.
✓ Branch 20 → 21 taken 31 times.
248 for (const QStringList &day : weekdayNames()) {
71 217 QStringList full;
72
5/5
✓ Branch 11 → 12 taken 465 times.
✓ Branch 11 → 13 taken 558 times.
✓ Branch 12 → 13 taken 465 times.
✓ Branch 15 → 9 taken 1023 times.
✓ Branch 15 → 16 taken 217 times.
1240 for (const QString &name : day) if (name.size() > 3) full << name;
73
1/1
✓ Branch 16 → 17 taken 217 times.
217 words << full;
74 217 }
75 31 return words;
76 ✗ }
77 10 QString parseTime(const QString &word) { return timeOf(word); }
78
79 25 Result parse(const QString &text, const QDate &today) {
80 25 Result result;
81 25 QStringList title;
82 25 QStringList location;
83 25 bool inLocation = false;
84
5/5
✓ Branch 5 → 6 taken 25 times.
✓ Branch 6 → 7 taken 25 times.
✓ Branch 7 → 8 taken 25 times.
✓ Branch 93 → 9 taken 76 times.
✓ Branch 93 → 94 taken 25 times.
101 for (const QString &word : text.split(QLatin1Char(' '), Qt::SkipEmptyParts)) {
85
7/9
✓ Branch 12 → 13 taken 76 times.
✓ Branch 13 → 14 taken 4 times.
✓ Branch 13 → 19 taken 72 times.
✓ Branch 15 → 16 taken 4 times.
✗ Branch 15 → 19 not taken.
✓ Branch 17 → 18 taken 4 times.
✗ Branch 17 → 19 not taken.
✓ Branch 20 → 21 taken 4 times.
✓ Branch 20 → 25 taken 72 times.
76 if (word.startsWith(QLatin1Char('/')) && word.size() > 1 && result.calendarHint.isEmpty()) {
86
1/1
✓ Branch 21 → 22 taken 4 times.
4 result.calendarHint = word.mid(1);
87 4 inLocation = false;
88 4 continue;
89 }
90
6/7
✓ Branch 27 → 28 taken 72 times.
✓ Branch 28 → 29 taken 2 times.
✓ Branch 28 → 32 taken 70 times.
✓ Branch 30 → 31 taken 2 times.
✗ Branch 30 → 32 not taken.
✓ Branch 33 → 34 taken 2 times.
✓ Branch 33 → 41 taken 70 times.
72 if (word.startsWith(QLatin1Char('@')) && location.isEmpty()) {
91 2 inLocation = true;
92
3/4
✓ Branch 35 → 36 taken 2 times.
✗ Branch 35 → 40 not taken.
✓ Branch 36 → 37 taken 2 times.
✓ Branch 37 → 38 taken 2 times.
2 if (word.size() > 1) location << word.mid(1);
93 2 continue;
94 }
95
2/2
✓ Branch 41 → 42 taken 4 times.
✓ Branch 41 → 44 taken 66 times.
70 if (inLocation) {
96
1/1
✓ Branch 42 → 43 taken 4 times.
4 location << word;
97 4 continue;
98 }
99
3/3
✓ Branch 44 → 45 taken 66 times.
✓ Branch 45 → 46 taken 55 times.
✓ Branch 45 → 51 taken 11 times.
66 if (!result.date.isValid()) {
100
4/4
✓ Branch 46 → 47 taken 55 times.
✓ Branch 47 → 48 taken 55 times.
✓ Branch 48 → 49 taken 22 times.
✓ Branch 48 → 50 taken 33 times.
55 if (const QDate date = dateOf(word, today); date.isValid()) {
101 22 result.date = date;
102 22 continue;
103 }
104 }
105
2/2
✓ Branch 52 → 53 taken 42 times.
✓ Branch 52 → 90 taken 2 times.
44 if (result.startTime.isEmpty()) {
106
1/1
✓ Branch 56 → 57 taken 42 times.
42 const QStringList range = word.split(QLatin1Char('-'));
107
1/1
✓ Branch 58 → 59 taken 42 times.
42 const QString start = timeOf(range.first());
108
3/3
✓ Branch 60 → 61 taken 3 times.
✓ Branch 60 → 63 taken 39 times.
✓ Branch 62 → 64 taken 3 times.
42 const QString end = range.size() == 2 ? timeOf(range.last()) : QString();
109
7/8
✓ Branch 65 → 66 taken 12 times.
✓ Branch 65 → 71 taken 30 times.
✓ Branch 67 → 68 taken 3 times.
✓ Branch 67 → 70 taken 9 times.
✓ Branch 69 → 70 taken 3 times.
✗ Branch 69 → 71 not taken.
✓ Branch 72 → 73 taken 12 times.
✓ Branch 72 → 76 taken 30 times.
42 if (!start.isEmpty() && (range.size() == 1 || !end.isEmpty())) {
110 12 result.startTime = start;
111 12 result.endTime = end;
112 12 continue;
113 }
114 66 }
115
1/1
✓ Branch 90 → 91 taken 32 times.
32 title << word;
116 25 }
117
1/1
✓ Branch 97 → 98 taken 25 times.
25 result.summary = title.join(QLatin1Char(' '));
118
1/1
✓ Branch 102 → 103 taken 25 times.
25 result.location = location.join(QLatin1Char(' '));
119 25 return result;
120 25 }
121
122 } // namespace EventQuickParse
123
124 795 QuickEventInput::QuickEventInput(std::function<QVariantList()> calendarChoices, QObject *parent)
125 795 : QObject(parent), m_calendarChoices(std::move(calendarChoices)) {}
126
127 10 QVariantMap QuickEventInput::parse(const QString &text) const {
128
2/2
✓ Branch 2 → 3 taken 10 times.
✓ Branch 3 → 4 taken 10 times.
10 const EventQuickParse::Result result = EventQuickParse::parse(text, QDate::currentDate());
129
2/4
✓ Branch 10 → 11 taken 10 times.
✓ Branch 10 → 12 taken 10 times.
✗ Branch 197 → 198 not taken.
✗ Branch 197 → 199 not taken.
30 QVariantMap defaults{{QStringLiteral("summary"), result.summary}};
130
5/5
✓ Branch 14 → 15 taken 10 times.
✓ Branch 15 → 16 taken 9 times.
✓ Branch 15 → 28 taken 1 time.
✓ Branch 16 → 17 taken 9 times.
✓ Branch 21 → 22 taken 9 times.
19 if (result.date.isValid()) defaults[QStringLiteral("dateIso")] = result.date.toString(Qt::ISODate);
131
3/3
✓ Branch 29 → 30 taken 9 times.
✓ Branch 29 → 40 taken 1 time.
✓ Branch 34 → 35 taken 9 times.
19 if (!result.startTime.isEmpty()) defaults[QStringLiteral("startTime")] = result.startTime;
132
3/3
✓ Branch 41 → 42 taken 2 times.
✓ Branch 41 → 52 taken 8 times.
✓ Branch 46 → 47 taken 2 times.
12 if (!result.endTime.isEmpty()) defaults[QStringLiteral("endTime")] = result.endTime;
133
3/3
✓ Branch 53 → 54 taken 1 time.
✓ Branch 53 → 64 taken 9 times.
✓ Branch 58 → 59 taken 1 time.
11 if (!result.location.isEmpty()) defaults[QStringLiteral("location")] = result.location;
134
2/2
✓ Branch 65 → 66 taken 3 times.
✓ Branch 65 → 172 taken 7 times.
10 if (!result.calendarHint.isEmpty()) {
135
5/5
✓ Branch 66 → 67 taken 3 times.
✓ Branch 67 → 68 taken 3 times.
✓ Branch 68 → 69 taken 3 times.
✓ Branch 147 → 70 taken 4 times.
✓ Branch 147 → 148 taken 1 time.
5 for (const QVariant &value : m_calendarChoices()) {
136
1/1
✓ Branch 71 → 72 taken 4 times.
4 const QVariantMap choice = value.toMap();
137
8/20
✓ Branch 76 → 77 taken 4 times.
✓ Branch 77 → 78 taken 4 times.
✓ Branch 78 → 79 taken 4 times.
✗ Branch 78 → 87 not taken.
✓ Branch 101 → 102 taken 4 times.
✗ Branch 101 → 103 not taken.
✓ Branch 103 → 104 taken 4 times.
✗ Branch 103 → 105 not taken.
✓ Branch 105 → 106 taken 4 times.
✗ Branch 105 → 107 not taken.
✓ Branch 107 → 108 taken 2 times.
✓ Branch 107 → 109 taken 2 times.
✗ Branch 271 → 272 not taken.
✗ Branch 271 → 273 not taken.
✗ Branch 275 → 276 not taken.
✗ Branch 275 → 277 not taken.
✗ Branch 279 → 280 not taken.
✗ Branch 279 → 281 not taken.
✗ Branch 283 → 284 not taken.
✗ Branch 283 → 285 not taken.
16 if (choice.value(QStringLiteral("readOnly")).toBool() ||
138
11/27
✓ Branch 83 → 84 taken 4 times.
✓ Branch 84 → 85 taken 4 times.
✓ Branch 85 → 86 taken 4 times.
✓ Branch 86 → 87 taken 2 times.
✓ Branch 86 → 88 taken 2 times.
✓ Branch 89 → 90 taken 4 times.
✗ Branch 89 → 91 not taken.
✓ Branch 91 → 92 taken 4 times.
✗ Branch 91 → 93 not taken.
✓ Branch 93 → 94 taken 4 times.
✗ Branch 93 → 95 not taken.
✓ Branch 95 → 96 taken 4 times.
✗ Branch 95 → 97 not taken.
✓ Branch 97 → 98 taken 4 times.
✗ Branch 97 → 99 not taken.
✓ Branch 99 → 100 taken 4 times.
✗ Branch 99 → 101 not taken.
✗ Branch 251 → 252 not taken.
✗ Branch 251 → 253 not taken.
✗ Branch 255 → 256 not taken.
✗ Branch 255 → 257 not taken.
✗ Branch 259 → 260 not taken.
✗ Branch 259 → 261 not taken.
✗ Branch 263 → 264 not taken.
✗ Branch 263 → 265 not taken.
✗ Branch 267 → 268 not taken.
✗ Branch 267 → 269 not taken.
12 !choice.value(QStringLiteral("displayName")).toString().startsWith(result.calendarHint, Qt::CaseInsensitive))
139 2 continue;
140
2/2
✓ Branch 113 → 114 taken 2 times.
✓ Branch 117 → 118 taken 2 times.
6 defaults[QStringLiteral("accountId")] = choice.value(QStringLiteral("accountId"));
141
2/2
✓ Branch 129 → 130 taken 2 times.
✓ Branch 133 → 134 taken 2 times.
6 defaults[QStringLiteral("calendarPath")] = choice.value(QStringLiteral("path"));
142 2 break;
143 7 }
144
3/3
✓ Branch 152 → 153 taken 3 times.
✓ Branch 155 → 156 taken 1 time.
✓ Branch 155 → 172 taken 2 times.
3 if (!defaults.contains(QStringLiteral("calendarPath")))
145
4/6
✓ Branch 159 → 160 taken 1 time.
✓ Branch 161 → 162 taken 1 time.
✓ Branch 165 → 166 taken 1 time.
✓ Branch 165 → 167 taken 1 time.
✗ Branch 336 → 337 not taken.
✗ Branch 336 → 338 not taken.
4 return {{QStringLiteral("error"), tr("No writable calendar is called “%1”.").arg(result.calendarHint)}};
146 }
147
8/8
✓ Branch 172 → 173 taken 9 times.
✓ Branch 176 → 177 taken 9 times.
✓ Branch 177 → 178 taken 9 times.
✓ Branch 178 → 179 taken 9 times.
✓ Branch 179 → 180 taken 9 times.
✓ Branch 180 → 181 taken 9 times.
✓ Branch 189 → 174 taken 9 times.
✓ Branch 189 → 190 taken 9 times.
18 qCInfo(lcEventInput) << "Quick appointment parsed; date" << result.date.isValid() << "time"
148
3/3
✓ Branch 182 → 183 taken 9 times.
✓ Branch 183 → 184 taken 9 times.
✓ Branch 185 → 186 taken 9 times.
9 << !result.startTime.isEmpty() << "place" << !result.location.isEmpty();
149 9 return defaults;
150 32 }
151