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: 93.6% 131 / 0 / 140
Functions: 91.7% 11 / 0 / 12
Branches: 76.3% 190 / 0 / 249

data/ServiceCatalog.cpp
Line Branch Exec Source
1 #include "ServiceCatalog.h"
2
3 #include <QFile>
4 #include <QJsonArray>
5 #include <QJsonDocument>
6 #include <QJsonObject>
7 #include <QLocale>
8 #include <QLoggingCategory>
9
10 ✗ Q_LOGGING_CATEGORY(lcServiceCatalog, "mailjd.contacts.social")
11
12 namespace {
13
14 494 QString fill(QString text, const QRegularExpressionMatch &match) {
15
2/2
✓ Branch 19 → 4 taken 1976 times.
✓ Branch 19 → 20 taken 494 times.
2470 for (const char *group : {"user", "server", "id", "url"})
16
3/3
✓ Branch 6 → 7 taken 1976 times.
✓ Branch 12 → 13 taken 1976 times.
✓ Branch 13 → 14 taken 1976 times.
5928 text.replace(QStringLiteral("{%1}").arg(QLatin1String(group)), match.captured(QLatin1String(group)));
17 494 return text;
18 }
19
20 // "https://anasilva.example/" → "anasilva.example".
21 2 QString shortLink(QString url) {
22
2/2
✓ Branch 13 → 4 taken 4 times.
✓ Branch 13 → 14 taken 2 times.
6 for (const char *scheme : {"https://", "http://"})
23
3/3
✓ Branch 5 → 6 taken 4 times.
✓ Branch 6 → 7 taken 2 times.
✓ Branch 6 → 12 taken 2 times.
4 if (url.startsWith(QLatin1String(scheme), Qt::CaseInsensitive))
24
1/1
✓ Branch 8 → 9 taken 2 times.
2 url = url.mid(int(qstrlen(scheme)));
25
3/3
✓ Branch 15 → 16 taken 2 times.
✓ Branch 16 → 17 taken 1 time.
✓ Branch 16 → 21 taken 1 time.
2 if (url.startsWith(QLatin1String("www.")))
26
1/1
✓ Branch 17 → 18 taken 1 time.
1 url = url.mid(4);
27
3/3
✓ Branch 25 → 26 taken 4 times.
✓ Branch 26 → 22 taken 2 times.
✓ Branch 26 → 27 taken 2 times.
4 while (url.endsWith(QLatin1Char('/')))
28 2 url.chop(1);
29 2 return url;
30 }
31
32 } // namespace
33
34 1010 const ServiceCatalog &ServiceCatalog::builtIn() {
35 32 static const ServiceCatalog catalog = [] {
36
1/1
✓ Branch 5 → 6 taken 16 times.
16 QFile file(QStringLiteral(":/contacts/services.json"));
37
2/3
✓ Branch 9 → 10 taken 16 times.
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 22 taken 16 times.
16 if (!file.open(QIODevice::ReadOnly)) {
38 ✗ qCWarning(lcServiceCatalog) << "Service catalog resource missing";
39 ✗ return ServiceCatalog{};
40 }
41 16 QString error;
42
2/2
✓ Branch 22 → 23 taken 16 times.
✓ Branch 23 → 24 taken 16 times.
16 ServiceCatalog loaded = fromJson(file.readAll(), &error);
43
1/2
✗ Branch 26 → 27 not taken.
✓ Branch 26 → 39 taken 16 times.
16 if (!error.isEmpty())
44 ✗ qCWarning(lcServiceCatalog) << "Service catalog:" << error;
45 16 return loaded;
46
4/7
✓ Branch 2 → 3 taken 16 times.
✓ Branch 2 → 9 taken 994 times.
✓ Branch 4 → 5 taken 16 times.
✗ Branch 4 → 9 not taken.
✓ Branch 5 → 6 taken 16 times.
✗ Branch 12 → 13 not taken.
✗ Branch 12 → 14 not taken.
1026 }();
47 1010 return catalog;
48 }
49
50 18 ServiceCatalog ServiceCatalog::fromJson(const QByteArray &json, QString *error) {
51 18 ServiceCatalog catalog;
52 18 QJsonParseError parseError;
53
1/1
✓ Branch 2 → 3 taken 18 times.
18 const QJsonDocument document = QJsonDocument::fromJson(json, &parseError);
54
3/3
✓ Branch 3 → 4 taken 18 times.
✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 11 taken 17 times.
18 if (!document.isObject()) {
55
1/2
✓ Branch 5 → 6 taken 1 time.
✗ Branch 5 → 10 not taken.
1 if (error)
56
1/1
✓ Branch 6 → 7 taken 1 time.
1 *error = parseError.errorString();
57 1 return catalog;
58 }
59
8/8
✓ Branch 11 → 12 taken 17 times.
✓ Branch 15 → 16 taken 17 times.
✓ Branch 16 → 17 taken 17 times.
✓ Branch 21 → 22 taken 17 times.
✓ Branch 22 → 23 taken 17 times.
✓ Branch 25 → 26 taken 289 times.
✓ Branch 235 → 24 taken 289 times.
✓ Branch 235 → 236 taken 17 times.
323 for (const QJsonValue &item : document.object().value(QStringLiteral("services")).toArray()) {
60
1/1
✓ Branch 26 → 27 taken 289 times.
289 const QJsonObject object = item.toObject();
61 289 Service service;
62
2/2
✓ Branch 30 → 31 taken 289 times.
✓ Branch 31 → 32 taken 289 times.
289 service.id = object.value(QStringLiteral("id")).toString();
63
2/2
✓ Branch 40 → 41 taken 289 times.
✓ Branch 41 → 42 taken 289 times.
289 service.kind = object.value(QStringLiteral("kind")).toString();
64
2/2
✓ Branch 50 → 51 taken 289 times.
✓ Branch 51 → 52 taken 289 times.
289 service.glyph = object.value(QStringLiteral("glyph")).toString();
65
3/3
✓ Branch 60 → 61 taken 289 times.
✓ Branch 61 → 62 taken 289 times.
✓ Branch 62 → 63 taken 289 times.
289 service.codepoint = object.value(QStringLiteral("codepoint")).toString().toUInt(nullptr, 16);
66
4/4
✓ Branch 70 → 71 taken 289 times.
✓ Branch 71 → 72 taken 289 times.
✓ Branch 75 → 76 taken 289 times.
✓ Branch 76 → 77 taken 289 times.
578 service.nameEn = object.value(QStringLiteral("names")).toObject().value(QStringLiteral("en")).toString();
67
4/4
✓ Branch 89 → 90 taken 289 times.
✓ Branch 90 → 91 taken 289 times.
✓ Branch 94 → 95 taken 289 times.
✓ Branch 95 → 96 taken 289 times.
578 service.nameDe = object.value(QStringLiteral("names")).toObject().value(QStringLiteral("de")).toString();
68
7/7
✓ Branch 108 → 109 taken 289 times.
✓ Branch 109 → 110 taken 289 times.
✓ Branch 113 → 114 taken 289 times.
✓ Branch 114 → 115 taken 289 times.
✓ Branch 117 → 118 taken 128 times.
✓ Branch 124 → 116 taken 128 times.
✓ Branch 124 → 125 taken 289 times.
417 for (const QJsonValue &alias : object.value(QStringLiteral("aliases")).toArray())
69
2/2
✓ Branch 118 → 119 taken 128 times.
✓ Branch 119 → 120 taken 128 times.
417 service.aliases.append(alias.toString());
70
7/7
✓ Branch 129 → 130 taken 289 times.
✓ Branch 130 → 131 taken 289 times.
✓ Branch 134 → 135 taken 289 times.
✓ Branch 135 → 136 taken 289 times.
✓ Branch 138 → 139 taken 433 times.
✓ Branch 195 → 137 taken 433 times.
✓ Branch 195 → 196 taken 289 times.
722 for (const QJsonValue &pattern : object.value(QStringLiteral("patterns")).toArray()) {
71
1/1
✓ Branch 139 → 140 taken 433 times.
433 Service::Pattern entry;
72
3/3
✓ Branch 141 → 142 taken 433 times.
✓ Branch 145 → 146 taken 433 times.
✓ Branch 146 → 147 taken 433 times.
1299 entry.match = QRegularExpression(pattern.toObject().value(QStringLiteral("match")).toString(),
73
1/1
✓ Branch 147 → 148 taken 433 times.
866 QRegularExpression::CaseInsensitiveOption);
74
3/3
✓ Branch 155 → 156 taken 433 times.
✓ Branch 159 → 160 taken 433 times.
✓ Branch 160 → 161 taken 433 times.
866 entry.requiredKind = pattern.toObject().value(QStringLiteral("kind")).toString();
75
3/3
✓ Branch 167 → 168 taken 433 times.
✓ Branch 168 → 169 taken 1 time.
✓ Branch 168 → 182 taken 432 times.
433 if (!entry.match.isValid()) {
76
1/2
✓ Branch 169 → 170 taken 1 time.
✗ Branch 169 → 181 not taken.
1 if (error)
77
2/2
✓ Branch 173 → 174 taken 1 time.
✓ Branch 174 → 175 taken 1 time.
1 *error = QStringLiteral("Invalid pattern in %1: %2").arg(service.id, entry.match.errorString());
78 1 continue;
79 }
80
1/1
✓ Branch 182 → 183 taken 432 times.
432 service.patterns.append(entry);
81 723 }
82
2/2
✓ Branch 200 → 201 taken 289 times.
✓ Branch 201 → 202 taken 289 times.
289 service.handleTemplate = object.value(QStringLiteral("handle")).toString();
83
2/2
✓ Branch 210 → 211 taken 289 times.
✓ Branch 211 → 212 taken 289 times.
289 service.urlTemplate = object.value(QStringLiteral("url")).toString();
84
2/2
✓ Branch 220 → 221 taken 289 times.
✓ Branch 221 → 222 taken 289 times.
289 service.uriTemplate = object.value(QStringLiteral("uri")).toString();
85
1/2
✓ Branch 228 → 229 taken 289 times.
✗ Branch 228 → 230 not taken.
289 if (!service.id.isEmpty())
86
1/1
✓ Branch 229 → 230 taken 289 times.
289 catalog.m_services.append(service);
87 306 }
88 17 return catalog;
89 18 }
90
91 1862 const ServiceCatalog::Service *ServiceCatalog::find(const QString &id) const {
92
2/2
✓ Branch 2 → 3 taken 1862 times.
✓ Branch 3 → 4 taken 1862 times.
1862 const QString key = id.trimmed().toLower();
93
2/2
✓ Branch 18 → 7 taken 15322 times.
✓ Branch 18 → 19 taken 2 times.
15324 for (const Service &service : m_services)
94
6/6
✓ Branch 9 → 10 taken 13465 times.
✓ Branch 9 → 12 taken 1857 times.
✓ Branch 11 → 12 taken 3 times.
✓ Branch 11 → 13 taken 13462 times.
✓ Branch 14 → 15 taken 1860 times.
✓ Branch 14 → 16 taken 13462 times.
15322 if (service.id == key || service.aliases.contains(key))
95 1860 return &service;
96 2 return nullptr;
97 1862 }
98
99 163 QStringList ServiceCatalog::idsForKind(const QString &kind) const {
100 163 QStringList ids;
101
2/2
✓ Branch 10 → 4 taken 2934 times.
✓ Branch 10 → 11 taken 163 times.
3097 for (const Service &service : m_services)
102
2/2
✓ Branch 6 → 7 taken 1384 times.
✓ Branch 6 → 8 taken 1550 times.
2934 if (service.kind == kind)
103
1/1
✓ Branch 7 → 8 taken 1384 times.
1384 ids.append(service.id);
104 163 return ids;
105 ✗ }
106
107 204 ServiceCatalog::Recognised ServiceCatalog::recognise(const QString &input, const QString &kindHint) const {
108
1/1
✓ Branch 2 → 3 taken 204 times.
204 const QString text = input.trimmed();
109 204 Recognised result;
110
2/2
✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 6 taken 203 times.
204 if (text.isEmpty())
111 1 return result;
112
2/2
✓ Branch 68 → 8 taken 1313 times.
✓ Branch 68 → 69 taken 2 times.
1315 for (const Service &service : m_services) {
113
2/2
✓ Branch 65 → 11 taken 2082 times.
✓ Branch 65 → 66 taken 1112 times.
3194 for (const Service::Pattern &pattern : service.patterns) {
114
6/6
✓ Branch 13 → 14 taken 52 times.
✓ Branch 13 → 17 taken 2030 times.
✓ Branch 15 → 16 taken 12 times.
✓ Branch 15 → 17 taken 40 times.
✓ Branch 18 → 19 taken 12 times.
✓ Branch 18 → 20 taken 2070 times.
2082 if (!pattern.requiredKind.isEmpty() && pattern.requiredKind != kindHint)
115 1881 continue;
116
1/1
✓ Branch 21 → 22 taken 2070 times.
2070 const QRegularExpressionMatch match = pattern.match.match(text);
117
3/3
✓ Branch 22 → 23 taken 2070 times.
✓ Branch 23 → 24 taken 1869 times.
✓ Branch 23 → 25 taken 201 times.
2070 if (!match.hasMatch())
118 1869 continue;
119 201 result.recognised = true;
120 201 result.service = service.id;
121 201 result.kind = service.kind;
122
1/1
✓ Branch 28 → 29 taken 201 times.
201 result.handle = fill(service.handleTemplate, match);
123
1/1
✓ Branch 33 → 34 taken 201 times.
201 result.url = fill(service.urlTemplate, match);
124
2/2
✓ Branch 39 → 40 taken 2 times.
✓ Branch 39 → 46 taken 199 times.
201 if (service.kind == QLatin1String("link"))
125
1/1
✓ Branch 41 → 42 taken 2 times.
2 result.handle = shortLink(result.url);
126
1/2
✓ Branch 50 → 51 taken 92 times.
✗ Branch 50 → 54 not taken.
293 result.value = service.kind == QLatin1String("chat") && !service.uriTemplate.isEmpty()
127
5/7
✓ Branch 48 → 49 taken 92 times.
✓ Branch 48 → 54 taken 109 times.
✓ Branch 52 → 53 taken 92 times.
✓ Branch 57 → 58 taken 92 times.
✓ Branch 57 → 59 taken 109 times.
✗ Branch 86 → 87 not taken.
✗ Branch 86 → 88 not taken.
695 ? fill(service.uriTemplate, match)
128 201 : result.url;
129 201 return result;
130 2070 }
131 }
132 2 return result;
133 204 }
134
135 1543 QString ServiceCatalog::displayName(const QString &serviceId, const QString &language) const {
136
1/1
✓ Branch 2 → 3 taken 1543 times.
1543 const Service *service = find(serviceId);
137
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 17 taken 1542 times.
1543 if (!service) {
138 // A service MailJD does not know: its stored name, readable.
139
1/1
✓ Branch 4 → 5 taken 1 time.
1 const QString name = serviceId.trimmed();
140
4/8
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 1 time.
✓ Branch 8 → 9 taken 1 time.
✓ Branch 11 → 12 taken 1 time.
✓ Branch 13 → 14 taken 1 time.
✗ Branch 13 → 15 not taken.
✗ Branch 39 → 40 not taken.
✗ Branch 39 → 41 not taken.
1 return name.isEmpty() ? QString() : name.at(0).toUpper() + name.mid(1);
141 1 }
142
9/13
✓ Branch 18 → 19 taken 1541 times.
✓ Branch 18 → 22 taken 1 time.
✓ Branch 19 → 20 taken 1541 times.
✓ Branch 20 → 21 taken 1541 times.
✓ Branch 21 → 23 taken 1541 times.
✓ Branch 23 → 24 taken 1541 times.
✓ Branch 23 → 25 taken 1 time.
✓ Branch 25 → 26 taken 1541 times.
✓ Branch 25 → 27 taken 1 time.
✗ Branch 46 → 47 not taken.
✗ Branch 46 → 48 not taken.
✗ Branch 50 → 51 not taken.
✗ Branch 50 → 52 not taken.
1542 const QString lang = language.isEmpty() ? QLocale().name().left(2) : language;
143
3/4
✓ Branch 29 → 30 taken 69 times.
✓ Branch 29 → 33 taken 1473 times.
✓ Branch 31 → 32 taken 69 times.
✗ Branch 31 → 33 not taken.
1542 return lang == QLatin1String("de") && !service->nameDe.isEmpty() ? service->nameDe : service->nameEn;
144 1542 }
145
146 129 ServiceCatalog::Recognised ServiceCatalog::describe(const ContactSocial &social) const {
147 // A stored value is shown with the service it was saved under; the
148 // patterns only supply the handle and a link.
149 129 Recognised result = recognise(social.value, social.kind);
150
1/1
✓ Branch 3 → 4 taken 129 times.
129 const Service *stored = find(social.service);
151
5/10
✓ Branch 4 → 5 taken 129 times.
✗ Branch 4 → 11 not taken.
✓ Branch 5 → 6 taken 128 times.
✓ Branch 5 → 12 taken 1 time.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 12 taken 128 times.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 31 taken 129 times.
129 if (!result.recognised || (stored && result.service != stored->id && result.kind == QLatin1String("link"))) {
152 ✗ result.recognised = stored != nullptr;
153 ✗ result.handle = social.user.isEmpty() ? shortLink(social.value) : social.user;
154 ✗ result.url = social.value.startsWith(QLatin1String("http"), Qt::CaseInsensitive) ? social.value : QString();
155 }
156
2/2
✓ Branch 31 → 32 taken 128 times.
✓ Branch 31 → 34 taken 1 time.
129 if (stored) {
157 128 result.service = stored->id;
158 128 result.kind = stored->kind;
159
3/6
✓ Branch 35 → 36 taken 1 time.
✗ Branch 35 → 40 not taken.
✓ Branch 38 → 39 taken 1 time.
✗ Branch 38 → 40 not taken.
✓ Branch 41 → 42 taken 1 time.
✗ Branch 41 → 43 not taken.
1 } else if (!social.service.isEmpty() && result.kind == QLatin1String("link")) {
160 1 result.service = social.service; // e.g. an Apple "flickr" profile
161 }
162
5/6
✓ Branch 44 → 45 taken 45 times.
✓ Branch 44 → 49 taken 84 times.
✓ Branch 47 → 48 taken 45 times.
✗ Branch 47 → 49 not taken.
✓ Branch 50 → 51 taken 45 times.
✓ Branch 50 → 52 taken 84 times.
129 if (!social.user.isEmpty() && result.kind != QLatin1String("chat"))
163 45 result.handle = social.user;
164 129 result.value = social.value;
165 129 return result;
166 ✗ }
167
168 2 ContactSocial ServiceCatalog::toSocial(const Recognised &recognised) {
169 2 ContactSocial social;
170
6/6
✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 9 taken 1 time.
✓ Branch 14 → 15 taken 1 time.
✓ Branch 14 → 16 taken 1 time.
✓ Branch 16 → 17 taken 1 time.
✓ Branch 16 → 18 taken 1 time.
4 social.kind = recognised.kind == QLatin1String("chat") ? QStringLiteral("chat") : QStringLiteral("profile");
171 2 social.service = recognised.service;
172 2 social.value = recognised.value;
173
3/4
✓ Branch 22 → 23 taken 1 time.
✓ Branch 22 → 29 taken 1 time.
✓ Branch 24 → 25 taken 1 time.
✗ Branch 24 → 29 not taken.
3 if (social.kind == QLatin1String("profile") && recognised.handle != recognised.value &&
174
3/4
✓ Branch 27 → 28 taken 1 time.
✗ Branch 27 → 29 not taken.
✓ Branch 30 → 31 taken 1 time.
✓ Branch 30 → 32 taken 1 time.
3 recognised.kind != QLatin1String("link"))
175 1 social.user = recognised.handle;
176 2 return social;
177 }
178