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: 83.3% 85 / 0 / 102
Functions: 100.0% 10 / 0 / 10
Branches: 53.6% 128 / 0 / 239

presentation/CalendarEventDetails.cpp
Line Branch Exec Source
1 #include "CalendarEventDetails.h"
2 #include "util/HostMatch.h"
3
4 #include "CalendarLayout.h"
5 #include "data/ICalendar.h"
6
7 #include <QCoreApplication>
8 #include <QLocale>
9 #include <QRegularExpression>
10 #include <QStringList>
11
12 namespace CalendarEventDetails {
13
14 namespace {
15
2/2
✓ Branch 2 → 3 taken 104 times.
✓ Branch 3 → 4 taken 104 times.
104 QString dateText(const QDate &date) { return QLocale().toString(date, QLocale::LongFormat); }
16
17 class DetailsText {
18 60 Q_DECLARE_TR_FUNCTIONS(CalendarEventDetails)
19 };
20
21 648 const QRegularExpression &urlPattern() {
22 static const QRegularExpression pattern(
23 28 QStringLiteral(R"((https?://[^\s<>"']+|mailto:[^\s<>"']+))"),
24
4/7
✓ Branch 2 → 3 taken 14 times.
✓ Branch 2 → 15 taken 634 times.
✓ Branch 4 → 5 taken 14 times.
✗ Branch 4 → 15 not taken.
✓ Branch 9 → 10 taken 14 times.
✗ Branch 24 → 25 not taken.
✗ Branch 24 → 26 not taken.
676 QRegularExpression::CaseInsensitiveOption);
25 648 return pattern;
26 }
27
28
29 15 QString trimUrl(QString url) {
30
9/16
✓ Branch 5 → 6 taken 16 times.
✗ Branch 5 → 13 not taken.
✓ Branch 9 → 10 taken 16 times.
✓ Branch 10 → 11 taken 16 times.
✓ Branch 11 → 12 taken 1 time.
✓ Branch 11 → 13 taken 15 times.
✓ Branch 14 → 15 taken 16 times.
✗ Branch 14 → 16 not taken.
✓ Branch 16 → 17 taken 16 times.
✗ Branch 16 → 18 not taken.
✓ Branch 18 → 3 taken 1 time.
✓ Branch 18 → 19 taken 15 times.
✗ Branch 21 → 22 not taken.
✗ Branch 21 → 23 not taken.
✗ Branch 25 → 26 not taken.
✗ Branch 25 → 27 not taken.
32 while (!url.isEmpty() && QStringLiteral(".,;:)>]").contains(url.back()))
31 1 url.chop(1);
32 15 return url;
33 }
34
35 } // namespace
36
37 // S106-230: one trust level for equally untrusted bytes. The conference
38 // properties, the text and a location that is a URL all name the "Join"
39 // target only for a known meeting provider — these surfaces (details,
40 // Next up, reminders) show no host the way the invitation card does, so an
41 // arbitrary https URL there would pose as the meeting. Other links stay
42 // clickable in the location and the notes.
43 307 QString meetingUrl(const CalendarEvent &event) {
44
2/2
✓ Branch 3 → 4 taken 20 times.
✓ Branch 3 → 42 taken 287 times.
307 if (!event.icalRaw.isEmpty()) {
45
1/1
✓ Branch 7 → 8 taken 20 times.
20 const ICal::Component component = ICal::parseFirst(event.icalRaw, QStringLiteral("VEVENT"));
46 77 for (const char *name : {"X-GOOGLE-CONFERENCE", "X-MICROSOFT-SKYPETEAMSMEETINGURL",
47
2/2
✓ Branch 34 → 13 taken 58 times.
✓ Branch 34 → 35 taken 19 times.
97 "X-MICROSOFT-ONLINEMEETINGURL"}) {
48
4/6
✓ Branch 14 → 15 taken 58 times.
✓ Branch 15 → 16 taken 58 times.
✓ Branch 16 → 17 taken 58 times.
✓ Branch 17 → 18 taken 58 times.
✗ Branch 128 → 129 not taken.
✗ Branch 128 → 130 not taken.
58 const QString value = ICal::unescapeText(component.value(QString::fromLatin1(name))).trimmed();
49
5/5
✓ Branch 21 → 22 taken 58 times.
✓ Branch 23 → 24 taken 58 times.
✓ Branch 24 → 25 taken 58 times.
✓ Branch 27 → 28 taken 1 time.
✓ Branch 27 → 29 taken 57 times.
58 if (HostMatch::isKnownMeetingUrl(QUrl::fromEncoded(value.toUtf8(), QUrl::StrictMode)))
50 1 return value;
51 57 }
52 20 }
53
2/2
✓ Branch 82 → 46 taken 600 times.
✓ Branch 82 → 83 taken 293 times.
1811 for (const QString &text : {event.location, event.description}) {
54
2/2
✓ Branch 46 → 47 taken 600 times.
✓ Branch 48 → 49 taken 600 times.
600 auto matches = urlPattern().globalMatch(text);
55
3/3
✓ Branch 74 → 75 taken 602 times.
✓ Branch 75 → 50 taken 15 times.
✓ Branch 75 → 76 taken 587 times.
602 while (matches.hasNext()) {
56
3/3
✓ Branch 50 → 51 taken 15 times.
✓ Branch 51 → 52 taken 15 times.
✓ Branch 52 → 53 taken 15 times.
15 const QString url = trimUrl(matches.next().captured(1));
57
9/13
✓ Branch 56 → 57 taken 15 times.
✓ Branch 57 → 58 taken 15 times.
✗ Branch 57 → 62 not taken.
✓ Branch 58 → 59 taken 15 times.
✓ Branch 59 → 60 taken 15 times.
✓ Branch 60 → 61 taken 13 times.
✓ Branch 60 → 62 taken 2 times.
✓ Branch 63 → 64 taken 15 times.
✗ Branch 63 → 65 not taken.
✓ Branch 65 → 66 taken 13 times.
✓ Branch 65 → 68 taken 2 times.
✗ Branch 142 → 143 not taken.
✗ Branch 142 → 144 not taken.
15 if (url.startsWith(QLatin1String("https://"), Qt::CaseInsensitive) && HostMatch::isKnownMeetingUrl(QUrl(url)))
58 13 return url;
59 15 }
60 1518 }
61
1/1
✓ Branch 89 → 91 taken 293 times.
293 const QString location = event.location.trimmed();
62
4/5
✓ Branch 91 → 92 taken 293 times.
✓ Branch 93 → 94 taken 293 times.
✓ Branch 94 → 95 taken 293 times.
✗ Branch 97 → 98 not taken.
✓ Branch 97 → 100 taken 293 times.
293 if (HostMatch::isKnownMeetingUrl(QUrl::fromEncoded(location.toUtf8(), QUrl::StrictMode)))
63 ✗ return location;
64 293 return {};
65 293 }
66
67 48 QString linkified(const QString &plainText) {
68 48 QString result;
69 48 qsizetype last = 0;
70
2/2
✓ Branch 2 → 3 taken 48 times.
✓ Branch 4 → 5 taken 48 times.
48 auto matches = urlPattern().globalMatch(plainText);
71
2/3
✓ Branch 47 → 48 taken 48 times.
✗ Branch 48 → 6 not taken.
✓ Branch 48 → 49 taken 48 times.
48 while (matches.hasNext()) {
72 ✗ const auto match = matches.next();
73 ✗ const QString url = trimUrl(match.captured(1));
74 ✗ result += plainText.mid(last, match.capturedStart(1) - last).toHtmlEscaped();
75 ✗ const QString escaped = url.toHtmlEscaped();
76 ✗ result += QStringLiteral("<a href=\"") + escaped + QStringLiteral("\">") + escaped + QStringLiteral("</a>");
77 ✗ last = match.capturedStart(1) + url.size();
78 ✗ }
79
3/3
✓ Branch 49 → 50 taken 48 times.
✓ Branch 50 → 51 taken 48 times.
✓ Branch 51 → 52 taken 48 times.
48 result += plainText.mid(last).toHtmlEscaped();
80
1/1
✓ Branch 59 → 60 taken 48 times.
48 result.replace(QLatin1Char('\n'), QStringLiteral("<br>"));
81 48 return result;
82 48 }
83
84 48 QString durationText(qint64 minutes) {
85
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 48 times.
48 if (minutes <= 0)
86 ✗ return {};
87 48 const qint64 days = minutes / 1440, hours = (minutes % 1440) / 60, rest = minutes % 60;
88 48 QStringList parts;
89
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 9 taken 48 times.
48 if (days > 0)
90 ✗ parts.append(DetailsText::tr("%n day(s)", nullptr, int(days)));
91
2/2
✓ Branch 9 → 10 taken 25 times.
✓ Branch 9 → 17 taken 23 times.
48 if (hours > 0)
92
3/3
✓ Branch 10 → 11 taken 25 times.
✓ Branch 12 → 13 taken 25 times.
✓ Branch 13 → 14 taken 25 times.
50 parts.append(DetailsText::tr("%1 h").arg(hours));
93
2/2
✓ Branch 17 → 18 taken 23 times.
✓ Branch 17 → 25 taken 25 times.
48 if (rest > 0)
94
3/3
✓ Branch 18 → 19 taken 23 times.
✓ Branch 20 → 21 taken 23 times.
✓ Branch 21 → 22 taken 23 times.
46 parts.append(DetailsText::tr("%1 min").arg(rest));
95
1/1
✓ Branch 27 → 28 taken 48 times.
48 return parts.join(QLatin1Char(' '));
96 48 }
97
98 1 QString plainText(const CalendarEvent &event, const QString &whenText, const QString &recurrenceText) {
99
2/4
✓ Branch 6 → 7 taken 2 times.
✓ Branch 6 → 8 taken 1 time.
✗ Branch 35 → 36 not taken.
✗ Branch 35 → 37 not taken.
3 QStringList lines{event.summary, whenText};
100
1/2
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 1 time.
1 if (!recurrenceText.isEmpty())
101 ✗ lines.append(recurrenceText);
102
1/2
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 20 taken 1 time.
1 if (!event.location.isEmpty())
103 ✗ lines.append(DetailsText::tr("Location: %1").arg(event.location));
104
1/2
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 27 taken 1 time.
1 if (!event.description.isEmpty())
105 ✗ lines << QString() << event.description;
106
1/1
✓ Branch 29 → 30 taken 1 time.
2 return lines.join(QLatin1Char('\n'));
107 2 }
108
109 48 QString reminderText(const QList<int> &minutesBefore) {
110 48 QStringList parts;
111
2/2
✓ Branch 52 → 4 taken 6 times.
✓ Branch 52 → 53 taken 48 times.
54 for (const int minutes : minutesBefore) {
112 6 const int magnitude = qAbs(minutes);
113 6 QString amount;
114
1/4
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 11 taken 6 times.
✗ Branch 7 → 8 not taken.
✗ Branch 8 → 9 not taken.
6 if (magnitude == 0) { parts << DetailsText::tr("At the start"); continue; }
115 // English source texts are final: singular and plural separately.
116
1/2
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 18 taken 6 times.
6 if (magnitude % 1440 == 0)
117 ✗ amount = magnitude == 1440 ? DetailsText::tr("1 day") : DetailsText::tr("%n days", nullptr, magnitude / 1440);
118
1/2
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 25 taken 6 times.
6 else if (magnitude % 60 == 0)
119 ✗ amount = magnitude == 60 ? DetailsText::tr("1 hour") : DetailsText::tr("%n hours", nullptr, magnitude / 60);
120 else
121
2/4
✗ Branch 25 → 26 not taken.
✓ Branch 25 → 27 taken 6 times.
✗ Branch 26 → 28 not taken.
✓ Branch 27 → 28 taken 6 times.
6 amount = magnitude == 1 ? DetailsText::tr("1 minute") : DetailsText::tr("%n minutes", nullptr, magnitude);
122
6/15
✓ Branch 31 → 32 taken 6 times.
✗ Branch 31 → 35 not taken.
✓ Branch 32 → 33 taken 6 times.
✓ Branch 34 → 38 taken 6 times.
✗ Branch 35 → 36 not taken.
✗ Branch 37 → 38 not taken.
✓ Branch 38 → 39 taken 6 times.
✗ Branch 40 → 41 not taken.
✓ Branch 40 → 42 taken 6 times.
✓ Branch 42 → 43 taken 6 times.
✗ Branch 42 → 44 not taken.
✗ Branch 72 → 73 not taken.
✗ Branch 72 → 74 not taken.
✗ Branch 77 → 78 not taken.
✗ Branch 77 → 79 not taken.
6 parts << (minutes > 0 ? DetailsText::tr("%1 before").arg(amount) : DetailsText::tr("%1 after the start").arg(amount));
123 6 }
124
1/1
✓ Branch 56 → 57 taken 48 times.
96 return parts.join(QStringLiteral(", "));
125 48 }
126
127 // "Monday, 5 October 2026, 10:00–11:00" in local time; all-day and
128 // multi-day appointments show their dates.
129 104 QString whenText(const CalendarEvent &event) {
130
2/3
✓ Branch 2 → 3 taken 104 times.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 104 times.
104 if (!event.dtStart.isValid()) return {};
131
2/2
✓ Branch 5 → 6 taken 6 times.
✓ Branch 5 → 42 taken 98 times.
104 if (event.allDay) {
132
5/6
✓ Branch 6 → 7 taken 6 times.
✓ Branch 8 → 9 taken 6 times.
✓ Branch 9 → 10 taken 6 times.
✓ Branch 10 → 11 taken 6 times.
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 17 taken 6 times.
12 const QDate last = event.dtEnd.isValid() && event.dtEnd.date() > event.dtStart.date().addDays(1)
133
2/5
✓ Branch 7 → 8 taken 6 times.
✗ Branch 7 → 17 not taken.
✗ Branch 14 → 15 not taken.
✗ Branch 15 → 16 not taken.
✓ Branch 17 → 18 taken 6 times.
12 ? event.dtEnd.date().addDays(-1) : event.dtStart.date();
134
1/1
✓ Branch 18 → 19 taken 6 times.
6 return last == event.dtStart.date() ? dateText(last)
135
7/28
✓ Branch 20 → 21 taken 6 times.
✗ Branch 20 → 22 not taken.
✓ Branch 21 → 30 taken 6 times.
✗ Branch 22 → 23 not taken.
✗ Branch 26 → 27 not taken.
✗ Branch 27 → 28 not taken.
✗ Branch 28 → 29 not taken.
✗ Branch 29 → 30 not taken.
✗ Branch 31 → 32 not taken.
✓ Branch 31 → 33 taken 6 times.
✗ Branch 33 → 34 not taken.
✓ Branch 33 → 35 taken 6 times.
✗ Branch 35 → 36 not taken.
✓ Branch 35 → 37 taken 6 times.
✗ Branch 37 → 38 not taken.
✓ Branch 37 → 39 taken 6 times.
✗ Branch 39 → 40 not taken.
✓ Branch 39 → 41 taken 6 times.
✗ Branch 124 → 125 not taken.
✗ Branch 124 → 126 not taken.
✗ Branch 128 → 129 not taken.
✗ Branch 128 → 130 not taken.
✗ Branch 133 → 134 not taken.
✗ Branch 133 → 135 not taken.
✗ Branch 137 → 138 not taken.
✗ Branch 137 → 139 not taken.
✗ Branch 141 → 142 not taken.
✗ Branch 141 → 143 not taken.
12 : dateText(event.dtStart.date()) + QStringLiteral(" – ") + dateText(last);
136 }
137
1/1
✓ Branch 42 → 43 taken 98 times.
98 const QDateTime start = event.dtStart.toLocalTime();
138
3/4
✓ Branch 43 → 44 taken 98 times.
✓ Branch 44 → 45 taken 98 times.
✗ Branch 44 → 46 not taken.
✓ Branch 47 → 48 taken 98 times.
98 const QDateTime end = (event.dtEnd.isValid() ? event.dtEnd : event.dtStart).toLocalTime();
139
3/4
✓ Branch 48 → 49 taken 98 times.
✓ Branch 49 → 50 taken 98 times.
✗ Branch 51 → 52 not taken.
✓ Branch 51 → 90 taken 98 times.
98 if (end.date() != start.date())
140 ✗ return dateText(start.date()) + QStringLiteral(", ") + CalendarLayout::timeText(start) + QStringLiteral(" – ") +
141 ✗ dateText(end.date()) + QStringLiteral(", ") + CalendarLayout::timeText(end);
142
6/6
✓ Branch 94 → 95 taken 98 times.
✓ Branch 98 → 99 taken 98 times.
✓ Branch 99 → 100 taken 98 times.
✓ Branch 100 → 101 taken 98 times.
✓ Branch 101 → 102 taken 98 times.
✓ Branch 102 → 103 taken 98 times.
294 return dateText(start.date()) + QStringLiteral(", ") + CalendarLayout::timeText(start) + QStringLiteral("–") +
143
2/2
✓ Branch 90 → 91 taken 98 times.
✓ Branch 103 → 104 taken 98 times.
294 CalendarLayout::timeText(end);
144 98 }
145
146 } // namespace CalendarEventDetails
147